R: How to best extract two XML attributes from a node?

1.2k views Asked by At

The following code extracts one attribute (or all) from an XML file:

library(xml2);library(magrittr);library(readr);library(tibble);library(knitr)   
fname<-'https://raw.githubusercontent.com/wardblonde/ODM-to-i2b2/master/odm/examples/CDISC_ODM_example_3.xml'
fname
x<-read_xml(fname)
xpath="//d1:ItemDef"
itemsNames <- x %>% xml_find_all(xpath, ns=xml_ns(x)) %>%  xml_attr('Name')
items <- x %>% xml_find_all(xpath, ns=xml_ns(x))

Item looks like this:

<ItemDef OID="IT.ABNORM" Name="Normal/Abnormal/Not Done" DataType="integer" Length="1" ...

Sample file can be viewed here: https://raw.githubusercontent.com/wardblonde/ODM-to-i2b2/master/odm/examples/CDISC_ODM_example_3.xml

Using pipes and xml_attr, what is the best way to extract both the Name and DataType attributes and have them rbinded?

Ideally it would be a single line of super efficient piped code. I can extract names and types and have 'data.frame(name=names,type=types)' but that seems not the best and most modern.

The result should be a tibble with columns name and data type.

1

There are 1 answers

1
hrbrmstr On BEST ANSWER
library(purrr)

map(items, xml_attrs) %>% 
  map_df(as.list) %>% 
  select(Name, DataType)
## # A tibble: 94 × 2
##                                   Name DataType
##                                  <chr>    <chr>
## 1             Normal/Abnormal/Not Done  integer
## 2          Actions taken re study drug     text
## 3                 Actions taken, other     text
## 4    Stop Day - Enter Two Digits 01-31     text
## 5                    Derived Stop Date     text
## 6  Stop Month - Enter Two Digits 01-12     text
## 7    Stop Year - Enter Four Digit Year     text
## 8                              Outcome     text
## 9           Relationship to study drug     text
## 10                            Severity     text
## # ... with 84 more rows

One "base" version:

lapply(items, xml_attrs) %>% 
  lapply(function(x) as.data.frame(as.list(x))[,c("Name", "DataType")]) %>% 
  do.call(rbind, .) %>%
  tbl_df()

NOTE: an issue with ^^ is that if Name or DataType is missing then you're SOL. You can mitigate that with:

lapply(items, xml_attrs) %>% 
  lapply(function(x) as.data.frame(as.list(x))[,c("Name", "DataType")]) %>% 
  data.table::rbindlist(fill=TRUE) %>% 
  tbl_df()

or:

lapply(items, xml_attrs) %>% 
  lapply(function(x) as.data.frame(as.list(x))[,c("Name", "DataType")]) %>% 
  bind_rows() %>% 
  tbl_df()

if you don't like purrr.