Слияние иерархий документов XML
Фон
Я разрабатываю приложение Perl, которое использует XML-файлы в качестве входных данных для информации о конфигурации и настройках. Будет иерархия документов с глобальными данными, переопределенными более локальной информацией.
Моя программа будет вызываться с наиболее локальным файлом настроек, который будет содержать пути к более общим файлам. Некоторые локальные настройки будут абсолютными, и они будут жестко заданы в программе.
Задача инициализации состоит в том, чтобы получить настройки для вызова на самом высоком уровне, прочитать их, а затем перейти на каждый уровень и объединить / объединить их как один XML-документ.
Пример данных
Global_layouts_100.xml
<CONFIG>
<GRP1>
<FIELD foo="abs" format="%.4f">QTY</FIELD>
<FIELD default="" format="%.2f">COST</FIELD>
<FIELD default="0" format="%.2f">AMT</FIELD>
<FIELD default="1960-01-01" format="YYYMMDD">TRANDATE</FIELD>
<FIELD>ACCOUNT</FIELD>
<FIELD default="0">ACCT_TYPE</FIELD>
</GRP1>
<GRP2>
<FIELD> 1 </FIELD>
<FIELD> 2 </FIELD>
<FIELD> 3 </FIELD>
</GRP2>
</CONFIG>
Global_properties_100.xml
<CONFIG>
<CUS>
<GRP>GRP1</GRP>
<HDR>CUSTOMER</HDR>
<TLR>TLR${cnt}</TLR>
</CUS>
<XYZ>
<GRP>GRP2</GRP>
<HDR>ACCOUNTS</HDR>
<TLR>TLR${cnt}</TLR>
</XYZ>
</CONFIG>
Global_70.xml
<CONFIG>
<PARENT_SETTINGS>Global_layouts_100</PARENT_SETTINGS>
<PARENT_SETTINGS>Global_properties_100</PARENT_SETTINGS>
<LOOKUPS>
<MAP type="file">
<NAME>ACCT_TYPE_LOOKUP</NAME>
<PATH>${PATH}acct_type.csv</PATH>
<HEADERS>
<COLUMN>ACCT_TYPE</COLUMN>
<COLUMN>SOURCE_VALUE</COLUMN>
</HEADERS>
<KEYS>
<COLUMN>SOURCE_VALUE</COLUMN>
</KEYS>
</MAP>
</LOOKUPS>
</CONFIG>
local.xml
<CONFIG>
<PARENT_SETTINGS>Global_70</PARENT_SETTINGS>
<BATCH>
<CUS>
<SRCFILE type="csv" delimiter="|">/path/to/src_file</SRCFILE>
<OUTFILE>/path/to/out_file</OUTFILE>
<FIELDS>
<CUSTOMER>&CUSTOMER;</CUSTOMER>
<QTY default="0.0" col="23"></QTY>
<COST format="%.4f" col="21"></COST>
<FEE col="18"></FEE>
</FIELDS>
</CUS>
<XYZ>
<SRCFILE />
<OUTFILE />
<FIELDS>
<FIELD_1 />
<FIELD_2 />
<FIELD_3 />
<FIELD_4 />
<FIELD_5 />
</FIELDS>
</XYZ>
</BATCH>
</CONFIG>
Теперь, если бы программе дали local.xml для запуска и CUS в качестве аргумента для обработки, я бы хотел увидеть этот XML (или эквивалентную структуру данных perl):
<CONFIG>
<HDR>CUSTOMER</HDR>
<TLR>TLR${cnt}</TLR>
<SRCFILE type="csv" delimiter="|">/path/to/src_file</SRCFILE>
<OUTFILE>/path/to/out_file</OUTFILE>
<LOOKUPS>
<MAP type="file">
<NAME>ACCT_TYPE_LOOKUP</NAME>
<PATH>${PATH}acct_type.csv</PATH>
<HEADERS>
<COLUMN>ACCT_TYPE</COLUMN>
<COLUMN>SOURCE_VALUE</COLUMN>
</HEADERS>
<KEYS>
<COLUMN>SOURCE_VALUE</COLUMN>
</KEYS>
</MAP>
</LOOKUPS>
<CUS>
<FIELD foo="abs" format="%.4f" default="0.0" col="23">QTY</FIELD>
<FIELD default="" format="%.4f" col="21">COST</FIELD>
<FIELD default="0" format="%.2f">AMT</FIELD>
<FIELD default="1960-01-01" format="YYYMMDD">TRANDATE</FIELD>
<FIELD>ACCOUNT</FIELD>
<FIELD default="0">ACCT_TYPE</FIELD>
<FIELDS>
<CUSTOMER>&CUSTOMER;</CUSTOMER>
<QTY default="0.0" col="23"></QTY>
<COST format="%.4f" col="21"></COST>
<FEE col="18"></FEE>
</FIELDS>
</CUS>
</CONFIG>
И, если бы программе дали local.xml для запуска и XYZ в качестве аргумента для обработки, я бы хотел увидеть этот XML (или эквивалентную структуру данных perl):
<CONFIG>
<HDR>ACCOUNTS</HDR>
<TLR>TLR${cnt}</TLR>
<SRCFILE />
<OUTFILE />
<LOOKUPS>
<MAP type="file">
<NAME>ACCT_TYPE_LOOKUP</NAME>
<PATH>${PATH}acct_type.csv</PATH>
<HEADERS>
<COLUMN>ACCT_TYPE</COLUMN>
<COLUMN>SOURCE_VALUE</COLUMN>
</HEADERS>
<KEYS>
<COLUMN>SOURCE_VALUE</COLUMN>
</KEYS>
</MAP>
</LOOKUPS>
<XYZ>
<FIELD> 1 </FIELD>
<FIELD> 2 </FIELD>
<FIELD> 3 </FIELD>
<FIELDS>
<FIELD_1 />
<FIELD_2 />
<FIELD_3 />
<FIELD_4 />
<FIELD_5 />
</FIELDS>
</XYZ>
</CONFIG>
Вопрос
Каков наиболее эффективный способ объединения этих XML-документов?
Я могу сделать это сам с помощью структур данных, возвращаемых XML::Simple
или, может быть, есть другие инструменты XML, которые я должен использовать?
Я надеюсь, что мой вопрос достаточно ясен и не нуждается в образцах данных XML. Если вам нужно что-то увидеть, я могу опубликовать некоторые примеры.
Вкратце вопрос в том, как лучше всего объединить иерархию отдельных XML-документов?
2 ответа
Предисловие: я ничего не знаю о Perl, но один вариант - использовать XSLT, декларативный язык специального назначения для стилизации / преобразования документов XML.
И я знаю, что большинство языков, таких как PHP (в некоторой степени потомок Perl), Python, Java, C# и т. Д., Поддерживают библиотеки XML и аналогично XSLT-преобразованию. Итак, рассмотрите возможность применения процессора Perl XSLT, где вы используете файл XSLT для объединения документов (для которых вы можете указать конкретные узлы)
Используя ваши образцы данных, ниже таблицы стилей будут отображать ваши окончательные структуры XML для CUS и XYZ. Обязательно храните все производные XML-файлы в одном каталоге.
CUS VERSION
<?xml version="1.0" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="CONFIG">
<xsl:copy>
<xsl:copy-of select="document('Global_properties_100.xml')/CONFIG/CUS/HDR" />
<xsl:copy-of select="document('Global_properties_100.xml')/CONFIG/CUS/TLR" />
<xsl:copy-of select="BATCH/CUS/SRCFILE" />
<xsl:copy-of select="BATCH/CUS/OUTFILE" />
<xsl:copy-of select="document('Global_70.xml')/CONFIG/LOOKUPS" />
<CUS>
<xsl:copy-of select="document('Global_layouts_100.xml')/CONFIG/GRP1/*" />
<xsl:copy-of select="BATCH/CUS/FIELDS" />
</CUS>
</xsl:copy>
</xsl:template>
</xsl:transform>
XYZ ВЕРСИЯ
<?xml version="1.0" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="CONFIG">
<xsl:copy>
<xsl:copy-of select="document('Global_properties_100.xml')/CONFIG/XYZ/HDR" />
<xsl:copy-of select="document('Global_properties_100.xml')/CONFIG/XYZ/TLR" />
<xsl:copy-of select="BATCH/XYZ/SRCFILE" />
<xsl:copy-of select="BATCH/XYZ/OUTFILE" />
<xsl:copy-of select="document('Global_70.xml')/CONFIG/LOOKUPS" />
<CUS>
<xsl:copy-of select="document('Global_layouts_100.xml')/CONFIG/GRP2/*" />
<xsl:copy-of select="BATCH/XYZ/FIELDS" />
</CUS>
</xsl:copy>
</xsl:template>
</xsl:transform>
Я могу дать вам более конкретный пример с некоторыми примерами данных, но при приближении к этому я склонен использовать XML::Twig
,
В частности - XML::Twig
имеет встроенную поддержку cut
а также paste
так что вы можете создать новое дерево документов и сохранить нужные элементы в том порядке, в котором я хочу.
Что-то вроде этого:
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
my $twig = XML::Twig -> parse ( \*DATA );
my $newdoc = XML::Twig -> new ('pretty_print' => 'indented_a');
$newdoc -> set_root ( XML::Twig::Elt -> new ( 'new_root_here' ) );
$newdoc -> set_xml_version ('1.0');
$newdoc -> set_encoding('utf-8');
foreach my $value_elt ( $twig -> findnodes ( '//value' ) ) {
$value_elt -> cut;
$value_elt -> paste ( $newdoc -> root );
}
$newdoc -> print;
__DATA__
<root>
<value>fish</value>
<dont_copy>this thing</dont_copy>
</root>
(Есть еще один пример: как объединить данные из двух файлов XML в одну структуру?)