将XML对象写入磁盘

4

我有一大堆xml文件需要处理。为此,我希望能够读取这些文件,并将生成的对象列表保存到磁盘上。我尝试使用readr::write_rds保存列表,但在重新读取后,对象会被修改并且不再有效。有什么办法可以缓解这个问题吗?

library(readr)
library(xml2)

x <- read_xml("<foo>
              <bar>text <baz id = 'a' /></bar>
              <bar>2</bar>
              <baz id = 'b' />
              </foo>")

# function to save and read object
roundtrip <- function(obj) {
  tf <- tempfile()
  on.exit(unlink(tf))

  write_rds(obj, tf)
  read_rds(tf)
}

list(x)
#> [[1]]
#> {xml_document}
#> <foo>
#> [1] <bar>text <baz id="a"/></bar>
#> [2] <bar>2</bar>
#> [3] <baz id="b"/>
roundtrip(list(x))
#> [[1]]
#> {xml_document}

identical(x, roundtrip(x))
#> [1] FALSE
all.equal(x, roundtrip(x))
#> [1] TRUE
xml_children(roundtrip(x))
#> Error in fun(x$node, ...): external pointer is not valid
as_list(roundtrip(x))
#> Error in fun(x$node, ...): external pointer is not valid

一些背景信息

我有大约500,000个xml文件。为了处理它们,我计划使用 xml2::as_list 将它们转换成列表,并编写代码提取我需要的内容。后来我意识到,运行 as_list 的代价非常昂贵。我可以选择:

  1. 重新编写已经经过仔细调试的代码以直接解析数据(xml_childxml_text等),或者
  2. 使用 as_list

为了加速第二种方法,我可以在另一台具有更多核心的机器上运行它,但我想将单个文件传递到该机器,因为收集和复制所有文件很耗时。

1个回答

4

xml2对象具有外部指针,当您天真地将它们序列化时,这些指针会变得无效。该软件包提供了xml_serialize()xml_unserialize()对象来为您处理此问题。不幸的是,API略显繁琐,因为base::serialize()base::unserialize()假定打开了连接。


library(xml2)

x <- read_xml("<foo>
              <bar>text <baz id = 'a' /></bar>
              <bar>2</bar>
              <baz id = 'b' />
              </foo>")

# function to save and read object
roundtrip <- function(obj) {
  tf <- tempfile()
  con <- file(tf, "wb")
  on.exit(unlink(tf))

  xml_serialize(obj, con)
  close(con)
  con <- file(tf, "rb")
  on.exit(close(con), add = TRUE)
  xml_unserialize(con)
}
x
#> {xml_document}
#> <foo>
#> [1] <bar>text <baz id="a"/></bar>
#> [2] <bar>2</bar>
#> [3] <baz id="b"/>
(y <- roundtrip(x))
#> {xml_document}
#> <foo>
#> [1] <bar>text <baz id="a"/></bar>
#> [2] <bar>2</bar>
#> [3] <baz id="b"/>

identical(x, y)
#> [1] FALSE
all.equal(x, y)
#> [1] TRUE
xml_children(y)
#> {xml_nodeset (3)}
#> [1] <bar>text <baz id="a"/></bar>
#> [2] <bar>2</bar>
#> [3] <baz id="b"/>
as_list(y)
#> $bar
#> $bar[[1]]
#> [1] "text "
#> 
#> $bar$baz
#> list()
#> attr(,"id")
#> [1] "a"
#> 
#> 
#> $bar
#> $bar[[1]]
#> [1] "2"
#> 
#> 
#> $baz
#> list()
#> attr(,"id")
#> [1] "b"

关于你提出的第二个问题,我建议使用XPATH表达式来提取所需数据,即使需要重新编写代码。


谢谢!不过您能详细说明一下为什么建议使用XPATH表达式吗?查看XML文档以了解结构感觉比使用“listviewer::jsonedit”之类的工具更加繁琐。这就是我最初选择使用列表进行操作的原因。 - Thomas K
你说你有50万个文档需要解析。使用Xpath仅提取你感兴趣的元素会比先将整个数据转换为列表再进行操作要快得多。 - Jim
这就是我发帖的原因。在我的情况下,使用XPATH提取单个元素比使用as_list快约17倍。我猜我会重新编写代码,因为一旦你学会如何处理XPATH,它更加灵活。无论如何,还是谢谢! - Thomas K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接