获取XElement的InnerXml的最佳方法是什么?
在下面的代码中获取混合body
元素的内容的最好方法是什么? 该元素可能包含XHTML或文本,但我只是希望其内容的stringforms。 XmlElement
types具有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:
- CreateReader – 实例猎人(0.113秒)
- 普通旧System.Xml – 格雷格·赫尔曼(0.134秒)
- 聚合string连接 – 迈克尔鲍威尔(0.324秒)
- StringBuilder – Vin(0.333秒)
- 数组上的String.Join – Terry(0.360秒)
- 数组上的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
。 我不认为Join
和Concat
方法在这些条件下会更有效率,因为将大列表转换为大列表(甚至在小列表中显而易见)。
我认为这是一个更好的方法(在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()
给你一切像XText
, XAttribute
等,但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(); }