首页 / 知识

关于.net:获取XElement的InnerXml的最佳方法?

2023-04-11 18:30:00

关于.net:获取XElement的InnerXml的最佳方法?

Best way to get InnerXml of an XElement?

在下面的代码中获取混合的body元素的内容的最佳方法是什么? 该元素可能包含XHTML或文本,但是我只希望其内容为字符串形式。 XmlElement类型具有InnerXml属性,这正是我所追求的。

编写的代码几乎可以实现我想要的功能,但是包含周围的 ... 元素,我不需要。

1
2
3
4
5
6
7
8
XDocument doc = XDocument.Load(new StreamReader(s));
var templates = from t in doc.Descendants("template")
                where t.Attribute("name").Value == templateName
                select new
                {
                   Subject = t.Element("subject").Value,
                   Body = t.Element("body").ToString()
                };

我想看看这些建议的解决方案中哪种效果最好,所以我进行了一些比较测试。出于兴趣,我还将LINQ方法与Greg建议的普通的System.Xml方法进行了比较。这种变化很有趣,而不是我所期望的,最慢的方法要比最快的方法慢3倍以上。

结果按最快到最慢的顺序排序:

  • CreateReader-实例猎人(0.113秒)
  • 普通的旧System.Xml-格雷格·霍尔曼(0.134秒)
  • 使用字符串连接聚合-Mike Powell(0.324秒)
  • StringBuilder-Vin(0.333秒)
  • String.Join加入数组-Terry(0.360秒)
  • 数组上的String.Concat-Marcin Kosieradzki(0.364)
  • 方法

    我使用了一个具有20个相同节点(称为"提示")的XML文档:

    1
    2
    3
    4
    5
    6
    <hint>
      Thinking of using a fake address?
      <br />
      Please don't. If we can't verify your address we might just
      have to reject your application.
    </hint>

    上面显示为秒的数字是提取20个节点(连续1000次)的"内部XML"并取5次运行的平均值(均值)的结果。我没有包括加载和解析XML到XmlDocument(对于System.Xml方法)或XDocument(对于所有其他XML)所花费的时间。

    我使用的LINQ算法是:(C#-全部使用XElement" parent"并返回内部XML字符串)

    CreateReader:

    1
    2
    3
    4
    var reader = parent.CreateReader();
    reader.MoveToContent();

    return reader.ReadInnerXml();

    用字符串连接聚合:

    1
    return parent.Nodes().Aggregate("", (b, node) => b += node.ToString());

    StringBuilder:

    1
    2
    3
    4
    5
    6
    7
    StringBuilder sb = new StringBuilder();

    foreach(var node in parent.Nodes()) {
        sb.Append(node.ToString());
    }

    return sb.ToString();

    String.Join在数组上:

    1
    return String.Join("", parent.Nodes().Select(x => x.ToString()).ToArray());

    数组上的String.Concat:

    1
    return String.Concat(parent.Nodes().Select(x => x.ToString()).ToArray());

    我没有在此处显示"普通的System.Xml"算法,因为它只是在节点上调用.InnerXml。

    结论

    如果性能很重要(例如很多XML,需要经常解析),我会每次使用Daniel的CreateReader方法。如果您只是做一些查询,则可能要使用Mike更简洁的Aggregate方法。

    如果您在具有很多节点(可能是100个)的大型元素上使用XML,那么您可能会开始发现使用StringBuilder优于Aggregate方法,而不是超过CreateReader。我不认为在这种情况下JoinConcat方法会更有效,因为将大列表转换为大数组(即使在较小的列表中也很明显)会带来麻烦。


    我认为这是一种更好的方法(在VB中,应该不难翻译):

    给定XElement x:

    1
    2
    3
    Dim xReader = x.CreateReader
    xReader.MoveToContent
    xReader.ReadInnerXml

    在XElement上使用这种"扩展"方法怎么样?为我工作!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static string InnerXml(this XElement element)
    {
        StringBuilder innerXml = new StringBuilder();

        foreach (XNode node in element.Nodes())
        {
            // append node's xml string to innerXml
            innerXml.Append(node.ToString());
        }

        return innerXml.ToString();
    }

    或使用一点Linq

    1
    2
    3
    4
    5
    6
    7
    public static string InnerXml(this XElement element)
    {
        StringBuilder innerXml = new StringBuilder();
        doc.Nodes().ToList().ForEach( node => innerXml.Append(node.ToString()));

        return innerXml.ToString();
    }

    注意:上面的代码必须使用element.Nodes()而不是element.Elements()。记住两者之间的区别非常重要。 element.Nodes()可以为您提供诸如XTextXAttribute等之类的所有内容,但是XElement仅是一个元素。


    归功于那些发现并证明了最佳方法的人(谢谢!),在此将其包裹在扩展方法中:

    1
    2
    3
    4
    5
    6
    public static string InnerXml(this XNode node) {
        using (var reader = node.CreateReader()) {
            reader.MoveToContent();
            return reader.ReadInnerXml();
        }
    }

    保持简单高效:

    1
    String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
    • 连接字符串时,聚合内存和性能低下
    • 使用Join(",sth)使用的字符串数组是Concat的两倍...并且在代码中看起来很奇怪。
    • 使用+ =看起来很奇怪,但是显然不比使用'+'更糟-可能会针对相同的代码进行优化,以防分配结果未使用并且可能被编译器安全删除。
    • StringBuilder非常必要-每个人都知道不必要的"状态"糟透了。

    我最终使用了这个:

    1
    Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());

    我个人最终使用Aggregate方法编写了InnerXml扩展方法:

    1
    2
    3
    4
    public static string InnerXml(this XElement thiz)
    {
       return thiz.Nodes().Aggregate( string.Empty, ( element, node ) => element += node.ToString() );
    }

    这样,我的客户端代码就和旧的System.Xml命名空间一样简洁:

    1
    var innerXml = myXElement.InnerXml();

    @Greg:看来您已将答案编辑为完全不同的答案。对此我的回答是,我可以使用System.Xml进行此操作,但希望通过LINQ to XML来解决问题。

    如果其他人想知道为什么我不能仅仅使用XElement的.Value属性来获取所需的内容,我将在下面保留原始答复:

    @Greg:Value属性连接任何子节点的所有文本内容。因此,如果body元素仅包含文本,则可以使用,但是如果包含XHTML,则可以将所有文本连接在一起,但不包含任何标签。


    doc.ToString()或doc.ToString(SaveOptions)完成工作。
    请参阅http://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement.tostring(v=vs.110).aspx


    //使用Regex可能会更快,以简单地修剪begin和end元素标签

    1
    2
    3
    4
    5
    var content = element.ToString();
    var matchBegin = Regex.Match(content, @"<.+?>");
    content = content.Substring(matchBegin.Index + matchBegin.Length);          
    var matchEnd = Regex.Match(content, @"</.+?>", RegexOptions.RightToLeft);
    content = content.Substring(0, matchEnd.Index);

    1
    2
    3
    4
    5
    6
    var innerXmlAsText= XElement.Parse(xmlContent)
                        .Descendants()
                        .Where(n => n.Name.LocalName =="template")
                        .Elements()
                        .Single()
                        .ToString();

    会为你做的工作


    你懂?最好的办法是回到CDATA :(我在这里看解决方案,但我认为CDATA是迄今为止最简单,最便宜的,而不是最方便的开发方法。


    想知道是否(注意我摆脱了b + =而只有b +)

    1
    2
    t.Element("body" ).Nodes()
     .Aggregate("", ( b, node ) => b + node.ToString() );

    效率可能会略低于

    1
    2
    string.Join("", t.Element.Nodes()
                      .Select( n => n.ToString() ).ToArray() );

    不是100%肯定的...但是浏览了Reflector中的Aggregate()和string.Join()...我想我把它读为Aggregate只是附加了一个返回值,所以本质上您会得到:

    字符串=字符串+字符串

    与string.Join相比,它在其中提到了FastStringAllocation之类的东西,这使我觉得Microsoft的人们可能在其中增加了一些额外的性能。当然,我的.ToArray()称呼我为否定,但我只想提出另一个建议。


    是否可以使用System.Xml命名空间对象代替LINQ在这里完成工作?正如您已经提到的,XmlNode.InnerXml正是您所需要的。


    1
    2
    3
    4
    5
    6
    7
    8
    public static string InnerXml(this XElement xElement)
    {
        //remove start tag
        string innerXml = xElement.ToString().Trim().Replace(string.Format("<{0}>", xElement.Name),"");
        ////remove end tag
        innerXml = innerXml.Trim().Replace(string.Format("</{0}>", xElement.Name),"");
        return innerXml.Trim();
    }


    方法元素文本混合

    最新内容

    相关内容

    热门文章

    推荐文章

    标签云

    猜你喜欢