99#
1010from abc import ABCMeta , abstractmethod
1111from collections .abc import Iterator
12+ from functools import lru_cache
1213from typing import cast , Any , Optional , Union
1314
1415from elementpath .exceptions import ElementPathTypeError
1516from elementpath .protocols import XsdTypeProtocol , XsdAttributeProtocol , \
16- XsdElementProtocol , XsdSchemaProtocol
17+ XsdElementProtocol , XsdSchemaProtocol , XsdAttributeGroupProtocol
18+ from elementpath .namespaces import XSD_ANY_ATOMIC_TYPE
1719from elementpath .datatypes import AtomicType
1820from elementpath .etree import is_etree_element
1921from elementpath .xpath_context import XPathSchemaContext
@@ -113,6 +115,16 @@ def find(self, path: str, namespaces: Optional[dict[str, str]] = None) \
113115 return self ._base_element .find (path , namespaces = namespaces )
114116 return self ._schema .find (path , namespaces )
115117
118+ @lru_cache (maxsize = None )
119+ def cached_find (self , expanded_path : str ) -> Optional [PathResult ]:
120+ """
121+ Find a schema element or attribute using an expanded XPath expression.
122+ Currently unused in the code, it may be deprecated in the future.
123+ """
124+ if self ._base_element is not None :
125+ return self ._base_element .find (expanded_path )
126+ return self ._schema .find (expanded_path )
127+
116128 @property
117129 def xsd_version (self ) -> str :
118130 """The XSD version, returns '1.0' or '1.1'."""
@@ -131,49 +143,23 @@ def get_type(self, qname: str) -> Optional[XsdTypeProtocol]:
131143 """
132144 return self ._schema .maps .types .get (qname )
133145
134- def get_attribute (self , name : str , xsd_type : Optional [XsdTypeProtocol ] = None ) \
135- -> Optional [XsdAttributeProtocol ]:
146+ def get_attribute (self , qname : str ) -> Optional [XsdAttributeProtocol ]:
136147 """
137- Get the XSD attribute if it's matching in the provided scope, otherwise return None.
138-
139- :param name: the name of the attribute to retrieve.
140- :param xsd_type: an optional XSD type that represents the scope where matching \
141- the attribute name. If not provided, the scope is assumed to be the global scope.
142- :returns: an object that represents an XSD attribute or `None`.
143- """
144- if xsd_type is None :
145- return self ._schema .maps .attributes .get (name )
146- elif hasattr (xsd_type , 'attributes' ):
147- if name in xsd_type .attributes :
148- return cast (XsdAttributeProtocol , xsd_type .attributes [name ])
149- elif None not in xsd_type .attributes or \
150- not xsd_type .attributes [None ].is_matching (name ):
151- return None
152-
153- return self ._schema .maps .attributes .get (name )
148+ Get the XSD global attribute from the schema's scope.
154149
155- def get_element ( self , name : str , xsd_type : Optional [ XsdTypeProtocol ] = None ) \
156- -> Optional [ XsdElementProtocol ]:
150+ :param qname: the fully qualified name of the attribute to retrieve.
151+ :returns: an object that represents an XSD type or `None`.
157152 """
158- Get the XSD element if it's matching in the provided scope, otherwise return None.
153+ return self . _schema . maps . attributes . get ( qname )
159154
160- :param name: the name of the element to match.
161- :param xsd_type: an optional XSD type that represents the scope where matching \
162- the element name. If not provided, the scope is assumed to be the global scope.
163- :returns: an object that represents an XSD element or `None`.
155+ def get_element (self , qname : str ) -> Optional [XsdElementProtocol ]:
164156 """
165- if xsd_type is None :
166- return self ._schema .maps .elements .get (name )
157+ Get the XSD global element from the schema's scope.
167158
168- content = xsd_type .model_group
169- if content is not None :
170- for xsd_element in content .iter_elements ():
171- if xsd_element .is_matching (name ):
172- if xsd_element .name != name :
173- # a wildcard or a substitute
174- return self ._schema .maps .elements .get (name )
175- return xsd_element
176- return None
159+ :param qname: the fully qualified name of the element to retrieve.
160+ :returns: an object that represents an XSD type or `None`.
161+ """
162+ return self ._schema .maps .elements .get (qname )
177163
178164 def get_substitution_group (self , qname : str ) -> Optional [set [XsdElementProtocol ]]:
179165 """
@@ -185,6 +171,63 @@ def get_substitution_group(self, qname: str) -> Optional[set[XsdElementProtocol]
185171 """
186172 return self ._schema .maps .substitution_groups .get (qname )
187173
174+ def get_attribute_type (self , name : str , parent_type : Optional [XsdTypeProtocol ] = None ) \
175+ -> Optional [XsdTypeProtocol ]:
176+ """
177+ Get the XSD attribute type if the provided name is matching in the scope,
178+ otherwise return None.
179+
180+ :param name: the name of the attribute to retrieve.
181+ :param parent_type: an optional XSD type that represents the scope where matching \
182+ the attribute name. If not provided, the scope is assumed to be the global scope.
183+ """
184+ if parent_type is None :
185+ try :
186+ return self ._schema .maps .attributes [name ].type
187+ except KeyError :
188+ return None
189+ elif hasattr (parent_type , 'attributes' ):
190+ attributes = cast (XsdAttributeGroupProtocol , parent_type .attributes )
191+ if name in attributes :
192+ return attributes [name ].type
193+ elif None in attributes and attributes [None ].is_matching (name ):
194+ try :
195+ return self ._schema .maps .attributes [name ].type
196+ except KeyError :
197+ return None
198+ elif name .startswith ('{http://www.w3.org/2001/XMLSchema-instance}' ):
199+ return self ._schema .maps .types .get (XSD_ANY_ATOMIC_TYPE )
200+
201+ return None
202+
203+ def get_child_type (self , name : str , parent_type : Optional [XsdTypeProtocol ] = None ) \
204+ -> Optional [XsdTypeProtocol ]:
205+ """
206+ Get the child XSD type if the provided name is matching in the scope,
207+ otherwise return None.
208+
209+ :param name: the name of the child element to match.
210+ :param parent_type: an optional XSD type that represents the scope where matching \
211+ the child element name. If `None` or not provided the scope is the schema.
212+ """
213+ if parent_type is None :
214+ try :
215+ return self ._schema .maps .elements [name ].type
216+ except KeyError :
217+ return None
218+ elif (content := parent_type .model_group ) is not None :
219+ for xsd_element in content .iter_elements ():
220+ if xsd_element .is_matching (name ):
221+ if xsd_element .name == name :
222+ return xsd_element .type
223+ else :
224+ # a wildcard or a substitute
225+ try :
226+ return self ._schema .maps .elements [name ].type
227+ except KeyError :
228+ return None
229+ return None
230+
188231 @abstractmethod
189232 def is_instance (self , obj : Any , type_qname : str ) -> bool :
190233 """
0 commit comments