21

R's XML package has an xmlToList function, but does not have the reverse, is there a function for R that will convert a list to an XML object?

I would like something like

listToXML(list('a'))

that returns

<a></a>

but the closest I can find is

library(XML)
xmlNode(list('a'))

which returns

</a>

help on this question, and understanding the conversion of R objects to XML in general appreciated (the XML package appears more focused on the use of R to read XML, with less support for creating XML).

Update... One reason that I could not figure this out is because I did not realize that the trailing '/' in <node/> indicates an empty node, equivalent to <node></node>

David LeBauer
  • 31,011
  • 31
  • 115
  • 189
  • `x = newXMLNode("bob"); addChildren(x, newXMLNode("el", "Red", "Blue", "Green", attrs = c(lang ="en")))` does what you're looking for? – Roman Luštrik Jun 06 '11 at 18:08
  • @Roman, that helps, but it does not do much to simplify the conversion of a list to XML. Mostly it did help me to figure out that `` = `` and thereby better understand how the XML package works. – David LeBauer Jun 06 '11 at 19:30

4 Answers4

17

The function newXMLNode does what you need, i.e., write XML output. See the detailed help and examples in ?newXMLNode for more details. Here is a short extract:

library(XML)    
top = newXMLNode("a")
newXMLNode("b", attrs=c(x=1, y='abc'), parent=top)
newXMLNode("c", "With some text", parent=top)
top

Resulting in:

<a>
  <b x="1" y="abc"/>
  <c>With some text</c>
</a> 
gung - Reinstate Monica
  • 11,583
  • 7
  • 60
  • 79
Andrie
  • 176,377
  • 47
  • 447
  • 496
16

I am surprised that no function already exists for this -- surely there's something already packaged out there.

Regardless, I use the following script to accomplish this:

root <- newXMLNode("root")
li <- list(a = list(aa = 1, ab=2), b=list(ba = 1, bb= 2, bc =3))
listToXML <- function(node, sublist){
    for(i in 1:length(sublist)){
        child <- newXMLNode(names(sublist)[i], parent=node);

        if (typeof(sublist[[i]]) == "list"){
            listToXML(child, sublist[[i]])
        }
        else{
            xmlValue(child) <- sublist[[i]]
        }
    } 
}
listToXML(root, li)

You can use the XML::saveXML() function to grab this as a character, if you prefer.

Jeff Allen
  • 17,277
  • 8
  • 49
  • 70
  • 1
    This looks promising to me. Still I don't manage to get it up and running. If I run your example I end up with nothing printed on my console. If I replace the last line with `xml <- listToXML(root, li)` the code runs, but `xml` is `NULL` in the end. I'm not so much into the `XML` package, but it seems obviously to me, that you're function doesn't return much, because it doesn't keep track of the `xml` object as it fills up. Can you help me further on this one? Can you provide a minimal example that returns an `xml` object as @Andrie 's answer does? Thanks in Advance – symbolrush Jun 15 '16 at 15:40
  • I can't believe after all this time no one caught the bug that made this code not work! In reply @symbolrush - there needed to be a return statement. The `xml` object you were looking for was `node` - it's almost as if it's overwritten or modified in place (not sure which one) as you go along and add child elements. – HFBrowning Sep 27 '16 at 18:55
7

Here is the listToXML function that we ended up creating

At first I adapted the answer by @Jeff

listToXml <- function(item, tag){
  if(typeof(item)!='list')
    return(xmlNode(tag, item))
  xml <- xmlNode(tag)
  for(name in names(item)){
    xml <- append.xmlNode(xml, listToXml(item[[name]], name))
  }
  return(xml)
}

But since the function has benefited from further development:

##' Convert List to XML
##'
##' Can convert list or other object to an xml object using xmlNode
##' @title List to XML
##' @param item 
##' @param tag xml tag
##' @return xmlNode
##' @export
##' @author David LeBauer, Carl Davidson, Rob Kooper
listToXml <- function(item, tag) {
  # just a textnode, or empty node with attributes
  if(typeof(item) != 'list') {
    if (length(item) > 1) {
      xml <- xmlNode(tag)
      for (name in names(item)) {
        xmlAttrs(xml)[[name]] <- item[[name]]
      }
      return(xml)
    } else {
      return(xmlNode(tag, item))
    }
  }

  # create the node
  if (identical(names(item), c("text", ".attrs"))) {
    # special case a node with text and attributes
    xml <- xmlNode(tag, item[['text']])
  } else {
    # node with child nodes
    xml <- xmlNode(tag)
    for(i in 1:length(item)) {
      if (names(item)[i] != ".attrs") {
        xml <- append.xmlNode(xml, listToXml(item[[i]], names(item)[i]))
      }
    }    
  }

  # add attributes to node
  attrs <- item[['.attrs']]
  for (name in names(attrs)) {
    xmlAttrs(xml)[[name]] <- attrs[[name]]
  }
  return(xml)
}
David LeBauer
  • 31,011
  • 31
  • 115
  • 189
  • This is useful, but please check the xmlToList example. listToXml(xmlToList(xmlParse(tt)), "x") almost returns the same object as xmlParse(tt) except node b is 1 instead of . – Chris S. Jan 09 '15 at 17:45
  • @ChrisS. thanks for your comment - in the specific application I wrote this for, the information is in the first format (`1`) which was determined by legacy Fortran code. – David LeBauer Jan 09 '15 at 19:48
  • would be very nice to add it to the XML package... thanks – Julien Colomb Jan 15 '20 at 13:14
1

needed to make some changes to the function for cases where there are multiple elements (i.e. lists with no name entry):

##' Convert List to XML
##'
##' Can convert list or other object to an xml object using xmlNode
##' @title List to XML
##' @param item 
##' @param tag xml tag
##' @return xmlNode
##' @export
##' @author David LeBauer, Carl Davidson, Rob Kooper, julien colomb
listToXml <- function(item, tag) {
  # just a textnode, or empty node with attributes
  if(typeof(item) != 'list') {
    if (length(item) > 1) {
      xml <- xmlNode(tag)
      for (name in names(item)) {
        xmlAttrs(xml)[[name]] <- item[[name]]
      }
      return(xml)
    } else {
      return(xmlNode(tag, item))
    }
  }

  # create the node
  if (identical(names(item), c("text", ".attrs"))) {
    # special case a node with text and attributes
    xml <- xmlNode(tag, item[['text']])
  } else {
    # node with child nodes
    xml <- xmlNode(tag)
    for(i in 1:length(item)) {
      if (length (item[[i]]) == 0) {}
      else if (names(item)[i] != ".attrs") {
        print(i)
        if (is.null (names(item[[i]][1])) ){
          print(i)
          for (j in c(1:length (item[[i]]))){
            child <- xmlNode(names(item)[i])
            xmlValue(child) <- item[[i]][j]
            xml <- append.xmlNode(xml,child)
          }
        } else {
          xml <- append.xmlNode(xml, listToXml(item[[i]], names(item)[i]))
        }

      }
    }    
  }

  # add attributes to node
  attrs <- item[['.attrs']]
  for (name in names(attrs)) {
    xmlAttrs(xml)[[name]] <- attrs[[name]]
  }
  return(xml)
}
Julien Colomb
  • 508
  • 4
  • 20