获取 XElement 在原始文档中的位置和长度

问题描述 投票:0回答:2

我正在解析特定节点的 XML 文档,并希望稍后在 ui 中显示 xml 文档,突出显示特定部分。为此,我需要知道文档文本中元素的位置及其长度。

到目前为止,我发现,在加载XDocument时,我应该指定

LoadOptions.SetLineInfo
,这样我至少可以获取原始xml字符串中的位置。这给了我元素名称开始的字符,所以我应该减去一,以获得标签的实际开始。然而,我无法找到一种方法来获取结束元素的位置。

到目前为止我尝试过的(LinqPad代码使用

.Dump()
,如果需要用
Console.WriteLine
替换),基本测试代码:

var xml = @"<xml>
  <myElement>
    <someProperty attribu=""attrVal1"" />
    <someOtherProp />
  </myElement>
</xml>";
// xml.Length => 105 (Note, there should be a TAB instead of four spaces before `<someOtherProp />`,
//                    to demonstrate problems)

var doc = XDocument.Parse(xml, LoadOptions.SetLineInfo);

var li = (IXmlLineInfo)doc;
$"{li.LineNumber - 1}:{li.LinePosition - 1}~{GetLen(doc.Root)}".Dump();

foreach (var el in doc.XPathSelectElements("//myElement/*"))
{
    li = (IXmlLineInfo) el;
    $"{li.LineNumber - 1}:{li.LinePosition - 1}~{GetLen(el)}".Dump();
}

现在,我的

GetLen
实现:

第一次尝试:使用

.ToString()

int GetLen(XElement el)
{
    return el.ToString().Length;
}

这将重新格式化代码,因此上面注释中提到的 TAB 将扩展为四个空格。

doc
将为 108 个字符,而不是现在的 105 个。所以,这不是一个选择。

第二次尝试:使用 XmlReader

int GetLen(XElement el)
{
    using (var r = el.CreateReader())
    {
        r.MoveToContent();
        var ox = r.ReadOuterXml();
        return ox.Length;
    }
}

这将丢弃任何不必要的空白,从而导致长度更短(

doc
为 86)。所以,这也不是一个选择。

除了我自己手动解析 XML 之外,我无法找到任何其他有意义的方法来完成我需要的任务,我想避免这样做。有谁有想法,我还能尝试什么?

当然,我可以读取 xml,重新格式化它,然后使用其中一个选项。但是,由于 XML 是由外部方提供的,我们想告诉他们我们在哪里发现了错误,所以最好知道他们的索引,而不是重新格式化后的索引。

感谢您的帮助!

c# xml position linq-to-xml
2个回答
0
投票

目前看来这是不可能的。相反,我们选择生成一个指向确切元素的 XPath 表达式。这样,我们就可以将格式保留为 UI 想要执行的任何操作,但始终拥有正确的元素。


0
投票

您可以通过围绕

XmlReader
类创建自己的代理,将其传递给
XDocument.Load()
并在每次
XmlReader
调用
Read()
时手动匹配代理中的开始和结束标记来完成此操作。其简化版本如下所示:

    struct LineCol
    {
        public int Line, Col;

        public LineCol(IXmlLineInfo info)
        {
            Line = info.LineNumber - 1;
            Col = info.LinePosition - 1;
        }

        public LineCol(int line, int col)
        {
            Line = line;
            Col = col;
        }
    }

    class ReaderProxy : XmlReader, IXmlLineInfo
    {
        private XmlReader _Inner;
        private IXmlLineInfo _InnerLineInfo;

        class StackEntry
        {
            public string Name;
            public LineCol Position;
        }

        Stack<StackEntry> _ElementStack = new Stack<StackEntry>();

        public ReaderProxy(XmlReader inner)
        {
            _Inner = inner;
            _InnerLineInfo = (IXmlLineInfo)inner;
        }

  
        public Dictionary<LineCol, LineCol> StartToEndLineMap = 
                            new Dictionary<LineCol, LineCol>();

        public override bool Read()
        {
            if (!_Inner.Read())
                return false;

            if (_Inner.NodeType == XmlNodeType.Element && !_Inner.IsEmptyElement)
            {
                _ElementStack.Push(new StackEntry { 
                      Name = _Inner.Name, 
                      Position = new LineCol(this)
                  });
            }
            else if (_Inner.NodeType == XmlNodeType.EndElement)
            {
                var el = _ElementStack.Pop();
                if (el.Name != _Inner.Name)
                    throw new Exception("Unbalanced element stack");

                StartToEndLineMap.Add(el.Position, new LineCol(this));
            }

            return true;
        }

    }

XmlReader
重写的其余方法应该只调用
_Inner
/
_InnerLineInfo
中相应的方法(VS 可以自动生成这些方法)。

然后您可以使用代理来查找每个元素的结束位置,如下所示:

var proxy = new ReaderProxy(XmlReader.Create(text));
var document = XDocument.Load(proxy, LoadOptions.SetLineInfo);
XElement element = ...;
var endPos = proxy.StartToEndLineMap[new LineCol((IXmlLineInfo)element)];
© www.soinside.com 2019 - 2024. All rights reserved.