【问题标题】:Retriving parent attribute of every child in XML with xpathSApply使用 xpathSApply 检索 XML 中每个子项的父属性
【发布时间】:2020-01-25 08:54:42
【问题描述】:

我正在尝试检索两个相同长度的向量,一个具有孩子的属性,第二个具有相应父母的属性。示例文件:

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

【问题讨论】:

    标签: r xml-parsing xpathsapply


    【解决方案1】:

    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,.)))
    

    希望其他人对最后一部分有更好的解决方案。

    【讨论】:

    • 它与我的简化示例完美配合,但我太愚蠢了,无法将它用于具有更多节点级别的文件。你能告诉我如何扩展你的代码,以便它从 4 节点级别的 xml 文件创建 3 个变量数据框,我将其包含在更新的问题中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    相关资源
    最近更新 更多