• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OpenDataServices / flatten-tool / 10282417405

07 Aug 2024 10:14AM UTC coverage: 95.709% (+0.01%) from 95.698%
10282417405

push

github

web-flow
Merge pull request #452 from OpenDataServices/450-451-custom-warnings-exceptions

Use custom warnings and exceptions

27 of 46 new or added lines in 7 files covered. (58.7%)

1 existing line in 1 file now uncovered.

3390 of 3542 relevant lines covered (95.71%)

11.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.44
/flattentool/sort_xml.py
1
"""
2
Sort an XML file according to one or more provided schemas.
3

4
Based on https://github.com/OpenDataServices/iati-utils/blob/master/sort_iati.py
5

6
Copyright (c) 2013-2014 Ben Webb
7
Copyright (c) 2016 Open Data Services Co-operative Limited
8

9
Permission is hereby granted, free of charge, to any person obtaining a copy of
10
this software and associated documentation files (the "Software"), to deal in
11
the Software without restriction, including without limitation the rights to
12
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
13
the Software, and to permit persons to whom the Software is furnished to do so,
14
subject to the following conditions:
15

16
The above copyright notice and this permission notice shall be included in all
17
copies or substantial portions of the Software.
18

19
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
21
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
22
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
23
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25

26
"""
27
from collections import OrderedDict
12✔
28
from warnings import warn
12✔
29

30
from flattentool.exceptions import FlattenToolWarning
12✔
31

32
try:
12✔
33
    import lxml.etree as ET
12✔
34

35
    # Note that lxml is now "required" - it's listed as a requirement in
36
    # setup.py and is needed for the tests to pass.
37
    # However, stdlib etree still exists as an unsupported feature.
38
except ImportError:
×
39
    import xml.etree.ElementTree as ET
×
40

NEW
41
    warn(
×
42
        "Using stdlib etree may work, but is not supported. Please install lxml.",
43
        FlattenToolWarning,
44
    )
45

46
# Namespaces necessary for opening schema files
47
namespaces = {"xsd": "http://www.w3.org/2001/XMLSchema"}
12✔
48

49

50
class XMLSchemaWalker(object):
12✔
51
    """
52
    Class for traversing one or more XML schemas.
53

54
    Based on the Schema2Doc class in https://github.com/IATI/IATI-Standard-SSOT/blob/version-2.02/gen.py
55
    """
56

57
    def __init__(self, schemas):
12✔
58
        """
59
        schema -- the filename of the schema to use, e.g.
60
                  'iati-activities-schema.xsd'
61
        """
62
        self.trees = [ET.parse(schema) for schema in schemas]
12✔
63

64
    def get_schema_element(self, tag_name, name_attribute):
12✔
65
        """
66
        Return the specified element from the schema.
67

68
        tag_name -- the name of the tag in the schema, e.g. 'complexType'
69
        name_attribute -- the value of the 'name' attribute in the schema, ie.
70
                          the name of the element/type etc. being described,
71
                          e.g. iati-activities
72
        """
73
        for tree in self.trees:
12✔
74
            schema_element = tree.find(
12✔
75
                "xsd:{0}[@name='{1}']".format(tag_name, name_attribute),
76
                namespaces=namespaces,
77
            )
78
            if schema_element is not None:
12✔
79
                return schema_element
12✔
80
        return schema_element
12✔
81

82
    def handle_complexType(self, complexType):
12✔
83
        type_elements = []
12✔
84
        if complexType is not None:
12✔
85
            extension = complexType.find(
12✔
86
                "xsd:complexContent/xsd:extension", namespaces=namespaces
87
            )
88
            if extension:
12✔
89
                base = extension.attrib.get("base")
12✔
90
                complexType = self.get_schema_element("complexType", base)
12✔
91
                type_elements = self.handle_complexType(complexType)
12✔
92
            else:
93
                type_elements = []
12✔
94
            type_elements += complexType.findall(
12✔
95
                "xsd:choice/xsd:element", namespaces=namespaces
96
            ) + complexType.findall("xsd:sequence/xsd:element", namespaces=namespaces)
97
        return type_elements
12✔
98

99
    def element_loop(self, element, path):
12✔
100
        """
101
        Return information about the children of the supplied element.
102
        """
103
        a = element.attrib
12✔
104
        type_elements = []
12✔
105
        if "type" in a:
12✔
106
            complexType = self.get_schema_element("complexType", a["type"])
12✔
107
            type_elements += self.handle_complexType(complexType)
12✔
108

109
        children = (
12✔
110
            element.findall(
111
                "xsd:complexType/xsd:choice/xsd:element", namespaces=namespaces
112
            )
113
            + element.findall(
114
                "xsd:complexType/xsd:sequence/xsd:element", namespaces=namespaces
115
            )
116
            + element.findall(
117
                "xsd:complexType/xsd:all/xsd:element", namespaces=namespaces
118
            )
119
            + type_elements
120
        )
121
        child_tuples = []
12✔
122
        for child in children:
12✔
123
            a = child.attrib
12✔
124
            if "name" in a:
12✔
125
                child_tuples.append(
12✔
126
                    (a["name"], child, None, a.get("minOccurs"), a.get("maxOccurs"))
127
                )
128
            else:
129
                child_tuples.append(
12✔
130
                    (a["ref"], None, child, a.get("minOccurs"), a.get("maxOccurs"))
131
                )
132
        return child_tuples
12✔
133

134
    def create_schema_dict(self, parent_name, parent_element=None):
12✔
135
        """
136
        Create a nested OrderedDict representing the structure (and order!) of
137
        elements in the provided schema.
138
        """
139
        if parent_element is None:
12✔
140
            parent_element = self.get_schema_element("element", parent_name)
12✔
141
        if parent_element is None:
12✔
142
            return {}
×
143

144
        return OrderedDict(
12✔
145
            [
146
                (name, self.create_schema_dict(name, element))
147
                for name, element, _, _, _ in self.element_loop(parent_element, "")
148
            ]
149
        )
150

151

152
def sort_element(element, schema_subdict):
12✔
153
    """
154
    Sort the given element's children according to the order of schema_subdict.
155
    """
156
    children = list(element)
12✔
157
    for child in children:
12✔
158
        element.remove(child)
12✔
159
    keys = list(schema_subdict.keys())
12✔
160

161
    def index_key(x):
12✔
162
        if x.tag in keys:
12✔
163
            return keys.index(x.tag)
12✔
164
        else:
165
            return len(keys) + 1
12✔
166

167
    for child in sorted(children, key=index_key):
12✔
168
        element.append(child)
12✔
169
        sort_element(child, schema_subdict.get(child.tag, {}))
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc