Change Value in "broken" .xml File in R

Viewed 106

I got a "broken" .xml file with missing header and root element

myBrokenXML.xml

  <attribute1>false</attribute1>
  <subjects>
    <subject>
      <population>Adult</population>
      <name>adult1</name>
    </subject>
  </subjects>

This .xml file is the input for a program that i have to use and the structure cannot be changed. I would like to change the attribute "name" to adult5.

  • I tried using the xml2 package but it requires a proper xml file for read_xml() which returns this error message "Extra content at the end of the document"
  • I tried reading the file line by line using readLines and then writing a new line with writeLines() but this again resulted in an error message "cannot write to this connection"

Anny suggestions are greatly appreciated. I am new to R and XML and been at this for hours (and cursed the developers a few times in the process)

Thanks in advance!

Code using xml2:

XMLFile <- read_xml("myBrokenXML.xml")

Code using readLines/writeLines; would still require to delete the original line

  conn <- file("myBrokenXML.xml", open = "r")
  lines <- readLines(conn)
  for (i in 1:length(lines)){
    print(lines[i])
    if (lines[i] == "\t\t<name>adult1</name>"){
      writeLines("\t\t<name>adult5</name>", conn)
    }
  }

GOAL i need to change the value of "name" from adult1 to adult5 and the file must be in the same structure (no header, no root element) at the end.

2 Answers

The easiest way to do this is to use read_html instead of read_xml, since read_html will attempt to parse even broken documents, whereas read_xml requires strict formatting. It is possible to use this fact to create a repaired xml document by creating a new xml_document and writing the nodes obtained from read_html into it. This function will allow fragments of xml to be repaired into a proper xml document:

fix_xml <- function(xml_path, root_name = "root")
{
  my_xml   <- xml2::xml_new_root("root")
  root     <- xml2::xml_find_all(my_xml, "//root")
  my_html  <- xml2::read_html(xml_path)
  fragment <- xml2::xml_find_first(my_html, xpath = "//body")
  new_root <- xml2::xml_set_name(fragment, root_name)
  new_root <- xml2::xml_replace(root, fragment)
  return(my_xml)
}

So we can do:

fix_xml("myBrokenXML.xml")
#> {xml_document}
#> <root>
#> [1] <attribute1>false</attribute1>
#> [2] <subjects>\n  <subject>\n <population>Adult</population>\n <name>adult1...

The Answer from Allan Cameron (#1) works fine, as long as your file does not include case sensitive elements.

If someone ever runs into the same problem, here is what worked for me.

fix_xml <- function(xmlPath){
  con <- file(xmlPath)
  lines <- readLines(con)

  firstLine <- c("<root>")
  lastLine <- c("</root>")

  lines <- append(lines, firstLine, after = 0)
  lines <- append(lines, lastLine, after = length(lines))

  write(lines, xmlPath)

  close(con)
}

This function insert a root element into a "broken" xml file. The fixed xml file can then be read using read_xml() and edited as desired.

The only difference to Allan's answer is, that using read_html does not care about upper case letters and reads the whole file as all-lower-case.

My solution is not as versatile, but it keeps upper case letters.

Related