qbektrix / xml2json-xslt

Automatically exported from code.google.com/p/xml2json-xslt
BSD 3-Clause "New" or "Revised" License
0 stars 0 forks source link

siblings with the same name are not collected into an array if other elements exist at the same level #3

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
Transform: <root><item>1</item><item>2</item><name>test</name></root>

What is the expected output? What do you see instead?
Results in: {"item":1,"item":2,"name":"test"}

One solution is to replace the current array handling template:

  <xsl:template match="*[count(../*[name(../*)=name(.)])=count(../*) and
count(../*)&gt;1]">
    <xsl:if test="not(preceding-sibling::*)">[</xsl:if>
    <xsl:choose>
      <xsl:when test="not(child::node())">
        <xsl:text>null</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="child::node()"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:if test="not(following-sibling::*)">]</xsl:if>
  </xsl:template>

With the following template:

  <!-- defer processing siblings with the same name until the last element
with that 
       name at that level is found, at which time they can be collected
into an array -->
  <xsl:template match="*[count(../*[name(current())=name()])&gt;1]">
    <xsl:variable name="el" select="name()"/>
    <xsl:if test="not(preceding-sibling::*)">{</xsl:if>
    <xsl:if test="not(following-sibling::*[name()=$el])">
      <xsl:call-template name="escape-string">
        <xsl:with-param name="s" select="$el"/>
      </xsl:call-template>
      <xsl:text>:[</xsl:text>
      <xsl:for-each select="../*[name()=$el]">
        <xsl:if test="position()!=1">,</xsl:if>
        <xsl:choose>
          <xsl:when test="not(child::node())">
            <xsl:text>null</xsl:text>
          </xsl:when>
          <xsl:otherwise>
            <xsl:apply-templates select="child::node()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
      <xsl:text>]</xsl:text>
      <xsl:if test="following-sibling::*">,</xsl:if>
    </xsl:if>
    <xsl:if test="not(following-sibling::*)">}</xsl:if>
  </xsl:template>  

This transforms the source XML above into:
{"root":{"item":[1,2],"name":"test"}}

I've attached my changes, along with matt.perrin and root.node's.

Original issue reported on code.google.com by waynebur...@gmail.com on 29 Dec 2007 at 1:57

Attachments:

GoogleCodeExporter commented 9 years ago
Some XSLT processors throw an error on your match pattern:
"The current() function may not be used in a match pattern."

To get around this error, I had to combine the "object" and "array" templates 
into 1 
new template that can handle both.  I used your same pattern to check for an 
array, 
but it is located in an <xsl:when>'s test expression, instead, which avoids the 
error.

<!-- objects and arrays -->
<xsl:template match="*" name="base">
  <xsl:if test="not(preceding-sibling::*)">{</xsl:if>
  <xsl:choose>
    <!-- array -->
    <xsl:when test="count(../*[name(current())=name()])>1">
      <xsl:variable name="el" select="name()"/>
      <xsl:if test="not(following-sibling::*[name()=$el])">
        <xsl:call-template name="escape-string">
          <xsl:with-param name="s" select="$el"/>
        </xsl:call-template>
        <xsl:text>:[</xsl:text>
        <xsl:for-each select="../*[name()=$el]">
          <xsl:if test="position()!=1">,</xsl:if>
          <xsl:choose>
            <xsl:when test="not(child::node())">
              <xsl:text>null</xsl:text>
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="child::node()"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
        <xsl:text>]</xsl:text>
        <xsl:if test="following-sibling::*">,</xsl:if>
      </xsl:if>
    </xsl:when>
    <!-- object -->
    <xsl:otherwise>
      <xsl:call-template name="escape-string">
        <xsl:with-param name="s" select="name()"/>
      </xsl:call-template>
      <xsl:text>:</xsl:text>
      <!-- check type of node -->
      <xsl:choose>
        <!-- null nodes -->
        <xsl:when test="count(child::node())=0">null</xsl:when>
        <!-- other nodes -->
        <xsl:otherwise>
            <xsl:apply-templates select="child::node()"/>
        </xsl:otherwise>
      </xsl:choose>
      <!-- end of type check -->
      <xsl:if test="following-sibling::*">,</xsl:if>
    </xsl:otherwise>
  </xsl:choose>
  <xsl:if test="not(following-sibling::*)">}</xsl:if>
</xsl:template>

I've attached my changes in the full XSLT file.

Original comment by Jason.Mo...@gmail.com on 26 Feb 2008 at 5:55

Attachments:

GoogleCodeExporter commented 9 years ago
How does this deal with single children?  Does it make them an array or an 
object and can you override it?
See issue 6 for the array case.

Original comment by docw...@gmail.com on 27 Mar 2008 at 5:35

GoogleCodeExporter commented 9 years ago
I'm not sure this is a bug.  The spec. say that it'll only be an array if all 
the siblings have the same name().  
Otherwise, I think it is too confusing.

I'll think about it for a while.  Maybe one of the other developers can chime 
in.

Original comment by docw...@gmail.com on 31 Mar 2008 at 4:46

GoogleCodeExporter commented 9 years ago
Whups!  I get it.  You can't have "test": twice.  Hmm....
More interesting case:
<root>
  <item>1</item>
  <name>Georg</name>
  <item>2</item>
</root>

Should these just throw an error of some kind or actually work right?

Ciao!

Original comment by docw...@gmail.com on 31 Mar 2008 at 5:23

GoogleCodeExporter commented 9 years ago
Anthony:
  On the comments, you expressed interest in handling this one.  Should I just leave this to you?

Original comment by docw...@gmail.com on 31 Mar 2008 at 5:25

GoogleCodeExporter commented 9 years ago
I have an initial solution for this I've used in my application, just needs 
some more
testing to make sure it doesn't work only for my case.  Yes, I can accept it.  I
think my solution parallels the ones that I see in the comments - hopefully I 
will
have some time to tackle this later this week.

Original comment by anthony....@gmail.com on 31 Mar 2008 at 7:26

GoogleCodeExporter commented 9 years ago
I've at last had a chance to tackle this and I've mostly cleaned up my 
solution. 
Before I check it in I want to do testing, because I am not convinced what I was
working on is really that different from what was already proposed.

A few quick questions:

- The expected output of 

<root><item>1</item><item>2</item><name>test</name></root>

should be:

{"root":{"item":[1,2],"name":"test"}}

correct?

- To Jason: what XSLT processors fail on Wayne's script?  I'm trying to compare 
your
solution to my solution to see which one will be more robust.  What I was doing 
seems
like it might suffer the same current() problem you found in Wayne's file, so I 
want
to replicate this.

I've inlined the diff stanzas below, and attached the current version of the 
file.

  <!-- item:null -->
  <xsl:template match="*[count(child::node())=0]">
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="local-name()"/>
    </xsl:call-template>
    <xsl:text>:null</xsl:text>
    <xsl:choose>
      <xsl:when test="following-sibling::*">,</xsl:when>
      <xsl:otherwise>}</xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- object -->
  <xsl:template match="*" name="base">
    <xsl:if test="not(preceding-sibling::*)">{</xsl:if>
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="name()"/>
    </xsl:call-template>
    <xsl:text>:</xsl:text>
    <!-- check type of node -->
    <xsl:apply-templates select="child::node()"/>
    <!-- end of type check -->
    <xsl:choose>
      <xsl:when test="following-sibling::*">,</xsl:when>
      <xsl:otherwise>}</xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- partial array: children without repeated names need to be shown as singletons --> 
  <xsl:template match="*[count(../*[name()=name(current())])<count(../*) and
count(../*[name()=name(current())])>1]">
    <xsl:if test="not(preceding-sibling::*)">{</xsl:if>
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="name()"/>
    </xsl:call-template>
    <xsl:text>: </xsl:text>
    <xsl:apply-templates select="child::node()"/>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:if test="not(following-sibling::*)">}</xsl:if>
  </xsl:template>

  <!-- partial array: children with repeated names need to be grouped in an array -->
  <xsl:template match="*[count(../*[name()=name(current())])>1]">
    <xsl:if test="not(preceding-sibling::*)">{</xsl:if>
    <xsl:if test="not(preceding-sibling::*[name()=name(current())])">
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="name()"/>
    </xsl:call-template>
    <xsl:text>:[</xsl:text></xsl:if>
    <xsl:choose>
      <xsl:when test="not(child::node())">
        <xsl:text>null</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="child::node()"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="following-sibling::*[name()=name(current())]">,</xsl:if>
    <xsl:if test="not(following-sibling::*[name()=name(current())])">
      <xsl:if test="following-sibling::*">],</xsl:if>
      <xsl:if test="not(following-sibling::*)">]}</xsl:if>
    </xsl:if>
  </xsl:template>

  <!-- array -->
  <xsl:template match="*[count(../*[name(../*)=name(.)])=count(../*) and
count(../*)>1]">
    <xsl:if test="not(preceding-sibling::*)">[</xsl:if>
    <xsl:choose>
      <xsl:when test="not(child::node())">
        <xsl:text>null</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="child::node()"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:if test="not(following-sibling::*)">]</xsl:if>
  </xsl:template>

Original comment by anthony....@gmail.com on 1 May 2008 at 10:01

Attachments:

GoogleCodeExporter commented 9 years ago
Waiting to hear back from various people before checking anything in.   In the
meantime, attached is the script I've been using to run the unit tests, and the
new/changed files in the test data.  

Original comment by anthony....@gmail.com on 1 May 2008 at 10:04

Attachments:

GoogleCodeExporter commented 9 years ago
I have "java.lang.RuntimeException: XSLT TransformerFactory Error" trying to 
apply
transformation from last Antony's message as well as first wayneburkett's 
stylesheet.
Jason's solution seems to be working fine. I believe you can reproduce the 
error -
just try to make transformation using standard java transformer ( 
javax.xml.transform ).

Original comment by ase...@gmail.com on 4 May 2008 at 11:27

GoogleCodeExporter commented 9 years ago
Aserba,

Thank you!  Sorry for the late reply, I started a new project May 5th and it's 
eating
a lot of my time.

I will try to replicate your error and will make corrections.  Maybe it would be
better to try to make Jason's solution pass the unit tests rather than to fix my
solution.  I will find out and let you know.

-Anthony

Original comment by anthony....@gmail.com on 9 May 2008 at 9:33

GoogleCodeExporter commented 9 years ago
Ok, I was able to replicate that error in a slightly different way.   To fix the
problem, I have taken Jason's XSLT sheets for JSON, modified them so that all 
the
unit tests pass, and replicated the changes into the JS XSLT.  His code was 
pretty
easy to work with and fairly easy to port back to the Javascript.

In initial testing, I no longer get the same errors.  I am not directly testing 
using
javax.xml.transform, though, which I will try next.  In the meantime, here are 
the
initial versions of these files.  Please check them out and let me know if this 
looks
like it is going in the right direction.

Original comment by anthony....@gmail.com on 31 May 2008 at 12:59

Attachments:

GoogleCodeExporter commented 9 years ago
Anthony,

"The expected output of 

<root><item>1</item><item>2</item><name>test</name></root>

should be:

{"root":{"item":[1,2],"name":"test"}}

correct?"

Why not just {"item":[1,2],"name":"test"}. The root node should be absorbed.

/Jakob

Original comment by kr...@kruse-net.dk on 2 Jun 2008 at 9:02

GoogleCodeExporter commented 9 years ago
Ok, I will take a look at that and see if I can make that happen.  I didn't 
realize
that requirement in the convention to absorb the root node would interact with 
this
change - in my own code the arrays are usually deeply embedded so it never came 
up.

Original comment by anthony....@gmail.com on 3 Jun 2008 at 5:26

GoogleCodeExporter commented 9 years ago
Anthony, could you possibly put the latest versions of the XSLT's that fix this 
issue in the repository, in a branch maybe?

Original comment by emyr.tho...@gmail.com on 3 Sep 2010 at 2:17

GoogleCodeExporter commented 9 years ago
newbee!

I have the following issue:

A section of my XML looks like this:

<RatingDetails>
<RatingDetail>
 <Semantic>Communication Ability</Semantic>
 <Rating>5</Rating>
 </RatingDetail>
<RatingDetail>
 <Semantic>Responsiveness</Semantic>
 <Rating>5</Rating>
 </RatingDetail>
<RatingDetail>
 <Semantic>Quality of Service</Semantic>
 <Rating>5</Rating>
 </RatingDetail>
<RatingDetail>
 <Semantic>Value for Money</Semantic>
 <Rating>5</Rating>
 </RatingDetail>
 </RatingDetails>

But after applying the stylesheet - I get this:

"RatingDetails":[{"Semantic":"Communication 
Ability","Rating":5},{"Semantic":"Responsiveness","Rating":5},{"Semantic":"Quali
ty of Service","Rating":5},{"Semantic":"Value for Money","Rating":5}]

What happened to the "RatingDetail" node? (without the 's')

Original comment by maxh...@gmail.com on 8 Jun 2011 at 1:57

GoogleCodeExporter commented 9 years ago
I just to learn it,could you give me some example about how to call it? thank 
you very much

Original comment by chengdou...@gmail.com on 2 Jul 2013 at 9:10

GoogleCodeExporter commented 9 years ago
Guys, you forgot about skip hint!

Original comment by dms.pop3...@gmail.com on 22 Apr 2014 at 12:19

GoogleCodeExporter commented 9 years ago
we faced the same issue as reported above 
Here is the updated output for user input

{"RatingDetails":{"RatingDetail":[{"Semantic":"Communication 
Ability","Rating":5},{"Semantic":"Responsiveness","Rating":5},{"Semantic":"Quali
ty of Service","Rating":5},{"Semantic":"Value for Money","Rating":5}]}}

also attaching the updated xsl.

Please update if it passes your tests.

Original comment by findsand...@gmail.com on 25 Jul 2014 at 6:38

Attachments: