使用XSLT的XML到CSV - 分组节点

时间:2013-10-11 20:29:35

标签: xml xslt csv xpath

所以我有一个XML文件,我是从php curl响应生成的,然后转换为CSV,这样下面的每个mods元素都是一行。我在检查的答案here中使用样式表获得了一些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 个答案:

答案 0 :(得分:2)

执行此操作的一种方法是首先将XSLT更改为仅选择没有具有相同子名称的previous-sibling的元素(即选择每个组中“first”的元素)

<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 来取消递归模板,当选择多个元素时,该属性将具有“separator”属性。

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