Об одном случае использования XML


Статья о том, как НЕ надо использовать XML.

Попалась мне на глаза, как то раз, одна обработка выгрузки данных, которую хотелось бы прокомментировать. Текст представлен в том виде, в каком был мной найден, я поправил, разве что, лишь форматирование:

//*******************************************
 
перем xml_fso;
перем xml_file;
перем xml_СтекТегов;
перем xml_ТегОткрыт;
перем xml_Отступы;
перем Фвыгр экспорт, ИмяФайла экспорт, ПутьКФайлу экспорт, БД экспорт;
перем КодФирм;
 
//*******************************************
 
Процедура xml_СоздатьФайл(прм_ИмяФайла)
 
    xml_fso=СоздатьОбъект("Scripting.FileSystemObject");
 
//создать файл, перезаписывая существующий.
 
    xml_file=xml_fso.CreateTextFile(прм_ИмяФайла,-1, 0);
 
//Пишем заголовок XML.
 
    xml_file.WriteLine("<?xml version=""1.0"" encoding=""windows-1251""?>");
    xml_СтекТегов=СоздатьОбъект("СписокЗначений");
    xml_Отступы="";
    xml_ТегОткрыт=0;
 
КонецПроцедуры
 
//*******************************************
 
//Записывает начало элемента (тега XML). Имя можно указывать в угловых скобках.
 
//
Процедура xml_ЗаписатьНачалоЭлемента(прм_ИмяТега)
    перем стр;
 
    Если xml_ТегОткрыт=1 Тогда
        xml_ТегОткрыт=0;
        xml_file.WriteLine(">");
        xml_Отступы=xml_Отступы+"  ";
    КонецЕсли;
 
    стр=прм_ИмяТега;
    стр=СтрЗаменить(стр, "<", "");
    стр=СтрЗаменить(стр, ">", "");
 
    xml_СтекТегов.ДобавитьЗначение(стр);
    xml_file.Write(xml_Отступы+"<"+стр);
    xml_ТегОткрыт=1;
 
КонецПроцедуры
 
//*******************************************
 
//Записывает атрибут (параметр) тега XML.
 
//
 
Процедура xml_ЗаписатьАтрибут(прм_ИмяАтрибута, прм_ЗначениеАтрибута)
 
    Если xml_ТегОткрыт=0 Тогда
 
        Сообщить("Перед записью атрибута необходимо записать начало элемента!","!");
 
        а=10/0;
 
    КонецЕсли;
 
    стр=прм_ЗначениеАтрибута;
    стр=СтрЗаменить(стр, "&", "&"+"a"+"m"+"p"+";");
    стр=СтрЗаменить(стр, """", "&"+"q"+"u"+"o"+"t;");
    стр=СтрЗаменить(стр, "<", "&"+"l"+"t"+";");
    стр=СтрЗаменить(стр, ">", "&"+"g"+"t"+";");
    стр=СтрЗаменить(стр, "'", "&"+"a"+"p"+"o"+"s;");
    xml_file.Write(" "+прм_ИмяАтрибута+"="+""""+стр+"""");
 
КонецПроцедуры    // xml_ЗаписатьЗаписатьАтрибут
 
//*******************************************
 
//Записывает конец элемента (тега XML). Имя закрываемого тега можно
 
//указывать в угловых скобках, а можно - не указывать вовсе.
 
//
Процедура xml_ЗаписатьКонецЭлемента(прм_ОжидаемоеИмяТега="")
    перем стрИмяТега, а;
    Если xml_СтекТегов.РазмерСписка()<1 Тогда
        Сообщить("Попытка закрыть неоткрытый элемент!","!");
 
        а=10/0;
    КонецЕсли;
 
    стрИмяТега=xml_СтекТегов.ПолучитьЗначение(xml_СтекТегов.РазмерСписка());
 
    Если ПустаяСтрока(прм_ОжидаемоеИмяТега)=0 Тогда
        стр=прм_ОжидаемоеИмяТега;
        стр=СтрЗаменить(стр, "<", "");
        стр=СтрЗаменить(стр, ">", "");
        стр=СтрЗаменить(стр, "/", "");
 
        Если стр<>стрИмяТега Тогда
            Сообщить("Ожидается имя тега "+стр+", а закрыто "+стрИмяТега,"!"); а=10/0;
        КонецЕсли;
    КонецЕсли;
 
    xml_СтекТегов.УдалитьЗначение(xml_СтекТегов.РазмерСписка());
    Если xml_ТегОткрыт=1 Тогда
        xml_ТегОткрыт=0;
        xml_file.WriteLine("/>");
        Возврат;
    КонецЕсли;
    xml_Отступы=лев(xml_Отступы, СтрДлина(xml_Отступы)-2);
 
    xml_file.WriteLine(xml_Отступы+"</"+стрИмяТега+">");
 
КонецПроцедуры    // xml_ЗаписатьКонецЭлемента
 
//*******************************************
 
//Закрывает открытый файл XML. После окончания работы
 
//с файлом его необходимо закрыть.
 
//
Функция xml_Закрыть()
 
    xml_file.Close();
    Если xml_СтекТегов.РазмерСписка()<>0 Тогда
        Сообщить("Имеются незакрытые элементы XML!","!");
 
        а=10/0;
    КонецЕсли;
 
КонецФункции    // xml_Закрыть
 
//*******************************************

Представленный выше набор процедур, служит для формирования файла выгрузки в формате XML и содержит процедуры создания тегов элементов и атрибутов в структуре Data Object Model [DOM], а также, некоторые элементы синтаксического контроля [функция xml_Закрыть()] полученного текста.

Пример — как пример, вполне, так себе, обычный. Сейчас, с модой на всякие XML, и учетом обязательных, в некоторых случаях, требований, многие так делают. Я бы сейчас не хотел заострять внимание на данной, конкретной, прямо таки небезупречной, реализации, сколько хотел бы поговорить о подобном подходе, в общем и целом. Представленный выше механизм является универсальным, поэтому, для примера, вполне можно воспользоваться им, чтобы выгрузить какой нибудь справочник. Текст процедуры, в данном случае, будет выглядеть примерно так:

//*******************************************
 
Процедура ВыгрузитьОбъект(Элемент)
 
    xml_ЗаписатьНачалоЭлемента("<Код>");
    xml_ЗаписатьАтрибут("Код",СокрЛП(Элемент.Код));
 
    xml_ЗаписатьНачалоЭлемента("<Наименование>");
    xml_ЗаписатьАтрибут("Наименование", СокрЛП(Элемент.Наименование));
    xml_ЗаписатьКонецЭлемента("</Наименование>");
 
    xml_ЗаписатьНачалоЭлемента("<ПолнНаименование>");
    xml_ЗаписатьАтрибут("ПолнНаименование",СокрЛП(Элемент.КПП));
    xml_ЗаписатьКонецЭлемента("</ПолнНаименование>");
 
    xml_ЗаписатьНачалоЭлемента("<ИНН>");
    xml_ЗаписатьАтрибут("ИНН",СокрЛП(Элемент.ИНН));
    xml_ЗаписатьКонецЭлемента("</ИНН>");
 
    xml_ЗаписатьНачалоЭлемента("<КПП>");
    xml_ЗаписатьАтрибут("КПП",СокрЛП(Элемент.КПП));
    xml_ЗаписатьКонецЭлемента("</КПП>");
 
    xml_ЗаписатьНачалоЭлемента("<ЮрАдрес>");
    xml_ЗаписатьАтрибут("ЮрАдрес",СокрЛП(Элемент.ЮрАдрес));
    xml_ЗаписатьКонецЭлемента("</ЮрАдрес>");
 
    xml_ЗаписатьНачалоЭлемента("<ФактАдрес>");
    xml_ЗаписатьАтрибут("ФактАдрес",СокрЛП(Элемент.ФактАдрес));
    xml_ЗаписатьКонецЭлемента("</ФактАдрес>");
 
    xml_ЗаписатьНачалоЭлемента("<ВалютаДоговора>");
    xml_ЗаписатьАтрибут("Код",СокрЛП(Элемент.ВалютаДоговора.Код));
    xml_ЗаписатьАтрибут("Наименование",СокрЛП(Элемент.ВалютаДоговора.Наименование));
    xml_ЗаписатьКонецЭлемента("</ВалютаДоговора>");
 
    xml_ЗаписатьНачалоЭлемента("<ГлубинаКредита>");
    xml_ЗаписатьАтрибут("ГлубинаКредита",СокрЛП(Элемент.ГлубинаКредита));
    xml_ЗаписатьКонецЭлемента("</ГлубинаКредита>");
 
    xml_ЗаписатьНачалоЭлемента("<Комментарий>");
    xml_ЗаписатьАтрибут("Комментарий",СокрЛП(Элемент.Комментарий));
    xml_ЗаписатьКонецЭлемента("</Комментарий>");
 
//конец табличной части
    xml_ЗаписатьКонецЭлемента("</Код>");
 
КонецПроцедуры
 
//*******************************************
Процедура Выполнить()
 
    xml_СоздатьФайл("C:\scripts\test1.xml");
 
    спрКонтрагенты = СоздатьОбъект("Справочник.Контрагенты");
 
    спрКонтрагенты.ВыбратьЭлементы();
    Пока спрКонтрагенты.ПолучитьЭлемент() = 1 Цикл
        ВыгрузитьОбъект(спрКонтрагенты.ТекущийЭлемент());
    КонецЦикла;
 
    xml_Закрыть();
 
КонецПроцедуры
 
//*******************************************

Здесь очень хорошо видно, что используемый нами набор процедур осуществляет только какую-то часть функций по синтаксическому контролю полученного файла. Контроль же за структурой полученного в результате выгрузки объекта 1С блока текста, полностью лежит на процедуре-обработчике данного типа объекта. То есть, таких процедур-обработчиков должно быть примерно столько-же, сколько выгружается типов объектов. И, примерно, столько-же, в процедуре загрузки данных.

Преобразование данных осуществляется как перед выгрузкой данных в рамках процедур-обработчиков, так, возможно, и при загрузке. Объекты в базе-приемнике, сопоставляются по значениям выгружаемых реквизитов. В данном примере [и в «боевом» варианте] данные справочника «Валюты» в реквизите «ВалютаДоговора» дублируются по числу элементов справочника «Контрагенты». Что не очень хорошо влияет на скорость формирования файла и объем этого самого файла.

Полученный в результате текст выглядит примерно так:

<?xml version="1.0" encoding="windows-1251"?>
<Код Код="00001">
    <Наименование Наименование="ИП Иванов"/>
    <ПолнНаименование ПолнНаименование="123"/>
    <ИНН ИНН="123"/>
    <КПП КПП="123"/>
    <ЮрАдрес ЮрАдрес="его ю. адр"/>
    <ФактАдрес ФактАдрес="его ф. адр"/>
    <ВалютаДоговора Код="1" Наименование="Руб"/>
    <ГлубинаКредита ГлубинаКредита="7"/>
    <Комментарий Комментарий="1"/>
</Код>
<Код Код="00002">
    <Наименование Наименование="ИП Петров"/>
    <ПолнНаименование ПолнНаименование="123"/>
    <ИНН ИНН="234"/>
    <КПП КПП="123"/>
    <ЮрАдрес ЮрАдрес="его ю. адр"/>
    <ФактАдрес ФактАдрес="его ф. адр"/>
    <ВалютаДоговора Код="1" Наименование="Руб"/>
    <ГлубинаКредита ГлубинаКредита="7"/>
    <Комментарий Комментарий="2"/>
</Код>
    <Код Код="00003">
    <Наименование Наименование="ИП Сидоров"/>
    <ПолнНаименование ПолнНаименование="123"/>
    <ИНН ИНН="345"/>
    <КПП КПП="123"/>
    <ЮрАдрес ЮрАдрес="его ю. адр"/>
    <ФактАдрес ФактАдрес="его ф. адр"/>
    <ВалютаДоговора Код="1" Наименование="Руб"/>
    <ГлубинаКредита ГлубинаКредита="7"/>
    <Комментарий Комментарий="3"/>
</Код>

Результат не является XML-файлом по определению [ссылка], так как не содержит корневого элемента. Выходит, что функции синтаксического контроля так и не справились со своей задачей. Значения реквизитов объекта располагается в реквизитах соответствующих элементов DOM, что делает информацию, представленную в файле, избыточной. С другой стороны, такой способ сохранения данных в файле выбран для того, чтобы иметь возможность сохранять данные реквизитов, которые, в свою очередь, сами являются объектами. В данном примере — это «ВалютаДоговора».

Вся процедура обмена, формирование файла выгрузки, его передача и анализ с последующей загрузкой будет выполняться медленно, ввиду избыточности информации, заключенной в файле. Анализатор XML-файлов, реализованный в обработке загрузки, не позволяет делать поиск и выборку элементов DOM — только последовательное сканирование и определение меток конца чтения элемента. Таким образом, обработки, использующие такой формат файла ничем не отличаются от таковых, если бы формат файла выгрузки был бы текстовым с разделителями.

Иными словами, формат файла выгрузки — изменился, а мышление программиста, писавшего эти обработки, — нет. Это печально…