获得XElement的InnerXml的最佳方法?
在下面的代码中获取混合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() };
解决方案
回答
是否可以使用System.Xml命名空间对象代替LINQ在此处完成工作?正如我们已经提到的,XmlNode.InnerXml正是我们所需要的。
回答
@Greg:我们似乎已将答案编辑为完全不同的答案。对此我的回答是,我可以使用System.Xml进行此操作,但希望通过LINQ to XML来解决。
如果其他人想知道为什么我不能仅仅使用XElement的.Value属性来获取所需的内容,我将在下面保留原始答复:
@Greg:Value属性连接任何子节点的所有文本内容。因此,如果body元素仅包含文本,则可以使用,但是如果包含XHTML,则可以将所有文本连接在一起,但不包含任何标签。
回答
我最终使用了这个:
Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
回答
在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()
会为我们提供诸如XText
,XAttribute
等之类的所有内容,但是XElement
仅是一个元素。
回答
想知道是否(注意我摆脱了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()称呼我为否定,但我只想提出另一个建议。
回答
我认为这是一种更好的方法(在VB中,应该不难翻译):
给定XElement x:
Dim xReader = x.CreateReader xReader.MoveToContent xReader.ReadInnerXml
回答
你懂?最好的办法是回到CDATA :(我在这里看解决方案,但我认为CDATA是迄今为止最简单,最便宜的,而不是最方便的开发方法。
回答
保持简单高效:
String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
- 连接字符串时,聚合内存和性能低下
- 使用Join("",sth)使用的字符串数组是Concat的两倍...在代码中看起来很奇怪。
- 使用+ =看起来很奇怪,但显然并不比使用'+'差很多-可能会针对相同的代码进行优化,以防分配结果未使用并且可能被编译器安全删除。
- StringBuilder非常必要-每个人都知道不必要的"状态"糟透了。
回答
我想看看这些建议的解决方案中哪种效果最好,所以我进行了一些比较测试。出于兴趣,我还将LINQ方法与Greg建议的普通的System.Xml方法进行了比较。这种变化很有趣,而不是我所期望的,最慢的方法要比最快的方法慢3倍以上。
结果按最快到最慢的顺序排序:
- CreateReader-实例猎人(0.113秒)
- 普通的旧System.Xml-格雷格·赫尔曼(Greg Hurlman)(0.134秒)
- 使用字符串连接进行汇总-Mike Powell(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个节点(连续1000次)的"内部XML"并取5次运行的平均值(均值)的结果。我没有包括将XML加载并解析为XmlDocument(对于System.Xml方法)或者XDocument(对于所有其他文件)所花费的时间。
我使用的LINQ算法是:(调用以XElement
为" parent"并返回内部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,那么我们可能会开始发现使用StringBuilder而不是Aggregate方法的好处,而不是使用CreateReader的好处。我不认为在这种情况下Join
和Concat
方法会更有效,因为将大列表转换为大数组会带来麻烦(即使在较小的列表中也很明显)。
回答
我个人最终使用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();
回答
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(); }