Generate a xml from a R list

Viewed 99

I'm new to xml and processing it in R.

I've been able to read and retrieve info from xml files using the xml2 package, but creating xml files from R objects has proven to be more challenging.

In particular, I'd like to generate a xml file from a R list. Consider the example below:

library(reprex)
library(xml2)

r_list <- list(person1 = list(starts = letters[1:3], ends = letters[4:6]), person2 = list(starts = LETTERS[1:4], ends = LETTERS[5:8]))
str(r_list)
#> List of 2
#>  $ person1:List of 2
#>   ..$ starts: chr [1:3] "a" "b" "c"
#>   ..$ ends  : chr [1:3] "d" "e" "f"
#>  $ person2:List of 2
#>   ..$ starts: chr [1:4] "A" "B" "C" "D"
#>   ..$ ends  : chr [1:4] "E" "F" "G" "H"

test1 <- xml2::as_xml_document((r_list))
#> Error: Root nodes must be of length 1

new_xml <- xml_new_root(.value = "category", name = "personList")

for(person in names(r_list)){
  xml_add_child(new_xml, as_xml_document(r_list[person]))
}

new_xml
#> {xml_document}
#> <category name="personList">
#> [1] <person1>ad</person1>
#> [2] <person2>AE</person2>

Created on 2021-11-25 by the reprex package (v2.0.1)

I tried to directly coerce the list to xml using the as_xml_document function, but I get the error Root nodes must be of length 1.

Following the idea on this question, I tried to create the xml document with a root node and xml_add_child() to this document, but I did not get the expected result (see code output). In that question, they transform from an R data frame and not a list.

I'd also like to have personalized tag names and add attributes to these tags. The wished output would be:

<category name="personList">
    <pers name="person1">
        <starts>
            <value>a</value>
            <value>b</value>
            <value>c</value>
        </starts>
        <ends>
            <value>d</value>
            <value>e</value>
            <value>f</value>
        </ends>
    </pers>
    <pers name="person2">
        <starts>
            <value>A</value>
            <value>B</value>
            <value>C</value>
            <value>D</value>
        </starts>
        <ends>
            <value>D</value>
            <value>E</value>
            <value>F</value>
            <value>G</value>
        </ends>
    </pers>
</category>

Thanks for your help and have a nice day

2 Answers

R list attributes can be mapped to XML attributes:

library(xml2)
library(tidyverse)

r_list <- list(person1 = list(starts = letters[1:3], ends = letters[4:6]), person2 = list(starts = LETTERS[1:4], ends = LETTERS[5:8]))
r_list

new_xml <- xml_new_root(.value = "category", name = "personList")

for (person in names(r_list)) {
  p <- list()
  p[["pers"]] <- list(
    starts = r_list[[person]]$starts %>% map(~list(value = list(.x))),
    ends = r_list[[person]]$ends %>% map(~list(value = list(.x)))
  )
  attr(p[["pers"]], "name") <- person
  
  xml_add_child(new_xml, as_xml_document(p))
}

write_xml(new_xml, "foo.xml")

output:

<?xml version="1.0" encoding="UTF-8"?>
<category name="personList">
  <pers name="person1">
    <starts>
      <value>a</value>
      <value>b</value>
      <value>c</value>
    </starts>
    <ends>
      <value>d</value>
      <value>e</value>
      <value>f</value>
    </ends>
  </pers>
  <pers name="person2">
    <starts>
      <value>A</value>
      <value>B</value>
      <value>C</value>
      <value>D</value>
    </starts>
    <ends>
      <value>E</value>
      <value>F</value>
      <value>G</value>
      <value>H</value>
    </ends>
  </pers>
</category>

Following the comment by @Limey (to see this question), I could generate the wished output with the following code (posted as answer just for completeness, as @danlooo answer also produces the same output).

library(XML)

r_list <- list(person1 = list(starts = letters[1:3], ends = letters[4:6]), person2 = list(starts = LETTERS[1:4], ends = LETTERS[5:8]))
str(r_list)

category = newXMLNode("category", attrs = c(name="personList"))

for(person in names(r_list)){
  pers <- newXMLNode("pers", attrs = c(name = person), parent = category)
  startsn <- newXMLNode("starts", parent = pers)
  for(value in seq_along(r_list[[person]][["starts"]])){
    svalue <- newXMLNode("value", r_list[[person]][["starts"]][[value]], parent = startsn)
  }
  endsn <- newXMLNode("ends", parent = pers)
  for(value in seq_along(r_list[[person]][["ends"]])){
    evalue <- newXMLNode("value", r_list[[person]][["ends"]][[value]], parent = endsn)
  }
}
category
Related