XSLT-1.0: How to insert an element at a specific position with respect to the parent element

|▌冷眼眸甩不掉的悲伤 提交于 2021-01-28 07:22:20

问题


I am trying to find out the position of a child element with respect to its parent.
I have the following input XML:

<Begin>
    <tag1>g</tag1>
    <tag2>b</tag2>
    <tag3>c</tag3>
    <tag5>e</tag5>
</Begin>

I need to know the position of <tag3> with respect to <Begin>, i.e. 3

Can this be done in XSLT?
My current XSLT looks like this:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Begin[count(tag4)=0]">
    <xsl:copy>
      <!-- Need to find the position of /Begin/tag3" here -->
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Basically, what I am trying to do is to find the position of <tag3> in the input XML
and insert <tag4> exactly after <tag3> using the position.

So my question is:
How can I insert an element <tag4> at the position between <tag3> and <tag5>?
This is my intent!


回答1:


An easy way to insert a new element sorted alphabetically is to use <xsl:sort> in the identity template like this:

<xsl:template match="node()|@*">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*">
      <xsl:sort select="local-name()" />
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>

This modification does copy all elements sorted alphabetically by the local-name/tag-name/element-name.




回答2:


You can find out the position of the child using either xsl:number, or count(preceding-sibling::*).

But that doesn't help you with the next part of the problem: insert <tag4> exactly after <tag3> using the position. In XSLT you always add new data to the current writing position in the result tree, you can't add it in a specific position. So I think we need to know what you actually want to achieve, rather than your proposed method of achieving it.




回答3:


You do not need to know the position of a node in order to insert another node after (or before) it. In your given example, you can use:

XSLT

<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:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="tag3">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    <tag4>d</tag4>
</xsl:template>

</xsl:stylesheet>

to return:

<?xml version="1.0" encoding="UTF-8"?>
<Begin>
    <tag1>g</tag1>
    <tag2>b</tag2>
    <tag3>c</tag3>
    <tag4>d</tag4>
    <tag5>e</tag5>
</Begin>

Of course, if there is no tag3 element in the provided input, then nothing will happen. And if there are several, the new element will be inserted after each one.




回答4:


Like mentioned in my comment, and very similar to @michael.hor257k's answer, instead of checking the position, just change your match to Begin[not(tag4)]/tag3 and then output both tag3 and the new tag4...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Begin[not(tag4)]/tag3">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    <tag4>d</tag4>
  </xsl:template>
</xsl:stylesheet>

Hi @DanielHaley- :) that actually works. But just curious.. how can we find the positon of tag3 w.r.t Begin?

If you were using XSLT 2.0, you could just use <xsl:number select="tag3" count="*"/> (1.0 doesn't allow the select element on xsl:number).

In 1.0, you can use a moded template that uses either xsl:number or count(preceding-sibling::*) (as suggested by @michael-kay)...

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Begin[not(tag4)]">
    <xsl:copy>
      <!-- Need to find the position of /Begin/tag3" here -->
      <xsl:apply-templates select="tag3" mode="getPos"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*" mode="getPos">
    <xsl:number count="*"/>
    <!-- alternative to xsl:number:
    <xsl:value-of select="count(preceding-sibling::*) + 1"/>
    -->
  </xsl:template>

</xsl:stylesheet>

The output would be:

<Begin>3</Begin>


来源:https://stackoverflow.com/questions/43359395/xslt-1-0-how-to-insert-an-element-at-a-specific-position-with-respect-to-the-pa

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