获得XElement的InnerXml的最佳方法?


147

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

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

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()
                };

Answers:


208

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

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

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

方法

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

<hint>
  <strong>Thinking of using a fake address?</strong>
  <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(对于所有其他方法)所花费的时间。

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

CreateReader:

var reader = parent.CreateReader();
reader.MoveToContent();

return reader.ReadInnerXml();

用字符串连接聚合:

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

StringBuilder:

StringBuilder sb = new StringBuilder();

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

return sb.ToString();

String.Join数组:

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

数组上的String.Concat:

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

我没有在这里显示“普通的System.Xml”算法,因为它只是在节点上调用.InnerXml。


结论

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

如果您在具有许多节点(可能是100个)的大型元素上使用XML,那么您可能会开始发现使用StringBuilderAggregate方法带来的好处,而不是over CreateReader。我不认为Joinand Concat方法在这些情况下会更有效,因为将大列表转换为大数组(即使在这里使用较小的列表也很明显)会带来麻烦。


StringBuilder版本可以写在一行上:var result = parent.Elements()。Aggregate(new StringBuilder(),(sb,xelem)=> sb.AppendLine(xelem.ToString()),sb => sb.ToString( ))
Softlion 2011年

7
您错过了parent.CreateNavigator().InnerXml(需要using System.Xml.XPath扩展方法)。
理查德

我本来不会以为您需要.ToArray()内部空间.Concat,但它似乎会使它变得更快
drzaus14年

如果你不滚动到这些答案的底部:只考虑从剥离容器/根.ToString()每次这个答案。似乎更快……
drzaus 2014年

2
您实际上应该将其包装var reader = parent.CreateReader();在using语句中。
BrainSlugs83 2015年

70

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

给定XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml

真好!这比提出的其他一些方法要快得多(我对它们都进行了测试-有关详细信息,请参见我的答案)。尽管他们所有人都可以完成这项工作,但是这是最快的-甚至比System.Xml.Node.InnerXml本身看得更快!
卢克·桑普森

4
XmlReader是一次性的,因此请不要忘记使用它包装(如果我知道VB,我会自己编辑答案)。
德米特里·费多尔科夫

19

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

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

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只有一个元素。


15

归功于那些发现并证明了最佳方法的人(谢谢!),这里将其扩展为一种扩展方法:

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}

10

保持简单高效:

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

7

我最终使用了这个:

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

这将进行很多字符串连接-我更喜欢Vin自己使用StringBuilder。手动foreach不是负面的。
马克·格雷夫

今天,这种方法确实为我省了一笔钱,试图用新的构造函数写出一个XElement,而其他任何一种方法都不能方便地使用它。谢谢!
2014年

3

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

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

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

var innerXml = myXElement.InnerXml();

2

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

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

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


我遇到了一个完全相同的问题,并认为这是一个错误:我混合了内容(即<root>random text <sub1>child</sub1> <sub2>child</sub2></root>),random text childchild通过XElement.Parse(...).Value
drzaus 2014年

1

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

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
整齐。使用起来甚至更快IndexOfvar xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus14年



0

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

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

效率可能略低于

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

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

字符串=字符串+字符串

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


0

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



-2
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();
}

而且,如果元素具有任何属性或什至只有太多的空间,则逻辑将失败。
Christoph
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.