У меня есть несколько очень больших XML файлов, которые мне нужно будет проанализировать и извлечь соответствующие данные в файл csv, по сути выполняя частичное выравнивание документа XML. Файл XML будет иметь «тег записи», в котором хранятся все записи. Это будет выглядеть примерно так, например:
<persons>
<person id="1">
<firstname>James</firstname>
<lastname>Smith</lastname>
<middlename></middlename>
<dob_year>1980</dob_year>
<dob_month>1</dob_month>
<gender>M</gender>
<salary currency="Euro">10000</salary>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname></lastname>
<middlename>Rose</middlename>
<dob_year>1990</dob_year>
<dob_month>6</dob_month>
<gender>M</gender>
<salary currency="Dollor">10000</salary>
</persons>
Тег записи здесь 'person', и результирующее преобразование в CSV будет выглядеть так:
_id, dob_month, dob_year,firstname,gender,lastname, middlename salary
1,1,1980, James,M,Smith,,{"_VALUE":10000,"_currency":"Euro"}
2,6,1990, Michael M,, Rose,{"_VALUE":10000,"_currency":"Dollor"}
This может быть неправильно - я быстро набрал его - но вы поняли.
Есть некоторые ограничения, которые следует иметь в виду:
- Файл очень большой - 1 ГБ + - поэтому я могу не загружайте это в память.
- Я должен прочитать это только один раз.
- (obvioulsy) данные не могут быть потеряны или неверны.
Хорошо, так что я в настоящее время иметь синтаксический анализатор, который преобразует простой XML файл в csv с заданным «ROWTAG», т. е. тегом, в котором есть записи (person
в этом примере). Вы можете видеть это здесь .
Но на это есть некоторые ограничения. Я знаю, как исправить / устранить большинство из них, но есть две, которые я не могу понять, не нарушая ограничений.
- В зависимости от того, как реализован синтаксический анализатор, порядок теги имеют значение. Допустим, у меня есть файл xml, который выглядит следующим образом:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Jordan</lastname>
<middlename>Rose</middlename>
</person>
Среднее имя в первом теге записи идет вторым, а во втором - третьим. В результате файл CSV будет выглядеть следующим образом:
firstname,middlename,lastname
James,,Smith,
Michael,Jordan,Rose
Имя Майкла было записано как Майкл Джордан Роуз, когда это должен был быть Майкл Роуз Джордан.
Если атрибуты добавлены, удалены или изменены, программа не отражает это. Это потому, что программа просматривает только теги из первого элемента записи и не заботится о тегах в следующих элементах (как показано в первом примере).
Давайте рассмотрим этот пример :
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Jordan</lastname>
<middlename>Rose</middlename>
<dob>1/10/11</dob>
</person>
Полученный CSV будет выглядеть следующим образом:
firstname,middlename,lastname
James,,Smith,
Michael,Jordan,Rose, 1/10/11
Конечно, это большая проблема, и ее нужно решить.
Мой вид решения
Прежде чем перейти к решению, я собираюсь очень быстро подвести итог, как именно работает программа. Синтаксический анализатор перемещается по документу XML и каждый раз, когда он встречает тег, он сплевывает это обратно в мою программу. В моей программе есть "rowTag", который, как я объяснил, программа ищет. Как только он обнаружен, моя программа начинает поиск всех тегов и значений внутри этого rowTag и сохраняет их в StringBuilder. Они будут сбрасывать эту информацию, когда встречаются с конечным тегом rowTag. Во время первой итерации он также сохраняет все заголовки, с которыми сталкивается, а затем, перед тем как сбросить значения записей после достижения конечного тега, он сначала сбросит заголовки.
Теперь ... как я упомянуто, это создает проблему с сохранением порядка и с любыми измененными, удаленными или добавленными тегами. У меня есть решение, которое решает заказ, и оно должно решить проблему заголовка, не будучи обновленной, но я не уверен, выполнимо ли это для моего сценария использования (который я объясню почему через минуту).
Моя идея состоит в том, чтобы иметь что-то вроде хэш-карты, которая собирает значения тегов и порядок, с которым они встречаются с течением времени. Ключом будет значение тега, а значением будет порядок первого появления тега.
Когда мы собираем записи по мере продвижения по программе, мы помещаем их в массив, который является настолько большим как хэш-карта в правильном месте, он должен быть. Если мы столкнемся с новыми тегами, мы просто изменим размер массива и добавим тег в хэш-карту НЕ со значением порядка, с которым он встречался в данный момент (потому что это может что-то перезаписать), а скорее со значением предыдущего элемента + 1 ( это будет упорядоченная хеш-карта, так что я бы знал, что это за предыдущий элемент).
Как только мы закончим с программой, мы сбросим заголовки, собранные в первую строку файла.
Итак, давайте посмотрим на первый пример:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Jordan</lastname>
<middlename>Rose</middlename>
</person>
Хэш-карта после первого запуска будет выглядеть так:
{firstname: 0, middlename: 1, lastname: 2}
Когда мы доберемся до второй записи, где меняются местоположения среднего и фамилии, мы просто помещаем фамилию в точку array[2]
, а среднее имя в точку array[1]
.
Если мы что-то упустили, например, что-то вроде этого:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Rose</lastname>
</person>
Массив во время второго запуска будет нулевым во втором значении (потому что middlename там нет), что мы просто конвертируем в пустую строку, и к ней добавляется запятая, как обычно.
Интересная часть, когда что-то добавляется:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Rose</lastname>
<dob></dob>
</person>
Это приведет к CSV, который выглядит как это:
firstname,middlename,lastname,dob
James,,Smith
Michael,,Rose,1/10/11
В первом столбце нет этого дополнительного ,
после Smith
, но кажется, что даже если это не правильный CSV, это нормально? Круто.
В любом случае - теперь я думаю, что проблема в этом. На самом деле я не использую bufferedreader / bufferedwriter в java. Мы используем потоковое считывающее / записывающее устройство, которое поставляется с Azure, потому что, конечно, все эти файлы находятся в облаке, а под капотом это в основном просто остальные вызовы API. Поэтому я не думаю, что смогу вывести заголовки в первую строку файла. Я даже не уверен, что это было бы возможно, независимо от того.
Итак. Есть гении, у которых есть идеи?