Разделить XML-файл по количеству записей / пороговое значение и скопировать заголовок — XSLT 1.0

#xslt #filesplitting

#xslt #разделение файлов

Вопрос:

У меня есть следующая структура XML

 <?xml version="1.0" encoding="UTF-8"?>
<ExportData>
<TransportHeader>
    <Timestamp>2011-01-16 06:00:33</Timestamp>
    <From>
        <Name>DynamicExport</Name>
        <Version>1.</Version>
    </From>
    <MessageId>d7b5c5b69a83</MessageId>
</TransportHeader>
<ExportConfig>
    <DateTimeFormat>yyyy-MM-dd HH:mm:ss</DateTimeFormat>
    <DecimalSymbol>.</DecimalSymbol>
</ExportConfig>
<DataSet> 
    <Tables>
        <Table>
            <RH>...</RH>
            <Rows>  
                <R>Data1</R>
                <R>Data2</R>
                <R>Data3</R>
                <R>Data4</R>
                <R>Data5</R>
            </Rows>
        </Table>
    </Tables>
</DataSet>
</ExportData>
  

и мне нужно разделить файл в зависимости от количества <R> элементов.
Если имеется более 3 <R> элементов, необходимо сгенерировать второй выходной файл. Обоим файлам также требуется информация о заголовке.

Я придумал этот XSLT:

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:redirect="http://xml.apache.org/xalan/redirect"
extension-element-prefixes="redirect"
exclude-result-prefixes="xd"
version="1.0">

<xsl:template match="/">
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="text() | @*"/>

<xsl:template match="Rows" name="Rows"> 
    <Rows>    
        <xsl:for-each select="R">
            <xsl:variable name="filename1" select="concat('output1','.xml')"/>
            <xsl:variable name="filename2" select="concat('output2','.xml')"/>
            <xsl:variable name="nodePosition" select="position()" />
            <xsl:if test="$nodePosition amp;< 3">
                <redirect:write select="$filename1">
                    <xsl:copy-of select="." />
                </redirect:write>
            </xsl:if>
            <xsl:if test="$nodePosition = 3 or $nodePosition amp;> 3">
                <redirect:write select="$filename2">
                        <xsl:copy-of select="." />
                </redirect:write> 
            </xsl:if>
        </xsl:for-each>  
    </Rows>        
</xsl:template>
</xsl:stylesheet>
  

Но два выходных файла, которые генерируются, содержат только «Data2» и «Data5».
Не могли бы вы помочь мне выяснить, почему отсутствуют другие 3 элемента данных?
И как я могу добавить данные заголовка?

Для заголовка я придумал этот XSLT:

 <xsl:template match="//Rows">
    <xsl:apply-templates select="@*|Rows"/>
</xsl:template>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>
  

который работает, когда я применяю его к упомянутому XML. Но я не смог объединить 2 XSLT-файла — результат просто перепутывается.

Комментарии:

1. Предоставленный XSLT-код даже не является хорошо сформированным XML. redirect: Префикс не привязан ни к какому пространству имен. Пожалуйста, исправьте. Похоже, вы используете какой-то элемент расширения для создания нескольких выходных данных. Какую библиотеку расширений вы используете?

2. Также в <xsl:stylesheet> инструкции отсутствует определение пространства имен и по крайней мере еще один необходимый атрибут. Пожалуйста, предоставьте код, который можно выполнить — для целей воспроизведения и чтобы не запутать ваших читателей.

3. Здравствуйте, Димитр, спасибо за ваш ответ. Я исправил свой ошибочный код. Я использую XALAN в качестве процессора, но я не знаю библиотеку расширений.

4. @Peter: Я не использую Xalan. Почему бы вам не рассмотреть EXSLT и его <exslt:document> элемент расширения? Если я хорошо помню, Xalan реализует EXSLT. exslt.org/exsl/elements/document/index.html . Или использовать XSLT 2.0, где <xsl:result-document> инструкция является стандартной?

5. @Peter: Проверьте мой ответ на полное решение.

Ответ №1:

Это преобразование показывает, как выполнить разделение. Это оставлено в качестве упражнения для адаптации к вашим потребностям:

 <xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="R[position() mod 3 =1]">
  <RGroup>
   <xsl:copy-of select=
    ".|following-sibling::R[not(position() > 2)]"/>
  </RGroup>
 </xsl:template>

 <xsl:template match="R"/>
</xsl:stylesheet>
  

При применении к этому XML-документу (фрагменту первоначально предоставленного):

 <Rows>
    <R>Data1</R>
    <R>Data2</R>
    <R>Data3</R>
    <R>Data4</R>
    <R>Data5</R>
</Rows>
  

произведено требуемое разделение:

 <Rows>
   <RGroup>
      <R>Data1</R>
      <R>Data2</R>
      <R>Data3</R>
   </RGroup>
   <RGroup>
      <R>Data4</R>
      <R>Data5</R>
   </RGroup>
</Rows>
  

Комментарии:

1. Здравствуйте, Димитр, спасибо за вашу помощь. Я адаптирую это к своим потребностям, но общая логика работает. Я также попытаюсь выяснить проблему с данными заголовка. С наилучшими пожеланиями, Питер

2. Это решение также отлично работает без копирования данных заголовка. Это кажется мне не таким сложным и, следовательно, также более надежным?

3. @Peter: Да, обратите внимание, что это решение является более общим, чем принятый в настоящее время ответ.

Ответ №2:

Эта таблица стилей:

 <xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:redirect="http://xml.apache.org/xalan/redirect"
 extension-element-prefixes="redirect">
    <xsl:strip-space elements="*"/>
    <xsl:param name="pLimit" select="3"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:param name="pGroup"/>
        <xsl:copy>
            <xsl:apply-templates select="node()|@*">
                <xsl:with-param name="pGroup" select="$pGroup"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/">
        <xsl:for-each select="//R[position() = 1 or position() = $pLimit   1]">
            <redirect:write select="concat('output',position(),'.xml')">
                <xsl:apply-templates select="/node()">
                    <xsl:with-param name="pGroup" select="position()-1"/>
                </xsl:apply-templates>
            </redirect:write>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="R">
        <xsl:param name="pGroup"/>
        <xsl:if test="position() > $pLimit = $pGroup">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
  

output1.xml :

 <ExportData>
    <TransportHeader>
        <Timestamp>2011-01-16 06:00:33</Timestamp>
        <From>
            <Name>DynamicExport</Name>
            <Version>1.</Version>
        </From>
        <MessageId>d7b5c5b69a83</MessageId>
    </TransportHeader>
    <ExportConfig>
        <DateTimeFormat>yyyy-MM-dd HH:mm:ss</DateTimeFormat>
        <DecimalSymbol>.</DecimalSymbol>
    </ExportConfig>
    <DataSet>
        <Tables>
            <Table>
                <RH>...</RH>
                <Rows>
                    <R>Data1</R>
                    <R>Data2</R>
                    <R>Data3</R>
                </Rows>
            </Table>
        </Tables>
    </DataSet>
</ExportData>
  

output2.xml :

 <ExportData>
    <TransportHeader>
        <Timestamp>2011-01-16 06:00:33</Timestamp>
        <From>
            <Name>DynamicExport</Name>
            <Version>1.</Version>
        </From>
        <MessageId>d7b5c5b69a83</MessageId>
    </TransportHeader>
    <ExportConfig>
        <DateTimeFormat>yyyy-MM-dd HH:mm:ss</DateTimeFormat>
        <DecimalSymbol>.</DecimalSymbol>
    </ExportConfig>
    <DataSet>
        <Tables>
            <Table>
                <RH>...</RH>
                <Rows>
                    <R>Data4</R>
                    <R>Data5</R>
                </Rows>
            </Table>
        </Tables>
    </DataSet>
</ExportData>
  

Примечание: Это простое решение для не вложенных таблиц. Использование extension-element-prefixes атрибута.

Комментарии:

1. Привет, Алехандро, это решение работает отлично. Спасибо! Я посмотрю на «extension-element-prefixes» — я никогда раньше с этим не работал. С наилучшими пожеланиями, Питер