.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"