How to create sections from a linear structure with title nodes?

Discussion in 'XML' started by Erhard Schwenk, Aug 1, 2003.

  1. Hi there,

    Maybe this is a faq, if so, point me the URL please.

    I have to do some xml to xml transformations in xslt, where I have -
    simplified - the following:

    input:
    <doc>
    <a/><a/><b/><a><a><a><b/><a><b/><a><a>
    </doc>

    output:
    <doc>
    <section><x/><x/></section>
    <section><y/><x/><x/><x/></section>
    <section><y/><x/></section>
    <section><y/><x/><x/>
    </doc>

    e.g. I want to transform <a/> to <x/> and <b/> to <y/>, but start a
    new section at each <b/>

    For Example, you could think of a Text with headlines and paragraphs
    and I want to start a new Page at each Headline.

    Now there is the question, how to do this in xslt. Some Idea was the
    following:

    <section>
    <xsl:apply-templates select="All a-nodes before the first b-node"/>
    </section>
    <xsl:for-each select="b">
    <section>
    <xsl:apply-templates select="b"/>
    <xsl:apply-templates select="all following a-nodes up to the next
    b-node"/>
    </section>
    </xsl:for-each>

    My Problem are now the two xpath-Expressions "All a-nodes before the
    first b-node" and "all following a-nodes up to the next b". Googling
    around I did not find any useful tips on this, so perhaps someone here
    has an Idea?

    MfG,
     
    Erhard Schwenk, Aug 1, 2003
    #1
    1. Advertisements

  2. Erhard Schwenk

    Andy Fish Guest

    Erhard

    you're going about the problem the wrong way. your XML is essentially
    document-oriented rather than strongly structured so you should process it
    by matching templates. something like:

    <xsl:template match="a">
    <x><xsl:apply-templates/></x>
    </xsl:template>

    <xsl:template match="b">
    <section><y><xsl:apply-templates/></y></section>
    </xsl:template>

    don't think of XSL as a programming language - it's a declarative way of
    specifying how you want the XML transformed. I suggest you read a good book
    like Jeni Tennison's 'beginning xslt' which will explain it all in a
    sensible order and show you the various techniques.

    Andy
     
    Andy Fish, Aug 1, 2003
    #2
    1. Advertisements

  3. Andy Fish wrote:


    Hmm meanwhile I got some solution like this:

    <xsl:template match="/">
    <section>
    <xsl:apply-templates
    select="*[(count(preceding-sibling::a)) = 0 and name != 'a']"/>
    </section>

    <xsl:for-each select="a">
    <xsl:variable name="number" select="1+count(preceding-sibling::a"/>

    <section>
    <x/>
    <xsl:apply-templates
    select="../*[count(preceding-sibling::a) = $number
    and name != 'a'"
    />
    </section>
    </xsl:for-each>
    </xsl:template>

    <xsl:template match="b">
    <y/>
    </xsl:template>


    Seems to work, maybe there is something better. Thanks for your hints
    anyway.

    BTW: if there is someone who maintains a faq with XSL Snippets, I think
    this one would be a great addition. Feel free to copy.
     
    Erhard Schwenk, Aug 4, 2003
    #3
  4. It's certainly very similar to the question posted here by Marc
    Baumgartner () in June.
    Here's a general approach to this kind of problem.

    Construct an XPath that will identify one member of each group. In
    this case, the first <a> is the obvious thing to choose (if there was
    always a <b> in the group, the <b> would be a better choice). We can
    identify the first <a> in each group as being an <a> whose immediate
    preceding-sibling is not an <a>:

    a[local-name(preceding-sibling::*[1]) != 'a']

    (If there is an <a> at the start without a preceding b, this will
    still work because local-name retrns an exmpty string for an empty
    node-set.)

    Call apply-templates on that path. Use a mode, for reasons that will
    be apparent later:

    <xsl:template match="doc">
    <doc>
    <xsl:apply-templates mode="group"
    select="a[local-name(preceding-sibling::*[1]) != 'a']"/>
    </doc>
    </xsl:template>

    In the template for the selected representative, output the group
    wrapper.

    <xsl:template match="a" mode="group">
    <section>
    ...
    </section>
    </xsl:template>

    Now construct an XPath that will select all the elements in the same
    group as the representative.

    The XPath to select the group members will use something they have in
    common with the representative element. Thinking up a test for this
    is often the tricky bit. In this case, we want the <b> before the
    representative <a> (if there is one) and the <a>s that are following
    siblings of the representative <a> without any intervening <b>s. We
    can do it by selecting the siblings that have the same number of
    following-sibling <b>s as the representative <a>:

    ../*[count(following-sibling::b) = count(current()/following-sibling::b)]

    Note the use of current() to get hold of the representative <a> inside
    the predicate. We could have assigned it to a variable instead, but
    this is simpler.

    Sometimes the obvious test involves comparing two nodes for identity.
    For that you can use the trick generate-id(node1) = generate-id(node2).

    Inside the wrapper, call apply-templates on the members of the group.
    This is why we used a mode for the group template; we are going to
    call apply-templates on the representative again in its role as a
    member of the group.

    <xsl:template match="a" mode="group">
    <section>
    <xsl:apply-templates select="../*[count(following-sibling::b) =
    count(current()/following-sibling::b)]"/>
    </section>
    </xsl:template>

    Now write templates for the group members, which is trivial in this case.

    <xsl:template match="a">
    <a/>
    </xsl:template>

    <xsl:template match="b">
    <y/>
    </xsl:template>

    -- Richard
     
    Richard Tobin, Aug 5, 2003
    #4
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.