Copying and adding a parent node with multiple children

不打扰是莪最后的温柔 提交于 2020-01-06 07:23:13

问题


I made an error on a previous post.

I have XML data that works like this (this is only an example and number of chapters and pages are both variable).

<books>
 <chapter></chapter>
 <page></page>
 <page></page>
 <page></page>
 <chapter></chapter>
 <page></page>
 <page></page>
 <chapter></chapter>
 <page></page>
 <page></page>
 <page></page>
 <page></page>
</books>

I am trying to recreate it to look like this

<books>
 <book>
  <chapter></chapter>
  <page></page>
  <page></page>
  <page></page>
 </book>
 <book>
  <chapter></chapter>
  <page></page>
  <page></page>
 </book>
 <book>
  <chapter></chapter>
  <page></page>
  <page></page>
  <page></page>
  <page></page>
 </book>
</books>

As far as I can tell there isn't a way to put a loop inside a loop until there is a new chapter.


回答1:


try something like this:

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

    <xsl:output indent="yes"/>

    <xsl:template match="/">
        <books>
            <xsl:for-each select="books/chapter">
                <!-- for each chapter node, record the number of preceding sibling,
                     for the first chapter there is none, so that is why I added +1,
                     so when I count all the preceding sibling chapter of page, I will
                     get a match -->
                <xsl:variable name="chapter_count" select="count(preceding-sibling::chapter) + 1"/>
                <book>
                    <xsl:copy-of select="."/>
                    <!-- This code will ensure that the following sibling pages that
                         will be copied has the same number of preceding sibling
                         chapter (for pages, notice that I did not add 1 in the
                         predicate). So for the first chapter node, $chapter_count is 1
                         and the number of preceding sibling chapters at page node is 1,
                         thus the match -->
                    <xsl:copy-of select="following-sibling::page[count(preceding-sibling::chapter) = $chapter_count]"/>
                </book>
            </xsl:for-each>
        </books>
    </xsl:template>

</xsl:stylesheet>



回答2:


I believe the simple - and efficient(!) - way to do this is by using a key:

<?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" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="page-by-chapter" match="page" use="generate-id(preceding-sibling::chapter[1])" />

<xsl:template match="/">
    <books>
        <xsl:for-each select="books/chapter">
            <book>
                <xsl:copy-of select=". | key('page-by-chapter', generate-id())"/>
            </book>
        </xsl:for-each>
    </books>
</xsl:template>

</xsl:stylesheet>



回答3:


@JoelMLamsen has the right idea and his solution will work fine, but it could be simplified a bit to not use counting. We'll try to directly represent the basic logic of

For each chapter, process the following pages whose immediately preceding chapter is this one.

We can do it like this:

<xsl:template match="books">
    <books>
        <xsl:apply-templates select="chapter"/>
    </books>
</xsl:template>

<xsl:template match="chapter">
    <xsl:variable name="this" select="generate-id()"/>
    <book>
        <xsl:copy-of select="."/>
        <xsl:copy-of 
            select="following-sibling::page[generate-id(preceding-sibling::chapter[1]) = $this]"/>
    </book>
</xsl:template>

In case you need help understanding the condition, you can read it in English as:

following-sibling           of all the following
::page                      page elements
[                           take the ones where
  generate-id(              the unique id of 
    preceding-sibling       of all its preceding
    ::chapter               chapter elements
    [1]                     (the most recent one)
  )
  =                         is equal to
  $this                     the unique id of the chapter we are on
]

A couple of notes for those who are newer to XSLT:

  1. We remember the unique id of the current chapter in the this variable. Alternatively, we could use generate-id(current()) inside the [] condition.

  2. The preceding-sibling axis returns the results in reverse document order, so the [1] element is the immediately preceding one.

  3. Instead of looping over chapters in the root template using for-each, this uses templates for books and chapter, which some might say is a bit more idiomatic XSLT. The default root template will take care of invoking the books template.



来源:https://stackoverflow.com/questions/26377953/copying-and-adding-a-parent-node-with-multiple-children

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!