Объединить коллекцию тегов XML в строку с LINQ - PullRequest
1 голос
/ 31 марта 2009

Я застрял при использовании веб-службы, над которой у меня нет контроля, и пытаюсь проанализировать XML, возвращенный этой службой, в стандартный объект.

Часть структуры XML выглядит следующим образом

<NO>
   <L>Some text here </L>
   <L>Some additional text here </L>
   <L>Still more text here </L>
</NO>

В конце я хочу получить одно свойство String, которое будет выглядеть так: «Здесь немного текста. Здесь немного текста. То, что у меня есть для начального прохода, это то, что следует. Я думаю, что я на правильном пути, но еще не совсем там:

XElement source = \\Output from the Webservice
List<IndexEntry> result;

result = (from indexentry in source.Elements(entryLevel)
    select new IndexEntry()
    {
        EtiologyCode = indexentry.Element("IE") == null ? null : indexentry.Element("IE").Value,
        //some code to set other properties in this object
        Note = (from l in indexentry.Elements("NO").Descendants
                select l.value)  //This is where I stop
                               // and don't know where to go
    }

Я знаю, что мог бы добавить оператор ToList () в конце этого запроса, чтобы вернуть коллекцию. Есть ли какой-нибудь оператор или метод, который позволил бы мне встроить конкатенацию этой коллекции в одну строку?

Не стесняйтесь спрашивать дополнительную информацию, если это не ясно.

Спасибо.

Ответы [ 4 ]

7 голосов
/ 31 марта 2009

LINQ to XML - действительно путь здесь:

var textArray = topElement.Elements("L")
                          .Select(x => x.Value)
                          .ToArray();

var text = string.Join(" ", textArray);

РЕДАКТИРОВАТЬ: Судя по комментарию, похоже, вам просто нужен способ выражения этого с одним выражением. Это легко, хотя и несколько уродливо:

result = (from indexentry in source.Elements(entryLevel)
    select new IndexEntry
    {
        EtiologyCode = indexentry.Element("IE") == null 
                           ? null 
                           : indexentry.Element("IE").Value,
        //some code to set other properties in this object
        Note = string.Join(" ", indexentry.Elements("NO")
                                          .Descendants()
                                          .Select(x => x.Value)
                                          .ToArray())
    };

Другой альтернативой является извлечение его в отдельный метод расширения (он должен быть в статическом классе верхнего уровня):

public static string ConcatenateTextNodes(
    this IEnumerable<XElement> elements)
{
    string[] values = elements.Select(x => x.Value).ToArray();
    // You could parameterise the delimiter here if you wanted
    return string.Join(" ", values);
}

затем измените свой код на:

result = (from indexentry in source.Elements(entryLevel)
    select new IndexEntry
    {
        EtiologyCode = indexentry.Element("IE") == null 
                           ? null 
                           : indexentry.Element("IE").Value,
        //some code to set other properties in this object
        Note = indexentry.Elements("NO")
                         .Descendants()
                         .ConcatenateTextNodes()
    }

РЕДАКТИРОВАТЬ: примечание об эффективности

Другие ответы предлагают использовать StringBuilder во имя эффективности. Я бы проверил доказательства того, что это правильный путь, прежде чем использовать его. Если вы подумаете об этом, StringBuilder и ToArray делают схожие вещи - они создают буфер больше, чем нужно, добавляют к нему данные, изменяют его размер при необходимости и в конце получают результат. Надеюсь, вам не нужно будет слишком часто менять размер.

Разница между StringBuilder и ToArray - вот что буферизуется - в StringBuilder это все содержимое строки, которую вы создали до сих пор. С ToArray это просто ссылок. Другими словами, изменение размера внутреннего буфера, используемого для ToArray, вероятно, будет дешевле, чем изменение размера для StringBuilder, , особенно , если отдельные строки длинные.

После выполнения буферизации в ToArray, string.Join чрезвычайно эффективен: он может просматривать все строки, с которых нужно начинать, точно сколько места выделяется и затем объединить его без необходимости копировать фактические символьные данные.

Это резкий контраст с предыдущим ответом, который я дал - но, к сожалению, я не думаю, что когда-либо писал эталонный тест.

Я, конечно, не ожидал бы, что ToArray будет значительно медленнее, и я думаю, что это делает код проще здесь - не нужно использовать побочные эффекты и т. Д., Агрегирование и т. Д.

1 голос
/ 31 марта 2009

Другой вариант - использовать Aggregate ()

var q = topelement.Elements("L")
                  .Select(x => x.Value)
                  .Aggregate(new StringBuilder(), 
                             (sb, x) => return sb.Append(x).Append(" "),
                             sb => sb.ToString().Trim());

edit : первая лямбда в агрегате - это аккумулятор Это берет все ваши ценности и создает одно значение из них. В этом случае он создает StringBuilder с нужным вам текстом. Вторая лямбда - это селектор результата . Это позволяет вам преобразовать накопленную стоимость в желаемый результат. В этом случае изменение StringBuilder на String.

0 голосов
/ 31 марта 2009

Мне нравится LINQ так же, как и следующему парню, но вы изобретаете колесо здесь. Свойство XmlElement.InnerText делает именно то, что запрашивается.

Попробуйте это:

using System.Xml;

class Program
{
    static void Main(string[] args)
    {
        XmlDocument d = new XmlDocument();
        string xml =
            @"<NO>
  <L>Some text here </L>
  <L>Some additional text here </L>
  <L>Still more text here </L>
</NO>";
        d.LoadXml(xml);
        Console.WriteLine(d.DocumentElement.InnerText);
        Console.ReadLine();
    }
}
0 голосов
/ 31 марта 2009

У меня самого нет опыта работы с ним, но мне кажется, что LINQ to XML может значительно упростить ваш код. Сделайте выбор документа XML, затем переберите его и используйте StringBuilder, чтобы добавить элемент L к некоторой строке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...