На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Обязательно указание:
1) типа базы данных (Paradox/Oracle/Interbase и т.п.)
2) способа доступа к базе данных (ODBC/ADO/DAO/BDE и т.п.)
Например: Paradox/BDE, MS Access/ADO

Наиболее часто задаваемые вопросы:
Базы даных для начинающих. Первые шаги. Понятие о BDE.
Переход на клиент-сервер и начала ADO
Приёмы работы с BLOB (OLE/Memo) полями
Запросы и параметры или как избавиться от многих проблем. Проблемы с датами в запросах.
Нужели мне нужно устанавливать BDE? (или почему не работает программа на другом компьютере)
Модераторы: Bas, Rouse_
  
> XML to SQL2000
    на входе есть XML-файл и данные из него нужно как-то запихнуть в плоскую таблицу на SQL2000.
    На СКЛе есть OPENXML, но читать он может только из строковой переменной максимум в 8000 символов. Мне же нужно читать файлы 1-30Мб.

    Подскажите, какие средства есть для этого?
    В голову пока приходит только парсинг xml'я, что мне кажется изобретением велосипеда, т.к. наверняка не одному мне потребовалось это сделать.
    Да и непонятно, как быть с вложенными объектами.

    Замечательно с этим справляется ексель: открываешь в нем xml-ку и получаешь плоскую таблицу. Но большой минус в том, что ексель зачем-то по своему усмотрению преобразовывает числа и отключить это никак нельзя.
    Например, длинный id может из вида 1234567890 преобразоваться в "1+10", а число 1.2 легко стать датой 2017.01.02 и превратиться в число 42375.

    Есть ли идеи, чем можно быстро перенести данные из xml в sql, кроме парсинга и построчного добавления в таблицу?
    Идея пришла в его голову и теперь упорно ищет мозг
      Непонятно, чем не устроил парсинг - разумеется, не самописный, с нуля, а с помощью готовых классов, которых как грязи. Файлы мелкие, поэтому можно брать любую реализацию.
      Если хочется совсем уж выёжиться, то можно подключить xml через clientdataset как БД. Вроде бы даже стандартными средствами этого можно достичь.
      Codero ergo sum
      // Программирую — значит, существую
        Цитата Fr0sT @
        Непонятно, чем не устроил парсинг

        как из дерева получить плоскую таблицу?

        Цитата Fr0sT @
        Файлы мелкие, поэтому можно брать любую реализацию.

        файлы до 30Мб, что мне кажется не особо мелким

        Цитата Fr0sT @
        Если хочется совсем уж выёжиться, то можно подключить xml через clientdataset как БД

        провайдера какого-то задать? Какого, например?
        Идея пришла в его голову и теперь упорно ищет мозг
          Цитата Voice @
          как из дерева получить плоскую таблицу?

          Пробегать по узлам. Какие-то узлы это записи, какие-то - поля. Все зависит от схемы данных.
          Цитата Voice @
          файлы до 30Мб, что мне кажется не особо мелким

          Надо проверять на конкретных движках, но по нынешним меркам это пыль. Нормальный движок должен проглотить и не поморщиться. Это я к тому, что не требуется SAX, а можно грузить файл целиком.
          Цитата Voice @
          провайдера какого-то задать? Какого, например?

          Поищи в хелпе, я недавно натыкался прямо в стандартной справке по этому классу, как его натравить на XML
          Codero ergo sum
          // Программирую — значит, существую
            Цитата Fr0sT @
            Поищи в хелпе, я недавно натыкался прямо в стандартной справке по этому классу, как его натравить на XML

            а натыкался где? У меня Д7


            Цитата Fr0sT @
            Пробегать по узлам. Какие-то узлы это записи, какие-то - поля. Все зависит от схемы данных.

            так и делаю сейчас. Но схемы данных нет, есть голая xml-ка, формат которой заранее неизвестен. Ексель же тоже как-то получает на вход xml и делает из нее таблицу.
            Проблема в том, чтобы понять, какая ветвь - это уже новая запись в таблице.
            Например, есть такая xml-ка (СИЛЬНО упрощенный вид):
            продажи от дистрибьюторов клиентам какой-то продукции
            ExpandedWrap disabled
              <distributor name="Ivanov">
                 <client name="Petrov">
                    <prod_info>
                       <prod_code>1</prod_code>
                       <prod_name>Пряник<prod_name>
                       <sale_info>
                          <amount>100</amoount>
                          <summa>123.45</summa>
                       </sale_info>
                    </prod_info>
                    <prod_info>
                       <prod_code>2</prod_code>
                       <prod_name>Печенька<prod_name>
                       <sale_info>
                          <amount>200</amoount>
                          <summa>500.00</summa>
                       </sale_info>
                    </prod_info>
                 </client>
                 <client name="Sidorov">
                    <prod_info>
                       <prod_code>3</prod_code>
                       <prod_name>Колбаса<prod_name>
                       <sale_info>
                          <amount>100</amoount>
                          <summa>123.45</summa>
                       </sale_info>
                    </prod_info>
                    <prod_info>
                       <prod_code>4</prod_code>
                       <prod_name>Сыр<prod_name>
                       <sale_info>
                          <amount>200</amoount>
                          <summa>500.00</summa>
                       </sale_info>
                    </prod_info>
                 </client>
              </distributor>


            Сейчас решил, что ищем самого последнего потомка (это и будет одна запись) и к нему подтягиваем св-ва всех его предков (это будет наполнение записи).
            Проблема в том, что в разных ветвях могут отсутствовать какие-то разделы. Например, у второго покупателя может быть информация о продукте, но не будет информации о продаже.
            Хотя сейчас вот подумал, что это не должно особо мешать. Просто найдем этого последнего потомка (в данном случае будет prod_info) и к нему подтянем св-ва всех его предков. А в полях sale_info:amount и sale_info:summa будет пусто.

            Вот тут получается, что я изобретаю велосипед и хожу по граблям. Тема создавалась с вопросом, есть ли что-то готовое. Тем более, что задача не особо специфическая, а ИМХО довольно распространенная.
            Сообщение отредактировано: Voice -
            Идея пришла в его голову и теперь упорно ищет мозг
              Цитата Voice @
              а натыкался где? У меня Д7

              Слушай, ну ты вроде не чайник по разговору, а спрашиваешь элементарные вещи. Справка Д7 по клиентдатасету, раздел Using provider components.

              Что касается схемы XML - это уже другая тема для беседы. Но если есть хотя бы какая-то определенность в структуре - дело существенно упрощается. Например, ты говоришь, что разделов может не быть. Но максимально полный набор узлов же известен? Вот и разбирать на его основе, а отсутствующие узлы игнорировать.
              Погонял Эксель. На первый взгляд ничего сложного.
              1. Пробег по узлам рекурсивно
              2. Если у узла есть атрибут - добавить его как столбец итоговой таблицы
              3. Если узел конечный (элемент с текстом) - добавить его как столбец итоговой таблицы
              4. Узел, содержащий дочерние конечные узлы, закончился - добавить строку таблицы со всеми заполненными ячейками и перейти на уровень выше
              Сообщение отредактировано: Fr0sT -
              Codero ergo sum
              // Программирую — значит, существую
                по ClientDataSet так не доразобрался. Там нужно XMLTransform прицеплять. А ему в свою очередь нужен какой-то файл трансформации, который можно получить из xml с помощью дельфевой утилиты XML Mapping. Она у меня почему-то запускаться не хочет (критическую ошибку выдает).
                Да и нафига мне эти сложности с посторонними утилитами.

                Крче, решил вопрос определения новой строки так:
                - идем рекурсивно по ветвям (пробегаем все дочерние ветви. И для каждой дочерней ветви все ее дочерние ветви. И т.д. вглубь дерева)
                - если ПЕРЕД этой ветвью есть ветвь с таким же именем (while prevSibling <> nil), добавляем для текущей ветви строку
                - строим полный путь к ветви: получаем строку вида "aaa.bbb.ccc"
                - если в списке полей такого поля нет, добавляем и запоминаем индекс этого поля, т.к. следующая ветвь возможно будет значением.
                - если встретилось значение ветви (nodeType <> ELEMENT_NODE), пишем в таблицу (в только что запомненное поле)
                Сообщение отредактировано: Voice -
                Идея пришла в его голову и теперь упорно ищет мозг
                  .... хотел было алгоритм скорректировать, потом решил просто код выложить. Код тренировочный, подчистил немного, но могло остаться много мусора (ненужные переменные, юниты и т.п.)
                  ExpandedWrap disabled
                    unit Unit1;
                     
                    interface
                     
                    uses
                      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
                      Dialogs, XMLDoc, XMLIntf, StdCtrls, XMLdom,
                      TypInfo;
                     
                    type
                      TForm1 = class(TForm)
                        Button1: TButton;
                        Memo1: TMemo;
                        dlgOpen: TOpenDialog;
                        procedure Button1Click(Sender: TObject);
                        procedure FormCreate(Sender: TObject);
                      private
                        { Private declarations }
                      public
                        { Public declarations }
                      end;
                     
                    type
                       TCsvRow = record
                          AFieldName, ACell: string;
                       end;
                     
                       TObjInfo = array of TCsvRow;
                     
                     
                    const
                       cnstFileName = 'C:\test.xml';
                       cnstFieldDelim = '.'; // разделитель xml-полей
                     
                    var
                      Form1: TForm1;
                      CsvTable: array of array of string; // таблица-массив [строка][столбец]
                      ObjInfo: TObjInfo; // информация о текущем объекте (все поля с текущими значениями). При переходе на след.объект все потомки очищаются для заполнения новыми данными
                      TmpFieldIndex: integer; // текущее поле в табл.CsvTable для заполнения
                      TmpRecordIndex: integer; // текущая строка в табл.CsvTable для заполнения
                     
                    implementation
                     
                    {$R *.dfm}
                     
                    function FieldIndex(AName: string): integer; // ищем поле в таблице-массиве
                    var
                       n: integer;
                    begin
                    Result := -1;
                    for n := 0 to high(CsvTable[0]) do
                       if CsvTable[0][n] = AName then
                          begin
                          Result := n;
                          exit;
                          end;
                    end;
                     
                    procedure AddField(AName: string); // добавляем новое поле (если нужно) в таблицу-массив и запоминаем индекс
                    var
                       m: integer;
                    begin
                    TmpFieldIndex := FieldIndex(AName);
                    if TmpFieldIndex < 0 then // добавляем НОВОЕ поле
                       begin
                       for m := 0 to high(CsvTable) do // бежим по строкам и в каждую добавляем новое поле
                          SetLength(CsvTable[m], length(CsvTable[m]) + 1);
                       // записываем поле в конец
                       TmpFieldIndex := high(CsvTable[0]);
                       CsvTable[0][TmpFieldIndex] := AName;
                       end;
                    end;
                     
                    procedure SaveCsv(AFileName: string); // сохраняем таблицу-массив в csv-файл
                    var
                       n, m, i: integer;
                       f: TextFile;
                       TmpStr: string;
                    begin
                    try
                       AssignFile(f, AFileName);
                       Rewrite(f);
                       for n := 0 to high(CsvTable) do // читаем строки
                          begin
                          TmpStr := '';
                          for m := 0 to high(CsvTable[n]) do // читаем столбцы
                             begin
                             if n = 0 then // 0-я строка - названия столбцов (вырезаем имя поля из пути)
                                begin
                                i := LastDelimiter(cnstFieldDelim, cnstFieldDelim + CsvTable[n][m]); // на всякий добавляем в начало пустое поле, если строка пустая
                                TmpStr := TmpStr + #9 + CsvTable[n][m]; //copy(CsvTable[n][m], i, length(CsvTable[n][m]) - i + 1);
                                end else // значения (строка >= 1)
                                TmpStr := TmpStr + #9 + CsvTable[n][m];
                             end; // for m
                          WriteLn(f, TmpStr);
                          end; // for n
                       finally
                          CloseFile(f);
                       end; // try
                    end;
                     
                    function GetPath(ANode: IDOMNode): string; // получаем путь до заданного объекта
                    begin
                    if ANode = nil then
                       exit;
                    {
                    if ANode.localName = '' then
                       Result := GetPath(ANode.ParentNode)
                       else Result := GetPath(ANode.ParentNode) + cnstFieldDelim + ANode.localName;
                    }
                    if ANode.localName = '' then
                       Result := GetPath(ANode.ParentNode)
                       else Result := ANode.localName + cnstFieldDelim + GetPath(ANode.ParentNode);
                    end;
                     
                    procedure GetChildNodes(Node: IDOMNode; OffSet: String); // рекурсивно перебираем дерево объектов
                     
                       procedure SetParentFields; // заполняем поля текущей строки информацией по родительским объектам
                       var
                          n, m: integer;
                       begin
                       for n := 0 to high(ObjInfo) do
                          begin
                          m := FieldIndex(ObjInfo[n].AFieldName);
                          if m >= 0 then
                             CsvTable[TmpRecordIndex][m] := ObjInfo[n].ACell;
                          end;
                       end;
                     
                       procedure AddObjInfo(AField, AValue: string); // запоминаем информацию о родителе
                       var
                          n: integer;
                       begin
                       // ищем поле и обновляем значение
                       for n := 0 to high(ObjInfo) do
                          if ObjInfo[n].AFieldName = AField then
                             begin
                             ObjInfo[n].ACell := AValue;
                             exit;
                             end;
                     
                       // если поле не нашли, добавляем
                       SetLength(ObjInfo, length(ObjInfo) + 1);
                       ObjInfo[high(ObjInfo)].AFieldName := AField;
                       ObjInfo[high(ObjInfo)].ACell := AValue;
                       end;
                     
                       procedure ClearObjInfo(AParentPath: string); // чистим поля внутри заданного объекта (родителя)
                       var
                          n, m: integer;
                       begin
                       for n := 0 to high(ObjInfo) do
                          begin
                          {
                          if copy(ObjInfo[n].AFieldName, 1, length(AParentPath) + length(cnstFieldDelim)) = AParentPath + cnstFieldDelim then
                             ObjInfo[n].ACell := '';
                          }
                          m := length(AParentPath) + length(cnstFieldDelim);
                          if copy(ObjInfo[n].AFieldName, length(ObjInfo[n].AFieldName) - m + 1, m) = cnstFieldDelim + AParentPath then
                             ObjInfo[n].ACell := '';
                          end;
                       end;              
                     
                    var
                      n, m: integer;
                      TmpPath, TmpStr: string;
                      TmpNode: IDOMNode;
                      TmpObjInfo: TObjInfo;
                      TmpFlag: boolean;
                    begin
                    // если ПЕРЕД этой ветвью есть ветвь с таким же именем, добавляем для текущей ветви строку
                    TmpFlag := false;
                    TmpNode := Node.previousSibling;
                    while TmpNode <> nil do
                       begin
                       if TmpNode.nodeName = Node.nodeName then
                          begin
                          Inc(TmpRecordIndex);
                          TmpFlag := true; // запоминаем, что уже добавили строку
                          break;
                          end;
                       TmpNode := TmpNode.previousSibling;
                       end;
                    if not TmpFlag then // если строка еще добавлена не была, проверяем другое условие
                       if (TmpNode <> nil) and (TmpNode.nextSibling = nil) then // если ПОСЛЕ этой ветви на ее уровне никого нет, добавляем строку
                          begin
                          Inc(TmpRecordIndex);
                          end;
                     
                    // добавляем новую строку в таблицу-массив, если надо
                    if TmpRecordIndex > high(CsvTable) then
                       begin
                       // добавляем новую строку в CsvTable
                       SetLength(CsvTable, TmpRecordIndex + 1);
                       SetLength(CsvTable[TmpRecordIndex], length(CsvTable[0])); // делаем у новой строки такое же кол-во полей, как и у 0-й строки (у заголовков)
                       end;
                     
                    TmpPath := GetPath(Node);
                    ClearObjInfo(TmpPath); // зайдя в новый объект, чистим вложенные объекты
                     
                    // добавляем поля
                    AddField(TmpPath);
                    CsvTable[TmpRecordIndex][TmpFieldIndex] := Node.nodeValue; // записываем значение в ячейку
                    //Form1.Memo1.Lines.Add(IntToStr(TmpRecordIndex) + '_val ' + OffSet + TmpPath + ': ' + Node.nodeValue);
                     
                    AddObjInfo(TmpPath, Node.nodeValue); // добавляем прочитанную информацию об объекте
                     
                    if Node.attributes <> nil then // если есть атрибуты, обрабатываем, как значения
                       for n := 0 to Node.attributes.length - 1 do
                          begin
                          AddField(Node.attributes[n].nodeName + ':' + TmpPath); // добавляем атрибут как поле (если такого поля еще нету)
                          CsvTable[TmpRecordIndex][TmpFieldIndex] := Node.attributes[n].nodeValue; // записываем значение в ячейку
                          Form1.Memo1.Lines.Add(IntToStr(TmpRecordIndex) + '_atr ' + Offset + Node.attributes[n].nodeName + ':' + TmpPath + ': ' + Node.attributes[n].nodeValue);
                     
                          AddObjInfo(Node.attributes[n].nodeName + ':' + TmpPath, Node.attributes[n].nodeValue);
                          end;
                     
                    // идем дальше по дереву
                    for n := 0 to Node.childNodes.length - 1 do
                       GetChildNodes(Node.childNodes[n], OffSet + '  ');
                     
                    SetParentFields; // присваиваем прочитанные ранее св-ва объектов-родителей
                    end;
                     
                    procedure TForm1.Button1Click(Sender: TObject);
                    var
                      xmlDocument: TXMLDocument;
                      mainNode: IXMLNode;
                      nodes: IXMLNodeList;
                      n: Integer;
                      TmpObjInfo: TObjInfo;
                    begin
                    FormCreate(Sender); // обнуляем массив CsvTable
                    if not dlgOpen.Execute then
                       exit;
                     
                    Memo1.Lines.LoadFromFile(dlgOpen.FileName);
                     
                    xmlDocument := TXMLDocument.Create(Owner);
                    //xmlDocument.LoadFromFile(dlgOpen.FileName);
                    xmlDocument.XML.Text := Memo1.Lines.Text;
                     
                    xmlDocument.Options := xmlDocument.Options + [doAttrNull];
                    xmlDocument.Active := True;
                    n := 0;
                    Memo1.Lines.BeginUpdate;
                    SetLength(ObjInfo, 0);
                    GetChildNodes(xmlDocument.DocumentElement.DOMNode, '');
                    Memo1.Lines.EndUpdate;
                    SaveCsv(dlgOpen.FileName + '.txt');
                    xmlDocument.Active := False;
                    xmlDocument.Free;
                    Memo1.Lines.Add('Готово');
                    Memo1.Lines.Add(#13#10#13#10#13#10#13#10#13#10);
                    end;
                     
                    procedure TForm1.FormCreate(Sender: TObject);
                    var
                       n: integer;
                    begin
                    dlgOpen.FileName := cnstFileName;
                    // чистим массив
                    Finalize(CsvTable);
                    Finalize(ObjInfo);
                     
                    SetLength(CsvTable, 1); // добавляем первую строку (назв.столбцов)
                     
                    TmpFieldIndex := -1; // текущее поле в табл.CsvTable для заполнения
                    TmpRecordIndex := 1; // текущая строка в табл.CsvTable для заполнения
                    end;
                     
                    end.
                  Сообщение отредактировано: Voice -
                  Идея пришла в его голову и теперь упорно ищет мозг
                  1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                  0 пользователей:


                  Рейтинг@Mail.ru
                  [ Script Execution time: 0,1150 ]   [ 15 queries used ]   [ Generated: 22.11.17, 13:06 GMT ]