2009-12-23 2 views
2

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

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, чтобы joing их:

<?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?

+0

Ваш пример XML не показан ... –

+0

Я имею в виду, что XML присоединиться к ним - не file1 и file2 –

+0

Спасибо! Сейчас исправлено –

ответ

1

(Смотрите мой комментарий на «один-ходовым Объединить» на 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 путем параметризации URL-адресов для загружаемых 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> 
+0

Я думаю, что вам не хватает определения переменных узлов. Я предполагаю, что он определен как: С этим изменением я получаю \t <БЕЗОПАСНОСТИ CUSIP =" CUSIP1 "> \t \t <идентификатор клиента =" M1 "/> [...] <идентификатор клиента =" B3" /> \t \t < CUSIP SECURITY = "CUSIP3"> \t \t <КЛИЕНТ ID = "M4" /> [...] <КЛИЕНТ ID = "M6" /> \t \t <БЕЗОПАСНОСТИ CUSIP = "CUSIP1"> \t \t <идентификатор клиента = "M1" /> [...] <идентификатор клиента = "B3" /> \t \t <БЕЗОПАСНОСТИ CUSIP = "CUSIP2"> \t \t <КЛИЕНТ ID = "B4" /> [...] <КЛИЕНТ ID = "B6" /> \t

+0

Так дублирует ценные бумаги, которые появляются в обоих множества, ти всех своих детей. Поэтому я все еще что-то упускаю. В любом случае, спасибо за вашу помощь! –

+0

Я забыл знак доллара при создании переменных узлов ... моя ошибка С этим изменением он работает. Спасибо –

1

Функция 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

К сожалению, мне не разрешено изменять версию XSL, так как это изменение относится к производственной системе. Но решение, которое дал мне Роланд, хотя и не очень эффективно, работает; поэтому я буду использовать это. Благодарим за ваше предложение. –

1

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

<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> 
+0

Ну, я хотел бы сделать это максимально гибким, потому что теперь мне нужно объединить только два файла, но в будущем у меня будет n файлов. Как вы сказали, есть еще кое-что, о чем я не упоминал. Структура файла более сложная, поскольку узлы-клиенты имеют дочерние элементы, но поскольку мне нужно скопировать все узлы, с дочерними элементами и атрибутами, для этой проблемы это не имеет особого значения, а именно для создания узла SECURITY для каждой отдельной безопасности во всех файлах, а затем добавить все дочерние элементы для этой безопасности во все файлы. Так что это действительно соединение. –

0

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

<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> 

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

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

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

+0

Хосе, можешь ли ты формально принять мой первый ответ? Вы должны быть в состоянии «проверить» его, чтобы появилась зеленая галочка. Если вы это сделаете, я получаю очки репутации, и, кроме того, вопрос будет отображаться как «решенный». TIA, роланд. –

Смежные вопросы