Пример #1
0
def within_distance(node, context):
    confirm_args_count(node, 4)
    property_name, coords, distance, unit = node.args
    property_name = _property_name_to_string(property_name, node)

    try:
        geo_point = GeoPoint.from_string(coords, flexible=True)
    except BadValueError as e:
        raise XPathFunctionException(
            _(f"The second argument to '{node.name}' must be valid coordinates"
              ), serialize(node)) from e

    try:
        distance = float(distance)
    except ValueError as e:
        raise XPathFunctionException(
            _(f"The third argument to '{node.name}' must be a number, got '{distance}'"
              ), serialize(node)) from e

    if unit not in DISTANCE_UNITS:
        raise XPathFunctionException(
            _(f"'{unit}' is not a valid distance unit. Expected one of {', '.join(DISTANCE_UNITS)}"
              ), serialize(node))

    return case_property_geo_distance(property_name, geo_point,
                                      **{unit: distance})
Пример #2
0
def _property_name_to_string(value, node):
    if isinstance(value, Step):
        return serialize(value)
    if isinstance(value, str):
        return value
    raise XPathFunctionException(
        _(f"The first argument to '{node.name}' must be a valid case property name"
          ), serialize(node))
Пример #3
0
def _create_xml_node(xast, node, context, insert_index=None):
    if isinstance(xast, ast.Step):
        if isinstance(xast.node_test, ast.NameTest):
            # check the predicates (if any) to verify they're constructable
            for pred in xast.predicates:
                if not _predicate_is_constructible(pred):
                    msg = (
                        "Missing element for '%s', and node creation is "
                        + "supported only for simple child and attribute "
                        + "nodes with simple predicates."
                    ) % (serialize(xast),)
                    raise Exception(msg)

            # create the node itself
            if xast.axis in (None, "child"):
                new_node = _create_child_node(node, context, xast, insert_index)
            elif xast.axis in ("@", "attribute"):
                new_node = _create_attribute_node(node, context, xast)

            # and create any nodes necessary for the predicates
            for pred in xast.predicates:
                _construct_predicate(pred, new_node, context)

            return new_node

        # if this is a text() node, we don't need to create anything further
        # return the node that will be parent to text()
        elif _is_text_nodetest(xast):
            return node

    elif isinstance(xast, ast.BinaryExpression):
        if xast.op == "/":
            left_xpath = serialize(xast.left)
            left_node = _find_xml_node(left_xpath, node, context)
            if left_node is None:
                left_node = _create_xml_node(xast.left, node, context)
            return _create_xml_node(xast.right, left_node, context)

    # anything else, throw an exception:
    msg = (
        "Missing element for '%s', and node creation is supported " + "only for simple child and attribute nodes."
    ) % (serialize(xast),)
    raise Exception(msg)
Пример #4
0
def _remove_xml(xast, node, context, if_empty=False):
    '''Remove a node or attribute.  For multipart XPaths that are
    constructible by :mod:`eulxml.xmlmap`, the corresponding nodes
    will be removed if they are empty (other than predicates specified
    in the XPath).

    :param xast: parsed xpath (xpath abstract syntax tree) from
	:mod:`eulxml.xpath`
    :param node: lxml node relative to which xast should be removed
    :param context: any context required for the xpath (e.g.,
    	namespace definitions)
    :param if_empty: optional boolean; only remove a node if it is
	empty (no attributes and no child nodes); defaults to False

    :returns: True if something was deleted
    '''
    if isinstance(xast, ast.Step):
        if isinstance(xast.node_test, ast.NameTest):
            if xast.axis in (None, 'child'):
                return _remove_child_node(node,
                                          context,
                                          xast,
                                          if_empty=if_empty)
            elif xast.axis in ('@', 'attribute'):
                return _remove_attribute_node(node, context, xast)
        # special case for text()
        # since it can't be removed, at least clear out any value in the text node
        elif _is_text_nodetest(xast):
            node.text = ''
            return True

    # If the xpath is a multi-step path (e.g., foo[@id="a"]/bar[@id="b"]/baz),
    # remove the leaf node.  If the remaining portions of that path
    # could have been constructed when setting the node and are empty
    # (other than any predicates defined in the xpath), remove them as well.
    elif isinstance(xast, ast.BinaryExpression):
        if xast.op == '/':
            left_xpath = serialize(xast.left)
            left_node = _find_xml_node(left_xpath, node, context)
            if left_node is not None:
                # remove the last element in the xpath
                removed = _remove_xml(
                    xast.right, left_node, context,
                    if_empty=if_empty)  # honor current if_empty flag

                # If the left portion of the xpath is something we
                # could have constructed, remove it if it is empty.
                if removed and _predicate_is_constructible(left_xpath):
                    _remove_xml(xast.left, node, context, if_empty=True)

                # report on whether the leaf node was removed or not,
                # regardless of what was done with left portion of the path
                return removed

    return False
Пример #5
0
def _create_xml_node(xast, node, context, insert_index=None):
    if isinstance(xast, ast.Step):
        if isinstance(xast.node_test, ast.NameTest):
            # check the predicates (if any) to verify they're constructable
            for pred in xast.predicates:
                if not _predicate_is_constructible(pred):
                    msg = ("Missing element for '%s', and node creation is " +
                           "supported only for simple child and attribute " +
                           "nodes with simple predicates.") % (
                               serialize(xast), )
                    raise Exception(msg)

            # create the node itself
            if xast.axis in (None, 'child'):
                new_node = _create_child_node(node, context, xast,
                                              insert_index)
            elif xast.axis in ('@', 'attribute'):
                new_node = _create_attribute_node(node, context, xast)

            # and create any nodes necessary for the predicates
            for pred in xast.predicates:
                _construct_predicate(pred, new_node, context)

            return new_node

        # if this is a text() node, we don't need to create anything further
        # return the node that will be parent to text()
        elif _is_text_nodetest(xast):
            return node

    elif isinstance(xast, ast.BinaryExpression):
        if xast.op == '/':
            left_xpath = serialize(xast.left)
            left_node = _find_xml_node(left_xpath, node, context)
            if left_node is None:
                left_node = _create_xml_node(xast.left, node, context)
            return _create_xml_node(xast.right, left_node, context)

    # anything else, throw an exception:
    msg = ("Missing element for '%s', and node creation is supported " + \
           "only for simple child and attribute nodes.") % (serialize(xast),)
    raise Exception(msg)
Пример #6
0
def _remove_xml(xast, node, context, if_empty=False):
    '''Remove a node or attribute.  For multipart XPaths that are
    constructible by :mod:`eulxml.xmlmap`, the corresponding nodes
    will be removed if they are empty (other than predicates specified
    in the XPath).

    :param xast: parsed xpath (xpath abstract syntax tree) from
	:mod:`eulxml.xpath`
    :param node: lxml node relative to which xast should be removed
    :param context: any context required for the xpath (e.g.,
    	namespace definitions)
    :param if_empty: optional boolean; only remove a node if it is
	empty (no attributes and no child nodes); defaults to False

    :returns: True if something was deleted
    '''
    if isinstance(xast, ast.Step):
        if isinstance(xast.node_test, ast.NameTest):
            if xast.axis in (None, 'child'):
                return _remove_child_node(node, context, xast, if_empty=if_empty)
            elif xast.axis in ('@', 'attribute'):
                return _remove_attribute_node(node, context, xast)
        # special case for text()
        # since it can't be removed, at least clear out any value in the text node
        elif _is_text_nodetest(xast):
            node.text = ''
            return True

    # If the xpath is a multi-step path (e.g., foo[@id="a"]/bar[@id="b"]/baz),
    # remove the leaf node.  If the remaining portions of that path
    # could have been constructed when setting the node and are empty
    # (other than any predicates defined in the xpath), remove them as well.
    elif isinstance(xast, ast.BinaryExpression):
        if xast.op == '/':
            left_xpath = serialize(xast.left)
            left_node = _find_xml_node(left_xpath, node, context)
            if left_node is not None:
                # remove the last element in the xpath
                removed = _remove_xml(xast.right, left_node, context,
                                      if_empty=if_empty) # honor current if_empty flag

                # If the left portion of the xpath is something we
                # could have constructed, remove it if it is empty.
                if removed and _predicate_is_constructible(left_xpath):
                    _remove_xml(xast.left, node, context, if_empty=True)

                # report on whether the leaf node was removed or not,
                # regardless of what was done with left portion of the path
                return removed

    return False
Пример #7
0
def _remove_predicates(xast, node, context):
    '''Remove any constructible predicates specified in the xpath
    relative to the specified node.

    :param xast: parsed xpath (xpath abstract syntax tree) from
	:mod:`eulxml.xpath`
    :param node: lxml element which predicates will be removed from
    :param context: any context required for the xpath (e.g.,
    	namespace definitions)

    :returns: updated a copy of the xast without the predicates that
	were successfully removed
    '''
    # work from a copy since it may be modified
    xast_c = deepcopy(xast)
    # check if predicates are constructable
    for pred in list(xast_c.predicates):
        # ignore predicates that we can't construct
        if not _predicate_is_constructible(pred):
            continue

        if isinstance(pred, ast.BinaryExpression):
            # TODO: support any other predicate operators?
            # predicate construction supports op /

            # If the xml still matches the constructed value, remove it.
            # e.g., @type='text' or level='leaf'
            if pred.op == '=' and \
                   node.xpath(serialize(pred), **context) is True:
                # predicate xpath returns True if node=value

                if isinstance(pred.left, ast.Step):
                    if pred.left.axis in ('@', 'attribute'):
                        if _remove_attribute_node(node, context, pred.left):
                            # remove from the xast
                            xast_c.predicates.remove(pred)
                    elif pred.left.axis in (None, 'child'):
                        if _remove_child_node(node,
                                              context,
                                              pred.left,
                                              if_empty=True):
                            xast_c.predicates.remove(pred)

                elif isinstance(pred.left, ast.BinaryExpression):
                    # e.g., level/@id='b' or level/deep='deeper'
                    # - value has already been checked by xpath above,
                    # so just remove the multipart path
                    _remove_xml(pred.left, node, context, if_empty=True)

    return xast_c
Пример #8
0
def _remove_predicates(xast, node, context):
    '''Remove any constructible predicates specified in the xpath
    relative to the specified node.

    :param xast: parsed xpath (xpath abstract syntax tree) from
	:mod:`eulxml.xpath`
    :param node: lxml element which predicates will be removed from
    :param context: any context required for the xpath (e.g.,
    	namespace definitions)

    :returns: updated a copy of the xast without the predicates that
	were successfully removed
    '''
    # work from a copy since it may be modified
    xast_c = deepcopy(xast)
    # check if predicates are constructable
    for pred in list(xast_c.predicates):
        # ignore predicates that we can't construct
        if not _predicate_is_constructible(pred):
            continue

        if isinstance(pred, ast.BinaryExpression):
            # TODO: support any other predicate operators?
            # predicate construction supports op /

            # If the xml still matches the constructed value, remove it.
            # e.g., @type='text' or level='leaf'
            if pred.op == '=' and \
                   node.xpath(serialize(pred), **context) is True:
                # predicate xpath returns True if node=value

                if isinstance(pred.left, ast.Step):
                    if pred.left.axis in ('@', 'attribute'):
                        if _remove_attribute_node(node, context, pred.left):
                            # remove from the xast
                            xast_c.predicates.remove(pred)
                    elif pred.left.axis in (None, 'child'):
                        if _remove_child_node(node, context, pred.left, if_empty=True):
                            xast_c.predicates.remove(pred)

                elif isinstance(pred.left, ast.BinaryExpression):
                    # e.g., level/@id='b' or level/deep='deeper'
                    # - value has already been checked by xpath above,
                    # so just remove the multipart path
                    _remove_xml(pred.left, node, context, if_empty=True)

    return xast_c
Пример #9
0
    def set(self, xpath, xast, node, context, mapper, value):
        xvalue = mapper.to_xml(value)
        match = _find_xml_node(xpath, node, context)

        if xvalue is None:
            # match must be None. if it exists, delete it.
            if match is not None:
                removed = _remove_xml(xast, node, context)
                # if a node can't be removed, warn since it could have unexpected results
                if not removed:
                    logger.warn("""Could not remove xml for '%s' from %r""" % (serialize(xast), node))
        else:
            if match is None:
                match = _create_xml_node(xast, node, context)
            # terminal (rightmost) step informs how we update the xml
            step = _find_terminal_step(xast)
            _set_in_xml(match, xvalue, context, step)
Пример #10
0
    def set(self, xpath, xast, node, context, mapper, value):
        xvalue = mapper.to_xml(value)
        match = _find_xml_node(xpath, node, context)

        if xvalue is None:
            # match must be None. if it exists, delete it.
            if match is not None:
                removed = _remove_xml(xast, node, context)
                # if a node can't be removed, warn since it could have unexpected results
                if not removed:
                    logger.warn('''Could not remove xml for '%s' from %r''' % \
                                (serialize(xast), node))
        else:
            if match is None:
                match = _create_xml_node(xast, node, context)
            # terminal (rightmost) step informs how we update the xml
            step = _find_terminal_step(xast)
            _set_in_xml(match, xvalue, context, step)
Пример #11
0
def _remove_xml(xast, node, context):
    'Remove a node or attribute; returns True when something is deleted'
    if isinstance(xast, ast.Step):
        if isinstance(xast.node_test, ast.NameTest):
            if xast.axis in (None, 'child'):
                return _remove_child_node(node, context, xast)
            elif xast.axis in ('@', 'attribute'):
                return _remove_attribute_node(node, context, xast)
        # special case for text()
        # since it can't be removed, at least clear out any value in the text node
        elif _is_text_nodetest(xast):
            node.text = ''
            return True
    elif isinstance(xast, ast.BinaryExpression):
        if xast.op == '/':
            left_xpath = serialize(xast.left)
            left_node = _find_xml_node(left_xpath, node, context)
            if left_node is not None:
                return _remove_xml(xast.right, left_node, context)
    return False
Пример #12
0
def _remove_child_node(node, context, xast, if_empty=False):
    """Remove a child node based on the specified xpath.
    
    :param node: lxml element relative to which the xpath will be
    	interpreted
    :param context: any context required for the xpath (e.g.,
    	namespace definitions)
    :param xast: parsed xpath (xpath abstract syntax tree) from
	:mod:`eulxml.xpath`
    :param if_empty: optional boolean; only remove a node if it is
	empty (no attributes and no child nodes); defaults to False

    :returns: True if a node was deleted
    """
    xpath = serialize(xast)
    child = _find_xml_node(xpath, node, context)
    if child is not None:
        # if if_empty was specified and node has children or attributes
        # other than any predicates defined in the xpath, don't remove
        if if_empty is True and not _empty_except_predicates(xast, child, context):
            return False
        node.remove(child)
        return True
Пример #13
0
def _remove_child_node(node, context, xast, if_empty=False):
    '''Remove a child node based on the specified xpath.

    :param node: lxml element relative to which the xpath will be
    	interpreted
    :param context: any context required for the xpath (e.g.,
    	namespace definitions)
    :param xast: parsed xpath (xpath abstract syntax tree) from
	:mod:`eulxml.xpath`
    :param if_empty: optional boolean; only remove a node if it is
	empty (no attributes and no child nodes); defaults to False

    :returns: True if a node was deleted
    '''
    xpath = serialize(xast)
    child = _find_xml_node(xpath, node, context)
    if child is not None:
        # if if_empty was specified and node has children or attributes
        # other than any predicates defined in the xpath, don't remove
        if if_empty is True and \
               not _empty_except_predicates(xast, child, context):
            return False
        node.remove(child)
        return True
Пример #14
0
def confirm_args_count(node, expected):
    actual = len(node.args)
    if actual != expected:
        raise XPathFunctionException(
            _(f"The '{node.name}' function accepts exactly {expected} arguments, got {actual}"
              ), serialize(node))
Пример #15
0
 def round_trip(self, xpath_str):
     xp = xpath.parse(xpath_str)
     self.assertEqual(xpath_str, serialize(xp))
Пример #16
0
 def round_trip(self, xpath_str):
     xp = xpath.parse(xpath_str)
     self.assertEqual(xpath_str, serialize(xp))
Пример #17
0
def _remove_child_node(node, context, xast):
    xpath = serialize(xast)
    child = _find_xml_node(xpath, node, context)
    if child is not None:
        node.remove(child)
        return True