Parse XML with getNodeSet - Identify missing tags

2019-05-27 11:45发布

I am parsing a XML file with getNodeSet(). Assume I have a XML file from a bookstore with 4 different books listed, but for one book the tag "authors" is missing.

If I parse the XML for the tag "authors" by using data.nodes.2 <- getNodeSet(data,'//*/authors'), R returns a list of 3 elements.

However, this is not exactly what I want. How do get "getNodeSet()" to return a list which has 4 instead of three elements, i.e. one element that has a missing value where the tag "authors" does not exist.

I appreciate any help.

library(XML)

file <- "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n<!-- Edited by XMLSpy® -->\r\n<bookstore>\r\n<book category=\"cooking\">\r\n<title lang=\"en\">Everyday Italian</title>\r\n<authors>\r\n<author>Giada De Laurentiis</author>\r\n</authors>\r\n<year>2005</year>\r\n<price>30.00</price>\r\n</book>\r\n<book category=\"children\">\r\n<title lang=\"en\">Harry Potter</title>\r\n<authors>\r\n<author>J K. Rowling</author>\r\n</authors>\r\n<year>2005</year>\r\n<price>29.99</price>\r\n</book>\r\n<book category=\"web\">\r\n<title lang=\"en\">XQuery Kick Start</title>\r\n<authors>\r\n<author>James McGovern</author>\r\n<author>Per Bothner</author>\r\n<author>Kurt Cagle</author>\r\n<author>James Linn</author>\r\n<author>Vaidyanathan Nagarajan</author>\r\n</authors>\r\n<year>2003</year>\r\n<price>49.99</price>\r\n</book>\r\n<book category=\"web\" cover=\"paperback\">\r\n<title lang=\"en\">Learning XML</title>\r\n\r\n<year>2003</year>\r\n<price>39.95</price>\r\n</book>\r\n</bookstore>"

data <- xmlParse(file)

data.nodes.1 <- getNodeSet(data,'//*/book')

data.nodes.2 <- getNodeSet(data,'//*/authors')


# Data

# <?xml version="1.0" encoding="ISO-8859-1"?>
# <!-- Edited by XMLSpy® -->
# <bookstore>
#   <book category="cooking">
#     <title lang="en">Everyday Italian</title>
#     <authors>
#       <author>Giada De Laurentiis</author>
#     </authors>
#     <year>2005</year>
#     <price>30.00</price>
#   </book>
#   <book category="children">
#     <title lang="en">Harry Potter</title>
#     <authors>
#       <author>J K. Rowling</author>
#     </authors>
#     <year>2005</year>
#     <price>29.99</price>
#   </book>
#   <book category="web">
#     <title lang="en">XQuery Kick Start</title>
#     <authors>
#       <author>James McGovern</author>
#       <author>Per Bothner</author>
#       <author>Kurt Cagle</author>
#       <author>James Linn</author>
#       <author>Vaidyanathan Nagarajan</author>
#     </authors>
#     <year>2003</year>
#     <price>49.99</price>
#   </book>
#   <book category="web" cover="paperback">
#     <title lang="en">Learning XML</title>
#     <year>2003</year>
#     <price>39.95</price>
#   </book>
# </bookstore>

4条回答
够拽才男人
2楼-- · 2019-05-27 12:12

You can also try xmlToDataFrame for some XML

x <-xmlToDataFrame(doc)

If you don't like the authors mashed together, you can sometimes fix that with pattern matching

x$authors <- gsub("([a-z]{2})([A-Z])", "\\1, \\2", x$authors)
x
              title                                                                     authors year price
1  Everyday Italian                                                         Giada De Laurentiis 2005 30.00
2      Harry Potter                                                                J K. Rowling 2005 29.99
3 XQuery Kick Start James McGovern, Per Bothner, Kurt Cagle, James Linn, Vaidyanathan Nagarajan 2003 49.99
4      Learning XML                                                                        <NA> 2003 39.95

Other options are to loop through the book nodes (see ?getNodeSet to create and free subnodes) or follow Martin's answer (and if you want 4 rows instead, try this)

authors <- sapply(authors, paste, collapse=",")
data.frame(title, authors)
查看更多
时光不老,我们不散
3楼-- · 2019-05-27 12:15

One option is to use R's list processing to extract authors from each node

books <- getNodeSet(doc, "//book")
authors <- lapply(books, xpathSApply, ".//author", xmlValue)
authors[sapply(authors, is.list)] <- NA

and to munge that with book-level info

title <- sapply(books, xpathSApply, "string(.//title/text())")

giving

>     data.frame(Title=rep(title, sapply(authors, length)),
+                Author=unlist(authors))
              Title                 Author
1  Everyday Italian    Giada De Laurentiis
2      Harry Potter           J K. Rowling
3 XQuery Kick Start         James McGovern
4 XQuery Kick Start            Per Bothner
5 XQuery Kick Start             Kurt Cagle
6 XQuery Kick Start             James Linn
7 XQuery Kick Start Vaidyanathan Nagarajan
8      Learning XML                   <NA>
查看更多
太酷不给撩
4楼-- · 2019-05-27 12:21

Here is an xml2 approach.

The code is very readable, and therefor easy to maintain.

code

library( xml2 )

#read the xml file
data <- xml2::read_xml( file )

#get all book-titles and store them in a data.frame
books <- data.frame( 
  title = xml_find_all( data, ".//book/title" ) %>% xml_text(),
  stringsAsFactors = FALSE
  )

#find all author-nodes
authors      <- xml_find_all( data, ".//author" )

#create a dataframe with all authors, an the book they wrote
authors <- data.frame( 
  #loop over the author-nodes, and get the title from the ancestor-node (i.e. book)
  title  = xml_find_first( authors, ".//ancestor::book/title") %>% xml_text(),
  #get the text from the autor-node
  author = xml_text( authors ),
  stringsAsFactors = FALSE
  )

#left_join the books with the authors
left_join( books, authors, by = "title")

ouput

#               title                 author
# 1  Everyday Italian    Giada De Laurentiis
# 2      Harry Potter           J K. Rowling
# 3 XQuery Kick Start         James McGovern
# 4 XQuery Kick Start            Per Bothner
# 5 XQuery Kick Start             Kurt Cagle
# 6 XQuery Kick Start             James Linn
# 7 XQuery Kick Start Vaidyanathan Nagarajan
# 8      Learning XML                   <NA>

sample data

file <- '<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<bookstore>
  <book category="cooking">
    <title lang="en">Everyday Italian</title>
    <authors>
      <author>Giada De Laurentiis</author>
    </authors>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book category="children">
    <title lang="en">Harry Potter</title>
    <authors>
      <author>J K. Rowling</author>
    </authors>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book category="web">
    <title lang="en">XQuery Kick Start</title>
    <authors>
      <author>James McGovern</author>
      <author>Per Bothner</author>
      <author>Kurt Cagle</author>
      <author>James Linn</author>
      <author>Vaidyanathan Nagarajan</author>
    </authors>
    <year>2003</year>
    <price>49.99</price>
  </book>
  <book category="web" cover="paperback">
    <title lang="en">Learning XML</title>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>'
查看更多
相关推荐>>
5楼-- · 2019-05-27 12:22

You can use the plyr library

library(plyr)
> ldply(xpathApply(data, '//book', getChildrenStrings), rbind)
              title                                                             authors year price
1  Everyday Italian                                                 Giada De Laurentiis 2005 30.00
2      Harry Potter                                                        J K. Rowling 2005 29.99
3 XQuery Kick Start James McGovernPer BothnerKurt CagleJames LinnVaidyanathan Nagarajan 2003 49.99
4      Learning XML                                                                <NA> 2003 39.95
查看更多
登录 后发表回答