Package xmlschema_acue :: Module xpath

Source Code for Module xmlschema_acue.xpath

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c), 2016-2019, SISSA (International School for Advanced Studies). 
  4  # All rights reserved. 
  5  # This file is distributed under the terms of the MIT License. 
  6  # See the file 'LICENSE' in the root directory of the present 
  7  # distribution, or http://opensource.org/licenses/MIT. 
  8  # 
  9  # @author Davide Brunato <brunato@sissa.it> 
 10  # 
 11  """ 
 12  This module defines a mixin class for enabling XPath on schemas. 
 13  """ 
 14  from __future__ import unicode_literals 
 15  from abc import abstractmethod 
 16  from elementpath import XPath2Parser, XPathContext 
 17   
 18  from xmlschema_acue.compat import Sequence 
 19  from xmlschema_acue.qnames import XSD_SCHEMA 
20 21 22 -class ElementPathContext(XPathContext):
23 """ 24 XPath dynamic context class for XMLSchema. Implements safe iteration methods for 25 schema elements that recognize circular references. 26 """
27 - def _iter_descendants(self):
28 def safe_iter_descendants(context): 29 elem = context.item 30 yield elem 31 if elem.text is not None: 32 context.item = elem.text 33 yield context.item 34 if len(elem): 35 context.size = len(elem) 36 for context.position, context.item in enumerate(elem): 37 if context.item.is_global: 38 for item in safe_iter_descendants(context): 39 yield item 40 elif getattr(context.item, 'ref', None) is not None: 41 yield context.item 42 elif context.item not in local_items: 43 local_items.append(context.item) 44 for item in safe_iter_descendants(context): 45 yield item
46 47 local_items = [] 48 return safe_iter_descendants(self)
49
50 - def _iter_context(self):
51 def safe_iter_context(context): 52 elem = context.item 53 yield elem 54 if elem.text is not None: 55 context.item = elem.text 56 yield context.item 57 58 for item in elem.attrib.items(): 59 context.item = item 60 yield item 61 62 if len(elem): 63 context.size = len(elem) 64 for context.position, context.item in enumerate(elem): 65 if context.item.is_global: 66 for item in safe_iter_context(context): 67 yield item 68 elif getattr(context.item, 'ref', None) is not None: 69 yield context.item 70 elif context.item not in local_items: 71 local_items.append(context.item) 72 for item in safe_iter_context(context): 73 yield item
74 75 local_items = [] 76 return safe_iter_context(self) 77
78 79 -class ElementPathMixin(Sequence):
80 """ 81 Mixin abstract class for enabling ElementTree and XPath API on XSD components. 82 83 :cvar text: The Element text. Its value is always `None`. For compatibility with the ElementTree API. 84 :cvar tail: The Element tail. Its value is always `None`. For compatibility with the ElementTree API. 85 """ 86 _attrib = {} 87 text = None 88 tail = None 89 namespaces = {} 90 xpath_default_namespace = None 91 92 @abstractmethod
93 - def __iter__(self):
94 pass
95
96 - def __getitem__(self, i):
97 try: 98 return [e for e in self][i] 99 except AttributeError: 100 raise IndexError('child index out of range')
101
102 - def __reversed__(self):
103 return reversed([e for e in self])
104
105 - def __len__(self):
106 return len([e for e in self])
107 108 @property
109 - def tag(self):
110 """Alias of the *name* attribute. For compatibility with the ElementTree API.""" 111 return getattr(self, 'name')
112 113 @property
114 - def attrib(self):
115 """Returns the Element attributes. For compatibility with the ElementTree API.""" 116 return getattr(self, 'attributes', self._attrib)
117
118 - def get(self, key, default=None):
119 """Gets an Element attribute. For compatibility with the ElementTree API.""" 120 return self.attrib.get(key, default)
121
122 - def iterfind(self, path, namespaces=None):
123 """ 124 Creates and iterator for all XSD subelements matching the path. 125 126 :param path: an XPath expression that considers the XSD component as the root element. 127 :param namespaces: is an optional mapping from namespace prefix to full name. 128 :return: an iterable yielding all matching XSD subelements in document order. 129 """ 130 path = path.strip() 131 if path.startswith('/') and not path.startswith('//'): 132 path = ''.join(['/', XSD_SCHEMA, path]) 133 if namespaces is None: 134 namespaces = {k: v for k, v in self.namespaces.items() if k} 135 136 parser = XPath2Parser(namespaces, strict=False, default_namespace=self.xpath_default_namespace) 137 root_token = parser.parse(path) 138 context = ElementPathContext(self) 139 return root_token.select(context)
140
141 - def find(self, path, namespaces=None):
142 """ 143 Finds the first XSD subelement matching the path. 144 145 :param path: an XPath expression that considers the XSD component as the root element. 146 :param namespaces: an optional mapping from namespace prefix to full name. 147 :return: The first matching XSD subelement or ``None`` if there is not match. 148 """ 149 path = path.strip() 150 if path.startswith('/') and not path.startswith('//'): 151 path = ''.join(['/', XSD_SCHEMA, path]) 152 if namespaces is None: 153 namespaces = {k: v for k, v in self.namespaces.items() if k} 154 parser = XPath2Parser(namespaces, strict=False, default_namespace=self.xpath_default_namespace) 155 root_token = parser.parse(path) 156 context = ElementPathContext(self) 157 return next(root_token.select(context), None)
158
159 - def findall(self, path, namespaces=None):
160 """ 161 Finds all XSD subelements matching the path. 162 163 :param path: an XPath expression that considers the XSD component as the root element. 164 :param namespaces: an optional mapping from namespace prefix to full name. 165 :return: a list containing all matching XSD subelements in document order, an empty \ 166 list is returned if there is no match. 167 """ 168 path = path.strip() 169 if path.startswith('/') and not path.startswith('//'): 170 path = ''.join(['/', XSD_SCHEMA, path]) 171 if namespaces is None: 172 namespaces = {k: v for k, v in self.namespaces.items() if k} 173 174 parser = XPath2Parser(namespaces, strict=False, default_namespace=self.xpath_default_namespace) 175 root_token = parser.parse(path) 176 context = ElementPathContext(self) 177 return root_token.get_results(context)
178
179 - def iter(self, tag=None):
180 """ 181 Creates an iterator for the XSD element and its subelements. If tag is not `None` or '*', 182 only XSD elements whose matches tag are returned from the iterator. Local elements are 183 expanded without repetitions. Element references are not expanded because the global 184 elements are not descendants of other elements. 185 """ 186 def safe_iter(elem): 187 if tag is None or elem.is_matching(tag): 188 yield elem 189 for child in elem: 190 if child.is_global: 191 for e in safe_iter(child): 192 yield e 193 elif getattr(child, 'ref', None) is not None: 194 if tag is None or elem.is_matching(tag): 195 yield child 196 elif child not in local_elements: 197 local_elements.append(child) 198 for e in safe_iter(child): 199 yield e
200 201 if tag == '*': 202 tag = None 203 local_elements = [] 204 return safe_iter(self)
205
206 - def iterchildren(self, tag=None):
207 """ 208 Creates an iterator for the child elements of the XSD component. If *tag* is not `None` 209 or '*', only XSD elements whose name matches tag are returned from the iterator. 210 """ 211 if tag == '*': 212 tag = None 213 for child in self: 214 if tag is None or child.is_matching(tag): 215 yield child
216