Трансляция CSV-XML. Пример. УФ

В задаче было загрузить данные из двух таблиц — CSV и XML, содержащих разные данные по одним и тем же объектам и объединить их в одну финальную таблицу XML.

Обработка считывает любой CSV файл.

Она также способна считать любой XML файл структуры вида (названия тегов не важны):

<Xml>
<Object>
<Field1>Value1</Field>
<Field2>Value1</Field>
<Field3>Value1</Field>
…..
<Object>
….
</Xml>

Вся обработка осуществляется на клиенте, чтобы не передавать данные на сервер.

Для универсальности данные из файла загружаются в структуру Данные с полям:

  1. Колонки — соответствие Имя колонки — Номер колонки (с единицы)
  2. Строки — массив строк. В строке может быть меньше колонок, чем в файле, но порядок колонок соблюдается.
  3. Массив колонок — формируется через индексированное соответствия Колонки. В каждом элементе — название колонки на этой позиции.

В коде можно посмотреть, как считываются CSV и XML файлы.

Для считывания CSV использовался не очень быстрый метод считывания файла в строку с последующим разбором отсюда. Метод реально медленный на 72 Мб файле работал 10 минут. Но работает правильно, т.к. обрабатывает переносы строк.

XML считывается и разбирается быстрыми эффективными рекурсивными методами.

Считанный CSV или XML можно выгрузить в XML файл в структуру Вида xmldata — Products — FieldN:

В коде можно посмотреть, как было сделано объединение файлов:

Обработка имеет форму:

Сначала нужно прочитать XML или CSV.

Далее присвоить в результат CSV или XML.

После этого можно получить финальный XML через «Записать результат в XML».

Обработка была написана за 3.5 часа.

Список полей для вставки в XML стал универсальным.

Первоначальная версия обработки.

комментариев 16

  1. bob:

    Надо же какой перфоманс в 1С. На Go подобная задача будет обрабатывать 150-метровые XML-ки за несколько секунд (больше скорость диска влияет). Делал, знаю.

    • Тут XML тоже быстро гоняется. А вот CSV обрабатывается как строка в памяти, поэтому медленно.

      • bob:

        Буфферизация чтения. CSV парсится еще быстрее XML. Проблема выбора адекватных инструментов для решения подобных задач.
        Если задача делается редко — пишется скрипт на Python, если надо часто — нормальное консольное приложение на Java/Go/C/C++.
        1C для решения данной задачи не является адекватным выбором (если только мы не грузим данные сразу в 1С)

        • Тут на самом деле много критериев выбора.
          В тч и наличие кадров, которые могут это сделать.
          если это может сделать знакомый 1сник, зачем искать незнакомого питониста?

          • bob:

            1Сик может написать и на Питоне (или другом скриптовом языке). Скрипты неотьемлимая часть работы программиста и такое знание не повредит никому. Выбирать инструмент надо адекватно задаче, а не пытаться забивать микроскопом гвозди. К сожалению, многие решают задачу на том, что знают, а не что адекватно задаче. Это не связано с 1С и встречается среди всех типов программистов.

          • Видишь ли, сложно знать широко и глубоко одновременно.
            Если задача нормально решается на 1С, смысл тратить силы мозга на изучение питона?
            Аналогично, есть регулярные выражения, но они сложнее, чем поиск и замена.
            Причем сложнее не в плане понять их, а в плане повседневного использования, если используешь их редко.
            Это специализация.
            Программист 1С заточен не на строковый парсинг.

  2. fajij28770:

    уверен, что тут дело не в производительности 1С, а в том что фикса использовал какой-нибудь неэффективный алгоритм, типа, делал реплейс из начала строки, пока она не стала пустой (хз, как еще можно парсить 70 мб CSV 10 минут)
    хотя парсер CSV на любом языке делается тривиально за линейное амортизированное время:
    data = read(csv).split(‘\n’).map(line => line.split(‘,’))

    • Да, я использовал готовый разбор CSV строки, мне было его лень переписывать. Задача не требовала эффективности.
      А в этом алгоритме была склейка по одному символу в строку.
      Если интересно, могу код найти и сюда выложить. Хотя можно сказать обработку и посмотреть, он там есть.

      • fajij28770:

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

        • &НаКлиенте
          Функция ПрочитатьCSV(Знач Строка, Разделитель, КоличествоКолонок) Экспорт
          //http://chel1c.ru/%D0%B8%D0%BC%D0%BF%D0%BE%D1%80%D1%82-%D0%B8%D0%B7-csv-%D0%B2-1%D1%81/

          Результат = Новый Массив; // массив строк

          Скобка=ЛОЖЬ;
          Начало=1;
          Колонка=0;
          СтрокаФайла=новый Массив; // массив колонок в строке

          для Позиция = 1 по СтрДлина(Строка) Цикл // обходим файл посимвольно

          Символ=Сред(Строка, Позиция, 1);

          //если встречается кавычка, фиксируем ее открытие, прекращаем итерацию и продолжаем цикл.
          Если Символ=»»»» И Скобка=Ложь Тогда
          Скобка=Истина;
          Продолжить;
          //Если встречается закрывающаяся кавычка, фиксируем ее закрытие и тоже продолжаем цикл
          ИначеЕсли Символ=»»»» И Скобка=Истина ТОгда
          Скобка=Ложь;
          Продолжить;
          КонецЕсли;

          //Если встречается разделитель или перенос строки вне кавычек,
          //вносим информацию в массив
          Если (Символ=Разделитель ИЛИ Символ=Символы.ПС) И Скобка=Ложь Тогда
          Конец=Позиция;
          Колонка=Колонка+1;

          //Получаем текущий элемент
          ТекЗн = СокрЛП(Сред(Строка, Начало, Конец-Начало));
          Если Лев(ТекЗн, 1) = «»»» Тогда
          ТекЗн = Сред(ТекЗн, 2);
          КонецЕсли;
          Если Прав(ТекЗн, 1) = «»»» Тогда
          ТекЗн = Сред(ТекЗн, 1, СтрДлина(ТекЗн) — 1);
          КонецЕсли;

          СтрокаФайла.Добавить(ТекЗн);

          //Если набралось количество колонок, равное их количеству в шапке, записываем всю строку
          //в массив и переходим к следующей
          Если Колонка=КоличествоКолонок Тогда

          Результат.Добавить(СтрокаФайла);
          СтрокаФайла=Новый Массив;

          Колонка=0;
          КонецЕсли;

          Начало=Позиция+1;
          КонецЕсли;
          КонецЦикла;

          //Удаляем шапку таблицы из массива строк
          //Результат.Удалить(0);
          Возврат Результат;

          КонецФункции

  3. fajij28770:

    Во-первых, у тебя известно наперед кол-во колонок, нужно этим пользоваться:
    >СтрокаФайла=Новый Массив(КоличествоКолонок);

    Это уже, по идее, должно сэкономить жополиард переалокаций памяти, при добавлении новых значений в конец массива.

    Во-вторых, здесь очевидно неэффективный код:
    ТекЗн = СокрЛП(Сред(Строка, Начало, Конец-Начало));
    Если Лев(ТекЗн, 1) = «»»» Тогда
    ТекЗн = Сред(ТекЗн, 2);
    КонецЕсли;
    Если Прав(ТекЗн, 1) = «»»» Тогда
    ТекЗн = Сред(ТекЗн, 1, СтрДлина(ТекЗн) — 1);
    КонецЕсли;

    Для значений в кавычках строка будет скопирована два лишних раза. Тут надо сдвинуть значения индексов Начало и Конец, а потом один раз сделать копирование строки, примерно так:
    Если Сред(Строка, Начало, 1) = «»»» Тогда
    Начало=Начало+1;
    КонецЕсли;

    Если 1С писали не обезьяны 🙂 то этих исправлений будет достаточно, чтобы ускорить алгоритм на порядок.

    • На переаллокациях там много не сэкономишь.
      Проблема в скорости тут в том, что автор функции работает с единой длинной строкой, а надо работать построчно.
      Но при работе построчно код чутка усложняется, поэтому автор и работает с длинной строкой.
      А поиск символа в строке с позицией 888373 это знаешь ли большая нагрузка на скорость. 😉

  4. fajij28770:

    >На переаллокациях там много не сэкономишь.
    ты, видимо, не в курсе как работают динамически расширяемые массивы, раз такое пишешь

    >А поиск символа в строке с позицией 888373 это знаешь ли большая нагрузка на скорость.
    ты строку последовательно перебираешь, а не рандомно из нее символы дергаешь. поэтому это никак не повлияет на скорость, потому что процессор сможет эффективно закэшировать обращения к памяти

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

    • Откуда ты знаешь, как кэширует 1С обращение к строке.
      Вот есть функция Сред(Строка, Начало, длина).
      Почему ты думаешь что функция кэширует строку в памяти, а не каждый раз ищет в Строке символ. Ведь строка может и другая быть передана.
      Не думай об 1с лучше, чем она есть.

  5. fajij28770:

    >Откуда ты знаешь, как кэширует 1С обращение к строке.
    я знаю, как кэширует обращение к памяти процессор на архитектуре x86 (ну и на других распространенных архитектурах), знаю, как устроены фундаментальные структуры данных и знаю как работают интерпертаторы. и этого достаточно, чтобы делать разумные предположения о времени работы функций, даже 1С, с которым никогда не имел дело.

    >Вот есть функция Сред(Строка, Начало, длина).
    >Почему ты думаешь что функция кэширует строку в памяти, а не каждый раз ищет в Строке символ.
    потому что строки в памяти, обычно, представлены как непрервыне массивы символов в памяти. и обращение к символу в строке по индексу всегда работает за константное время O(1).

    а когда ты напишешь Сред(Строка, Начало, длина) у себя в 1С (или str[start:len] в Python, или str.substring(start,end) в JS, и т.д.), все что сделает интерпретатор, это создат новую область в памяти (с помощью вызова системной функции для выделения памяти в хипе) для подстроки и скопирует в нее символы из старой строки. и это работает за O(n), где n — это длина подстроки.

    >Не думай об 1с лучше, чем она есть.
    конечно, это все базовые разумные предположения, но они легко проверяются, если есть сомнения. напиши бенчмарк, который будет брать символ из короткой строки и из очень длинной, запусти, и если увидишь, что время доступа одинковое, значит все работает, как я написал. а твое предположение, что время доступа увеличивает в зависимости от длины строки, неверное. готов поспорить на деньги, если что 😉

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *