将XML标记表示为字符范围

时间:2013-04-22 19:47:58

标签: xml xml-parsing

您是否知道任何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)。

3 个答案:

答案 0 :(得分: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属性)或从下一个节点的LinePosition减去1来计算结束偏移。

我不知道你是否觉得这种方法不那么痛苦,但是会在哪里开始(之前从未解决过这样的问题)。

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

答案 1 :(得分: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"

答案 2 :(得分: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"