Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.218.169.50] |
|
Сообщ.
#1
,
|
|
|
на входе есть XML-файл и данные из него нужно как-то запихнуть в плоскую таблицу на SQL2000.
На СКЛе есть OPENXML, но читать он может только из строковой переменной максимум в 8000 символов. Мне же нужно читать файлы 1-30Мб. Подскажите, какие средства есть для этого? В голову пока приходит только парсинг xml'я, что мне кажется изобретением велосипеда, т.к. наверняка не одному мне потребовалось это сделать. Да и непонятно, как быть с вложенными объектами. Замечательно с этим справляется ексель: открываешь в нем xml-ку и получаешь плоскую таблицу. Но большой минус в том, что ексель зачем-то по своему усмотрению преобразовывает числа и отключить это никак нельзя. Например, длинный id может из вида 1234567890 преобразоваться в "1+10", а число 1.2 легко стать датой 2017.01.02 и превратиться в число 42375. Есть ли идеи, чем можно быстро перенести данные из xml в sql, кроме парсинга и построчного добавления в таблицу? |
Сообщ.
#2
,
|
|
|
Непонятно, чем не устроил парсинг - разумеется, не самописный, с нуля, а с помощью готовых классов, которых как грязи. Файлы мелкие, поэтому можно брать любую реализацию.
Если хочется совсем уж выёжиться, то можно подключить xml через clientdataset как БД. Вроде бы даже стандартными средствами этого можно достичь. |
Сообщ.
#3
,
|
|
|
Цитата Fr0sT @ Непонятно, чем не устроил парсинг как из дерева получить плоскую таблицу? Цитата Fr0sT @ Файлы мелкие, поэтому можно брать любую реализацию. файлы до 30Мб, что мне кажется не особо мелким Цитата Fr0sT @ Если хочется совсем уж выёжиться, то можно подключить xml через clientdataset как БД провайдера какого-то задать? Какого, например? |
Сообщ.
#4
,
|
|
|
Цитата Voice @ как из дерева получить плоскую таблицу? Пробегать по узлам. Какие-то узлы это записи, какие-то - поля. Все зависит от схемы данных. Цитата Voice @ файлы до 30Мб, что мне кажется не особо мелким Надо проверять на конкретных движках, но по нынешним меркам это пыль. Нормальный движок должен проглотить и не поморщиться. Это я к тому, что не требуется SAX, а можно грузить файл целиком. Цитата Voice @ провайдера какого-то задать? Какого, например? Поищи в хелпе, я недавно натыкался прямо в стандартной справке по этому классу, как его натравить на XML |
Сообщ.
#5
,
|
|
|
Цитата Fr0sT @ Поищи в хелпе, я недавно натыкался прямо в стандартной справке по этому классу, как его натравить на XML а натыкался где? У меня Д7 Цитата Fr0sT @ Пробегать по узлам. Какие-то узлы это записи, какие-то - поля. Все зависит от схемы данных. так и делаю сейчас. Но схемы данных нет, есть голая xml-ка, формат которой заранее неизвестен. Ексель же тоже как-то получает на вход xml и делает из нее таблицу. Проблема в том, чтобы понять, какая ветвь - это уже новая запись в таблице. Например, есть такая xml-ка (СИЛЬНО упрощенный вид): продажи от дистрибьюторов клиентам какой-то продукции <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 будет пусто. Вот тут получается, что я изобретаю велосипед и хожу по граблям. Тема создавалась с вопросом, есть ли что-то готовое. Тем более, что задача не особо специфическая, а ИМХО довольно распространенная. |
Сообщ.
#6
,
|
|
|
Цитата Voice @ а натыкался где? У меня Д7 Слушай, ну ты вроде не чайник по разговору, а спрашиваешь элементарные вещи. Справка Д7 по клиентдатасету, раздел Using provider components. Что касается схемы XML - это уже другая тема для беседы. Но если есть хотя бы какая-то определенность в структуре - дело существенно упрощается. Например, ты говоришь, что разделов может не быть. Но максимально полный набор узлов же известен? Вот и разбирать на его основе, а отсутствующие узлы игнорировать. Погонял Эксель. На первый взгляд ничего сложного. 1. Пробег по узлам рекурсивно 2. Если у узла есть атрибут - добавить его как столбец итоговой таблицы 3. Если узел конечный (элемент с текстом) - добавить его как столбец итоговой таблицы 4. Узел, содержащий дочерние конечные узлы, закончился - добавить строку таблицы со всеми заполненными ячейками и перейти на уровень выше |
Сообщ.
#7
,
|
|
|
по ClientDataSet так не доразобрался. Там нужно XMLTransform прицеплять. А ему в свою очередь нужен какой-то файл трансформации, который можно получить из xml с помощью дельфевой утилиты XML Mapping. Она у меня почему-то запускаться не хочет (критическую ошибку выдает).
Да и нафига мне эти сложности с посторонними утилитами. Крче, решил вопрос определения новой строки так: - идем рекурсивно по ветвям (пробегаем все дочерние ветви. И для каждой дочерней ветви все ее дочерние ветви. И т.д. вглубь дерева) - если ПЕРЕД этой ветвью есть ветвь с таким же именем (while prevSibling <> nil), добавляем для текущей ветви строку - строим полный путь к ветви: получаем строку вида "aaa.bbb.ccc" - если в списке полей такого поля нет, добавляем и запоминаем индекс этого поля, т.к. следующая ветвь возможно будет значением. - если встретилось значение ветви (nodeType <> ELEMENT_NODE), пишем в таблицу (в только что запомненное поле) |
Сообщ.
#8
,
|
|
|
.... хотел было алгоритм скорректировать, потом решил просто код выложить. Код тренировочный, подчистил немного, но могло остаться много мусора (ненужные переменные, юниты и т.п.)
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. |