使用管道运算符和xml2编写XML

时间:2018-08-08 18:47:58

标签: r xml2

xml2 包允许用户创建XML文档。我正在尝试使用管道运算符%>%创建一个文档,以添加子节点和兄弟节点的各种组合。我无法弄清楚如何在原始孩子的兄弟姐妹后面的孩子节点中创建一个孩子节点(请参见下面的示例)。

是否可以“上升”一个级别然后创建更多节点,还是必须在链接命令之外创建它们?

我想要的

library(xml2)
x1 <- read_xml("<parent><child>1</child><child><grandchild>2</grandchild></child><child>3</child><child>4</child></parent>")
message(x1)
#> <?xml version="1.0" encoding="UTF-8"?>
#> <parent>
#>  <child>1</child>
#>  <child>
#>    <grandchild>2</grandchild>
#>  </child>
#>  <child>3</child>
#>  <child>4</child>
#> </parent>

我创建的内容有误

library(magrittr)
library(xml2)
x2 <- xml_new_document()
x2 %>% 
  xml_add_child("parent") %>%
  xml_add_child("child", 1) %>%
  xml_add_sibling("child", 4, .where="after") %>%
  xml_add_sibling("child", 3) %>%
  xml_add_sibling("child", .where="before") %>%
  xml_add_child("grandchild", 2)
message(x2)
#> <?xml version="1.0" encoding="UTF-8"?>
#> <parent>
#>  <child>1</child>
#>  <child>4</child>
#>  <child>
#>    <grandchild>2</grandchild>
#>  </child>
#>  <child>3</child>
#> </parent>

使用XML软件包的解决方案

如果使用 XML 包完成,这实际上非常简单。

library(XML)
x2 <- newXMLNode("parent")
invisible(newXMLNode("child", 1, parent=x2))
invisible(newXMLNode("child", newXMLNode("grandchild", 2), parent=x2))
invisible(newXMLNode("child", 3, parent=x2))
invisible(newXMLNode("child", 4, parent=x2))
x2
#> <?xml version="1.0" encoding="UTF-8"?>
#> <parent>
#>  <child>1</child>
#>  <child>
#>    <grandchild>2</grandchild>
#>  </child>
#>  <child>3</child>
#>  <child>4</child>
#> </parent>

1 个答案:

答案 0 :(得分:5)

首先,我认为这通常是一个坏主意。 xml2使用指针工作,这意味着它具有引用语义(“按引用传递”),这不是R中的典型行为。xml2中的函数通过在XML树上产生副作用而不是通过返回值(如函数编程中的那样)来工作(“按值传递”)。

这意味着配管基本上是错误的原理。您只需要按照正确的顺序修改对象的一系列步骤即可。

也就是说,您可以这样做:

library("magrittr")
library("xml2")
x2 <- xml_new_document()
x2 %>% 
  xml_add_child(., "parent") %>%
{
  xml_add_child(., "child", 1, .where = "after")
  (xml_add_child(., "child") %>% xml_add_child("grandchild", 2))
  xml_add_child(., "child", 3, .where = "after")
  xml_add_child(., "child", 4, .where = "after")
}
message(x2)
## <?xml version="1.0" encoding="UTF-8"?>
## <parent>
##   <child>1</child>
##   <child>
##     <grandchild>2</grandchild>
##   </child>
##   <child>3</child>
##   <child>4</child>
## </parent>

.告诉%>%在随后对xml_add_child()的调用中将“父”节点放在何处。中间的()括号表达式利用了以下事实:您希望通过管道将其插入“子”节点,然后将该子节点通过管道传输到孙子节点。

如果您真的想在整个过程中使用管道,另一种选择是使用%T>%管道,而不是%>%管道(或者两者的混合)。两者之间的区别如下:

> 1:3 %>% mean() %>% str()
 num 2
> 1:3 %T>% mean() %>% str()
 int [1:3] 1 2 3

%T>%管道将左侧表达式的值压入右侧表达式,但进一步将其压入后续表达式。这意味着您可以在管道中间调用函数以获取其副作用,然后继续在管道中向前传递较早的对象引用。

这就是您说“提升水平”时要执行的操作-也就是说,备份到管道中的先前值并从那里开始工作。因此,您只需要%T>%管道,直到到达要%>%管道的位置(例如,创建孙子),然后返回%T>%管道以继续承载父节点对象引用向前。一个例子:

x3 <- xml_new_document()
x3 %>% 
  xml_add_child("parent") %T>%
    xml_add_child("child", 1, .where = "after") %T>%
    {xml_add_child(., "child") %>% xml_add_child("grandchild", 2)} %T>%
    xml_add_child("child", 3, .where = "after") %>%
    xml_add_child("child", 4, .where = "after")
message(x3)
## <?xml version="1.0" encoding="UTF-8"?>
## <parent>
##   <child>1</child>
##   <child>
##     <grandchild>2</grandchild>
##   </child>
##   <child>3</child>
##   <child>4</child>
## </parent>

请注意最后一个%>%而不是%T>%。如果将%>%交换为%T>%,则整个管道的值将仅是“父”节点树:

{xml_document}
<parent>
[1] <child>1</child>
[2] <child>\n  <grandchild>2</grandchild>\n</child>
[3] <child>3</child>
[4] <child>4</child>

(再次-最终-并不重要,因为我们实际上是在使用副作用构建x3,但是它将父节点树打印到控制台,这可能会造成混淆。)

同样,鉴于尴尬,我建议完全不要使用管道,但这取决于您。更好的方法是保留要附加子对象的每个对象,然后每次再次引用它。像在第一个示例中一样,将父节点另存为p,跳过所有管道,并在示例代码中使用p的任何地方仅引用.