获取XElement的InnerXml的最佳方法是什么?

在下面的代码中获取混合body元素的内容的最好方法是什么? 该元素可能包含XHTML或文本,但我只是希望其内容的stringforms。 XmlElementtypes具有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() }; 

我想看看这些build议的解决scheme哪一个最好,所以我跑了一些比较testing。 出于兴趣,我还将LINQ方法与Gregbuild议的普通旧式System.Xml方法进行了比较。 变化是有趣的,而不是我所期望的,最慢的方法比最快的方法慢3倍以上

结果以最快到最慢sorting:

  1. CreateReader – 实例猎人(0.113秒)
  2. 普通旧System.Xml – 格雷格·赫尔曼(0.134秒)
  3. 聚合string连接 – 迈克尔鲍威尔(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个节点的“内部XML”,连续1000次,并取5次运行的平均值的结果。 我没有包括加载和parsingXML到XmlDocument (用于System.Xml方法)或XDocument (用于所有其他)的时间。

我使用的LINQalgorithm是: (C# – 全部采用XElement “parent”并返回内部XMLstring)

CreateReader:

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

聚合string连接:

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

我没有在这里显示“Plain old System.Xml”algorithm,因为它只是在节点上调用.InnerXml。


结论

如果性能是重要的(例如,大量的XML,经常分析),我会每次使用Daniel的CreateReader方法 。 如果你只是做了几个查询,你可能想使用Mike的更简洁的Aggregate方法。

如果你在有很多节点的大型元素上使用XML(可能是100),你可能会开始看到使用StringBuilder而不是Aggregate方法的好处,但是不能通过CreateReader 。 我不认为JoinConcat方法在这些条件下会更有效率,因为将大列表转换为大列表(甚至在小列表中显而易见)。

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

给定一个XElement x:

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

在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() XAttribute element.Nodes()给你一切像XTextXAttribute等,但XElement只有一个元素。

对于那些发现并certificate是最好的方法的人(所有的应有的功劳)(谢谢!),这里用一个扩展方法来包装:

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

保持简单和高效:

 String.Concat(node.Nodes().Select(x => x.ToString()).ToArray()) 
  • 连接string时,聚合会降低内存和性能
  • 使用Join(“”,sth)使用比Concat大两倍的string数组…在代码中看起来很奇怪。
  • 使用+ =看起来很奇怪,但显然并没有比使用'+'差很多 – 可能会优化到相同的代码,因为赋值结果是未使用的,可能会安全地删除编译器。
  • StringBuilder非常重要 – 每个人都知道不必要的“状态”很糟糕。

我结束了使用这个:

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

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

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

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

 var innerXml = myXElement.InnerXml(); 

@Greg:看起来你已经编辑你的答案是一个完全不同的答案。 对于我的回答是肯定的,我可以使用System.Xml来做到这一点,但希望能够把LINQ to XML弄湿。

我将在下面留下我的原始答复,以防其他人想知道为什么我不能只使用XElement的.Value属性来获得我所需要的:

@Greg:Value属性连接任何子节点的所有文本内容。 所以如果body元素只包含文本,但是如果它包含XHTML,我会将所有文本连接在一起,但不包含任何标记。

//使用正则expression式可能会更快地修改开始和结束元素标记

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

doc.ToString()或doc.ToString(SaveOptions)做的工作。 请参阅http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

是否有可能使用System.Xml命名空间对象来完成这里的工作,而不是使用LINQ? 正如你已经提到的,XmlNode.InnerXml正是你所需要的。

想知道是否(注意我摆脱了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=string+string

与string.Join相比,它提到了FastStringAllocation或者其他的东西,这使得我微软的人可能会在这里增加一些额外的性能。 当然,我的.ToArray()呼吁我否定这一点,但我只是想提出另一个build议。

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

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