Как получить атрибуты с нескольких уровней XML do c, используя скрипт R - PullRequest
0 голосов
/ 07 февраля 2020

Я ищу способ использования xpath для извлечения атрибутов из узлов и их дочерних элементов (потенциально нескольких уровней вниз по иерархии) в табличную структуру.

Ниже приведен пример документа и требуемый результат.

Все подробности приведены в коде ниже.

require(XML)

# Generating example file
xmlstring <- "<CATALOG>
 <CD title='Great hooks' id='1'>
  <ARTISTS>
   <ARTIST type='composer' name='Peter Pan' id='11'>
    <INFO age='118'/>
   </ARTIST>
   <ARTIST type='singer' name='Tinkerbelle' id='12'>
    <INFO age='118'/>
   </ARTIST>
  </ARTISTS>
 </CD>
 <CD title='The Planets' id='2'>
  <ARTISTS>
   <ARTIST type='composer' name='Clyde Tombaugh' id='21'>
    <INFO age='91'/>
   </ARTIST>
   <ARTIST type='singer' name='Johann Galle' id='22'>
    <INFO age='207'/>
   </ARTIST>
   <ARTIST type='singer' name='Urbain Le Verrier' id='23'>
    <INFO age='208'/>
   </ARTIST>
  </ARTISTS>
 </CD>
 <CD title='45 Minutes Silence' id='3'>
  <ARTISTS>
   <ARTIST type='composer' name='John Cale' id='31'>
    <INFO age='77'/>
   </ARTIST>
  </ARTISTS>
 </CD>
</CATALOG>"

file.name <- "testxmlfile.xml"

writeChar(xmlstring, file.name)

# Reading example file using XML::xmlParse
doc <- xmlParse(file.name)

# I need a data frame or similar table structure with columns CD.title, Singer.name
# and Singer.age where 'Singer' is any artist with @type='singer'

# I can get the CD titles like this:
CD.titles <- xpathSApply(doc, "/CATALOG/CD", xmlGetAttr, "title")
# Singer names like this:
Singer.names <- xpathSApply(doc, "/CATALOG/CD/ARTISTS/ARTIST[@type='singer']", xmlGetAttr, "name")
# Singer ages like this:
Singer.ages <- xpathSApply(doc, "/CATALOG/CD/ARTISTS/ARTIST[@type='singer']/INFO", xmlGetAttr, "age")

# But how do I put them all together, taking into account the number of singers
# per CD is variable (and may be 0)?

# I am not interested in CDs without singer, so if there is no singer either the
# CD may be entirely omitted or Singer.name/Singer.age may be NA

# Desired result:
# CD.title                   | Singer.name         | Singer.age
# ===========================| ====================| ==========
# Great hooks                | Tinkerbelle         | 118
# The Planets                | Johann Galle        | 207
# The Planets                | Urbain Le Verrier   | 208

# Thanks in advance for suggestions.

Абсолютно ненужный спам, потому что я, очевидно, не могу опубликовать его, не набрав достаточно текста вне поля кода, независимо от того, что оно содержит все необходимые детали.

Ответы [ 2 ]

0 голосов
/ 09 февраля 2020

Используя идею, представленную в предыдущем ответе, я здесь использую два вложенных lapply вместо for циклов. Обратите внимание, что вам нужно установить dplyr (если он еще не установлен).

result.df <- dplyr::bind_rows(unlist(lapply(xpathSApply(doc, "//CD"), function(cd){
  lapply(xpathSApply(cd, ".//ARTIST[@type='singer']"), function(singer){
    data.frame(t(c(xpathSApply(cd, "./@title"), xpathSApply(singer, './@name|./INFO/@age'))), stringsAsFactors = F)
  })
}), recursive = F))

Надеюсь, это поможет.

0 голосов
/ 07 февраля 2020

Ну, вам нужно сделать следующее:

  1. Создать пустое data.frame, скажем df
  2. L oop через CD узлы
  3. Для каждого CD получите атрибут title и singer узлов (ы)
  4. Для каждого певец , если есть, получить атрибуты name и age
  5. Добавить каждую строку в df

Как показано ниже:

df <- data.frame()

CDs <- xpathSApply(doc, "/CATALOG/CD")

for (CD in CDs) {
  title <- xpathSApply(CD, ".", xmlGetAttr, "title")
  Singers <- xpathSApply(CD, "ARTISTS/ARTIST[@type='singer']")
  name <- NA
  age <- NA
  for(singer in Singers){
    name <- xpathSApply(singer, '.', xmlGetAttr, "name")
    age <- xpathSApply(singer, './INFO', xmlGetAttr, "age")
    df <- rbind(df, list(title=title, name=name, age=age),stringsAsFactors=FALSE)
  }
}

Вывод df:

#        title                    name   age
#1  Great hooks            Tinkerbelle   118
#2  The Planets           Johann Galle   207
#3  The Planets      Urbain Le Verrier   208

Позже я могу обновить ответ с более эффективным способом сделать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...