What is the most efficient way to find relative XPath between two elements?

Having looked at various popular modules for working with XML / XPath, I have not yet seen a direct way to achieve this.

Essentially, the interface will look something like this:

my $xpath = get_path($node1, $node2); 

... which returns the relative path from $ node1 to $ node2.

I include my own time in the calculation of "efficiency" - I will take any existing solution to this problem. Otherwise, I would like to know some of the pitfalls that can be encountered in any "obvious" home solutions.

At the top of my head, I could just imagine the first search for $ node2 in the descendants of $ node1, and then with the error that the fighters $ node1 do the same. How I fear it will be just as resource intensive?

In my particular use case, I can assume that the absolute paths of both $ node1 and $ node2 are known. Given this, I would like to think that there is some kind of “XPath math” that can be done between two full paths without having to run all over the tree, but I don’t know what this process will look like.

Summarizing:

1) Do any existing CPAN modules do what I want to do easily?

2) If not, what is the effective way to do this?

+6
source share
2 answers

Find the absolute path for both nodes.

 ref: root foo bar[2] baz[1] moo target: root foo bar[2] baz[2] moo 

Remove the common leading segments.

 ref: baz[1] moo target: baz[2] moo 

For each segment in the link, add a target with a segment ..

 .. .. baz[2] moo 

Convert to XPath.

 ../../baz[2]/moo 

the code:

 use XML::LibXML qw( XML_ATTRIBUTE_NODE XML_ELEMENT_NODE ); sub get_path_segs { my ($node) = @_; my @path = split(/\//, $node->nodePath()); shift(@path); return @path; } sub get_path { my ($ref, $targ) = @_; die if $ref->nodeType() != XML_ELEMENT_NODE && $ref->nodeType() != XML_ATTRIBUTE_NODE; die if $targ->nodeType() != XML_ELEMENT_NODE && $targ->nodeType() != XML_ATTRIBUTE_NODE; my @ref = get_path_segs($ref); my @targ = get_path_segs($targ); while (@ref && @targ && $ref[0] eq $targ[0]) { shift(@ref); shift(@targ); } while (@ref) { pop(@ref); unshift(@targ, '..'); } return @targ ? join('/', @targ) : '.'; } 

It currently supports element and attribute nodes. It can be extended to support other node types, perhaps trivially.

+6
source

There are two possible outcomes.

  • two nodes share a common ancestor
  • one node is a descendant of another

The logical course of action will be

  • Go through the parent nodes of each node and see if there is a common anchor.
  • At the same time, check whether one of the ancestors is really identical to another node.

In any case, the resulting path will be the shortest.

Create a relative XPath expression from the parent node chains. Finding an attractive presentation can be even the most difficult part of the whole problem.

+2
source

Source: https://habr.com/ru/post/895175/


All Articles