How to extract p tag out of ul tag in xslt

How to extract p tag out of ul tag in xslt

I am trying to transform multiple p tags. If tag has @style then it should be list item otherwise it should just be p tag itself. This is my input:

<blockquote>
<p style="margin-left: 40px;">test</p>
<p style="margin-left: 40px;">some test</p>
<p style="margin-left: 40px;">another test</p>
<p>
    <em>paragraph</em>
</p>
<p style="margin-left: 40px;">a</p>
<p style="margin-left: 40px;">b</p>
<p style="margin-left: 40px;">c</p>
<p>
    <em>another paragraph</em>
</p>
<p style="margin-left: 40px;">qwert</p>
<p style="margin-left: 40px;">asdf</p>
<p style="margin-left: 40px;">sdfg</p>
<p>
    <em>another para</em>
</p>
<p style="margin-left: 40px;">sdfg</p>
<p style="margin-left: 40px;">asdf</p>
<p style="margin-left: 40px;">qwert</p>
And this my xslt template:
    <xsl:template match="blockquote">
    <xsl:element name="div">
        <xsl:attribute name="class" select="'section-content'"/>
        <xsl:attribute name="data-class" select="'blockquote'"/>
        <xsl:attribute name="data-unlock" select="'ALL'"/>
        <xsl:element name="ul">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:element>
</xsl:template>

    <xsl:template match="blockquote/p[@style]">
    <xsl:element name="li">
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

<xsl:template match="blockquote/p[not(@style)]">
    <xsl:element name="p">
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

This is output I am getting currently but it is not html5 compliant since p cannot be child of ul tag:

<div class="section-content" data-class="blockquote" data-unlock="ALL">
<ul>
    <li>test</li>
    <li>some test</li>
    <li>another test</li>                     
    <p>
        <i>paragraph</i>
    </p>
    <li>a</li>
    <li>b</li>
    <li>c</li>
    <p>
        <i>another paragraph</i>
    </p>
    <li>qwert</li>
    <li>asdf</li>
    <li>sdfg</li>
    <p>
        <i>another para</i>
    </p>
    <li>sdfg</li>
    <li>asdf</li>
    <li>qwert</li>
</ul>

What I am trying to get is this markup:

<div class="section-content" data-class="blockquote" data-unlock="ALL">
<ul>
    <li>test</li>
    <li>some test</li>
    <li>another test</li>
</ul>                         
<p>
    <i>paragraph</i>
</p>
<ul>
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>   

<p>
    <i>another paragraph</i>
</p>
<ul>
    <li>qwert</li>
    <li>asdf</li>
    <li>sdfg</li>
</ul>
<p>
    <i>another para</i>
</p>
<ul>
    <li>sdfg</li>
    <li>asdf</li>
    <li>qwert</li>
</ul>

To achieve desired output what kind of template rules I should use?

Answer

Seems like a task for for-each-group group-adjacent:

  <xsl:template match="blockquote">
    <div class="section-content" data-class="blockquote" data-unlock="ALL">
      <xsl:for-each-group select="*" group-adjacent="boolean(self::p[@style])">
        <xsl:choose>
          <xsl:when test="current-grouping-key()">
            <ul>
              <xsl:apply-templates select="current-group()" mode="list-item"/>
            </ul>
          </xsl:when>
          <xsl:otherwise>
            <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </div>
  </xsl:template>
  
  <xsl:mode name="list-item" on-no-match="shallow-copy"/>
  
  <xsl:template mode="list-item" match="p">
    <li>
      <xsl:apply-templates mode="#current"/>
    </li>
  </xsl:template>
  
  <xsl:template match="p/em">
    <i>
      <xsl:apply-templates/>
    </i>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:output indent="yes"/>

Read more.

Enjoyed this article?

Check out more content on our blog or follow us on social media.

Browse more articles