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

在下面的代码中获取混合 body 元素的内容的最佳方式是什么?该元素可能包含XHTML或文本,但我只想以字符串形式表示内容。 XmlElement 类型具有 InnerXml 属性,这正是我所追求的。

The code as written almost does what I want, but includes the surrounding <body>...</body> element, which I don't want.

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

13 答案

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

0
额外

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

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

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

0
额外
我遇到了这个完全相同的问题,并认为这是一个错误:我有'混合'的内容(即 random text child child ),它通过 XElement.Parse(...)成为随机文本childchild 。Value
额外 作者 drzaus,

我结束了使用这个:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
0
额外
这种方法今天真的救了我,试图用新的构造函数写出一个XElement,而其他方法都没有把它们轻松地放在它上面,而这个方法做到了。谢谢!
额外 作者 delliottg,
这将做很多字符串连接 - 我宁愿Vin自己使用StringBuilder。手动foreach不是负面的。
额外 作者 Marc Gravell,

就我个人而言,我最终使用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();
0
额外

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

0
额外

对于那些发现并证明最佳方法的人(所有人都应有的赞誉)(谢谢!),这里用一种扩展方法来包装:

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

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

Note: The code above has to use element.Nodes() as opposed to element.Elements(). Very important thing to remember the difference between the two. element.Nodes() gives you everything like XText, XAttribute etc, but XElement only an Element.

0
额外

doc.ToString() or doc.ToString(SaveOptions) does the work. See http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

0
额外

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

鉴于XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
0
额外
XmlReader是一次性的,所以不要忘了用它来包装它(如果我知道VB,我会自己编辑答案)。
额外 作者 Dmitry Fedorkov,
+1这应该是被接受的答案。卢克·桑普森的答案很好读,但你在他面前给出了正确的答案。
额外 作者 Bazzz,
太好了!这比其他提出的方法快很多(我测试了所有的方法 - 查看我的答案了解详情)。虽然他们都做这个工作,但这个工作最快 - 甚至比System.Xml.Node.InnerXml本身更快!
额外 作者 Luke Sampson,
这个小代码片段非常有用,这应该是被接受的答案。
额外 作者 Frank Rosario,

保持简单和高效:

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

我想看看哪些建议的解决方案表现最好,所以我进行了一些比较测试。出于兴趣,我还将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 - 特里(0.360秒)
  6. 阵列上的String.Concat - Marcin Kosieradzki(0.364)

方法</强>

我使用了一个具有20个相同节点的单个XML文档(称为'提示'):


  Thinking of using a fake address?
  
Please don't. If we can't verify your address we might just have to reject your application.

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

The LINQ algorithms I used were: (C# - all take an XElement "parent" and return the inner xml string)

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.加入数组:

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方法。

如果你在有大量节点的大型元素上使用XML(可能是100),你可能会开始看到在方法上使用 StringBuilder 的好处,但不能通过 CreateReader < /代码>。我不认为在这些情况下, Join 和 Concat 方法会更有效率,因为将大列表转换为大数组会受到惩罚较小的名单)。

0
额外
哇,有趣的东西。感谢您花时间运行这些!
额外 作者 Mike Powell,
我不会想到你需要在 .Concat 里面的 .ToArray(),但它似乎使它更快
额外 作者 drzaus,
如果您不滚动到这些答案的底部:请考虑从 .ToString()中从这个答案。似乎更快......
额外 作者 drzaus,
StringBuilder版本可以写在一行上:var result = parent.Elements()。Aggregate(new StringBuilder(),(sb,xelem)=> sb.AppendLine(xelem.ToString()),sb => sb.ToString( ))
额外 作者 Softlion,
借调@Richard的评论。 parent.CreateNavigator()。InnerXml特别适合投影,因为它是内联的。
额外 作者 ccook,
您错过了 parent.CreateNavigator()。InnerXml (需要使用System.Xml.XPath 作为扩展方法)。
额外 作者 Richard,
你应该在using语句中包装 var reader = parent.CreateReader();
额外 作者 BrainSlugs83,
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();
}
0
额外
谈论同名的嵌套元素...
额外 作者 Lucero,

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

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);
0
额外
整齐。甚至可以更快地使用 IndexOfvar xml = root.ToString(); var begin = xml.IndexOf('>')+ 1; var end = xml.LastIndexOf('<');返回xml.Substring(begin,end-begin);
额外 作者 drzaus,