您是否知道任何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)。
答案 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,
'# "', ., '"
')"/>
<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"