def from_xml(cls, element: Element): if element[1].tag != QName(FES20, "LowerBoundary") or element[2].tag != QName( FES20, "UpperBoundary" ): raise ExternalParsingError( f"{element.tag} should have 3 child nodes: " f"(expression), <LowerBoundary>, <UpperBoundary>" ) lower = get_child(element, FES20, "LowerBoundary") upper = get_child(element, FES20, "UpperBoundary") if len(lower) != 1: raise ExternalParsingError( f"{lower.tag} should have 1 expression child node" ) if len(upper) != 1: raise ExternalParsingError( f"{upper.tag} should have 1 expression child node" ) return cls( expression=Expression.from_child_xml(element[0]), lowerBoundary=Expression.from_child_xml(lower[0]), upperBoundary=Expression.from_child_xml(upper[0]), _source=element.tag, )
def from_child_xml(self, element: Element, allowed_types=None) -> BaseNode: """Convert the element into a Python class. This locates the parser from the registered tag. It's assumed that the tag has an "from_xml()" method too. """ try: real_cls = self.resolve_class(element.tag) except ExternalParsingError as e: if allowed_types: # Show better exception message types = ", ".join(c.__name__ for c in allowed_types) raise ExternalParsingError( f"{e}, expected one of: {types}") from None raise # Check whether the resolved class is indeed a valid option here. if allowed_types is not None and not issubclass( real_cls, allowed_types): types = ", ".join(c.__name__ for c in allowed_types) raise ExternalParsingError( f"Unexpected {real_cls.__name__} for <{element.tag}> node, " f"expected one of: {types}") return real_cls.from_xml(element)
def _expect_tag_decorator(cls, element: Element, *args, **kwargs): if element.tag not in valid_tags: raise ExternalParsingError( f"{cls.__name__} parser expects an <{expect0}> node, got <{element.tag}>" ) if leaf and len(element): raise ExternalParsingError( f"Unsupported child element for {element.tag} element: {element[0].tag}." ) return func(cls, element, *args, **kwargs)
def resolve_class(self, tag_name) -> type[BaseNode]: # Resolve the dataclass using the tag name try: return self.parsers[tag_name] except KeyError: raise ExternalParsingError( f"Unsupported tag: <{tag_name}>") from None
def parse_bool(raw_value: str): if raw_value in ("true", "1"): return True elif raw_value in ("false", "0"): return False else: raise ExternalParsingError(f"Can't cast '{raw_value}' to boolean")
def get_attribute(element: Element, name) -> str: """Resolve an attribute, raise an error when it's missing.""" try: return element.attrib[name] except KeyError: raise ExternalParsingError( f"Element {element.tag} misses required attribute '{name}'" ) from None
def from_xml(cls, element: Element): geometries = gml.find_gml_nodes(element) if not geometries: raise ExternalParsingError( f"Missing gml element in <{element.tag}>") elif len(geometries) > 1: raise ExternalParsingError( f"Multiple gml elements found in <{element.tag}>") return cls( valueReference=ValueReference.from_xml( get_child(element, FES20, "ValueReference")), operatorType=DistanceOperatorName.from_xml(element), geometry=gml.parse_gml_node(geometries[0]), distance=Measure.from_xml(get_child(element, FES20, "Distance")), _source=element.tag, )
def to_python(self, raw_value): """Convert a raw string value to this type representation""" if self.is_geometry: # Leave complex values as-is. return raw_value try: return self._to_python_func(raw_value) except ExternalParsingError: raise # subclass of ValueError so explicitly caught and reraised except (TypeError, ValueError, ArithmeticError) as e: # ArithmeticError is base of DecimalException raise ExternalParsingError(f"Can't cast '{raw_value}' to {self}.") from e
def from_xml(cls, element: Element): children = len(element) if not children: # Common case: value is raw text raw_value = element.text elif children == 1 and is_gml_element(element[0]): # Possible: a <gml:Envelope> element to compare something against. raw_value = parse_gml_node(element[0]) else: raise ExternalParsingError( f"Unsupported child element for <Literal> element: {element[0].tag}." ) return cls(raw_value=raw_value, raw_type=element.get("type"))
def from_string(cls, bbox): """Parse the bounding box from an input string. It can either be 4 coordinates, or 4 coordinates with a special reference system. """ bbox = bbox.split(",") if not (4 <= len(bbox) <= 5): raise ExternalParsingError(f"Input does not contain bounding box, " f"expected 4 or 5 values, not {bbox}.") return cls( float(bbox[0]), float(bbox[1]), float(bbox[2]), float(bbox[3]), CRS.from_string(bbox[4]) if len(bbox) == 5 else None, )
def _expect_children_decorator(cls, element: Element, *args, **kwargs): if len(element) < min_child_nodes: type_names = ", ".join( sorted( set( chain.from_iterable(([child_type] if isinstance( child_type, str) else chain.from_iterable( sub_type.xml_tags for sub_type in child_type.__subclasses__( ))) for child_type in expect_types)))) suffix = f" (possible tags: {type_names})" if type_names else "" raise ExternalParsingError( f"<{element.tag}> should have {min_child_nodes} child nodes, " f"got {len(element)}{suffix}") return func(cls, element, *args, **kwargs)
def from_xml(cls, element: Element): operator_type = SpatialOperatorName.from_xml(element) if operator_type is SpatialOperatorName.BBOX and len(element) == 1: # For BBOX, the geometry operator is optional ref = None geo = element[0] else: if len(element) != 2: raise ExternalParsingError(f"{element.tag} should have 2 operators") ref, geo = list(element) return cls( operatorType=operator_type, operand1=ValueReference.from_xml(ref) if ref is not None else None, operand2=tag_registry.from_child_xml( geo, allowed_types=SpatialDescription.__args__ # get_args() in 3.8 ), _source=element.tag, )
def to_python(self, raw_value): """Convert a raw string value to this type representation""" if self.is_geometry: # Leave complex values as-is. return raw_value try: func = TYPES_TO_PYTHON[self] except KeyError: raise NotImplementedError( f'Casting to "{self}"> is not implemented.') from None try: return func(raw_value) except ExternalParsingError: raise except (TypeError, ValueError, ArithmeticError) as e: # ArithmeticError is base of DecimalException raise ExternalParsingError( f"Can't cast '{raw_value}' to {self}.") from e
def build_query(self, compiler: CompiledQuery) -> Q: lhs, rhs = self.expression if isinstance(rhs, Literal): value = str(rhs.value) # value could be auto-casted to int. # Not using r"\" here as that is a syntax error. if self.escapeChar != "\\": value = value.replace("\\", "\\\\").replace(self.escapeChar, "\\") if self.wildCard != "%": value = value.replace("%", r"\%").replace(self.wildCard, "%") if self.singleChar != "_": value = value.replace("_", r"\_").replace(self.singleChar, "_") rhs = Literal(raw_value=value) else: raise ExternalParsingError( f"Expected a literal value for the {self.tag} operator." ) # Use the FesLike lookup return self.build_compare(compiler, lhs=lhs, lookup="fes_like", rhs=rhs)
def from_string(cls, text: AnyStr) -> Filter: """Parse an XML <fes20:Filter> string. This uses defusedxml by default, to avoid various XML injection attacks. :raises ValueError: When data is incorrect, or XML has syntax errors. :raises NotImplementedError: When unsupported features are called. """ if isinstance(text, str): end_first = text.index(">") first_tag = text[:end_first].lstrip() if "xmlns" not in first_tag: # Allow KVP requests without a namespace # Both geoserver and mapserver support this. if first_tag == "<Filter" or first_tag.startswith("<Filter "): text = f'{first_tag} xmlns="{FES20}" xmlns:gml="{GML32}"{text[end_first:]}' try: root_element = fromstring(text) except ParseError as e: # Offer consistent results for callers to check for invalid data. raise ExternalParsingError(str(e)) from e return Filter.from_xml(root_element, source=text)
def parse_iso_datetime(raw_value: str) -> datetime: value = parse_datetime(raw_value) if value is None: raise ExternalParsingError( "Date must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format.") return value
def parse_gml_node(element: Element) -> Union[FES_GML_NODES]: """Parse the element""" if not is_gml_element(element): raise ExternalParsingError(f"Expected GML namespace for {element.tag}") return tag_registry.from_child_xml(element, allowed_types=FES_GML_NODES)