Получение родительского атрибута каждого дочернего элемента в XML с помощью xpathSApply - PullRequest
0 голосов
/ 25 сентября 2019

Я пытаюсь получить два вектора одинаковой длины, один с атрибутами дочерних элементов, а второй с атрибутами соответствующих родителей.Файл примера:

countries.xml <- "<country>
              <city id='1'>
                <place id='1.1'> xxx </place>
                <place id='1.2'> xxx </place>
                <place id='1.3'> xxx </place>
              </city>
              <city id='2'>
                <place id='2.1'> xxx </place>
                <place id='2.2'> xxx </place>
                <place id='2.3'> xxx </place>
              </city>
           </country>"

Мой код пока

library("XML")
doc = xmlTreeParse(countries.xml, useInternalNodes = T)
xpathSApply(doc, path = "//city/place/@id")
xpathSApply(doc, path = "//city/place/parent::*/@id")

Я надеялся получить такие векторы (с именем)

"1.1" "1.2" "1.3" "2.1" "2.2" "2.3"
"1" "1" "1" "2" "2" "2"

, но вместо второгопуть производит

"1" "2" 

Я могу получить то, что я хотел с циклом

library(glue)
place_id <- unname(xpathSApply(doc, path = "//city/place/@id"))
city_id <- vector()
for(i in place_id){
  city_id <- c(city_id,unname(xpathSApply(doc, path = glue("//city/place[@id={i}]/parent::*/@id"))))
}
city_id
"1" "1" "1" "2" "2" "2"

, но это очень неэффективно и занимает много времени с большим xml.file, с которым я имею дело.Я уверен, что есть способ получить то, что мне нужно, с помощью правильного пути в xpathSApply, но не смог его найти, поэтому, пожалуйста, кто-нибудь может меня осветить:)?

ОБНОВЛЕНИЕ @Решение Wietze314 прекрасно работает на моем простом примере, но я не могу адаптировать его к более сложному XML-файлу.Мне удалось изменить его код в соответствии с приведенным ниже примером

countries.xml <- "<continent>
          <country id='c1'>
          <city id='1'>
            <place id='1.1'> xxx </place>
            <place id='1.2'> xxx </place>
            <place id='1.3'> xxx </place>
          </city>
          <city id='2'>
            <place id='2.1'> xxx </place>
            <place id='2.2'> xxx </place>
            <place id='2.3'> xxx </place>
          </city>
       </country>
       <country id=c2'>
          <city id='1'>
            <place id='1.1'> xxx </place>
            <place id='1.2'> xxx </place>
            <place id='1.3'> xxx </place>
          </city>
          <city id='2'>
            <place id='2.1'> xxx </place>
            <place id='2.2'> xxx </place>
            <place id='2.3'> xxx </place>
          </city>
       </country>
        </continent>"

этот код

    pmap_df(list(
  xml_children(cntry) %>% map(xml_children) %>% 
    map(xml_attr,'id') %>% unlist() %>% as.list() %>%
    map(~as_tibble(.) %>% select(city = value)),
    xml_children(cntry) %>% xml_children() %>% map(xml_children) %>% 
    map(xml_attr,'id') %>%
    map(~as_tibble(.) %>% select(place = value))),cbind)

возвращает этот

    city place
1     1   1.1
2     1   1.2
3     1   1.3
4     2   2.1
5     2   2.2
6     2   2.3
7     3   3.1
8     3   3.2
9     3   3.3
10    4   4.1
11    4   4.2
12    4   4.3

, но тот же код применяется к файлуиз моих интересов не удается :( любое предложение?

pfile <- http://nextbike.net/maps/nextbike-official.xml",
                  useInternalNodes = T)
pmap_df(list(
  xml_children(pfile) %>% map(xml_children) %>% 
    map(xml_attr,'uid') %>% unlist() %>% as.list() %>%
    map(~as_tibble(.) %>% select(city = value)),
  xml_children(pfile) %>% xml_children() %>% map(xml_children) %>% 
    map(xml_attr,'uid') %>%
    map(~as_tibble(.) %>% select(place = value))),cbind) 

Error in data.frame(..., check.names = FALSE) : 
      arguments imply differing number of rows: 1, 0

1 Ответ

1 голос
/ 25 сентября 2019

Решение с tidyverse и xml2

require(xml2)
require(tidyverse)

cntry <- read_xml(countries.xml)


pmap_df(list(
  xml_children(cntry) %>% map(xml_attr,'id') %>% 
    map(~as_tibble(.) %>% select(country = value)),
  xml_children(cntry) %>% map(xml_children) %>% 
    map(xml_attr,'id') %>% 
    map(~as_tibble(.) %>% select(place = value))
  ),cbind)

РЕДАКТИРОВАТЬ:

Я пытался заставить это работать с более чем 2 уровнями,но не удалось.Это то, что я дошел до этого:

require(xml2)
require(tidyverse)

parsedxml <- read_xml(countries.xml)

get_ids <- function(xml){
  xml %>% xml_attr('id') %>% 
    map(~as_tibble(.))
}

country <- parsedxml %>% xml_children() %>% map(get_ids)
city <- parsedxml %>% xml_children() %>% map(~xml_children(.) %>% map(get_ids))
place <- parsedxml %>% xml_children() %>% map(~xml_children(.) %>% map(~xml_children(.) %>% map(get_ids)))

rbind(country[[1]],rbind(city[[1]][[1]],place[[1]][[1]])) %>% apply(1,unlist)

результат для одного города

      [,1] [,2] [,3] 
value "c1" "1"  "1.1"
value "c1" "1"  "1.2"
value "c1" "1"  "1.3"

этот уродливый код объединяет все это:

do.call(rbind,lapply(1:2,function(x) 
  lapply(1:2,function(y) 
    rbind(country[[x]],rbind(city[[x]][[y]],place[[x]][[y]])) %>% apply(1,unlist)) %>% do.call(rbind,.)))

Надеюсьу кого-то есть лучшее решение для этой последней части.

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