150

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

15 回答 15

213

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

结果按最快到最慢排序:

  1. CreateReader - 实例猎手(0.113 秒)
  2. 普通的旧 System.Xml - Greg Hurlman(0.134 秒)
  3. 使用字符串连接进行聚合 - Mike Powell(0.324 秒)
  4. StringBuilder - Vin (0.333 秒)
  5. String.Join on array - 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>

上面显示的秒数是连续提取 1000 次 20 个节点的“内部 XML”并取 5 次运行的平均值(平均值)的结果。我没有包括将 XML 加载和解析为XmlDocument(对于System.Xml方法)或XDocument(对于所有其他方法)所花费的时间。

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

创建阅读器:

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

return reader.ReadInnerXml();

用字符串连接聚合:

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

字符串生成器:

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,您可能会开始看到使用StringBuilderAggregate 方法的好处,但不会超过CreateReader. 我不认为JoinandConcat方法在这些条件下会更有效,因为将大列表转换为大数组的代价(即使在这里使用较小的列表也很明显)。

于 2009-11-09T23:08:14.530 回答
71

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

给定一个 XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
于 2009-03-18T17:17:25.360 回答
20

在 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()为您提供诸如等之类的所有内容XTextXAttributeXElement仅提供一个元素。

于 2008-08-19T20:29:41.767 回答
17

感谢那些发现并证明了最佳方法的人(谢谢!),在这里它被包含在一个扩展方法中:

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}
于 2013-01-04T20:21:41.603 回答
10

保持简单高效:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • 连接字符串时,聚合在内存和性能方面效率低下
  • 使用 Join("", sth) 使用的是比 Concat 大两倍的字符串数组......并且在代码中看起来很奇怪。
  • 使用 += 看起来很奇怪,但显然并不比使用 '+' 差多少 - 可能会针对相同的代码进行优化,因为分配结果未使用并且可能会被编译器安全地删除。
  • StringBuilder 是如此必要——每个人都知道不必要的“状态”很糟糕。
于 2009-10-31T17:22:59.363 回答
7

我最终使用了这个:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
于 2008-08-06T19:36:02.667 回答
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();
于 2010-03-17T08:47:45.507 回答
2

@Greg:看来您已将答案编辑为完全不同的答案。我的回答是肯定的,我可以使用 System.Xml 来做到这一点,但我希望能用 LINQ to XML 让我的脚湿透。

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

@Greg: Value 属性连接任何子节点的所有文本内容。因此,如果 body 元素仅包含文本,它可以工作,但如果它包含 XHTML,我会将所有文本连接在一起,但没有标签。

于 2008-08-06T18:25:08.877 回答
1

// 使用正则表达式可能会更快地简单地修剪开始和结束元素标记

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);
于 2014-02-08T04:55:35.983 回答
1

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

于 2014-10-13T20:08:32.527 回答
0

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

于 2008-08-06T18:20:18.903 回答
0

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

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

效率可能略低于

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

不是 100% 肯定...但是在反射器中查看 Aggregate() 和 string.Join()...我认为我将它读为 Aggregate 只是附加了一个返回值,所以基本上你得到:

字符串 = 字符串 + 字符串

与 string.Join 相比,它在其中提到了 FastStringAllocation 之类的东西,这让我觉得微软的人可能已经在其中增加了一些额外的性能提升。当然,我的 .ToArray() 对此表示否定,但我只是想提出另一个建议。

于 2009-03-18T16:57:12.803 回答
0

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

于 2009-10-25T23:39:21.693 回答
0
var innerXmlAsText= XElement.Parse(xmlContent)
                    .Descendants()
                    .Where(n => n.Name.LocalName == "template")
                    .Elements()
                    .Single()
                    .ToString();

将为您完成工作

于 2018-10-25T19:07:28.277 回答
-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();
}
于 2010-08-13T07:05:02.603 回答