检索元素的“outerxml”(如innerxml,但包含元素本身)

时间:2016-11-09 23:46:03

标签: go xml-parsing

我有一个使用输入流的Go程序,即os.Stdin:一个非常大的XML文件,所以我不能一次处理它。

我想要提取某些特定性质的XML元素进行后期处理。

我可以毫不费力地识别提取元素,并获取相关的开始和结束元素。但是,我不确定如何将整个元素转储为字符串,而不是仅使用内部XML。

例如,假设我有以下XML:

<a>
  <b somethingUseful="1">
    <c>Hello</c>
    <d>world</d>
  </b>
  <e>
    <foo/>
  </e>
  <!-- Imagine there were 1 billion lines in between -
       I need to stream this! -->
  <b somethingUseful="321">
    <c>Hello again</c>
  </b>
</a>

在这个例子中,我想从头到尾输出每个<b>元素。

innerxmlDecodeElement一起使用,我能够以流媒体方式实现这一目标:

Here comes a B:

    <c>Hello</c>
    <d>world</d>

Here comes a B:

    <c>Hello again</c>

如此接近,但它缺少<b>标签(和属性)本身。在没有牺牲解码的流媒体特性的情况下,我无法弄清楚如何完成最后一步。

要清楚,我想要的输出是这样的:

Here comes a B:
  <b somethingUseful="1">
    <c>Hello</c>
    <d>world</d>
  </b>
Here comes a B:
  <b somethingUseful="321">
    <c>Hello again</c>
  </b>

这是一个阐述这个例子的游乐场,以及我在这方面取得的成就:

https://play.golang.org/p/XqJY_1pa9j

2 个答案:

答案 0 :(得分:2)

受@ nothingmuch使用decoder.InputOffset的启发,我使用TeeReader将输入Reader分成两部分:通过解码器解析的标准,以及我们的缓冲区ll用于输出精确元素(位于遇到元素之前和之后的decoder.InputOffset之间)。

为了最大限度地减少内存使用量,缓冲区会一直清除,直到我们知道不可能匹配的点。我们维持抵消以跟踪这一情况。这种增加的复杂性是必要的,因为解码器可以在手头的令牌之前进一步从读取器获取字节,因此我们需要注意不要清除我们实际需要的东西。

因此额外的内存使用量只有:

  1. 最大的两个令牌,它们可以在被清除回一个缓冲区之前同时存储在缓冲区中。
  2. 要输出的实际元素的大小。
  3. 这是一个带有解决方案的更新游乐场:

    https://play.golang.org/p/H8WVDWI57r

答案 1 :(得分:1)

一种相当粗略的方法是通过在开始元素之前和结束元素之前向解码器询问the offset来保存偏移并仅读取这些字节。

参见this playground example,它将读者分成两个管道,其中一个管道进入XML解码器,而另一个管道被缓冲,然后用于提取对应于XML元素的字节范围。

然后,XML解码例程在通道上写入偏移对,另一个线程使用该偏移来从读取器流的副本跳过或输出感兴趣的区域。这应该比我做的黑客工作更严肃地完成,即使用堆栈和匹配过滤条件。

此解决方案假设Seek / ReadAt不可行,回想起来我可能在那里过度使用,如果您只是打开文件两次,假设它是一个文件,这会更简单。