XML в CSV с XSLT - Группировка узлов

Итак, у меня есть XML-файл, сгенерированный из ответа php curl, который затем преобразуется в CSV, так что каждый элемент mods ниже представляет собой одну строку. У меня есть несколько CSV с использованием таблицы стилей в проверенном ответе здесь, но это не совсем то, что я пытаюсь сделать.

Мой XML (упрощенный):

<xml>
<mods xmlns="http://www.loc.gov/mods/">
      <typeOfResource>StillImage</typeOfResource>
      <titleInfo ID="T-1">
        <title>East Bay Street</title>
      </titleInfo>
      <subject ID="SBJ-2">
        <topic>Railroads</topic>
      </subject>
      <subject ID="SBJ-3">
        <geographic>Low Country</geographic>
      </subject>
      <subject ID="SBJ-4">
        <geographic>Charleston (S.C.)</geographic>
      </subject>
      <subject ID="SBJ-7">
        <hierarchicalGeographic>
          <county>Charleston County (S.C.)</county>
        </hierarchicalGeographic>
      </subject>
      <physicalDescription>
        <form>Images</form>
      </physicalDescription>
      <note>Caption: &apos;War Views. No.179.  Ruins of the Northeastern Railway Depot, Charleston.&apos;  This is a stereograph image which measures 3 1/2&quot; X 7&quot;.  Date assumed to be 1865.</note>
      <originInfo>
        <dateCreated>1865</dateCreated>
      </originInfo>
      <location>
        <physicalLocation>The Charleston Museum Archives</physicalLocation>
      </location>
      <relatedItem type="host">
        <titleInfo>
          <title>Charleston Museum Civil War Photographs</title>
        </titleInfo>
      </relatedItem>
    </mods>

   <mods>
     more nodes...
   </mods>
</xml>

Мой текущий XSL из стека пост выше:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>

<xsl:strip-space elements="*" />

<xsl:template match="/*/child::*">
<xsl:for-each select="child::*">
<xsl:if test="position() != last()"><xsl:value-of select="normalize-space(.)"/>,        </xsl:if>
<xsl:if test="position()  = last()"><xsl:value-of select="normalize-space(.)"/>    <xsl:text>&#xD;</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>

 </xsl:stylesheet>

Это выдает CSV, где каждый элемент MODS представляет собой одну строку, а каждый дочерний элемент представляет собой значение в этой строке через запятую. Можно ли изменить XSL так, чтобы каждый элемент MODS представлял собой одну строку, но значения совпадающих дочерних элементов группировались? Что-то вроде:

StillImage,East Bay Street,Railroads,**Low County;Charleston (S.C.)**,Charleston County (S.C.), Images

.......и так далее.

Итак, когда узлы (например, несколько объектов -> географические записи) совпадают, они группируются и разделяются точкой с запятой, а не принимают несколько значений, разделенных запятыми? Надеюсь, у меня есть смысл. Спасибо!

1 ответ

Решение

Один из способов сделать это - сначала изменить свой XSLT, чтобы выбрать только те элементы, которые не имеют предшествующего брата с тем же дочерним именем (т.е. выбрать элементы, которые являются "первыми" в каждой группе)

<xsl:for-each select="*[name(*) != name(preceding-sibling::*[1]/*)]">

Затем вы можете определить переменную, чтобы получить следующий брат, если (и только если) он имеет то же имя, так что вы можете проверить, действительно ли текущий элемент находится в группе более 1.

<xsl:variable name="nextWithSameName" 
              select="following-sibling::*[1][name(*)=name(current()/*)]"/>
<xsl:if test="$nextWithSameName">**</xsl:if>

(Я не уверен, что вы действительно хотели ** в окончательных результатах, или они просто предназначены для выделения группы! Я держу их в своем примере, но, очевидно, будет достаточно легко удалить соответствующие строки кода).

Чтобы сгруппировать следующих братьев и сестер с одинаковыми именами, вы можете вызвать рекурсивный шаблон для первого следующего брата

<xsl:apply-templates select="$nextWithSameName" mode="group"/>

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

<xsl:template match="*" mode="group">
   <xsl:text>;</xsl:text>
   <xsl:value-of select="normalize-space(.)"/>
   <xsl:apply-templates select="following-sibling::*[1][name(*)=name(current()/*)]" />
</xsl:template>

Попробуйте следующее XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="text" encoding="iso-8859-1"/>
   <xsl:strip-space elements="*"/>

   <xsl:template match="/*/*">
      <xsl:for-each select="*[name(*) != name(preceding-sibling::*[1]/*)]">
         <xsl:variable name="nextWithSameName" select="following-sibling::*[1][name(*)=name(current()/*)]"/>
         <xsl:if test="position() &gt; 1">,    </xsl:if>
         <xsl:if test="$nextWithSameName">**</xsl:if>
         <xsl:value-of select="normalize-space(.)"/>
         <xsl:apply-templates select="$nextWithSameName" mode="group"/>
         <xsl:if test="$nextWithSameName">**</xsl:if>
      </xsl:for-each>
      <xsl:text>&#xD;</xsl:text>
   </xsl:template>

   <xsl:template match="*" mode="group">
      <xsl:text>;</xsl:text>
      <xsl:value-of select="normalize-space(.)"/>
      <xsl:apply-templates select="following-sibling::*[1][name(*)=name(current()/*)]" />
   </xsl:template>
</xsl:stylesheet>

Теперь, если бы вы могли использовать XSLT 2.0, все стало намного, намного проще, так как вы могли бы использовать конструкцию xsl:for-each-group, которая, помимо прочего, поставляется с операцией для "соседнего с группой". И вы также можете покончить с рекурсивным шаблоном, используя улучшенный xsl:value-of, который будет иметь свойство "разделитель" для использования при выборе нескольких элементов.

Для XSLT 2.0 также должно работать следующее

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="iso-8859-1"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*/*">
        <xsl:for-each-group select="*" group-adjacent="name(*)">
            <xsl:if test="position() &gt; 1">,    </xsl:if>
            <xsl:if test="current-group()[2]">**</xsl:if>
            <xsl:value-of select="current-group()" separator=";" />
            <xsl:if test="current-group()[2]">**</xsl:if>
        </xsl:for-each-group >
        <xsl:text>&#xD;</xsl:text>
    </xsl:template>
</xsl:stylesheet>
Другие вопросы по тегам