0

I have to do the following:

<A>Something <B>something else</B> more</A>

should become

<A>Something </A><B>something else</B><A> more</A>

I.e. whenever a <B> occurs as a direct child of an <A>, the <A> should be closed, the <B> should be outside the <A>, and after the <B> the <A> should be opened again.

What is the easiest way to achieve this?

Edit:

<A>Something <C>anything</C><B>something</B> foo</A>

should become

<A>Something <C>anything</C></A><B>something</B><A> foo</A>

I.e. <B> elements and only <B> elements should be freed from their direct <A> parent.

JohnB
  • 13,315
  • 4
  • 38
  • 65
  • Can you please post your try here. – Lingamurthy CS Jun 23 '15 at 09:38
  • The problem is somewhat similar to this one http://stackoverflow.com/questions/10859703/xpath-select-all-elements-between-two-specific-elements Yet I have no idea how to generalize that approach to the case where there might be arbitrarily many ``'s inside ``. – JohnB Jun 23 '15 at 09:57
  • One example does not make a rule. – michael.hor257k Jun 23 '15 at 09:59
  • I am absolutely sure you are trying to be helpful. Unfortunately, your comments do not bring me closer to a solution. – JohnB Jun 23 '15 at 10:04
  • 1
    @JohnB Before there can be a solution, there needs to be a problem. And the problem must be well-defined. Otherwise the only possible answer is a guess. – michael.hor257k Jun 23 '15 at 10:17

2 Answers2

2

The following stylesheet will return the requested result when applied to your example.

However, as I said in a comment to your question, there are many possible scenarios that your question does not cover. Specifically, what should happen to other children of A, that are neither B nor text nodes.

Note also that if A is the root element (as in your example), the result be will be an ill-formed XML document.

XSLT 1.0

<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="A[B]">
    <xsl:apply-templates select="@*|node()"/>
</xsl:template>

<xsl:template match="A[B]/text()">
    <A>
        <xsl:value-of select="."/>
    </A>
</xsl:template>

</xsl:stylesheet>

Test input:

<root>
    <A>Something <B>something else</B> more</A>
    <A>Something <B>something else</B> <C>altogether</C> more</A>
    <A>Something more</A>
    <A>Something <C>altogether</C> more</A>
</root>

Result

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <A>Something </A>
   <B>something else</B>
   <A> more</A>
   <A>Something </A>
   <B>something else</B>
   <C>altogether</C>
   <A> more</A>
   <A>Something more</A>
   <A>Something <C>altogether</C> more</A>
</root>

Edit

If I understand better your requirement (which is not at all certain), you want to do something like:

XSLT 1.0

<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="A[B]">
    <xsl:apply-templates select="B"/>
</xsl:template>

<xsl:template match="A/B">
    <xsl:variable name="i" select="position()" />
    <xsl:if test="$i=1">
        <xsl:variable name="first-group" select="preceding-sibling::node()"/>
        <xsl:if test="$first-group">
            <A>
                <xsl:copy-of select="$first-group"/>
            </A>
        </xsl:if>
    </xsl:if>
    <xsl:copy-of select="."/>
    <xsl:variable name="this-group" select="following-sibling::node()[not(self::B) and count(preceding-sibling::B)=$i]"/>
    <xsl:if test="$this-group">
        <A>
            <xsl:copy-of select="$this-group"/>
        </A>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
-1

You can try something like this

  <xsl:if test="count(A//B)!=0">
    <xsl:for-each select="A/child::text() | A/B">
      <xsl:choose>
        <xsl:when test="local-name()='B'">
          <xsl:copy-of select="."/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:element name="A">
            <xsl:value-of select="."/>
          </xsl:element>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:if>
Saurav
  • 592
  • 4
  • 21