Конструирование XML на лету
От: prVovik Россия  
Дата: 02.04.07 07:54
Оценка: 116 (13)
Нередко встречаются задачи, где на лету надо програмно сконструировать XML документ (XmlDocument), или ветку (XmlElement). XML библиотека из фреймворка сконструирована так, что делать это, мягко говоря, неудобно. Приходится объявлять множество локальных переменных (для каждого атрибута, элемента, текстового узла и т.д.), придумывать всем этим переменным осмысленные и уникальные имена, а потом еще не забыть у всех узлов, имеющих детей нужным образом вызвать метод AppendChild(). Тут сложно не запутаться, код получается очень "муторным", многословным, со множеством временных локальных переменных. Такой код тяжело читать и непросто понять, что у него на выходе.

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


XmlDocument doc = XmlConstructor.Construct()
.RootElement("RootNode")
    .Element("Node")
        .Attribute("attributeName1", "attribute value 1")
        .Attribute("attributeName2", "attribute value 2")
        .Text("text")
        .Element("Test")
            .Attribute("a1", "v1")
            .Attribute("a2", "v2")
        .End()
    .End()

    .Element("Node")
        .Attribute("attributeName1", "attribute value 1")
        .Attribute("attributeName2", "attribute value 2")
        .Text("text")
    .End()
.GetDocument();


Не трудно догадаться, что этот код сгенерирует XmlDocument следующего содержания:

<RootNode>
    <Node attributeName1="attribute value 1" attributeName2="attribute value 2">
        text
        <Test a1="v1" a2="v2"/>
    </Node>
    <Node attributeName1="attribute value 1" attributeName2="attribute value 2">
        text
    </Node>
</Matches>


Как бы выглядел код, генерирующий тот же самый XML стандартным способом, думаю может себе представить каждый, кто когда-либо занимался этим делом (генерацией XML, я имею ввиду), по-этому приводить тут этот ужас я не буду

А с помощью библиотеки можно генерировать вышепоказанным способом не только XmlDocument'ы целиком, но и ветки (XmlElement) для уже существующих документов. Также, библиотечка позволяет указывать пространства имен, генерировать коментарии, инструкции по обработке и прочие XML элементы.
Сама либа тут: http://www.rsdn.ru:80/File/16989/XmlConstructor.zip

02.04.07 12:28: Перенесено модератором из '.NET' — TK
лэт ми спик фром май харт
Re: Конструирование XML на лету
От: Lloyd Россия  
Дата: 02.04.07 08:17
Оценка:
Здравствуйте, prVovik, Вы писали:

V>Нередко встречаются задачи, где на лету надо програмно сконструировать XML документ (XmlDocument), или ветку (XmlElement). XML библиотека из фреймворка сконструирована так, что делать это, мягко говоря, неудобно.


Строго говоря, неудобно сконструирована не "библиотека из фреймворка", а DOM-модель из W3C.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: Конструирование XML на лету
От: prVovik Россия  
Дата: 02.04.07 08:19
Оценка:
Здравствуйте, Lloyd, Вы писали:

L>Здравствуйте, prVovik, Вы писали:


V>>Нередко встречаются задачи, где на лету надо програмно сконструировать XML документ (XmlDocument), или ветку (XmlElement). XML библиотека из фреймворка сконструирована так, что делать это, мягко говоря, неудобно.


L>Строго говоря, неудобно сконструирована не "библиотека из фреймворка", а DOM-модель из W3C.


Ну не суть важно
лэт ми спик фром май харт
Re: Конструирование XML на лету
От: _FRED_ Черногория
Дата: 02.04.07 11:00
Оценка: 7 (2) +1
Здравствуйте, prVovik, Вы писали:

V>Для решения этой проблемы хочу поделиться простой библиотечкой (точнее не столько библиотечкой, сколько идеей, так как библиотечка получилась ну очень простой


XLinq видел? Его можно использовать и из второго фреймворка. Использовать очень удобно. Пример:
XElement contacts =
    new XElement("contacts",
        new XElement("contact",
            new XElement("name", "Patrick Hines"),
            new XElement("phone", "206-555-0144", 
                new XAttribute("type", "home")),
            new XElement("phone", "425-555-0145",
                new XAttribute("type", "work")),
            new XElement("address",
                new XElement("street1", "123 Main St"),
                new XElement("city", "Mercer Island"),
                new XElement("state", "WA"),
                new XElement("postal", "68042")
            )
        )
    );
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Конструирование XML на лету
От: Closer  
Дата: 02.04.07 17:00
Оценка:
Здравствуйте, _FRED_, Вы писали:

[skipped]

Не сказал бы что удобно. Я вот на 100% уверен что со скобочками бы запутался используй его.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Мы были здесь. Но пора идти дальше. (с) Дуглас Коупленд, Рабы "Микрософт"
Re[3]: Конструирование XML на лету
От: ie Россия http://ziez.blogspot.com/
Дата: 03.04.07 06:48
Оценка:
Здравствуйте, Closer, Вы писали:

C>Не сказал бы что удобно.


На самом деле — вполне. Главный бонус в том, что можно собирать XML по частям:
XElement contacts =
    new XElement("contacts",
        new XElement("contact",
            CreateXElements(...)
        )
    );

В предложенной либе так не сделаешь. Да и умеет он собирать только документ целиком, хотя код не смотрел, может и по частям умеет.

C>Я вот на 100% уверен что со скобочками бы запутался используй его.


Ну зря ты так, вероятность запутаться есть, но не надо ее преувеличивать. Вон в либе prVovik'а тоже можно забыть End() влепить, та же самая путаница возникнет.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[4]: Конструирование XML на лету
От: ie Россия http://ziez.blogspot.com/
Дата: 03.04.07 06:50
Оценка:
Хотя действительно в XLinq должны были из методов аля Add возвращать, экземпляр объекта контэйнера. По аналогии с тем, как это в StringBuilder.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[4]: Конструирование XML на лету
От: prVovik Россия  
Дата: 03.04.07 08:34
Оценка:
Здравствуйте, ie, Вы писали:

ie>Здравствуйте, Closer, Вы писали:


C>>Не сказал бы что удобно.


ie>На самом деле — вполне. Главный бонус в том, что можно собирать XML по частям:

ie>
ie>XElement contacts =
ie>    new XElement("contacts",
ie>        new XElement("contact",
ie>            CreateXElements(...)
ie>        )
ie>    );
ie>

ie>В предложенной либе так не сделаешь. Да и умеет он собирать только документ целиком, хотя код не смотрел, может и по частям умеет.

Во-первых, она все умеет. Вот еще пример:


        static XmlElement CreateNode1(XmlDocument document)
        {
            XmlElement result = document.CreateElement("Node1");
            XmlAttribute attribute = document.CreateAttribute("attribute");
            attribute.Value = "value";
            result.Attributes.Append(attribute);
            return result;
        }

        static XmlElement CreateNode2(XmlDocument document)
        {
            return XmlConstructor.ConstructElement(document, "Node2").Attribute("attribute", "value").GetElement();
        }

        static void Main(string[] args)
        {
            XmlDocument doc = new XmlDocument();
            doc.AppendChild(
                XmlConstructor.ConstructElement(doc, "Nodes")
                    .Element("FirstNode")
                        .Attribute("a1", "v1")
                        .Attribute("a2", "v2")
                    .End()
                    .Element(CreateNode1(doc))
                    .Element(CreateNode2(doc))
                .GetElement())
            
            Console.WriteLine(doc.InnerXml);
        }


Прошу обратить внимание на то, что методы "CreateNode1" и "CreateNode2" возвращают стандартный XmlElement. Причем в первом случае XmlElement создается традиционным способом, а во втором с помощью библиотеки. Также можно посмотреть и на строки "XmlDocument doc = new XmlDocument(); ... doc.AppendChild(mlConstructor.ConstructElement...". То есть, библиотека не навязывает себя в отличии от XLinq, она как бы расположена сбоку и не предлагает отказаться от стандартных XmlDocument, XmlElement и т.д. У нас может быть уже есть приложение, которое работает с Xml через стандартные классы и данная библиотека не заставит что-либо менять в приложении. То есть она по сути представляет собой инструмент для работы со стандартными Xml классами из фреймворка.

Вот еще интересный пример использования:


XmlDocument doc;
...
doc.SelectSingleNode("//configurations").AppendChild(
    XmlConstructor.ConstructElement(doc, "config")
        .Element("node1")
            .Attribute("a1", "v1")
            .Attribute("a2", "v2")
        .End()
        .Element("node2")
            .Attribute("a1", "v1")
            .Attribute("a2", "v2")
        .End()
    .GetElement()
);


C>>Я вот на 100% уверен что со скобочками бы запутался используй его.

ie>Ну зря ты так, вероятность запутаться есть, но не надо ее преувеличивать. Вон в либе prVovik'а тоже можно забыть End() влепить, та же самая путаница возникнет.
Зато мухи отделены от котлет: скобки используются только в передаче параметров, а End в качестве закрывающего тега.
лэт ми спик фром май харт
Re: Конструирование XML на лету
От: prVovik Россия  
Дата: 03.04.07 08:36
Оценка:
Здравствуйте, prVovik, Вы писали:

Обновленная версия: здесь
лэт ми спик фром май харт
Re[5]: Конструирование XML на лету
От: ie Россия http://ziez.blogspot.com/
Дата: 03.04.07 09:15
Оценка:
Здравствуйте, prVovik, Вы писали:

V>Во-первых, она все умеет. Вот еще пример:


Ну тогда круто, заюзаю
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[5]: Конструирование XML на лету
От: ie Россия http://ziez.blogspot.com/
Дата: 03.04.07 09:27
Оценка: 12 (2) +1
Здравствуйте, prVovik, Вы писали:

V>Во-первых, она все умеет. Вот еще пример:


Еще было бы круто добавлять массив элементов, например так:

  XmlConstructor.ConstructElement(doc, "config")
    .Element("node1")
      .Attribute("a1", "v1")
      .Add(GetArrayOfAttributesAndElements())
      .Attribute("a2", "v2")
    .End()
    .Element("node2")
      .Add(GetArrayOfAttributesAndElements())
    .End()
  .GetElement()


Ну и избавиться от GetElement и его собратьев, и сделать implicit cast.
(Эээх.... понесло...) А еще избавиться от длинных имен типа XmlConstructor -> XBuilder, ConstructElement -> NewElement (или XElement — это видимо на меня уже XLinq отпечатов наложила) — ну это уже не обязательно, но наглядности (за счет краткости) ИМХО добавит.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[5]: Конструирование XML на лету
От: ie Россия http://ziez.blogspot.com/
Дата: 03.04.07 09:34
Оценка:
Здравствуйте, prVovik, Вы писали:

V>То есть, библиотека не навязывает себя в отличии от XLinq, она как бы расположена сбоку и не предлагает отказаться от стандартных XmlDocument, XmlElement и т.д. У нас может быть уже есть приложение, которое работает с Xml через стандартные классы и данная библиотека не заставит что-либо менять в приложении. То есть она по сути представляет собой инструмент для работы со стандартными Xml классами из фреймворка.


Еще маленький комент по этому поводу. Ну и ладно, что навязывает, это безобразие с xml давно надо было прекращать. Но пока XLinq не зарелизился, твоя либа неплохая альтернатива. Ну и для поддержки уже готового кода, XLinq тож не покатит.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[6]: Конструирование XML на лету
От: _FRED_ Черногория
Дата: 04.04.07 07:48
Оценка:
Здравствуйте, ie, Вы писали:

ie>Но пока XLinq не зарелизился, твоя либа неплохая альтернатива.


Не обязательно ждать

ie>Ну и для поддержки уже готового кода, XLinq тож не покатит.


Почему? Достаточно подключить к проекту System.Xml.XLinq и компилятор 2005-ой студии прекрасно соберёт проектик из примера:
namespace ConsoleApplication1
{
  using System;
  using System.Xml.XLinq;

  class Program
  {
    static void Main() {
      XElement contacts = new XElement("contacts",
        new XElement("contact",
          new XElement("name", "Patrick Hines"),
          new XElement("phone", "206-555-0144", new XAttribute("type", "home")),
          new XElement("phone", "425-555-0145", new XAttribute("type", "work")),
          new XElement("address",
            new XElement("street1", "123 Main St"),
            new XElement("city", "Mercer Island"),
            new XElement("state", "WA"),
            new XElement("postal", "68042")
          )
        )
      );
      contacts.Save("C:\\aaa.xml");
    }
  }
}

XxxLinq — это только расширегния языка и в "собранном", то бишь двоичном, виде Linq-библиотеки можно использовтаь уже сейчас.
... << RSDN@Home 1.2.0 alpha rev. 675>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Re: Конструирование XML на лету
От: vdimas Россия  
Дата: 04.04.07 08:14
Оценка: 5 (1)
Здравствуйте, prVovik, Вы писали:

Идея вполне норм.

Поделюсь своим опытом. Я для вещей конструирования иерархий активно пользую утилитные классы в качестве базовых, это очищает синтаксис от от всяких new или же от цепочки вызовов примерно как у тебя. Понятное дело, что в XML-либе нельзя создавать отдельно элементы (мне это не нравится), поэтому "цепочечное" представление напрашивается само собой. Но для подобных вещей нетрудно сделать легковесный прокси, который позволит декомпозировать всё это хозйство, и потом по структуре прокси можно сгенерить целевой XML-документ цепочечным принципом. Декомпозиция прикольна тем, что позволяет создавать (и даже кешировать) отдельные участки структуры и потом их многократно использовать. У тебя в декопозиции всегда надо передавать парента и вообще немного "портить" синтаксис.

Вот за 5 мин накидал, суть в следующем:

Создаём структурку c приватным конструктором:
    public struct XmlParam
    {
        private object param;

        private XmlParam(object p) {
            param = p;
        }
        ...


Добавляем несколько операторов:
       // text
        public static implicit operator XmlParam(string param) {
            return new XmlParam(param);
        }

        // element
        public static implicit operator XmlParam(KeyValuePair<string, XmlParam[]> param) {
            return new XmlParam(param);
        }

        // attributes
        public static implicit operator XmlParam(KeyValuePair<string, string> param) {
            return new XmlParam(param);
        }

        public static implicit operator XmlParam(KeyValuePair<string, long> param) {
            return new XmlParam(param);
        }

        public static implicit operator XmlParam(KeyValuePair<string, double> param) {
            return new XmlParam(param);
        }


Ну и в конце билдер какой-нить в XML:
        public XmlDocument CreateDocument() {
            return null; // сгенерить документ из приватного param
        }
    }


Утилитный класс:
    public class XmlBulderUtility
    {
        public static XmlParam Attribute(string name, string value) {
            return new KeyValuePair<string, string>(name, value);
        }

        public static XmlParam Attribute(string name, long value) {
            return new KeyValuePair<string, long>(name, value);
        }

        public static XmlParam Attribute(string name, double value) {
            return new KeyValuePair<string, double>(name, value);
        }

        public static XmlParam Element(string name, params XmlParam[] value) {
            return new KeyValuePair<string, XmlParam[]>(name, value);
        }
    }


Свои билдеры наследуем от утилитного и так же получаем простой синтаксис:
    internal class Program : XmlBulderUtility
    {
        private static void Main(string[] args) {
            XmlDocument xmlDoc =
                Element("RootNode",
                        Element("Node1",
                                Attribute("attr1", "value 1"),
                                Attribute("attr2", "value 2"),
                                "text",
                                Element("Test",
                                        Attribute("a1", "v1"),
                                        Attribute("a2", "v2")
                                    )
                            ),
                        Element("Node2",
                                Attribute("attr1", "value 1"),
                                Attribute("attr2", "value 2"),
                                "text")
                    ).CreateDocument();
        }
    }
Re[7]: Конструирование XML на лету
От: ie Россия http://ziez.blogspot.com/
Дата: 04.04.07 08:24
Оценка: +1
Здравствуйте, _FRED_, Вы писали:

ie>>Но пока XLinq не зарелизился, твоя либа неплохая альтернатива.

_FR>Не обязательно ждать

Я тоже так думал...

ie>>Ну и для поддержки уже готового кода, XLinq тож не покатит.

_FR>Почему? Достаточно подключить к проекту System.Xml.XLinq и компилятор 2005-ой студии прекрасно соберёт проектик из примера:

Говоря о поддержке я имел ввиду модификации в куче уже написанного кода с использованием текущей модели.

_FR>XxxLinq — это только расширегния языка и в "собранном", то бишь двоичном, виде Linq-библиотеки можно использовтаь уже сейчас.


Можно, но весьма в ограниченном виде. Например, в майском 2006-ом LinqCTP XElement не реализовывает даже System.Xml.XPath.IXPathNavigable, и для того, что бы скормить XElement Xslt мне надо перегонять его в строку, а затем в XmlReader. Сам понимаешь, с точки зрения производительности, это не всегда оправдано. Скорее всего, в текушей версии этот недостаток исправлен, но скачать новую студию возможности нет. Как и нет гарантии, что до релиза они никаких публичных интерфейсов не поменяют.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re: Обновление: Конструирование XML на лету
От: prVovik Россия  
Дата: 04.04.07 09:30
Оценка: 32 (3)
Здравствуйте, prVovik, Вы писали:

Cинтаксис XLinq имеет свое преимущество — при реформатировании исходников не слетает форматирование, в отличии от предложенного изначально варианта с цепочкой вызовов. Я переделал библиотеку на новый лад, оставив её функциональную сущность — а именно нацеленность на работу со стандартными XML классами из фреймворка. То есть, в отличии от XLinq, данная библиотека является инструментальной для удобной работы со стандартными ХМЛ классами, но при этом сохраняется синтаксис, принятый в XLinq. Вот пример ее использования:


XmlDocument doc = new XmlDocument();
doc.AppendChild(
    new Xml.Element(
        "rootNode",
        new Xml.Element(
            "node1",
            new Xml.Attribute("a1", "v1"),
            new Xml.Attribute("a2", "v2"),
            new Xml.Text("text")
            ),
        new Xml.Element(
            "node2",
            new Xml.Attribute("a1", "v1"),
            new Xml.Attribute("a2", "v2"),
            new Xml.Text("text")
            )
        ).Build(doc)
    );

doc.SelectSingleNode("//node2").AppendChild(
    new Xml.Element(
        "node3",
        new Xml.Attribute("attribute", "value"),
        new Xml.Text("text node 3")
        ).Build(doc)
    );
Console.WriteLine(doc.InnerXml);

Обновленный вариант лежит здесь
лэт ми спик фром май харт
Re: Обновление
От: prVovik Россия  
Дата: 05.04.07 04:15
Оценка:
Здравствуйте, prVovik, Вы писали:

Обновление: здесь
лэт ми спик фром май харт
Re[2]: Обновление: Конструирование XML на лету
От: vdimas Россия  
Дата: 05.04.07 08:18
Оценка:
Здравствуйте, prVovik, Вы писали:

V>Здравствуйте, prVovik, Вы писали:


V>Cинтаксис XLinq имеет свое преимущество — при реформатировании исходников не слетает форматирование, в отличии от предложенного изначально варианта с цепочкой вызовов.


Да, вот так интересней. Если еще и утилитный класс к этому всему нарисовать, то я это даже себе возьму.

Кстати, а что получится, если в параметрах Element указать несколько блоков Text?
Re[3]: Обновление: Конструирование XML на лету
От: prVovik Россия  
Дата: 05.04.07 11:24
Оценка:
Здравствуйте, vdimas, Вы писали:


V>Да, вот так интересней. Если еще и утилитный класс к этому всему нарисовать, то я это даже себе возьму.

Нарисовал.
здесь


V>Кстати, а что получится, если в параметрах Element указать несколько блоков Text?

Добавит текст несколькими кусками. Например
new Xml.Element(
    "node",
    new Xml.Text("text1 "),
    new Xml.Text("text2 ")
    ).Build(doc)
);

Сгенерирует XML
<node>text1 text2 </node>


Можно, еще так:

new Xml.Element(
    "node",
    new Xml.Text("text1"),
    new Xml.Comment("comment")
    new Xml.Text("text2"),
    ).Build(doc)
);

Получим:
<node>text1<!--comment-->text2</node>


Короче, это стандартное поведение Xml классов из фреймворка.
лэт ми спик фром май харт
Re: Обновление
От: prVovik Россия  
Дата: 05.04.07 11:25
Оценка:
Здравствуйте, prVovik, Вы писали:
Обновление
здесь
лэт ми спик фром май харт
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.