通过xpath表达式将XML文档快速拆分为data.frames

时间:2016-11-25 09:16:19

标签: r xml xpath libxml2 xml2

我编写了一个函数来将(大)异构XML文件拆分成数据框,其中拆分由xpath表达式完成。异质性我的意思是感兴趣的项目属于一组不同的"列"结构。但是,对于较大的 ish XML文件,比如50K项和5种类型,代码似乎更多"缓慢"比我期待的还要多。

问题是:是否存在我错过的现有功能,如果没有,是否有一种明显的方法可以提高下面代码的速度?

以下是我正在考虑的XML结构的最小示例:

xmldoc <- xml2::read_xml(
  '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <resp>

     <respMeta>
       <status>200</status>
       <!-- ... -->    
     </respMeta>

     <content>
       <list>
         <item>
           <Type>Type1</Type>
           <ColA>Foo</ColA>
           <ColB>Bar</ColB>
         </item>
         <item>
           <Type>Type2</Type>
           <ColC>Baz</ColC>
         </item>
         <item>
           <Type>Type3</Type>
           <ColA>Lorem</ColA>
           <ColB>Ipsum</ColB>
           <ColC>Dolor</ColC>
         </item>
       </list>
       <!-- ... many many more entries here -->
     </content>

   </resp>')

目标是将其转换为 N 数据框,其中 N //item/Type中唯一值的数量(在解析时未知)

这是我的实施:

#' Split XML Document into Dataframes by Xpath Expression
#'
#' @param xml An (xml2) xml document object.
#'
#' @param xpath the path to the values to split by. \code{xml_text} is used
#'   to get the value.
#'
#' @importFrom xml2 xml_text xml_find_all xml_find_first xml_children xml_name
#' @importFrom stats setNames
#' @importFrom dplyr bind_cols
#' @importFrom magrittr %>%
#'
#' @return list of data frames (tbl_df)
#'
#' @export
xml_to_dfs <- function(xml, xpath)
{
  u <- xml_find_all(xml, xpath) %>% xml_text %>% unique %>% sort

  select <- paste0(xpath, "[. ='", u, "']/..") %>% setNames(u)

  paths <-
    lapply(select, . %>% xml_find_first(x = xml) %>% xml_children %>% xml_name)

  queries <- Map(paste, select, paths, MoreArgs = list(sep = "/"))

  columns <-
    lapply(queries, . %>% lapply(. %>% xml_find_all(x = xml) %>% xml_text))

  Map(setNames, columns, paths) %>% lapply(bind_cols)
}

最小示例的结果是每帧中只有一行,然后是:

xml_to_dfs(xmldoc, "//item/Type") 
$Type1
# A tibble: 1 × 3
   Type  ColA  ColB
  <chr> <chr> <chr>
1 Type1   Foo   Bar

$Type2
# A tibble: 1 × 2
   Type  ColC
  <chr> <chr>
1 Type2   Baz

$Type3
# A tibble: 1 × 4
   Type  ColA  ColB  ColC
  <chr> <chr> <chr> <chr>
1 Type3 Lorem Ipsum Dolor

1 个答案:

答案 0 :(得分:1)

这样的事情:

require(xml2)
require(purrr)

oc <- read_xml("path-to-xml-file")
xml_find_all(doc, ".//item") %>% 
  map(xml_children) %>% 
  map(~as_tibble(t(set_names(xml_text(.), xml_name(.))))) #or map_df

我甚至会在最后一行中找到map_df

# A tibble: 3 × 4
   Type  ColA  ColB  ColC
  <chr> <chr> <chr> <chr>
1 Type1   Foo   Bar  <NA>
2 Type2  <NA>  <NA>   Baz
3 Type3 Lorem Ipsum Dolor

P.S。:这也与:https://github.com/hadley/purrr/issues/255

有关