基于 csv 文件以数组作为值的 XSLT 构建映射

XSLT build map with array as a value based on csv file

我想基于csv文件构建地图。

地图声明:

<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">

Csv 文件:

key1;value1
key1;value2
key2;value3

所以地图应该由两个元素组成: key1 => 数组 ['value1', 'value2'] key2 => 数组 ['value3']

我试过像这样创建地图:

<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
    <xsl:map>
        <xsl:if test="unparsed-text-available($csv-file, $csv-encoding)">
            <xsl:variable name="csv" select="unparsed-text($csv-file, $csv-encoding)"/>
            <xsl:analyze-string select="$csv" regex="\r\n?|\n">
                <xsl:non-matching-substring>
                    <xsl:variable name="row" select="tokenize(., '\t')"/>
                    <xsl:variable name="key" select="$row[1]"/>
                    <xsl:variable name="array_element" select="$row[2]"/>
                    <xsl:map-entry key="$key" select="$array_element"/>
                </xsl:non-matching-substring>
            </xsl:analyze-string>
        </xsl:if>
    </xsl:map>
</xsl:variable>

但我找不到合并地图条目的方法。

我的第二种方法是先声明地图:

<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
    <xsl:map/>
</xsl:variable>

然后我尝试根据 csv 文件内容填充它,如下所示:

<xsl:if test="unparsed-text-available($csv-file, $csv-encoding)">
    <xsl:variable name="csv" select="unparsed-text($csv-file, $csv-encoding)" />
    <xsl:analyze-string select="$csv" regex="\r\n?|\n">
        <xsl:non-matching-substring>
            <xsl:variable name="row" select="tokenize(., '\t')"/>
            <xsl:variable name="key" as="xs:string" select="$row[1]"/>
            <xsl:variable name="value" as="xs:string" select="$row[2]"/>            
            <xsl:choose>
                <xsl:when test="map:contains($myMap, $key)">
                    <xsl:variable name="valueArray" select="map:get($myMap,$key)"/>
                    <xsl:sequence select="array:append($valueArray, $value)" />
                    <xsl:sequence select="map:put($myMap, $key, $valueArray) />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:variable name="valueArray" as="array(xs:string)" select="[]"/>
                    <xsl:sequence select="array:append($valueArray, $value)" />
                    <xsl:sequence select="map:put($myMap, $key, $valueArray) />
                </xsl:otherwise>
            </xsl:choose>
        </xsl:non-matching-substring>
    </xsl:analyze-string>
</xsl:if>

是否可以从模板调用 array:append 和 map:put 方法?

我认为,鉴于您使用 XSLT 3,您可以将其视为分组问题,您可以将 CSV 中的行分组到 substring-before(., ';'):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="csv-string" as="xs:string">key1;value1
key1;value2
key2;value3</xsl:param>

  <xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
      <xsl:map>
          <xsl:for-each-group select="tokenize($csv-string, '\r?\n')[normalize-space()]" group-by="substring-before(., ';')">
              <xsl:map-entry key="current-grouping-key()" select="array{ current-group()!tokenize(substring-after(., ';'), ';') }"/>
          </xsl:for-each-group>
      </xsl:map>
  </xsl:variable>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="json" indent="yes" />

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:sequence select="$myMap"/>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/nc4NzRb

对于示例,我已经内联了您的 CSV 中的内容,但您也可以使用 <xsl:for-each-group select="unparsed-text-lines('file.csv')" group-by="substring-before(., ';')"> 而不是从文件加载。

至于构建和合并地图,如果您不需要 array(xs:string) 作为地图值但可以接受一系列字符串,您可以使用 map:merge 函数和一个选项合并同一键的值:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="csv-string" as="xs:string">key1;value1
key1;value2
key2;value3</xsl:param>

  <xsl:variable name="myMap" as="map(xs:string, xs:string*)"
     select="map:merge(
               tokenize($csv-string, '\r?\n')[normalize-space()]
               !
               (
                 let $values := tokenize(., ';')
                 return map { head($values) : tail($values) }
               ),
               map { 'duplicates' : 'combine' }
            )"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="adaptive" indent="yes" />

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:sequence select="$myMap"/>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/nc4NzRb/1