-2

To extend the solution provided here, the aim is to retrieving value of attributes in addition their names.

For this doc,

<a>
    <apple color="red"/>
    <apple color="green"/>
    <banana color="yellow"/>
    <sugar taste="sweet"/>
    <cat size="small"/>
</a>

The code, in the above post, provides this result:

For tags: apple 2, banana 1, sugar 1, cat 1 
For attributes: color 3, taste 1, size 1 

Now, the desired result is :

For tags: apple 2, banana 1, sugar 1, cat 1 
For attributes: color(red) 1, color(green) 1, color(yellow) 1, taste(sweet) 1, size (small) 1

Thanks a lot.

1 Answer 1

2

To extend the solution for your previous question provided by Rudramuni TP, you can add an additional key to get unique attribute values:

<xsl:key name="kAttribValue" match="@*" use="."/>

and an additional variable:

<xsl:variable name="var2">
  <xsl:for-each select="/a/*/@*[generate-id() = generate-id(key('kAttribValue', .))]">
     <xsl:value-of select="concat(name(.), '(', .,')', ' ', count(key('kAttribValue', .)))"/>
       <xsl:if test="not(position()=last())">
         <xsl:text>, </xsl:text>
       </xsl:if>
  </xsl:for-each>
</xsl:variable>

With these adjustments to Rudramuni TPs code:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
  <xsl:key name="kEleName" match="*" use="local-name()"/>
  <xsl:key name="kAttribName" match="@*" use="local-name()"/>
  <xsl:key name="kAttribValue" match="@*" use="."/>

  <xsl:variable name="var1">
    <xsl:for-each select="/a/*/@*[generate-id() = generate-id(key('kAttribName', name()))]">
      <xsl:value-of select="concat(name(.), ' ', count(key('kAttribName', name(.))))"/>
      <xsl:if test="not(position()=last())">
        <xsl:text>, </xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>
  <xsl:variable name="var2">
    <xsl:for-each select="/a/*/@*[generate-id() = generate-id(key('kAttribValue', .))]">
      <xsl:value-of select="concat(name(.), '(', .,')', ' ', count(key('kAttribValue', .)))"/>
      <xsl:if test="not(position()=last())">
        <xsl:text>, </xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:template match="/*">
    <xsl:apply-templates select="*[generate-id() = generate-id(key('kEleName', name()))]"/>
  </xsl:template>
  <xsl:template match="*">
    <xsl:if test="position()=1"><xsl:text>For tags: </xsl:text></xsl:if>
        <xsl:value-of select="concat(name(.), ' ', count(key('kEleName', name(.))))"/>
        <xsl:if test="following-sibling::*">
          <xsl:text>, </xsl:text>
        </xsl:if>
        <xsl:if test="position()=last()">
          <xsl:text>&#10;For attributes: </xsl:text>
        <xsl:value-of select="$var2"/>
     </xsl:if>
  </xsl:template>
</xsl:stylesheet>

when applied to your input XML

<a>
  <apple color="red"/>
  <apple color="green"/>
  <banana color="yellow"/>
  <sugar taste="sweet"/>
  <cat size="small"/>
</a>

the extended result is generated:

For tags: apple 2, banana 1, sugar 1, cat 1
For attributes: color(red) 1, color(green) 1, color(yellow) 1, taste(sweet) 1, size(small) 1

Update: As mentioned in the comment, above adjustment (as well as the answer for the previous question) only works for an input as provided in the question. Given an input XML like e.g.

<root>
  <a>
    <apple color="red"/>
    <apple color="green"/>
    <banana color="yellow"/>
    <sugar taste="sweet"/>
    <cat size="small"/>
  </a>
  <a>
    <apple color="red"/>
    <apple color="green"/>
    <banana color="yellow"/>
    <sugar taste="sweet"/>
    <dog size="big"/>
  </a>
</root> 

the template can be adjusted as follows:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
  <xsl:key name="kEleName" match="*" use="local-name()"/>
  <xsl:key name="kAttribName" match="@*" use="local-name()"/>
  <xsl:key name="kAttribValue" match="@*" use="."/>

  <xsl:variable name="var1">
    <xsl:for-each select="//a/*/@*[generate-id() = generate-id(key('kAttribValue', .))]">
      <xsl:value-of select="concat(name(.), ' (', .,')', ' ', count(key('kAttribValue', .)))"/>
      <xsl:if test="not(position()=last())">
        <xsl:text>, </xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:template match="/*">
    <xsl:apply-templates select="a/*[generate-id() = generate-id(key('kEleName', name()))]"/>
  </xsl:template>
  <xsl:template match="*">
    <xsl:if test="position()=1"><xsl:text>For tags: </xsl:text></xsl:if>
      <xsl:value-of select="concat(name(.), ' ', count(key('kEleName', name(.))))"/>
      <xsl:if test="following::*">
        <xsl:text>, </xsl:text>
      </xsl:if>
      <xsl:if test="position()=last()">
        <xsl:text>&#10;For attributes: </xsl:text>
        <xsl:value-of select="$var1"/>
      </xsl:if>
  </xsl:template>
</xsl:stylesheet>

When this is applied to the adjusted example input XML this generates the output:

For tags: apple 4, banana 2, sugar 2, cat 1, dog 1
For attributes: color (red) 2, color (green) 2, color (yellow) 2, taste (sweet) 2, size (small) 1, size (big) 1

The adjustments are as follows: This

<xsl:for-each select="/a/*/@*[generate-id() = generate-id(key('kAttribValue', .))]">

has to be adjusted to

<xsl:for-each select="//a/*/@*[generate-id() = generate-id(key('kAttribValue', .))]">

as otherwise only attributes of children of the first a node are matched.

This

<xsl:apply-templates select="*[generate-id() = generate-id(key('kEleName', name()))]"/>

has to be adjusted to

<xsl:apply-templates select="a/*[generate-id() = generate-id(key('kEleName', name()))]"/>

as otherwise the a tags would be selected (which would produce the output For tags: a 2,).

And this

<xsl:if test="following-sibling::*">
  <xsl:text>, </xsl:text>
</xsl:if>

has to be adjusted to

<xsl:if test="following::*">
  <xsl:text>, </xsl:text>
</xsl:if>

as the added dog in the second a node is not a following sibling but still a following element.

It's clear that this adjustment won't work if unknown requirements would allow to have different named parent nodes - as simplified example it won't work for an input like

<a>
  <apple color="red"/>
  <apple color="green"/>
</a>
<b>
  <banana color="yellow"/>
</b>

In case different parent nodes or nested structures like e.g.

<a>
  <apple color="red"/>
  <apple color="green"/>
  <b>
    <banana color="yellow"/>
  </b>
</a>

should also be handled, I suggest to ask a new question for this as this differs too much from the example input XML as provided in the question.

Sign up to request clarification or add additional context in comments.

3 Comments

works for this case but does not work for nested tags. In fact, with another record such as above example, it provides another result rather desired one.
@EiliaAbraham The template was only adjusted to extend the result as asked in the question for the provided input, but I've just updated my answer with an added second version to handle multiple records.
Thanks for that. In fact, my primary question was incomplete. I'll post a new question regarding mentioned issues.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.