1

您是否知道任何基于原始未标记文本将 XML 标记与字符范围或偏移信息进行转换的 XML 库?(我不太关心库的基础平台:它可能是 Java、Python、Perl 等)

例如,假设我有这个未标记的文本:

the calico cat and the black dog

标记为

the <PET>calico</PET> cat and the <PET>black do</PET>g

标记有位置错误(如上所示)。我知道如何解决这些错误:这不是这里的问题。但是使用传统的层次结构的 XML 解析器来做这件事是相当痛苦的。如果将 XML 标记转换为我可以轻松调整的带外字符范围会更容易:

PET: 4-10    # "calico"  (should be 4-14 "calico cat" )
PET: 23-31   # "black do" (should be 23-32 "black dog" )

修复偏移后,我将重新生成 XML。

我只找到了一些返回字符偏移信息的 XML 解析库,并且偏移基于 XML 文本,而不是未标记的文本。偏移量也可能是错误的(参见Java,XMLEvent location Characters)。

4

3 回答 3

1

下面是如何使用 .NET 中的 XmlReader 来完成此操作:

class MarkupSpan
{
    internal string Name;
    internal int Start;
    internal int Stop;
    internal List<object> ChildItems;

    internal MarkupSpan(string name, int start)
    {
        Name = name;
        Start = start;
        ChildItems = new List<object>();
    }

    public override string ToString()
    {
        return string.Concat(ChildItems);
    }
}


private static string ProcessMarkup(string text)
{
    Stack<MarkupSpan> inputStack = new Stack<MarkupSpan>();

    StringReader sr = new StringReader("<n>" + text + "</n>");

    XmlReader xr = XmlReader.Create(sr);
    int pos = 0;
    StringBuilder output = new StringBuilder();

    while (xr.Read())
    {
        if (xr.Depth > 0)
        {
            switch (xr.NodeType)
            {
                case XmlNodeType.Text:
                    pos += xr.Value.Length;
                    if (inputStack.Count != 0)
                    {
                        inputStack.Peek().ChildItems.Add(xr.Value);
                    }
                    break;
                case XmlNodeType.Element:
                    MarkupSpan ms = new MarkupSpan(xr.LocalName, pos);
                    if (inputStack.Count != 0)
                    {
                        inputStack.Peek().ChildItems.Add(ms);
                    }
                    inputStack.Push(ms);
                    break;
                case XmlNodeType.EndElement:
                    ms = inputStack.Pop();
                    ms.Stop = pos;
                    if (inputStack.Count == 0)
                    {
                        output.Append(OutputSpan(ms));
                    }
                    break;
            }
        }
    }

    return output.ToString();
}

private static string OutputSpan(MarkupSpan ms)
{
    string nameAndRange = string.Format("{0}: {1}-{2}",
                                        ms.Name, ms.Start, ms.Stop);
    return string.Format("{0,-14}# \"{1}\"", nameAndRange, ms) +
           Environment.NewLine +
           string.Concat(ms.ChildItems.OfType<MarkupSpan>().Select(OutputSpan));
}

在您的示例输入上运行时,结果是:

PET: 4-10     # "calico"
PET: 23-31    # "black do"

当在一个更有趣的例子上运行时(带有嵌套标签):

the <PET><COLOR>calico</COLOR></PET> cat and the <PET><COLOR>bla</COLOR>ck do</PET>g

结果是:

PET: 4-10     # "calico"
COLOR: 4-10   # "calico"
PET: 23-31    # "black do"
COLOR: 23-26  # "bla"
于 2013-04-22T20:50:48.450 回答
1

我已经提供了一个 .NET 答案,但这里是如何使用 XSLT 完成的:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />
  <xsl:variable name="space" select="'                  '" />
  <xsl:variable name="spaceLen" select="string-length($space)" />

  <xsl:template match="text()" />

  <xsl:template match="*/*">
    <xsl:param name="parentLeading" select="0" />
    <xsl:variable name="leadingText">
      <xsl:apply-templates select="preceding-sibling::node()" mode="value" />
    </xsl:variable>

    <xsl:variable name="leading" select="$parentLeading + 
                                             string-length($leadingText)" />

    <xsl:variable name="nameAndRange" 
                  select="concat(local-name(), ' ', $leading, 
                                 '-', $leading + string-length())" />
    <xsl:variable name="spacing"
                  select="substring($space, 1, 14 - string-length($nameAndRange))" />
    <xsl:value-of select="concat($nameAndRange, $spacing, 
                                 '# &quot;', ., '&quot;&#xA;')"/>
    <xsl:apply-templates>
      <xsl:with-param name="parentLeading" select="$leading" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="node()" mode="value">
    <xsl:value-of select="." />
  </xsl:template>
</xsl:stylesheet>

在此输入上运行时:

<n>the <PET>calico</PET> cat and the <PET>black do</PET>g</n>

结果是:

PET 4-10      # "calico"
PET 23-31     # "black do"

在此输入上运行时:

<n>the <PET><COLOR>calico</COLOR></PET> cat and the <PET><COLOR>bla</COLOR>ck do</PET>g</n>

结果是:

PET 4-10      # "calico"
COLOR 4-10    # "calico"
PET 23-31     # "black do"
COLOR 23-26   # "bla"
于 2013-04-22T21:11:45.223 回答
1

你反对.NET吗?

您可能想从 HTML 的角度来解决这个问题。有一个名为HtmlAgilityPack的库可以解析 HTML(无论如何它只是 XML)。在这样做时,您的示例看起来像一个节点列表,在文本节点和 HTML (XML)PET节点之间分解:

HtmlNode[n]
|
+--[0] "the " (text node)
|
+--[1] <PET>
|   |
|   +--[0] "calico" (text node)
|
+--[2] " cat and the " (text node)
|
+--[3] <PET>
|   |
|   +--[0] "black do" (text node)
|
+--[4] "g" (text node)

每个HtmlNode对象都有一个LinePosition属性,可以为您提供起始偏移量。可以通过添加节点文本(InnerText属性)的长度或从下一个节点的 中减去 1来计算结束偏移量LinePosition

我不知道您是否认为这种方法不那么痛苦,但这是要开始的地方(以前从未解决过这样的问题)。

这里有各种语言的 HTML 解析库列表。

于 2013-04-22T19:56:16.600 回答