Dynamic grouping on element and attributes names

前端 未结 2 880
谎友^
谎友^ 2021-01-24 20:01

I would like to categorize results from an XPath under headings by element name (and then by same attribute names). Note: XML data could be inconsistent and some elements with t

相关标签:
2条回答
  • 2021-01-24 20:20

    This transformation doesn't make any assumptions about the sets having the same number of attributes -- no assumptions at all.

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:ext="http://exslt.org/common"
     exclude-result-prefixes="ext">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:key name="kAnimalByProperties" match="animal"
      use="concat(@atype, .)"/>
    
     <xsl:variable name="vrtfNewDoc">
      <xsl:apply-templates select="/pets/*">
        <xsl:sort select="name()"/>
      </xsl:apply-templates>
     </xsl:variable>
    
     <xsl:template match="pets/*">
       <animal atype="{name()}">
         <xsl:copy-of select="@*"/>
         <xsl:for-each select="@*">
           <xsl:sort select="name()"/>
             <attrib>|<xsl:value-of select="name()"/>|</attrib>
         </xsl:for-each>
       </animal>
     </xsl:template>
    
     <xsl:template match="/">
       <xsl:for-each select="ext:node-set($vrtfNewDoc)">
         <xsl:for-each select=
         "*[generate-id()
           =generate-id(key('kAnimalByProperties',
                            concat(@atype, .)
                            )[1]
                        )
           ]">
            <table border="1">
              <tr>
                <td>Element Name</td>
                <xsl:for-each select="*">
                  <td><xsl:value-of select="translate(.,'|','')"/></td>
                </xsl:for-each>
              </tr>
              <xsl:for-each select=
              "key('kAnimalByProperties', concat(@atype, .))">
                <xsl:variable name="vcurAnimal" select="."/>
                <tr>
                  <td><xsl:value-of select="@atype"/></td>
                  <xsl:for-each select="*">
                    <td>
                      <xsl:value-of select=
                       "$vcurAnimal/@*[name()=translate(current(),'|','')]"/>
                    </td>
                  </xsl:for-each>
                </tr>
              </xsl:for-each>
            </table>
            <p/>
         </xsl:for-each>
       </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    When applied on the provided XML document:

    <pets>
        <dog name="Frank" cute="yes" color="brown" type="Lab"/>
        <cat name="Fluffy" cute="yes" color="brown"/>
        <cat name="Lucy" cute="no" color="brown"/>
        <dog name="Spot" cute="no" color="brown"/>
        <dog name="Rover" cute="yes" color="brown"/>
        <dog name="Rupert" cute="yes" color="beige" type="Pug"/>
        <cat name="Simba" cute="yes" color="grey"/>
        <cat name="Princess" color="brown"/>
    </pets>
    

    the wanted, correct result is produced:

    <table border="1">
       <tr>
          <td>Element Name</td>
          <td>color</td>
          <td>cute</td>
          <td>name</td>
       </tr>
       <tr>
          <td>cat</td>
          <td>brown</td>
          <td>yes</td>
          <td>Fluffy</td>
       </tr>
       <tr>
          <td>cat</td>
          <td>brown</td>
          <td>no</td>
          <td>Lucy</td>
       </tr>
       <tr>
          <td>cat</td>
          <td>grey</td>
          <td>yes</td>
          <td>Simba</td>
       </tr>
    </table>
    <p/>
    <table border="1">
       <tr>
          <td>Element Name</td>
          <td>color</td>
          <td>name</td>
       </tr>
       <tr>
          <td>cat</td>
          <td>brown</td>
          <td>Princess</td>
       </tr>
    </table>
    <p/>
    <table border="1">
       <tr>
          <td>Element Name</td>
          <td>color</td>
          <td>cute</td>
          <td>name</td>
          <td>type</td>
       </tr>
       <tr>
          <td>dog</td>
          <td>brown</td>
          <td>yes</td>
          <td>Frank</td>
          <td>Lab</td>
       </tr>
       <tr>
          <td>dog</td>
          <td>beige</td>
          <td>yes</td>
          <td>Rupert</td>
          <td>Pug</td>
       </tr>
    </table>
    <p/>
    <table border="1">
       <tr>
          <td>Element Name</td>
          <td>color</td>
          <td>cute</td>
          <td>name</td>
       </tr>
       <tr>
          <td>dog</td>
          <td>brown</td>
          <td>no</td>
          <td>Spot</td>
       </tr>
       <tr>
          <td>dog</td>
          <td>brown</td>
          <td>yes</td>
          <td>Rover</td>
       </tr>
    </table>
    <p/>
    
    0 讨论(0)
  • 2021-01-24 20:44

    This stylesheet:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:key name="ByName-AttNum" match="*/*[@color='brown']" use="concat(name(),'++',count(@*))"/>
        <xsl:template match="/">
            <html>
                <xsl:apply-templates/>
            </html>
        </xsl:template>
        <xsl:template match="*/*[generate-id(.) = generate-id(key('ByName-AttNum',concat(name(),'++',count(@*)))[1])]">
            <table>
                <tr>
                    <th>ElementName</th>
                    <xsl:apply-templates select="@*" mode="headers">
                        <xsl:sort select="name()"/>
                    </xsl:apply-templates>
                </tr>
                <xsl:apply-templates select="key('ByName-AttNum',concat(name(),'++',count(@*)))" mode="list"/>
            </table>
        </xsl:template>
        <xsl:template match="*" mode="list">
            <tr>
                <td>
                    <xsl:value-of select="name()"/>
                </td>
                <xsl:apply-templates select="@*" mode="list">
                    <xsl:sort select="name()"/>
                </xsl:apply-templates>
            </tr>
        </xsl:template>
        <xsl:template match="@*" mode="headers">
            <th>
                <xsl:value-of select="name()"/>
            </th>
        </xsl:template>
        <xsl:template match="@*" mode="list">
            <td>
                <xsl:value-of select="."/>
            </td>
        </xsl:template>
    </xsl:stylesheet>
    

    Result:

    <html>
        <table>
            <tr>
                <th>ElementName</th>
                <th>color</th>
                <th>cute</th>
                <th>name</th>
                <th>type</th>
            </tr>
            <tr>
                <td>dog</td>
                <td>brown</td>
                <td>yes</td>
                <td>Frank</td>
                <td>Lab</td>
            </tr>
        </table>
        <table>
            <tr>
                <th>ElementName</th>
                <th>color</th>
                <th>cute</th>
                <th>name</th>
            </tr>
            <tr>
                <td>cat</td>
                <td>brown</td>
                <td>yes</td>
                <td>Fluffy</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>brown</td>
                <td>no</td>
                <td>Lucy</td>
            </tr>
        </table>
        <table>
            <tr>
                <th>ElementName</th>
                <th>color</th>
                <th>cute</th>
                <th>name</th>
            </tr>
            <tr>
                <td>dog</td>
                <td>brown</td>
                <td>no</td>
                <td>Spot</td>
            </tr>
            <tr>
                <td>dog</td>
                <td>brown</td>
                <td>yes</td>
                <td>Rover</td>
            </tr>
        </table>
        <table>
            <tr>
                <th>ElementName</th>
                <th>color</th>
                <th>name</th>
            </tr>
            <tr>
                <td>cat</td>
                <td>brown</td>
                <td>Princess</td>
            </tr>
        </table>
    </html>
    

    Note: This assumes that all elements having the same number of attributes have also the same attribute's name (like in your input sample).

    EDIT: Better ouput markup.

    EDIT 2: Another kind of solution: one header with all posible attribute (like CSV pattern) and order element by attribute count and name.

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:key name="attrByName" match="pets/*/@*" use="name()"/>
        <xsl:variable name="attr" select="/pets/*/@*[count(.|key('attrByName',name())[1])=1]"/>
        <xsl:template match="pets">
            <html>
                <table>
                    <tr>
                        <th>ElementName</th>
                        <xsl:apply-templates select="$attr" mode="headers">
                            <xsl:sort select="name()"/>
                        </xsl:apply-templates>
                    </tr>
                    <xsl:apply-templates select="*[@color='brown']">
                        <xsl:sort select="count(@*)" order="descending"/>
                        <xsl:sort select="name()"/>
                    </xsl:apply-templates>
                </table>
            </html>
        </xsl:template>
        <xsl:template match="pets/*">
            <tr>
                <td>
                    <xsl:value-of select="name()"/>
                </td>
                <xsl:apply-templates select="$attr" mode="list">
                    <xsl:sort select="name()"/>
                    <xsl:with-param name="node" select="."/>
                </xsl:apply-templates>
            </tr>
        </xsl:template>
        <xsl:template match="@*" mode="headers">
            <th>
                <xsl:value-of select="name()"/>
            </th>
        </xsl:template>
        <xsl:template match="@*" mode="list">
            <xsl:param name="node"/>
            <td>
                <xsl:value-of select="$node/@*[name()=name(current())]"/>
            </td>
        </xsl:template>
    </xsl:stylesheet>
    

    Result:

    <html>
        <table>
            <tr>
                <th>ElementName</th>
                <th>color</th>
                <th>cute</th>
                <th>name</th>
                <th>type</th>
            </tr>
            <tr>
                <td>dog</td>
                <td>brown</td>
                <td>yes</td>
                <td>Frank</td>
                <td>Lab</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>brown</td>
                <td>yes</td>
                <td>Fluffy</td>
                <td></td>
            </tr>
            <tr>
                <td>cat</td>
                <td>brown</td>
                <td>no</td>
                <td>Lucy</td>
                <td></td>
            </tr>
            <tr>
                <td>dog</td>
                <td>brown</td>
                <td>no</td>
                <td>Spot</td>
                <td></td>
            </tr>
            <tr>
                <td>dog</td>
                <td>brown</td>
                <td>yes</td>
                <td>Rover</td>
                <td></td>
            </tr>
            <tr>
                <td>cat</td>
                <td>brown</td>
                <td></td>
                <td>Princess</td>
                <td></td>
            </tr>
        </table>
    </html>
    

    Note: This runs through the tree twice but without extension. Exact match for desired output without extensions would require to mimic key mechanism like this: run through the tree adding new keys (name of element plus attributes' names) to a param, then again for every key run through the tree filtering node by key (could be a little optimization keeping a node set for non matching elements...). Worst case (every node with distinc key) will pass trough a node: N (for key building) + (N + 1) * N / 2

    0 讨论(0)
提交回复
热议问题