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

OpenDataServices / flatten-tool / 6507626273

13 Oct 2023 11:25AM UTC coverage: 42.006% (-53.7%) from 95.72%
6507626273

Pull #433

github

odscjames
New "Geo" optional dependencies

https://github.com/OpenDataServices/flatten-tool/issues/424
Pull Request #433: New "Geo" optional dependencies

38 of 38 new or added lines in 6 files covered. (100.0%)

1466 of 3490 relevant lines covered (42.01%)

4.16 hits per line

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

20.0
/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
10✔
28
from warnings import warn
10✔
29

30
try:
10✔
31
    import lxml.etree as ET
10✔
32

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

39
    warn("Using stdlib etree may work, but is not supported. Please install lxml.")
×
40

41
# Namespaces necessary for opening schema files
42
namespaces = {"xsd": "http://www.w3.org/2001/XMLSchema"}
10✔
43

44

45
class XMLSchemaWalker(object):
10✔
46
    """
47
    Class for traversing one or more XML schemas.
48

49
    Based on the Schema2Doc class in https://github.com/IATI/IATI-Standard-SSOT/blob/version-2.02/gen.py
50
    """
51

52
    def __init__(self, schemas):
10✔
53
        """
54
        schema -- the filename of the schema to use, e.g.
55
                  'iati-activities-schema.xsd'
56
        """
57
        self.trees = [ET.parse(schema) for schema in schemas]
×
58

59
    def get_schema_element(self, tag_name, name_attribute):
10✔
60
        """
61
        Return the specified element from the schema.
62

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

77
    def handle_complexType(self, complexType):
10✔
78
        type_elements = []
×
79
        if complexType is not None:
×
80
            extension = complexType.find(
×
81
                "xsd:complexContent/xsd:extension", namespaces=namespaces
82
            )
83
            if extension:
×
84
                base = extension.attrib.get("base")
×
85
                complexType = self.get_schema_element("complexType", base)
×
86
                type_elements = self.handle_complexType(complexType)
×
87
            else:
88
                type_elements = []
×
89
            type_elements += complexType.findall(
×
90
                "xsd:choice/xsd:element", namespaces=namespaces
91
            ) + complexType.findall("xsd:sequence/xsd:element", namespaces=namespaces)
92
        return type_elements
×
93

94
    def element_loop(self, element, path):
10✔
95
        """
96
        Return information about the children of the supplied element.
97
        """
98
        a = element.attrib
×
99
        type_elements = []
×
100
        if "type" in a:
×
101
            complexType = self.get_schema_element("complexType", a["type"])
×
102
            type_elements += self.handle_complexType(complexType)
×
103

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

129
    def create_schema_dict(self, parent_name, parent_element=None):
10✔
130
        """
131
        Create a nested OrderedDict representing the structure (and order!) of
132
        elements in the provided schema.
133
        """
134
        if parent_element is None:
×
135
            parent_element = self.get_schema_element("element", parent_name)
×
136
        if parent_element is None:
×
137
            return {}
×
138

139
        return OrderedDict(
×
140
            [
141
                (name, self.create_schema_dict(name, element))
142
                for name, element, _, _, _ in self.element_loop(parent_element, "")
143
            ]
144
        )
145

146

147
def sort_element(element, schema_subdict):
10✔
148
    """
149
    Sort the given element's children according to the order of schema_subdict.
150
    """
151
    children = list(element)
×
152
    for child in children:
×
153
        element.remove(child)
×
154
    keys = list(schema_subdict.keys())
×
155

156
    def index_key(x):
×
157
        if x.tag in keys:
×
158
            return keys.index(x.tag)
×
159
        else:
160
            return len(keys) + 1
×
161

162
    for child in sorted(children, key=index_key):
×
163
        element.append(child)
×
164
        sort_element(child, schema_subdict.get(child.tag, {}))
×
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