summaryrefslogtreecommitdiffstats
path: root/share/extensions/inkex/tester/filters.py
blob: 281adfc79ad51ce040f55da345d4d014f8e8b966 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#
# Copyright (C) 2019 Thomas Holder
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# pylint: disable=too-few-public-methods
#
"""
Comparison filters for use with the ComparisonMixin.

Each filter should be initialised in the list of
filters that are being used.

.. code-block:: python

    compare_filters = [
        CompareNumericFuzzy(),
        CompareOrderIndependentLines(option=yes),
    ]

"""

import re
from ..utils import to_bytes


class Compare:
    """
    Comparison base class, this acts as a passthrough unless
    the filter staticmethod is overwritten.
    """

    def __init__(self, **options):
        self.options = options

    def __call__(self, content):
        return self.filter(content)

    @staticmethod
    def filter(contents):
        """Replace this filter method with your own filtering"""
        return contents


class CompareNumericFuzzy(Compare):
    """
    Turn all numbers into shorter standard formats

    1.2345678 -> 1.2346
    1.2300 -> 1.23, 50.0000 -> 50.0
    50.0 -> 50
    """

    @staticmethod
    def filter(contents):
        func = lambda m: b"%.3f" % (float(m.group(0)))
        contents = re.sub(rb"\d+\.\d+(e[+-]\d+)?", func, contents)
        contents = re.sub(rb"(\d\.\d+?)0+\b", rb"\1", contents)
        contents = re.sub(rb"(\d)\.0+(?=\D|\b)", rb"\1", contents)
        return contents


class CompareWithoutIds(Compare):
    """Remove all ids from the svg"""

    @staticmethod
    def filter(contents):
        return re.sub(rb' id="([^"]*)"', b"", contents)


class CompareWithPathSpace(Compare):
    """Make sure that path segment commands have spaces around them"""

    @staticmethod
    def filter(contents):
        def func(match):
            """We've found a path command, process it"""
            new = re.sub(rb"\s*([LZMHVCSQTAatqscvhmzl])\s*", rb" \1 ", match.group(1))
            return b' d="' + new.replace(b",", b" ") + b'"'

        return re.sub(rb' d="([^"]*)"', func, contents)


class CompareSize(Compare):
    """Compare the length of the contents instead of the contents"""

    @staticmethod
    def filter(contents):
        return len(contents)


class CompareOrderIndependentBytes(Compare):
    """Take all the bytes and sort them"""

    @staticmethod
    def filter(contents):
        return b"".join([bytes(i) for i in sorted(contents)])


class CompareOrderIndependentLines(Compare):
    """Take all the lines and sort them"""

    @staticmethod
    def filter(contents):
        return b"\n".join(sorted(contents.splitlines()))


class CompareOrderIndependentStyle(Compare):
    """Take all styles and sort the results"""

    @staticmethod
    def filter(contents):
        contents = CompareNumericFuzzy.filter(contents)

        def func(match):
            """Search and replace function for sorting"""
            sty = b";".join(sorted(match.group(1).split(b";")))
            return b'style="%s"' % (sty,)

        return re.sub(rb'style="([^"]*)"', func, contents)


class CompareOrderIndependentStyleAndPath(Compare):
    """Take all styles and paths and sort them both"""

    @staticmethod
    def filter(contents):
        contents = CompareOrderIndependentStyle.filter(contents)

        def func(match):
            """Search and replace function for sorting"""
            path = b"X".join(sorted(re.split(rb"[A-Z]", match.group(1))))
            return b'd="%s"' % (path,)

        return re.sub(rb'\bd="([^"]*)"', func, contents)


class CompareOrderIndependentTags(Compare):
    """Sorts all the XML tags"""

    @staticmethod
    def filter(contents):
        return b"\n".join(sorted(re.split(rb">\s*<", contents)))


class CompareReplacement(Compare):
    """Replace pieces to make output more comparable

    .. versionadded:: 1.1"""

    def __init__(self, *replacements):
        self.deltas = replacements
        super().__init__()

    def filter(self, contents):
        contents = to_bytes(contents)
        for _from, _to in self.deltas:
            contents = contents.replace(to_bytes(_from), to_bytes(_to))
        return contents


class WindowsTextCompat(CompareReplacement):
    """Normalize newlines so tests comparing plain text work

    .. versionadded:: 1.2"""

    def __init__(self):
        super().__init__(("\r\n", "\n"))