获取 XElement 的 InnerXml 的最佳方法?
Posted
技术标签:
【中文标题】获取 XElement 的 InnerXml 的最佳方法?【英文标题】:Best way to get InnerXml of an XElement? 【发布时间】:2010-09-05 10:49:12 【问题描述】:在下面的代码中获取混合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()
;
【问题讨论】:
【参考方案1】:我想看看这些建议的解决方案中哪一个表现最好,所以我进行了一些比较测试。出于兴趣,我还将 LINQ 方法与 Greg 建议的普通旧 System.Xml 方法进行了比较。变化很有趣,出乎我的意料,最慢的方法比最快的方法慢 3 倍以上。
按从快到慢排序的结果:
-
CreateReader - 实例猎人(0.113 秒)
普通旧 System.Xml - Greg Hurlman(0.134 秒)
使用字符串连接进行聚合 - Mike Powell(0.324 秒)
StringBuilder - Vin(0.333 秒)
String.Join on array - 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>
上面以秒显示的数字是连续 1000 次提取 20 个节点的“内部 XML”并取 5 次运行的平均值(平均值)的结果。我没有包括将 XML 加载并解析为 XmlDocument
(对于 System.Xml 方法)或 XDocument
(对于所有其他方法)所花费的时间。
我使用的 LINQ 算法是:(C# - 都采用 XElement
"parent" 并返回内部 XML 字符串)
创建阅读器:
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());
我没有在这里展示“Plain old System.Xml”算法,因为它只是在节点上调用 .InnerXml。
结论
如果性能很重要(例如大量 XML,经常解析),我会每次都使用 Daniel 的 CreateReader
方法。如果您只是做一些查询,您可能想要使用 Mike 更简洁的 Aggregate 方法。
如果您在具有大量节点(可能有 100 个节点)的大型元素上使用 XML,您可能会开始看到使用 StringBuilder
优于 Aggregate 方法的好处,但不会超过 CreateReader
。我不认为Join
和Concat
方法在这些情况下会更有效,因为将大列表转换为大数组的代价是(在较小的列表中更明显)。
【讨论】:
StringBuilder 版本可以写成一行: var result = parent.Elements().Aggregate(new StringBuilder(), (sb, xelem) => sb.AppendLine(xelem.ToString()) , sb => sb.ToString()) 你错过了parent.CreateNavigator().InnerXml
(扩展方法需要using System.Xml.XPath
)。
我没想到你需要 .Concat
中的 .ToArray()
,但它似乎让它更快
如果您不滚动到这些答案的底部:请考虑根据this answer 从.ToString()
中剥离容器/根目录。似乎更快...
你真的应该将 var reader = parent.CreateReader();
包装在 using 语句中。【参考方案2】:
我认为这是一个更好的方法(在 VB 中,应该不难翻译):
给定一个 XElement x:
Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
【讨论】:
不错!这比提出的其他一些方法要快得多(我对它们都进行了测试 - 有关详细信息,请参阅我的答案)。尽管他们都完成了这项工作,但这个速度最快——甚至比 System.Xml.Node.InnerXml 本身还要快! XmlReader 是一次性的,所以请不要忘记用 using 包装它(如果我知道 VB,我会自己编辑答案)。【参考方案3】:在 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
只是一个元素。
【讨论】:
【参考方案4】:感谢那些发现并证明了最佳方法的人(谢谢!),这里将其包含在扩展方法中:
public static string InnerXml(this XNode node)
using (var reader = node.CreateReader())
reader.MoveToContent();
return reader.ReadInnerXml();
【讨论】:
【参考方案5】:保持简单高效:
String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
连接字符串时,聚合会导致内存和性能效率低下
使用 Join("", sth) 使用的字符串数组是 Concat 的两倍...而且在代码中看起来很奇怪。
使用 += 看起来很奇怪,但显然并不比使用 '+' 差多少 - 可能会针对相同的代码进行优化,因为分配结果未被使用并且可能会被编译器安全地删除。
StringBuilder 非常必要 - 每个人都知道不必要的“状态”很糟糕。
【讨论】:
【参考方案6】:我最终使用了这个:
Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
【讨论】:
这将做很多字符串连接 - 我更喜欢 Vin 自己使用 StringBuilder。手动 foreach 不是否定的。 今天这个方法真的救了我,尝试用新的构造函数写出一个 XElement 并且没有其他方法可以方便地使用它,而这个方法做到了。谢谢!【参考方案7】:就个人而言,我最终使用 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();
【讨论】:
【参考方案8】:@Greg:看来您已将答案编辑为完全不同的答案。我的回答是肯定的,我可以使用 System.Xml 来做到这一点,但我希望能用 LINQ to XML 来搞定。
如果其他人想知道为什么我不能只使用 XElement 的 .Value 属性来获得我需要的东西,我会在下面留下我的原始回复:
@Greg:Value 属性连接任何子节点的所有文本内容。因此,如果 body 元素只包含文本,它可以工作,但如果它包含 XHTML,我会将所有文本连接在一起,但没有任何标签。
【讨论】:
我遇到了同样的问题并认为这是一个错误:我有“混合”内容(即<root>random text <sub1>child</sub1> <sub2>child</sub2></root>
)通过XElement.Parse(...).Value
变成random text childchild
【参考方案9】:
// 使用正则表达式可能会更快地简单地修剪开始和结束元素标签
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);
【讨论】:
整洁。使用IndexOf
: var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
更快【参考方案10】:
doc.ToString() 或 doc.ToString(SaveOptions) 完成工作。 见http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx
【讨论】:
不,它没有。它还包括具有所有属性的元素。只需要开始和结束标记之间的内容。【参考方案11】:是否可以使用 System.Xml 命名空间对象来完成这里的工作,而不是使用 LINQ?正如您已经提到的,XmlNode.InnerXml 正是您所需要的。
【讨论】:
【参考方案12】:想知道是否(请注意我摆脱了 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 之类的东西,这让我觉得微软的人可能会在其中增加一些额外的性能提升。当然,我的 .ToArray() 对此表示否定,但我只是想提出另一个建议。
【讨论】:
【参考方案13】:你知道吗?最好的办法是回到 CDATA :( 我在这里寻找解决方案,但我认为 CDATA 是迄今为止最简单和最便宜的,不是最方便的开发方式
【讨论】:
【参考方案14】:var innerXmlAsText= XElement.Parse(xmlContent)
.Descendants()
.Where(n => n.Name.LocalName == "template")
.Elements()
.Single()
.ToString();
会为你完成这项工作
【讨论】:
【参考方案15】: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();
【讨论】:
如果元素有任何属性,甚至只有一个空格太多,逻辑就会失败。以上是关于获取 XElement 的 InnerXml 的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章
使用 XmlDocument 获取 InnerText 或 InnerXml 时保留 XML 实体引用