How to get attribute name and its value properly?

2019-08-06 01:32发布

Input:

<?xml version="1.0" encoding="utf-8" ?>
<Software>
    <MS version="5.2.3.1"/>
    <Java version="5.1.0.29" />
    <Oracle id="A" version="1.0.1.11" />
    <SQL id="P" version="1.0.1.11" />  
</Software>

XSLT:

   <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <table>
      <tr>
        <xsl:for-each select="//*[1]">
          <xsl:for-each select="@*">
            <td>
              <xsl:value-of select="name()"/>
            </td>
          </xsl:for-each>
        </xsl:for-each>
      </tr>
      <xsl:for-each select="//*">
        <tr>
          <xsl:value-of select="local-name()"/> ::
          <xsl:for-each select="@*">
            <td>
              <xsl:value-of select="."/>
            </td>
          </xsl:for-each>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

Output:

Current output [wrong]:

This script gives output without column name "id". Some "version" column value added in "id(not visible)" column as there is no attribute name id in node. eg. MS node has version attribute only hence output result added version info in "id" column. Please go through output once, possibly save and check in html for proper understanding.

 <table>
  <tr>
    <td/>
    <td>
      <td>version</td>
    </td>
  </tr>
  <tr>
    <td>Software ::
              </td>
  </tr>
  <tr>
    <td>MS ::
              <td>5.2.3.1</td></td>
  </tr>
  <tr>
    <td>Java ::
              <td>5.1.0.29</td></td>
  </tr>
  <tr>
    <td>Oracle ::
              <td>A</td><td>1.0.1.11</td></td>
  </tr>
  <tr>
    <td>SQL ::
              <td>P</td><td>1.0.1.11</td></td>
  </tr>
</table>

Expected output:

Every attribute as columnname/header and all column have their own value. NOTE: Attributes MS, JAVA, etc as column Name should not be hard coded because the number of attributes may change on runtime.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <title></title>
    </head>
    <body>

<table>
  <tr>
    <td/>
    <td>
        id<td>version</td>
    </td>
  </tr>
  <tr>
    <td>MS ::</td>
              <td>Not exist</td><td>5.2.3.1</td>
  </tr>
  <tr>
    <td>Java ::</td>
              <td>Not exist</td><td>5.1.0.29</td>
  </tr>
  <tr>
    <td>Oracle ::</td>
              <td>A</td><td>1.0.1.11</td>
  </tr>
  <tr>
    <td>SQL ::</td>
              <td>P</td><td>1.0.1.11</td>
  </tr>
</table>    
    </body>
</html>

标签: html xslt
2条回答
smile是对你的礼貌
2楼-- · 2019-08-06 02:04

Hummm, I have tried to fix the HTML issue and the problem you are facing. This is a bit mannual, but can achieve your requirement. Here is the XSL:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">
        <table>
            <tr>
                <td></td>
                <td>id</td>
                <xsl:for-each select="//*[1]">
                    <xsl:for-each select="@*">
                        <td><xsl:value-of select="name()"/></td>
                    </xsl:for-each>
                </xsl:for-each>
            </tr>
            <xsl:for-each select="//*">
                <xsl:if test="position() &gt; 1">
                <tr>
                    <td><xsl:value-of select="local-name()"/> ::</td>
                    <td>
                        <xsl:choose>
                            <xsl:when test="@id"><xsl:value-of select="@id"/></xsl:when>
                            <xsl:otherwise>Not Exist</xsl:otherwise>
                        </xsl:choose>
                    </td>
                    <td>
                        <xsl:if test="@version"><xsl:value-of select="@version"/></xsl:if>
                    </td>
                </tr>
                </xsl:if>
            </xsl:for-each>
        </table>
    </xsl:template>
</xsl:stylesheet>

Here is the output of it:

<table>
  <tr>
    <td/>
    <td>id</td>
    <td>version</td>
  </tr>
  <tr>
    <td>MS ::</td>
    <td>Not Exist</td>
    <td>5.2.3.1</td>
  </tr>
  <tr>
    <td>Java ::</td>
    <td>Not Exist</td>
    <td>5.1.0.29</td>
  </tr>
  <tr>
    <td>Oracle ::</td>
    <td>A</td>
    <td>1.0.1.11</td>
  </tr>
  <tr>
    <td>SQL ::</td>
    <td>P</td>
    <td>1.0.1.11</td>
  </tr>
</table>

I have tested it on http://www.xslfiddle.net/

查看更多
不美不萌又怎样
3楼-- · 2019-08-06 02:20

If you are looking to output one column for each possible attribute name, and not restrict it to just id and version, then (in XSLT 1.0) you could make use of a technique called Muenchian Grouping to get the distinct attribute names.

First define a key to look up the attributes by their name

<xsl:key name="columns" match="@*" use="local-name()" />

Then, to get the distinct ones (or rather, get the first occurrence of each distinct name), you can define a variable like so

<xsl:variable name="columns" select="//@*[generate-id() = generate-id(key('columns', local-name())[1])]" />

Then, to output the column headers, you can just iterate over this variable

  <xsl:for-each select="$columns">
     <xsl:sort select="local-name()" />
     <td><xsl:value-of select="local-name()" /></td>
  </xsl:for-each>

For each row, you would take a similar approach. Assuming you were positioned on a child element, you could first define a variable that contains the attributes of the current element. Then you would iterate over the columns variable again, and output the value of the corresponding attribute for the current element

  <xsl:variable name="attributes" select="@*" />
  <xsl:for-each select="$columns">
     <xsl:sort select="local-name()" />
     <td><xsl:value-of select="$attributes[local-name() = local-name(current())]" /></td>
  </xsl:for-each>

Try this XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output omit-xml-declaration="yes" indent="yes" />
   <xsl:key name="columns" match="@*" use="local-name()" />
   <xsl:variable name="columns" select="//@*[generate-id() = generate-id(key('columns', local-name())[1])]" />
   <xsl:template match="Software">
      <table>
      <tr>
      <td>Software</td>
      <xsl:for-each select="$columns">
         <xsl:sort select="local-name()" />
         <td><xsl:value-of select="local-name()" /></td>
      </xsl:for-each>
      </tr>
      <xsl:apply-templates select="*" />
      </table>
   </xsl:template>

   <xsl:template match="Software/*">
      <tr>
      <td><xsl:value-of select="local-name()" /></td>
      <xsl:variable name="attributes" select="@*" />
      <xsl:for-each select="$columns">
         <xsl:sort select="local-name()" />
         <td><xsl:value-of select="$attributes[local-name() = local-name(current())]" /></td>
      </xsl:for-each>
      </tr>
   </xsl:template>
</xsl:stylesheet>

This should output the following

<table>
  <tr>
    <td>Software</td>
    <td>id</td>
    <td>version</td>
  </tr>
  <tr>
    <td>MS</td>
    <td/>
    <td>5.2.3.1</td>
  </tr>
  <tr>
    <td>Java</td>
    <td/>
    <td>5.1.0.29</td>
  </tr>
  <tr>
    <td>Oracle</td>
    <td>A</td>
    <td>1.0.1.11</td>
  </tr>
  <tr>
    <td>SQL</td>
    <td>P</td>
    <td>1.0.1.11</td>
  </tr>
</table>

Note, for brevity, I have not included code to output "Not exists" in the case where the attribute does not exist. But it should be simple enough to add a check for this. (Just store the value in a variable, and use an xsl:choose)

EDIT: If you want to restrict the attributes to specifically be for Software elements too, try changing the key to this:

<xsl:key name="columns" match="Software/*/@*" use="local-name()" />
查看更多
登录 后发表回答