예제 #1
0
    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,
        )
예제 #2
0
    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)
예제 #3
0
        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)
예제 #4
0
 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
예제 #5
0
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")
예제 #6
0
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
예제 #7
0
    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,
        )
예제 #8
0
    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
예제 #9
0
    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"))
예제 #10
0
    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,
        )
예제 #11
0
        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)
예제 #12
0
    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,
        )
예제 #13
0
    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
예제 #14
0
    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)
예제 #15
0
    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)
예제 #16
0
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
예제 #17
0
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)