Объединить XML-файлы с одинаковой структурой и разными данными - PullRequest
2 голосов
/ 23 декабря 2009

Я пытаюсь объединить два файла, которые имеют одинаковую структуру, и некоторые общие данные. Таким образом, если узел имеет одинаковое имя в обоих файлах, следует создать новый узел с дочерними элементами обоих исходных узлов. Исходные файлы следующие:

file1.xml
<?xml version='1.0' encoding='UTF-8'?>
<BROADRIDGE>
    <SECURITY CUSIP='CUSIP1' DESCRIPT='CUSIP1'>
        <CUSTOMER ID='M1'/>
        <CUSTOMER ID='M2'/>
        <CUSTOMER ID='M3'/>
    </SECURITY>
    <SECURITY CUSIP='CUSIP3' DESCRIPT='CUSIP3'>
        <CUSTOMER ID='M4'/>
        <CUSTOMER ID='M5'/>
        <CUSTOMER ID='M6'/>
    </SECURITY>
</BROADRIDGE>

file2.xml
<?xml version='1.0' encoding='UTF-8'?>
<BROADRIDGE>
    <SECURITY CUSIP='CUSIP1' DESCRIPT='CUSIP1'>
        <CUSTOMER ID='B1'/>
        <CUSTOMER ID='B2'/>
        <CUSTOMER ID='B3'/>
    </SECURITY>
    <SECURITY CUSIP='CUSIP2' DESCRIPT='CUSIP2'>
        <CUSTOMER ID='B4'/>
        <CUSTOMER ID='B5'/>
        <CUSTOMER ID='B6'/>
    </SECURITY>
</BROADRIDGE>

Идея состоит в том, чтобы создать новый XML-файл с той же структурой, который содержит информацию из обоих файлов, объединяя те узлы SECURITY, которые имеют одинаковый атрибут CUSIP. В этом случае результат должен быть следующим:

<?xml version="1.0" encoding="UTF-8"?>
<BROADRIDGE>
    <SECURITY CUSIP="CUSIP1">
        <CUSTOMER ID="M1"/>
        <CUSTOMER ID="M2"/>
        <CUSTOMER ID="M3"/>
        <CUSTOMER ID='B1'/>
        <CUSTOMER ID='B2'/>
        <CUSTOMER ID='B3'/>
    </SECURITY>
    <SECURITY CUSIP="CUSIP3">
        <CUSTOMER ID="M4"/>
        <CUSTOMER ID="M5"/>
        <CUSTOMER ID="M6"/>
    </SECURITY>
    <SECURITY CUSIP="CUSIP2">
        <CUSTOMER ID="B4"/>
        <CUSTOMER ID="B5"/>
        <CUSTOMER ID="B6"/>
    </SECURITY>
</BROADRIDGE>

Я определил фоллинг-xml для присоединения к ним:

<?xml version="1.0"?>                                  
<MASTERFILE>
   <FILE>\file1.xml</FILE>
   <FILE>\file2.xml</FILE>
</MASTERFILE>

И следующий XSL для слияния:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/MASTERFILE">
        <BROADRIDGE>
            <xsl:variable name="securities" select="document(FILE)/BROADRIDGE/SECURITY"/>
            <xsl:for-each select="$securities">
                <xsl:if test="generate-id(.) = generate-id($securities[@CUSIP=current()/@CUSIP])">
                    <SECURITY>
                        <xsl:attribute name="CUSIP" ><xsl:value-of select="@CUSIP"/></xsl:attribute>
                        <xsl:for-each select="CUSTOMER">
                            <CUSTOMER>
                                <xsl:attribute name="ID" ><xsl:value-of select="@ID"/></xsl:attribute>
                            </CUSTOMER>
                        </xsl:for-each>
                    </SECURITY>
                </xsl:if>
            </xsl:for-each>
        </BROADRIDGE>
    </xsl:template>
</xsl:stylesheet>

Но я получаю следующее:

<?xml version="1.0" encoding="UTF-8"?>
<BROADRIDGE>
    <SECURITY CUSIP="CUSIP1">
        <CUSTOMER ID="M1"/>
        <CUSTOMER ID="M2"/>
        <CUSTOMER ID="M3"/>
    </SECURITY>
    <SECURITY CUSIP="CUSIP3">
        <CUSTOMER ID="M4"/>
        <CUSTOMER ID="M5"/>
        <CUSTOMER ID="M6"/>
    </SECURITY>
    <SECURITY CUSIP="CUSIP2">
        <CUSTOMER ID="B4"/>
        <CUSTOMER ID="B5"/>
        <CUSTOMER ID="B6"/>
    </SECURITY>
</BROADRIDGE>

Любая идея, почему он не объединяет ЗАКАЗЧИКОВ из обоих файлов для БЕЗОПАСНОСТИ с CUSIP = CUSIP1

Ответы [ 4 ]

1 голос
/ 23 декабря 2009

(См. Мой комментарий по поводу "одностороннего слияния" на OP.) Вот мое (очень неэффективное) решение проблемы слияния:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="set1" select="document('file1.xml')/BROADRIDGE/SECURITY"/>
    <xsl:variable name="set2" select="document('file2.xml')/BROADRIDGE/SECURITY"/>

    <xsl:template match="/">
        <BROADRIDGE>
            <!-- walk over all relevant nodes -->
            <xsl:for-each select="$set1 | $set2">
                <xsl:variable name="position" select="position()"/>
                <xsl:variable name="cusip" select="@CUSIP"/>
                <!-- if we see this CUSIP for the first time, --> 
                <xsl:if test="count($nodes[position() &lt; $position][@CUSIP = $cusip])=0">
                    <SECURITY>                            
                        <xsl:attribute name="CUSIP"><xsl:value-of select="$cusip"/></xsl:attribute>
                        <!-- copy nodes from both sets with matching attribute -->
                        <xsl:copy-of select="$set1[@CUSIP = $cusip]/*"/>
                        <xsl:copy-of select="$set2[@CUSIP = $cusip]/*"/>
                    </SECURITY>
                </xsl:if>
            </xsl:for-each>
        </BROADRIDGE>
    </xsl:template>
</xsl:stylesheet>

Обратите внимание, что таблица стилей не предполагает какого-либо конкретного документа - она ​​просто загружает два файла как переменные. Можно улучшить дизайн xslt, задав параметры для загружаемых XML-документов

Чтобы применить объединение к нескольким документам, вы можете создать файл, скажем, master.xml, в котором перечислены все файлы для обработки следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="merge.xslt"?>
<files>
  <file>file1.xml</file>
  <file>file2.xml</file>
  ...
  <file>fileN.xml</file>    
</files>

В file1.xml у меня есть это:

<?xml version='1.0' encoding='UTF-8'?>
<BROADRIDGE>
  <SECURITY CUSIP='CUSIP1' DESCRIPT='CUSIP1'>
    <CUSTOMER ID='M1'/>
    <CUSTOMER ID='M2'/>
    <CUSTOMER ID='M3'/>
  </SECURITY>
  <SECURITY CUSIP='CUSIP3' DESCRIPT='CUSIP3'>
    <CUSTOMER ID='M4'/>
    <CUSTOMER ID='M5'/>
    <CUSTOMER ID='M6'/>
  </SECURITY>
</BROADRIDGE>

В file2.xml у меня есть это:

<?xml version='1.0' encoding='UTF-8'?>
<BROADRIDGE>
  <SECURITY CUSIP='CUSIP1' DESCRIPT='CUSIP1'>
    <CUSTOMER ID='B1'/>
    <CUSTOMER ID='B2'/>
    <CUSTOMER ID='B3'/>
  </SECURITY>
  <SECURITY CUSIP='CUSIP2' DESCRIPT='CUSIP2'>
    <CUSTOMER ID='B4'/>
    <CUSTOMER ID='B5'/>
    <CUSTOMER ID='B6'/>
  </SECURITY>
</BROADRIDGE>

merge.xslt является модифицированной версией более ранней версии, которая теперь может обрабатывать различное количество файлов (файлов, перечисленных в master.xml):

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <xsl:call-template name="merge-files"/>
</xsl:template>

<!-- loop through file names, load documents -->
<xsl:template name="merge-files">
  <xsl:param name="files" select="/files/file/text()"/>
  <xsl:param name="num-files" select="count($files)"/>
  <xsl:param name="curr-file" select="0"/>
  <xsl:param name="set" select="/*[0]"/>
  <xsl:choose> <!-- if we still have files, concat them to $set -->
    <xsl:when test="$curr-file &lt; $num-files">
      <xsl:call-template name="merge-files">
        <xsl:with-param name="files" select="$files"/>
        <xsl:with-param name="num-files" select="$num-files"/>
        <xsl:with-param name="curr-file" select="$curr-file + 1"/>
        <xsl:with-param name="set" select="$set | document($files[$curr-file+1])/BROADRIDGE/SECURITY"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise> <!-- no more files, start merging. -->
      <xsl:call-template name="merge">
        <xsl:with-param name="nodes" select="$set"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<!-- perform the actual merge -->
<xsl:template name="merge">
  <xsl:param name="nodes"/>
  <BROADRIDGE>
    <xsl:for-each select="$nodes"> <!-- look at all possible nodes to merge -->
      <xsl:variable name="position" select="position()"/>
      <xsl:variable name="cusip" select="@CUSIP"/>

      <!-- when we encounter this id for the 1st time -->
      <xsl:if test="count($nodes[position() &lt; $position][@CUSIP = $cusip])=0"> 
        <SECURITY>
          <xsl:attribute name="CUSIP"><xsl:value-of select="$cusip"/></xsl:attribute>
          <!-- copy all node data related to this cusip here -->
          <xsl:for-each select="$nodes[@CUSIP = $cusip]">
            <xsl:copy-of select="*"/>
          </xsl:for-each>
        </SECURITY>
      </xsl:if>
    </xsl:for-each>
  </BROADRIDGE>
</xsl:template>

</xsl:stylesheet>

Запуск этого дает мне этот вывод:

<BROADRIDGE>
  <SECURITY CUSIP="CUSIP1">
    <CUSTOMER ID="M1"/>
    <CUSTOMER ID="M2"/>
    <CUSTOMER ID="M3"/>
    <CUSTOMER ID="B1"/>
    <CUSTOMER ID="B2"/>
    <CUSTOMER ID="B3"/>
  </SECURITY>
  <SECURITY CUSIP="CUSIP3">
    <CUSTOMER ID="M4"/>
    <CUSTOMER ID="M5"/>
    <CUSTOMER ID="M6"/>
  </SECURITY>
  <SECURITY CUSIP="CUSIP2">
    <CUSTOMER ID="B4"/>
    <CUSTOMER ID="B5"/>
    <CUSTOMER ID="B6"/>
  </SECURITY>
</BROADRIDGE>
1 голос
/ 27 декабря 2009

Либо вы делаете это слишком сложным, либо есть другие аспекты этой проблемы, о которых вы не упомянули:

<xsl:variable name="file1" select="document(/MASTERFILE/FILE[1])"/>
<xsl:variable name="file2" select="document(/MASTERFILE/FILE[2])"/>

<xsl:template match="/">
   <BROADRIDGE>
      <xsl:apply-templates select="$file1/BROADRIDGE/SECURITY"/>
      <xsl:copy-of select="$file2/BROADRIDGE/SECURITY[not(@CUISP=$file1/BROADRIDGE/SECURITY/@CUISP)]"/>
   </BROADRIDGE>
</xsl:template>

<xsl:template match="SECURITY">
   <SECURITY>
      <xsl:copy-of select="*"/>
      <xsl:copy-of select="$file2/BROADRIDGE/SECURITY[@CUSIP=current()/@CUSIP]/*"/>
   </SECURITY>
</xsl:template>
1 голос
/ 23 декабря 2009

Функция generate-id () гарантированно будет разной для каждого узла, который участвует в данном преобразовании. Как вы называете это в разных документах, они не будут одинаковыми

Вам следует сравнивать строковые значения CUSIPS в документах, а не их идентификаторы.

Если вы можете использовать xslt 2.0 (что намного лучше, чем 1), это будет работать

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes"/>
        <xsl:template match="/MASTERFILE">
                <BROADRIDGE>
                        <xsl:variable name="securities" select="document(FILE)/BROADRIDGE/SECURITY"/>
                        <xsl:for-each select="distinct-values($securities/@CUSIP)">
                                <SECURITY>
                                        <xsl:attribute name="CUSIP">
                                                <xsl:value-of select="."/>
                                        </xsl:attribute>

                                        <xsl:for-each select="distinct-values($securities[@CUSIP = 'CUSIP1']/CUSTOMER/@ID)">
                                                <CUSTOMER>
                                                  <xsl:attribute name="ID">
                                                  <xsl:value-of select="."/>
                                                  </xsl:attribute>
                                                </CUSTOMER>
                                        </xsl:for-each>
                                </SECURITY>
                        </xsl:for-each>
                </BROADRIDGE>
        </xsl:template>
</xsl:stylesheet>
0 голосов
/ 06 января 2010

Роланд, спасибо за ваши примеры. На основании первого отправленного вами кода я разработал следующий шаблон:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="nodes" select="document(/MASTERFILE/FILE)/BROADRIDGE/SECURITY"/>
    <xsl:template match="/">
        <BROADRIDGE>
            <!-- walk over all relevant nodes -->
            <xsl:for-each select="$nodes">
                <xsl:variable name="position" select="position()"/>
                <xsl:variable name="cusip" select="@CUSIP"/>
                <!-- if we see this CUSIP for the first time, --> 
                <xsl:if test="count($nodes[position() &lt; $position][@CUSIP = $cusip])=0">
                    <SECURITY>                            
                        <xsl:attribute name="CUSIP"><xsl:value-of select="$cusip"/></xsl:attribute>
                        <xsl:attribute name="DESCRIPT"><xsl:value-of select="@DESCRIPT"/></xsl:attribute>
                        <!-- copy nodes from both sets with matching attribute -->
                        <xsl:copy-of select="$nodes[@CUSIP = $cusip]/*"/>
                    </SECURITY>
                </xsl:if>
            </xsl:for-each>
        </BROADRIDGE>
    </xsl:template>

Я просто предоставляю функции документа список файлов, поэтому он создает набор узлов со всеми узлами SECURITY из всех файлов. Когда я применяю это к следующему xml

<?xml version="1.0"?>
<MASTERFILE>
   <FILE>\file1.xml</FILE>
   <FILE>\file2.xml</FILE>
   <FILE>\file3.xml</FILE>
</MASTERFILE>

Работает отлично. Спасибо за ваши образцы

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...