Alex Rivera | Logout

How can I get array of elements, including missing elements, using XPath in XSLT?

Asked 2012-03-10T17:55:58.853
8

Given the following XML-compliant HTML:

<div>
 <a>a1</a>
 <b>b1</b>
</div>

<div>
 <b>b2</b>
</div>

<div>
 <a>a3</a>
 <b>b3</b>
 <c>c3</c>
</div>

doing //a will return:

[a1,a3]

The problem with above is that the third column data is now in second place, when A is not found it is completely skipped.

how can you express an xpath to get all A elements which will return:

[a1, null, a3]

same case for //c, I wonder if it's possible to get

[null, null, c3]

UPDATE: consider another scenario where are no common parents <div>.

<h1>heading1</h1>
 <a>a1</a>
 <b>b1</b>


<h1>heading2</h1>
 <b>b2</b>


<h1>heading3</h1>
 <a>a3</a>
 <b>b3</b>
 <c>c3</c>

UPDATE: I am now able to use XSLT as well.

Edit
Report

2 Answers

3

Solution for the first problem:

This XPath expression:

    /*/div/a
|
    /*/div[not(a)]

When evaluated against the following XML document:

<t>
    <div>
        <a>a1</a>
        <b>b1</b>
    </div>
    <div>
        <b>b2</b>
    </div>
    <div>
        <a>a3</a>
        <b>b3</b>
        <c>c3</c>
    </div>
</t>

selects the following three nodes (a, div, a):

<a>a1</a>
<div>
    <b>b2</b>
</div>
<a>a3</a>

In your java array any selected non-a element should be treated as (or replaced by) null.


Here is one solution to the second problem:

Use these XPath expressions for selecting the a elements from each group:

For the first group:

/*/h1[1]
   /following-sibling::a
      [not(/*/h1[2])
     or
       count(.|/*/h1[2]/preceding-sibling::a)
      =
       count(/*/h1[2]/preceding-sibling::a)
      ]

For the second group:

/*/h1[2]
   /following-sibling::a
      [not(/*/h1[3])
     or
       count(.|/*/h1[3]/preceding-sibling::a)
      =
       count(/*/h1[3]/preceding-sibling::a)
      ]

And for the 3rd group:

/*/h1[3]
   /following-sibling::a
      [not(/*/h1[4])
     or
      count(.|/*/h1[4]/preceding-sibling::a)
      =
       count(/*/h1[4]/preceding-sibling::a)
      ]

In case that:

count(/*/h1)

is $cnt,

generate $cnt such expressions (for i = 1 to $cnt) and evaluate all of them. The selected nodes by each of them either

answered 2012-03-17T05:05:31.373
3

I suggest you use the following, which might be rewritten to an xsl:function where the parent node name (here: div) is parametrized.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/">
    <root>
        <aList><xsl:copy-of select="$divIncludingNulls//a"/></aList>
        <bList><xsl:copy-of select="$divIncludingNulls//b"/></bList>
        <cList><xsl:copy-of select="$divIncludingNulls//c"/></cList>
    </root>
</xsl:template>

<xsl:variable name="divChild" select="distinct-values(//div/*/name())"/>

<xsl:variable name="divIncludingNulls">
    <xsl:for-each select="//div">
        <xsl:variable name="divElt" select="."/>
        <div>
            <xsl:for-each select="$divChild">
                <xsl:variable name="divEltvalue" select="$divElt/*[name()=current()]"/>
                <xsl:element name="{.}">
                    <xsl:choose>
                        <xsl:when test="$divEltvalue"><xsl:value-of select="$divEltvalue"/></xsl:when>
                        <xsl:otherwise>null</xsl:otherwise>
                    </xsl:choose>
                </xsl:element>
            </xsl:for-each>
       </div>
    </xsl:for-each>
</xsl:variable>

</xsl:stylesheet>

Applied to

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <div>
     <a>a1</a>
     <b>b1</b>
    </div>

    <div>
     <b>b2</b>
    </div>

    <div>
     <a>a3</a>
     <b>b3</b>
     <c>c3</c>
    </div>
</root>

the output is

answered 2012-03-19T09:39:11.353

Your Answer