I am starting with a large, complex XML-file and need to extract values and attributes of certain sub(sub...)nodes. But because not all subnodes have all wanted values (some are missing) I cannot easily use the very fast xml_find_all (xml2 package), because it will of course not include the subnodes with missing values.
My solution is to use a for-loop cycling through all of my xml-nodes (Objects) and check within each node, if my desired value is exists - if yes, extract it. Thanks to the index of the loop I know to which Object it belongs and write it to the corresponding data.frame$Feature[i].
This approach works fine, but for my large XML-Node it takes a VERY long time (20 min) and is very memory consuming (~1.5GB, because of if-loop). My XML: 100MB, about 30.000 "entries/Objects" each with about 50 features (~ 2 Mio lines)
The main problem which I figured out: xpathSApply(...xml_path(Obj[i]...) is very slow, if the indexing [i] of my loop is quite high (>5000)
My questions are:
- Is there any better/simpler ideas to solve my problem with a very complex and higly inhomogenic, structured XML, where not all features are present in all object(nodes)?
- I read this interesting approach, but could not figure out how to translate it to my very complex XML, where my desired values are in different Nodeset-levels...
- Is there maybe some nested xpathSApply-expression to circumvent a for-loop and avoid using index?
- Do you now any "vector"-processing approaches (which are quite faster in R) for my problem?
See my MWE-Code with some more comments below.
XML
<?xml version="1.0" encoding="UTF-8"?>
<featureMember>
<Object>
<XML_Name>Object 1</XML_Name>
<XML_Feature1>
<XML_Feature1a href="URL1"></XML_Feature1a>
</XML_Feature1>
<XML_Feature2>
<XML_Feature2a>1</XML_Feature2a>
<XML_Feature2a>1x</XML_Feature2a>
<XML_Feature2a>1y</XML_Feature2a>
</XML_Feature2>
<XML_Feature3>
<XML_Feature3a>F3a_1</XML_Feature2a>
<XML_Feature3b>F3b_1</XML_Feature2a>
</XML_Feature3>
<XML_Feature3>
<XML_Feature3a>F3a_2</XML_Feature2a>
<XML_Feature3b>F3b_2</XML_Feature2a>
</XML_Feature3>
<XML_Feature4>F4_1</XML_Feature4>
<XML_Feature4>F4_2</XML_Feature4>
</Object>
<Object >
<XML_Name>Object 2</XML_Name>
<XML_Feature1>
<XML_Feature1a href="URL2"></XML_Feature1a>
</XML_Feature1>
</Object>
<Object >
<XML_Name>Object 3</XML_Name>
<XML_Feature1>
<XML_Feature1>
</XML_Feature1>
</XML_Feature1>
<XML_Feature2>
<XML_Feature2a>Value 3</XML_Feature2a>
</XML_Feature2>
</Object>
</featureMember>
R
require(xml2)
require(XML)
test_xml2 <- read_xml("above_file.xml") # using Packet xml2 (for using xml_find_all)
test_XML <- xmlParse("above_file.xml") # Packet XML (for using xpathSApply)
# XML-Noteset of all Objects I want to process:
Obj <- xml_find_all(test_xml2, "//Object") # --> has 3 nodes, contains all Objects!
# initialize a destination dataframe and fill with NAs
df <- data.frame('Name'=integer(), 'f2a'=character() , 'f1a'=character(), stringsAsFactors = FALSE)
df[1:length(Obj),] <- NA
# My Initial approach to extract all features by xml_find_all (which is very fast) is not working because not all xml-nodes have all wanted xml-features:
Name <- xml_text(xml_find_all(test_xml2, "//XML_Name"))
# --> length(Name)=3, because all 3 Objects have a name!
f1a <- xml_attr(xml_find_all(test_xml2, "//XML_Feature1/XML_Feature1a"),"href")
# --> length(f1a)=2, because XML_Feature1a is missing in Object3!
f2a <- xml_text(xml_find_all(test_xml2, "//XML_Feature2/XML_Feature2a"))
# --> length(f2a)=2, because XML_Feature2a is missing in Object2!
# Joining these to a final df is not possible, because "Name", "f2a" and "f1a" have of course different lengths, plus correct data matching is not possible!
# Therefore I decided to make instead the following approach.
# 1.) crawl all features, which are present in all nodes, because its fast (here: "Name"):
df$Name <- xml_text(xml_find_all(test_xml2, "//XML_Name"))
# 2.) making a for-loop over all Objects/XML-Nodes of interest and check if eacht wanted feature exist.
# if yes: write to df$FeatureXY[i]
# if not: make nothing (thus df$FeatureXY[i]stays NA from initialization)
for (i in 1:length(Obj))
{ # 1. Feature:
tmp <- xpathSApply(test_XML, paste0(xml_path(Obj[i]),"/XML_Feature1/XML_Feature1a"), xmlGetAttr, "href")
if(length(tmp )>0) { df$f1a[i] <- tmp # otherwise it would produce an error-message}
# 2. Feature:
tmp <- xpathSApply(test_XML, paste0(xml_path(Obj[i]),"/XML_Feature2/XML_Feature2a"), xmlValue)
if(length(tmp )>0) { df$f2a[i] <- tmp}
}
# Result of df as it should be:
# Name f2a f1a f3a f3b f4
# Object 1 1 # 1x # 1y URL1 F3a_1 # F3a_2 F3b_1 # F3b_2 F4_1 # F4_2
# Object 2 NA URL2 NA NA NA
# Object 3 Value 3 NA NA NA NA
Edit 1: Extended XML example (multiple elements of feature2a, feature3a/b feature4)