1
votes

I am using XPath in R, and have an XML structure like this:

library(XML)

xml1 <- xmlParse('
<L0>
    <L1>
        <ID>Get this ID</ID>
        <L1N1>Ignore node 1</L1N1>
        <L1N2>
            <L2>
                <L2N1>Get this node and all others in L2</L2N1>
            </L2>
        </L1N2>
        <L1N3>Ignore node 3</L1N3>
    </L1>
    <L1>
        <ID>Get this ID</ID>
        <L1N1>Ignore node 1</L1N1>
        <L1N2>
            <L2>
                <L2N1>Get this node and all others in L2</L2N1>
            </L2>
        </L1N2>
        <L1N4>Ignore node 4</L1N4>
    </L1>
    <L1>
        <ID>Ignore this ID</ID>
        <L1N1>Ignore node 1</L1N1>
        <L1N3>Ignore node 3</L1N3>
        <L1N4>Ignore node 4</L1N4>
    </L1>
</L0>
                 ')

I would like to extract each L2 node and an uncle node (such as ID), but not other uncles. Each extracted result should go back to the grandparent node, L1. Here is the desired output:

## [[1]]
## <L1>
##   <ID>Get this ID</ID>
##   <L1N2>
##     <L2>
##       <L2N1>Get this node and all others in L2</L2N1>
##     </L2>
##  </L1N2>
## </L1> 

## [[2]]
## <L1>
##   <ID>Get this ID</ID>
##   <L1N2>
##     <L2>
##       <L2N1>Get this node and all others in L2</L2N1>
##     </L2>
##   </L1N2>
## </L1>

I can get the L1 nodes containing L2 descendants:

getNodeSet(xml1, "//L1[descendant::L2]")
## [[1]]
## <L1>
##   <ID>Get this ID</ID>
##   <L1N1>Ignore node 1</L1N1> ## *Want to exclude this*
##   <L1N2>
##     <L2>
##       <L2N1>Get this node and all others in L2</L2N1>
##     </L2>
##   </L1N2>
##   <L1N3>Ignore node 3</L1N3> ## *Want to exclude this*
## </L1> 
## 
## [[2]]
## <L1>
##   <ID>Get this ID</ID>
##   <L1N1>Ignore node 1</L1N1> ## *Want to exclude this*
##   <L1N2>
##     <L2>
##       <L2N1>Get this node and all others in L2</L2N1>
##     </L2>
##   </L1N2>
##   <L1N4>Ignore node 4</L1N4> ## *Want to exclude this*
## </L1>

...but this includes the uncles that I don't want. I can exclude those uncles and select the child nodes of L1 that I do want:

getNodeSet(xml1, "//L1/*[self::ID | child::L2]")
## [[1]]
## <ID>Get this ID</ID> 
##   
## [[2]]
## <L1N2>
##   <L2>
##     <L2N1>Get this node and all others in L2</L2N1>
##   </L2>
## </L1N2> 
## 
## [[3]]
## <ID>Get this ID</ID> 
##   
## [[4]]
## <L1N2>
##   <L2>
##     <L2N1>Get this node and all others in L2</L2N1>
##   </L2>
## </L1N2> 
## 
## [[5]]
## <ID>Ignore this ID</ID>

...but now ID and L2 are separate rather than under L1, and it's also including the elements from the third L1 node that doesn't have an L2.

Can XPath return the desired results? If not, can I use something else in R to achieve the results?

1

1 Answers

1
votes

This seems to do what you want (using your xml1):

trim <- function(node) {
  names     <- names(node)
  to.remove <- names[!(names %in% c("ID","L1N2"))]
  removeChildren(node,kids=to.remove)
}
lapply(xml1["//L1[descendant::L2]"],trim)
#  [[1]]
# <L1>
#   <ID>Get this ID</ID>
#   <L1N2>
#     <L2>
#       <L2N1>Get this node and all others in L2</L2N1>
#     </L2>
#   </L1N2>
# </L1> 
# 
# [[2]]
# <L1>
#   <ID>Get this ID</ID>
#   <L1N2>
#     <L2>
#       <L2N1>Get this node and all others in L2</L2N1>
#     </L2>
#   </L1N2>
# </L1> 

Of course you could use an anonymous function and put this on one line:

lapply(xml1["//L1[descendant::L2]"],function(node) removeChildren(node,kids=names(node)[!(names(node)%in%c("ID","L1N2"))]))