Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Волшебство программирования на 1С:Предприятие 7.7 и 8.0. Выпуск 98


Уважаемые программисты !

Компания "1-й Архитектор бизнеса" - одна из самых динамичных команд в 1С-бизнесе - дает вам шанс влиться в наши ряды. Работа по реализации проектов на платформе 1С Предприятие 7.7. и 8.0.

Наши правила:
∙ Профессиональный подход к делу
∙ Человечное отношение друг к другу
∙ Стремление к общему будущему

Что вас ожидает?
∙ Постоянно растущий объем интересной работы
∙ Непрерывное обучение и профессиональный рост
∙ Прогрессирующий заработок

Мы поможем:
∙ с жильем,
∙ оплатой метро,
∙ корпоративным мобильным

Если Вы:
∙ знаете типовые конфигурации на платформе 7.7 и 8.0.
∙ имеете опыт программирования в среде 1С
∙ и являетесь обладателем сертификатов 1С.

Звоните: 8 (495) 937-66-35
Резюме присылайте по электронной почте: padev@1ab.ru
Наш сайт: http://1ab.ru/


Волшебство программирования на 1С:Предприятие 7.7 и 8.0
Выпуск 98 / 15.02.2006

Чтение и запись текстовых файлов XML средствами 1С:Предприятие 7.7

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

Автор статьи: romix (Роман Чертанов)
URL: http://kb.mista.ru/article.php?id=73

Почему не 8.0? Потому что в 8.0 похожий XML-парсер уже есть. Вы найдете почти полное сходство между этим последовательным парсером XML (видимо, заимствованным из .NET, класс XmlTextReader и XmlTextWriter) и приведенными ниже библиотечными функциями (вы можете вынести их, например, в глобальный модуль, снабдив ключевым словом Экспорт, а можете этого не делать).

Почему я избегаю использовать штатные средства 1С 7.7? Потому что они потребляют память примерно 10-кратно по сравнению с объемом исходного файла XML. Это значит, что по мере роста файла XML будут расти "тормоза", вплоть до момента, когда память не будет исчерпана полностью (либо приведет к ситуации, когда загрузка уйдет "в своп"). Модель DOM (которую применяет 1С:Предприятие 7.7) расчитана на обработку небольших документов XML, которые должны располагаться в памяти целиком.

Конечно, внешней компонентой делать то же самое намного аккуратнее, и соблюдается принцип объектной ориентированности. Пример компоненты, сделанной средствами .NET приведен в этой статье:
Написание внешних компонент для 1С на VB.NET и C# (статья)
Однако, если внешние компоненты по каким-то причинам неприменимы (постоянно сталкиваюсь со страхом перед внешними компонентами, который я назвал бы "компонентофобия"), то примерно то же самое делает и приведенный ниже программный код на языке 1С.

Кроме того, модель DOM намного сложнее (по сравнению, например, с файлами DBF) - поэтому приходится сталкиваться еще и с XML-фобией: для обмена применяют все, что угодно, но не XML. Но это удобный, консервативный - происходит от древнего языка разметки SGML - и простой по своей сути формат (намного удобнее и приятнее DBF) - "тормоза" и непрактичность (в случае потоковой выгрузки) ему придает именно модель DOM. Всякому инструменту - свое применение, и вряд ли стоит применять объектную модель там, где подойдет простое последовательное чтение или запись текстового файла.

Имена функций я сделал такие же, как в 1С:Предприятие 8.0. Я предполагаю, что мало кто страдает "восьмеркофобией", и сможет понять способ парсинга (разбора) текстовых файлов XML (на примере справочника товаров). Тем более, что парсер занимает меньше строк кода, чем можно было бы предположить: фактически, он предельно прост.
//Ограничения на входящие файлы XML:                         

//1) В первой строке обязателен заголовок XML вида <?xml version="1.0" encoding="windows-1251"?>    
//2) Каждый тег должен быть записан в отдельной строке. Возможны отступы от начала строки и пустые строки.        
//3) Значения атрибутов - строго в двойных кавычках.                                                              
//4) Не поддерживаются текстовые значения (любые значения можно передавать только через атрибуты).                
//5) Комментарии и "подобные им" элементы XML не поддерживаются.                                                  
                
перем xml_fso;
перем xml_file; 
перем xml_ИмяТега;                                 

перем xml_СписокАтрибутов;                         
перем xml_сз;   
                
///////////////////////////////////////////////////////////////////////                                           
//Открывает XML-файл в режиме "только чтение"      
Процедура xml_ОткрытьФайл(прм_ИмяФайла)            
//прм_ИмяФайла - имя файла XML (укажите полный путь и расширение .XML).                                           
                
        xml_fso=СоздатьОбъект("Scripting.FileSystemObject");                                                      
        xml_file=xml_fso.OpenTextFile(прм_ИмяФайла, 1, 0, 0); //Открываем файл в режиме "только чтение"           

        стр=xml_file.ReadLine(); //Читаем заголовок XML вида <?xml version="1.0" encoding="windows-1251"?>        
        Если Найти(стр,"<?xml")=0 Тогда            
                Сообщить("Неправильный файл XML "+прм_ИмяФайла,"!"); а=10/0;                                      
        КонецЕсли;    
                             
        Если Найти(стр,"windows-1251")=0 Тогда     
                Сообщить("Требуется кодировка windows-1251 файла XML "+прм_ИмяФайла,"!"); а=10/0;                 
        КонецЕсли;                                 
        xml_СписокАтрибутов=СоздатьОбъект("СписокЗначений");                                                      
        xml_сз=СоздатьОбъект("СписокЗначений");    

КонецПроцедуры  // xml_ОткрытьФайл                 
                
///////////////////////////////////////////////////////////////////////                                           
//Считывает следующий тег XML.                     
//Возвращает 1, если тег прочитан и 0 - если был достигнут конец файла.                                           
//Заполняет переменную xml_ИмяТега именем считанного тега в формате "<ИмяТега>"                                   
Функция xml_Прочитать(прм_ОжидаемыеТеги="")        
//Если вы используете параметр прм_ОжидаемыеТеги, то заполните его списком тегов                                  
//через запятую, которые могут быть считаны в данный момент (это позволяет проверять                              
//структуру файла XML и сделать более ясным считывающий код).                                                     
                
        xml_СписокАтрибутов.УдалитьВсе();          
        стр=""; 
        Пока стр="" Цикл                           
                Если xml_file.AtEndOfStream=1 Тогда

                        Возврат 0; //тег не был прочитан, т.к. достигнут конец файла                              
                КонецЕсли;                         
                стр=СокрЛП(xml_file.ReadLine());   
        КонецЦикла; //цикл, чтобы пропустить пустые строки                                                        
                
                
        Если Найти(стр,"=")=0 Тогда //Случай, когда нет атрибутов                                                 

                xml_ИмяТега=СокрЛП(стр);           
        Иначе   
                //Разбиваем тег на структуру, удобную для ИзСтрокиСРазделителями()                                

                стр=СтрЗаменить(стр,"""",""","""); 
                стр=""""+стр+"""";                 
                
                сз=xml_сз;                         
                сз.УдалитьВсе();                   
                сз.ИзСтрокиСРазделителями(стр);    
                
                //В первом элементе списка у нас и имя тега, и имя первого атрибута. Разделим их.                 

                зн=сз.ПолучитьЗначение(1);         
                поз=Найти(зн , " ");               
                xml_ИмяТега =СокрЛП(Лев(зн, поз)); //Выделим имя тега            

 
                xml_ИмяТега =xml_ИмяТега+">";      
                
                //Выделим и обновим имя первого атрибута                                                          

                зн =СокрЛП(Сред(зн, поз));         
                сз.УстановитьЗначение(1,зн);       
                
                //Удаляем завершающий элемент списка /> или >

                сз.УдалитьЗначение(сз.РазмерСписка());                                                            
                
                //Переводим наш список значений в более удобный формат                                            
                i=1; //позиция в списке сз         

                рс=сз.РазмерСписка();              
                Пока i<=рс Цикл                    
                        имя=сз.ПолучитьЗначение(i);
                        имя=СокрЛП(СтрЗаменить(имя,"=",""));                                                      
                        зн=сз.ПолучитьЗначение(i+1);                                                              
                        //Заменяем спецсимволы XML 
                        зн=СтрЗаменить(зн,""","""");                                                              
                        зн=СтрЗаменить(зн,"'","'");
                        зн=СтрЗаменить(зн,"","");
                        зн=СтрЗаменить(зн,">",">");
                        зн=СтрЗаменить(зн,"&","&");

                        xml_СписокАтрибутов.ДобавитьЗначение(зн,имя);                                             
                        i=i+2;                     
                КонецЦикла;                        
        КонецЕсли;                                 
                
        Если ПустаяСтрока(прм_ОжидаемыеТеги)=0 Тогда                                                              
                //Проверяем наличие имени тега в списке ожидаемых тегов (если надо контролировать структуру)      

                Если Найти(прм_ОжидаемыеТеги,xml_ИмяТега)=0 Тогда                                                 
                        Сообщить("Неожиданный тег "+xml_ИмяТега+" в строке "+xml_file.line); a=10/0;              
                КонецЕсли;                         
        КонецЕсли;                                 
                
        Возврат 1; //успешное чтение тега          
КонецФункции    // xml_Прочитать                   
                
///////////////////////////////////////////////////////////////////////                                           
//Получает значение атрибута считанного тега по имени атрибута.                                                   
//Если надо получить атрибут по его номеру, читайте список значений xml_СписокАтрибутов                           
                
Функция xml_ПолучитьАтрибут(прм_ИмяАтрибута)       
        Возврат xml_СписокАтрибутов.Получить(прм_ИмяАтрибута);                                                    
КонецФункции    // xml_ПолучитьАтрибут             
                

///////////////////////////////////////////////////////////////////////                                           
//Закрывает открытый файл XML. По завершении работы с файлом его необходимо закрыть.                              
Функция xml_Закрыть()                              
        xml_file.Close();                          
КонецФункции    // xml_Закрыть                     
                
                
                
//*******************************************      
//Тестовая процедура, которая считывает XML файл в справочник Товаров.                                            
Процедура Выполнить()                              
        стрИмяФайла=КаталогИБ()+"XML\tovar.xml";   
                
        спр=СоздатьОбъект("Справочник.Товары");    
        НачатьТранзакцию();                                        //Для ускорения                                
        сч=0;                                                                            //Для ускорения                                
                
        Сообщить("Начало чтения XML: "+ТекущееВремя());                                                           
        xml_ОткрытьФайл(стрИмяФайла);
        
        xml_Прочитать("<Товары>"); 
        
        спрР=СоздатьОбъект("Справочник.Товары");
        
        Пока xml_Прочитать("<Элемент>,</Товары>")=1 Цикл
                сч=сч+1;                                                                      //Для ускорения

                Если сч%1000=0 Тогда                                      //Для ускорения
                        ЗафиксироватьТранзакцию();              //Для ускорения
                        НачатьТранзакцию();                                //Для ускорения
                КонецЕсли;                                                             //Для ускорения
                
                Если xml_ИмяТега="</Товары>" Тогда 
                        Прервать;
                КонецЕсли;
                
                Код=Число(xml_ПолучитьАтрибут("Код"));
                Наименование=xml_ПолучитьАтрибут("Наименование");
                Единица=xml_ПолучитьАтрибут("Единица");
                Цена=Число(xml_ПолучитьАтрибут("Цена"));
                ЭтоГруппа=Число(xml_ПолучитьАтрибут("ЭтоГруппа"));
                КодГруппы=Число(xml_ПолучитьАтрибут("Группа"));
                
                спрР.НайтиПоКоду(КодГруппы,0);
                
                Если спр.НайтиПоКоду(Код)=0 Тогда
                        спр.ИспользоватьРодителя(спрР.ТекущийЭлемент());
                        Если ЭтоГруппа=1 Тогда
                                спр.НоваяГруппа();
                        Иначе
                                спр.Новый();
                        КонецЕсли;
                        спр.Код = Код;
                КонецЕсли;
                
                спр.Родитель=спрР.ТекущийЭлемент();
                
                спр.Наименование = Наименование;
                спр.Цена = Цена;
                спр.Единица = Единица;
                спр.Записать();
                        
                
        КонецЦикла;     //по элементам XML

        
        xml_Закрыть(); 
        Сообщить("Обработка завершена! "+ТекущееВремя(),"i");
        ЗафиксироватьТранзакцию();                                 //Для ускорения
        
    
КонецПроцедуры

Это был пример обработки, которая читает файл XML, и создает многоуровневый справочник товаров.
А теперь приведу пример записи того же самого файла XML.

перем xml_fso;  

перем xml_file; 
перем xml_СтекТегов;                               
перем xml_ТегОткрыт;                               
перем xml_Отступы;                                 
                
                
///////////////////////////////////////////////////////////////////////                                           
//Открывает файл XML в режиме записи. Если файл существовал, перезаписывает его.                                  
//Принимает параметр прм_ИмяФайла - имя файла (укажите полный путь и расширение .XML)                             
Процедура xml_СоздатьФайл(прм_ИмяФайла)            
    xml_fso=СоздатьОбъект("Scripting.FileSystemObject");                                                          
    xml_file=xml_fso.CreateTextFile(прм_ИмяФайла, -1, 0); //создать файл, перезаписывая существующий              

    xml_file.WriteLine("<?xml version=""1.0"" encoding=""windows-1251""?>"); //Пишем заголовок XML                
    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;                                  
    КонецЕсли;  
                
                
    стр=прм_ЗначениеАтрибута;                      
    стр=СтрЗаменить(стр, "&", "&");                
    стр=СтрЗаменить(стр, """", """);               
    стр=СтрЗаменить(стр, "", "");                
    стр=СтрЗаменить(стр, ">", ">");                
    стр=СтрЗаменить(стр, "'", "'");                
    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\tovar_out.xml";   
    Сообщить("Начало записи: "+стрИмяФайла);       
    Сообщить("Время начала: "+ТекущееВремя());     
    xml_СоздатьФайл(стрИмяФайла);                  
    xml_ЗаписатьНачалоЭлемента("<Товары>");        
      спр=СоздатьОбъект("Справочник.Товары");      
      спр.ВыбратьЭлементы();                       
      Пока спр.ПолучитьЭлемент()=1 Цикл            
          xml_ЗаписатьНачалоЭлемента("<Элемент>"); 
          xml_ЗаписатьАтрибут("Код", спр.Код);     
          xml_ЗаписатьАтрибут("Наименование", СокрЛП(спр.Наименование));                                          
          Если спр.ЭтоГруппа()=1 Тогда             
              xml_ЗаписатьАтрибут("ЭтоГруппа", "1");                                                              
          Иначе 
              xml_ЗаписатьАтрибут("Единица", СокрЛП(спр.Единица));                                                
              xml_ЗаписатьАтрибут("Цена", СокрЛП(спр.Цена));                                                      
          КонецЕсли;                               
          Если ПустоеЗначение(спр.Родитель)=0 Тогда
              xml_ЗаписатьАтрибут("Группа", спр.Родитель.Код);
          КонецЕсли;  
          xml_ЗаписатьКонецЭлемента("</Элемент>");
      КонецЦикла;
    xml_ЗаписатьКонецЭлемента("</Товары>");
    xml_Закрыть();
    Сообщить("Конец записи: "+ТекущееВремя());
КонецПроцедуры

Как видим, весь алгоритм записи файла XML занимает 100 строк, а весь алгоритм чтения (парсинга) XML - 116 строк (учитывая комментарии и пустые строчки, которые я добавил в код для ясности).

Чтение и запись XML в этом примере происходит намного быстрее, и не потребляет память, по сравнению с тем, как это делает парсер DOM (от которого, напомню, фирма 1С отказалась в версии 1С:Предприятие 8.0, и применила более простые средства последовательного доступа к XML). Имена библиотечных функций, приведенных выше я сделал такими же, как в 8-ке. Удалось даже, на мой взгляд, несколько улучшить алгоритм. А именно, методы для чтения элемента и для записи завершающего элемента принимают "контрольные" параметры - ожидаемый тег или список ожидаемых тегов.

Пока xml_Прочитать("<Элемент>,</Товары>")=1 Цикл

//...
xml_ЗаписатьКонецЭлемента("</Элемент>");
Это, на мой взгляд, помогает сделать код яснее.

Ссылка на файл с работающим примером конфигурации для 1С 7.7:
http://x-romix.narod.ru/XML_doc.rar
(скачивать левой кнопкой мыши, 288К) 


Официальный сайт рассылки - www.mista.ru
Волшебный форум -
www.forum.mista.ru
Книга знаний - www.kb.mista.ru

С уважением,
Станислав Митичкин (Волшебник)
stasmit@mail.ru


В избранное