Лучший способ получить InnerXml XElement?
Какой лучший способ получить содержимое смешанного элемента 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 с простым старым System.Xml методом, предложенным Грегом. Вариант был интересным, а не тем, что я ожидал, причем самые медленные методы были более чем в 3 раза медленнее, чем самые быстрые.
Результаты, упорядоченные быстрее всего медленнее:
- CreateReader - Охотник за экземпляром (0.113 секунд)
- Обычная старая System.Xml - Грег Херлман (0.134 секунды)
- Совокупность с конкатенацией строк - Майк Пауэлл (0,324 секунды).
- StringBuilder - Vin (0.333 секунд)
- String.Join on array - Terry (0.360 секунд)
- String.Concat на массиве - Марцин Косерадзки (0.364)
Метод
Я использовал один XML-документ с 20 идентичными узлами (называемый "подсказкой" ):
<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>
Цифры, показанные в секундах выше, являются результатом извлечения "внутреннего XML" из 20 узлов, 1000 раз подряд и среднего значения (5). Я не учитывал время загрузки и анализа XML в XmlDocument
(для метода System.Xml) или XDocument
(для всех остальных).
Алгоритмы LINQ, которые я использовал, были: (С# - все принимают родительский элемент XElement
и возвращают внутреннюю 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 on array:
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
каждый раз. Если вы просто выполняете несколько запросов, вы можете использовать более сжатый метод Aggregate Майка.
Если вы используете XML на больших элементах с большим количеством узлов (возможно, 100-ые), вы, вероятно, начнете видеть преимущество использования StringBuilder
по методу Aggregate, но не более CreateReader
. Я не думаю, что методы Join
и Concat
когда-либо были бы более эффективными в этих условиях из-за штрафа за преобразование большого списка в большой массив (даже очевидный здесь с меньшими списками).
Ответ 2
Я думаю, что это намного лучший метод (в VB, не сложно перевести):
Учитывая XElement x:
Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
Ответ 3
Как использовать этот метод расширения для XElement? работал на меня!
public static string InnerXml(this XElement element)
{
StringBuilder innerXml = new StringBuilder();
foreach (XNode node in element.Nodes())
{
// append node 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());
Ответ 7
Лично я написал метод расширения 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();
Ответ 8
@Greg: Кажется, вы отредактировали свой ответ, чтобы быть совершенно другим ответом. На что я отвечаю "да", я мог бы сделать это с помощью System.Xml, но надеялся, чтобы мои ноги были мокрыми с LINQ to XML.
Я оставлю свой первоначальный ответ ниже, если кто-то еще задается вопросом, почему я не могу просто использовать свойство XElement.Value, чтобы получить то, что мне нужно:
@Greg: свойство Value объединяет все текстовые содержимое любых дочерних узлов. Поэтому, если элемент body содержит только текст, он работает, но если он содержит XHTML, я получаю весь текст, конкатенированный вместе, но ни один из тегов.
Ответ 9
//использование Regex может быть проще просто обрезать тег начала и конца элемента
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);
Ответ 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%... но смотря на Aggregate() и string.Join() в Reflector... Я думаю, что я прочитал его как Aggregate, просто добавляя возвращаемое значение, так что по существу вы получаете:
string = строка + строка
и string.Join, в нем есть некоторые упоминания о FastStringAllocation или что-то еще, что делает меня тем, что люди в Microsoft, возможно, добавили дополнительный прирост производительности. Конечно, мой .ToArray() вызывает мое отрицание, но я просто хотел предложить другое предложение.
Ответ 13
ты знаешь? самое лучшее, что нужно сделать, это вернуться на CDATA:( im смотрю на решения здесь, но я думаю, что CDATA на сегодняшний день является самым простым и дешевым, не самым удобным для разработки с помощью
Ответ 14
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();
}