用于将XML转换为CSV的通用XSLT - 几乎存在,但卡住了

时间:2016-11-12 18:54:23

标签: xslt

我从这些论坛收集了这个XSLT的点点滴滴。我试图通过指定应包含在CSV文件中的节点的路径来完全创建一个可用于将XML转换为CSV的单一通用XSLT。

我有三件事情,经过大约10个小时的捣乱,我仍然无法弄清楚。

  1. 我想迭代 csv:columns 中命名的每个。在每次迭代期间,我需要提取并存储的text()。我认为这是迭代的方式,但我想确保:

    <xsl:for-each select="document('')/*/csv:columns/*">
    
  2. 一旦我从获得了text(),我需要将它放入 columnname 变量中,使其在以下情况下起作用与 getNodeValue 一起使用。

  3. 我无法使用变量设置 columnname 。如果我没有对值进行硬编码(由撇号包围),我无法使其工作。这就是为什么我在代码中有以下行:

         <xsl:variable name="columnname" select="'location/city'" />
    
    1. 我想将 getNodeValue 的结果传递给 quotevalue ,以便正确引用结果。
    2. XSLT:

      <?xml version="1.0"?>
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv" xpath-default-namespace="http://nowhere/" >
      <xsl:output method="text" encoding="utf-8" />
      <xsl:strip-space elements="*" />
      
      <xsl:variable name="delimiter" select="','" />
      
      <csv:columns>
          <column>title</column>
          <column>location/city</column>
      </csv:columns>
      
      <xsl:template match="job">
         <xsl:value-of select="concat(@id, ',')"/>
      
         <!-- #1 I WANT TO LOOP THROUGH ALL OF THE CSV COLUMNS HERE -->
      
         <!-- #2 How do I put the text into the variable 'columnname' variable so that it works with getNodeValue? -->
         <xsl:variable name="columnname" select="'location/city'" />
         <xsl:variable name="vXpathExpression" select="$columnname"/>
      
         <xsl:call-template name="getNodeValue">
             <xsl:with-param name="pExpression" select="$vXpathExpression"/>
         </xsl:call-template>
      
         <!-- #3 After getNodeValue gets the value, I want to send that value into 'quotevalue' -->
      
         <xsl:text>&#xa;</xsl:text>
      </xsl:template>
      
      <xsl:template name="getNodeValue">
        <xsl:param name="pExpression"/>
        <xsl:param name="pCurrentNode" select="."/>
      
        <xsl:choose>
         <xsl:when test="not(contains($pExpression, '/'))">
           <xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/>
         </xsl:when>
         <xsl:otherwise>
           <xsl:call-template name="getNodeValue">
             <xsl:with-param name="pExpression"
               select="substring-after($pExpression, '/')"/>
             <xsl:with-param name="pCurrentNode" select=
             "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
           </xsl:call-template>
         </xsl:otherwise>
        </xsl:choose>
      </xsl:template>
      
      <xsl:template name="quotevalue">
         <xsl:param name="value"/>
         <xsl:choose>
         <!-- Quote the value if required -->
             <xsl:when test="contains($value, '&quot;')">
                 <xsl:variable name="x" select="replace($value, '&quot;',  '&quot;&quot;')"/>
                 <xsl:value-of select="concat('&quot;', $x, '&quot;')"/>
             </xsl:when>
             <xsl:when test="contains($value, $delimiter)">
                 <xsl:value-of select="concat('&quot;', $value, '&quot;')"/>
             </xsl:when>
             <xsl:otherwise>
                 <xsl:value-of select="$value"/>
             </xsl:otherwise>
         </xsl:choose>
      </xsl:template>
      

      示例XML

      <?xml version="1.0" encoding="utf-8"?>
      <positionfeed
         xmlns="http://nowhere/"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          version="2006-04">
         <job id="2830302">
             <employer>Acme</employer>
             <title>Manager</title>
             <description>Full time</description>
             <postingdate>2016-09-15T23:12:13Z</postingdate>
             <location>
                <city>Los Angeles</city>
                <state>California</state>
             </location>
          </job>
          <job id="2830303">
             <employer>Acme</employer>
             <title>Clerk, evenings</title>
             <description>Part time</description>
             <postingdate>2016-09-15T23:12:13Z</postingdate>
             <location>
                <city>Albany</city>
                <state>New York</state>
             </location>
          </job>
      </positionfeed>
      

      使用我提供的XSLT的当前输出

      2830302,Los Angeles
      2830303,Albany
      

      XSLT按需运行时的输出

      2830302,Manager,Los Angeles
      2830303,"Clerk, evenings",Albany
      

      解决方案(非常感谢Tim的帮助)

      <?xml version="1.0"?>
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv" xpath-default-namespace="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/" >
      
          <xsl:output method="text" encoding="utf-8" />
          <xsl:strip-space elements="*" />
      
          <!-- Set the value of the delimiter character -->
          <xsl:variable name="delimiter" select="','" />
      
          <!-- The name of the node that contains the column values -->
          <xsl:param name="containerNodeName" select="'job'"/>
      
          <!-- All nodes that should be ignored during processing -->
          <xsl:template match="source|feeddate"/>
      
          <!-- The names of the nodes to be included in the CSV file -->
          <xsl:variable name="columns" as="element()*">
             <column header="Title">title</column>
             <column header="Category">category</column>
             <column header="Description">description</column>
             <column header="PostingDate">postingdate</column>
             <column header="URL">joburl</column>
             <column header="City">location/city</column>
             <column header="State">location/state</column>
          </xsl:variable>
      
          <!-- ************** DO NOT TOUCH BELOW **************** -->
          <!-- ************** DO NOT TOUCH BELOW **************** -->
          <!-- ************** DO NOT TOUCH BELOW **************** -->
          <!-- ************** DO NOT TOUCH BELOW **************** -->
          <!-- ************** DO NOT TOUCH BELOW **************** -->
      
          <!-- Warn about unmatched nodes -->
          <xsl:template match="*">
             <xsl:message terminate="no">
                <xsl:text>WARNING: Unmatched element: </xsl:text>
                <xsl:value-of select="name()"/>
             </xsl:message>
      
             <xsl:apply-templates/>
          </xsl:template>
      
          <!-- Generate the column headers -->
          <xsl:template match="//*[*[local-name()=$containerNodeName]]">
             <xsl:value-of select="'Id'"/>
             <xsl:value-of select="$delimiter"/>
             <xsl:for-each select="$columns/@header">
             <xsl:variable name="colname" select="." />
                 <xsl:value-of select="$colname"/>
                  <xsl:if test="position() != last()">
                      <xsl:value-of select="$delimiter"/>
                  </xsl:if>
             </xsl:for-each>
             <xsl:text>&#xa;</xsl:text>
             <xsl:apply-templates />
          </xsl:template>
      
          <!-- Generate the rows of column data -->
          <xsl:template match="//*[local-name()=$containerNodeName]">
      
             <!-- TODO: Handle attributes generically -->
             <xsl:value-of select="@id"/>
      
             <xsl:variable name="container" select="." />
             <xsl:for-each select="$columns">
                 <xsl:value-of select="$delimiter"/>
                 <xsl:variable name="vXpathExpression" select="."/>
                 <xsl:call-template name="getQuotedNodeValue">
                     <xsl:with-param name="pCurrentNode" select="$container"/>
                     <xsl:with-param name="pExpression" select="$vXpathExpression"/>
                 </xsl:call-template>
             </xsl:for-each>
             <xsl:text>&#xa;</xsl:text>
          </xsl:template>
      
          <xsl:template name="getQuotedNodeValue">
            <xsl:param name="pExpression"/>
            <xsl:param name="pCurrentNode" select="."/>
      
            <xsl:choose>
             <xsl:when test="not(contains($pExpression, '/'))">
               <xsl:variable name="result" select="$pCurrentNode/*[name()=$pExpression]"/>
               <xsl:call-template name="quotevalue">
                 <xsl:with-param name="value" select="$result"/>
               </xsl:call-template>
             </xsl:when>
             <xsl:otherwise>
               <xsl:call-template name="getQuotedNodeValue">
                 <xsl:with-param name="pExpression" select="substring-after($pExpression, '/')"/>
                 <xsl:with-param name="pCurrentNode" select= "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
               </xsl:call-template>
             </xsl:otherwise>
            </xsl:choose>
          </xsl:template>
      
          <xsl:template name="quotevalue">
             <xsl:param name="value"/>
             <xsl:choose>
                 <xsl:when test="contains($value, '&quot;')">
                     <!-- Quote the value and escape the double-quotes -->
                     <xsl:variable name="x" select="replace($value, '&quot;',  '&quot;&quot;')"/>
                     <xsl:value-of select="concat('&quot;', $x, '&quot;')"/>
                 </xsl:when>
                 <xsl:otherwise>
                      <!-- Quote the value -->
                     <xsl:value-of select="concat('&quot;', $value, '&quot;')"/>
                 </xsl:otherwise>
             </xsl:choose>
          </xsl:template>
      
      </xsl:stylesheet>
      

      展示解决方案的示例数据

      <?xml version="1.0" encoding="utf-8"?>
      <positionfeed
          xmlns="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/ http://www.job-search-engine.com/add-jobs/positionfeed.xsd"
          version="2006-04">
      <source>Casting360</source>
      <feeddate>2016-11-11T21:48:34Z</feeddate><job id="1363612">
      <employer>Casting360</employer>
      <title>The Robert Irvine Show Is Seeking Guests</title>
      <category>Reality TV</category>
      <description>TV personality ROBERT IRVINE (Restaurant Impossible) is seeking guests looking for solutions to their unique problems to share their stories on his show!
      
      Our next show is Thursday, September 22nd in LA. If you're not in LA we will provide your airfare, hotel, car service, and per diem. 
      
      Please note: WE ARE NOT LOOKING FOR RESUMES; THIS IS NOT AN ACTING GIG. We are looking for real people to share their stories!
      
      *appearance fee (TBD)
      
      If you or someone you know has a conflict that they need help resolving, WE WANT TO HEAR FROM YOU. 
      
      Please email tvgal.ri@gmail.com the following information:
      Name
      Phone number
      Your story in 2-3 paragraphs
      1-3 photos of yourself.</description>
      <postingdate>2016-09-15T23:12:13Z</postingdate>
      <joburl>http://casting360.com/lgj/8886644624?jobid=1363612&amp;city=Los+Angeles&amp;state=CA</joburl>
      <location>
      <nation>USA</nation>
      <city>Los Angeles</city>
      <state>California</state>
      </location>
      <jobsource>Casting360</jobsource>
      </job><job id="1370302">
      <employer>Casting360</employer>
      <title>Photoshoot for Publication</title>
      <category>Modeling</category>
      <description>6 FEMALE Models are wanted for publication photoshoot.
      
      If you're not in the NYC Vicinity (NY, Pa, Ct,) DO NOT REPLY because your response will be summarily ignored.
      
      Chosen models will be given a 5 look photo shoot. The shoot will occur on location (outdoors) in highly public locations chosen both for it's convenience and scenery.
      
      The 5 looks (outfits) will be pre-determined by our staff of items most outfits within a model's wardrobe. 
      
      THIS IS A TF (UNPAID) SHOOT.  After the release of the magazine, the photos agreed upon from the shoot shall be given to the model (in digital format) for her to build her portfolio. 
      
      Chosen models will receive a 5 outfit photo shoot at no cost to them by a NY Fashion Photographer.As a result, chosen  models not only receive a free photo shoot, but also become PUBLISHED MODELS featured in a magazine. 
      The model (Janeykay) centered in the photo attached (Please look at the attached photo) is a Casting360 member who not only received her photo shoot, not only is being featured in a magazine, but also made the cover becoming a Cover Model from her shoot with us.</description>
      <postingdate>2016-10-03T00:34:43Z</postingdate>
      <joburl>http://casting360.com/lgj/8886644624?jobid=1370302&amp;city=New+York&amp;state=NY</joburl>
      <location>
      <nation>USA</nation>
      <city>New York</city>
      <state>New York</state>
      </location>
      <jobsource>Casting360</jobsource>
      </job><job id="1370962">
      <employer>Casting360</employer>
      <title>Actresses Needed for &quot;Red Shore&quot;, Action Film</title>
      <category>Acting</category>
      <description>CASTING (non-union)
      We are a New Independent company looking to shoot our first feature. We are currently looking to fill two Major roles.
      
      Female/African American, Hispanic, Asian, Pacific Islander/ 5'5-5'10/ Age Late 30's-Early 40's.
      
      Project description: A long standing feud between two best friends turned enemies escalates over a valuable Diamond on display in a New York City Museum. With the stakes high they each seek the help of both friends and strangers to settle their feud once and for all. 
      
      Please note this is a non-paid project. 
      Fight training will be provided for free. 
      
      Please email including age and height in your e-mail.
      Those selected will be invited to our audition.</description>
      <postingdate>2016-10-03T14:18:20Z</postingdate>
      <joburl>http://casting360.com/lgj/8886644624?jobid=1370962&amp;city=New+York&amp;state=NY</joburl>
      <location>
      <nation>USA</nation>
      <city>New York</city>
      <state>New York</state>
      </location>
      <jobsource>Casting360</jobsource>
      </job>
      </positionfeed>
      

1 个答案:

答案 0 :(得分:1)

在使用XSLT 2.0时,您可以在变量中定义列,如下所示:

<xsl:variable name="columns" as="element()*">
    <column>title</column>
    <column>location/city</column>
</xsl:variable>

然后你可以用一个简单的语句迭代它们

<xsl:for-each select="$columns">

但您可能遇到的问题是,在此xsl:for-each内您已经改变了背景。您不再位于job元素上,而是位于column元素上,并且您不希望表达式与此相关。您真的需要交换回作业元素,只需在job之前设置xsl:for-each元素的变量引用,然后将其用作命名模板的参数即可:

<xsl:template match="job">
   <xsl:value-of select="@id"/>
   <xsl:variable name="job" select="." />
   <xsl:for-each select="$columns">
       <xsl:value-of select="$delimiter"/>
       <xsl:variable name="vXpathExpression" select="."/>
       <xsl:call-template name="getNodeValue">
           <xsl:with-param name="pCurrentNode" select="$job"/>
           <xsl:with-param name="pExpression" select="$vXpathExpression"/>
       </xsl:call-template>
   </xsl:for-each>
   <xsl:text>&#xa;</xsl:text>
</xsl:template>

至于引用结果;而不仅仅是xsl:value-of只需使用值作为参数调用quote模板

 <xsl:when test="not(contains($pExpression, '/'))">
   <xsl:call-template name="quotevalue">
     <xsl:with-param name="value" select="$pCurrentNode/*[name()=$pExpression]" />
   </xsl:call-template>
 </xsl:when>

编辑:如果你想要一个列名称的标题行,你必须匹配job节点的父节点,然后只输出$ column变量的值

<xsl:template match="*[job]">
    <xsl:value-of select="$columns" separator="," />
    <xsl:text>&#xa;</xsl:text>
    <xsl:apply-templates />
</xsl:template>

或者如果您不想要完整路径

<xsl:value-of select="$columns/(tokenize(., '/')[last()])" separator="," />

或者您可以将columns变量扩展为包含标题文本

<xsl:variable name="columns" as="element()*">
    <column header="Title">title</column>
    <column header="City">location/city</column>
</xsl:variable>

然后你会这样做......

 <xsl:value-of select="$columns/@header" separator="," />