This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="@*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="p[not(node())][last()][count(../p[not(node())]) mod 2]" priority="1">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="p[not(node())][not(position() mod 2)]" priority="1"/>
<xsl:template match="p[not(node())]">
<p>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</p>
<xsl:apply-templates select="following-sibling::p[not(node())][1]/following-sibling::node()[1]"/>
</xsl:template>
</xsl:stylesheet>
With inputs:
1 - (p
with content and two empty p
)
<text><p>some text</p> <p/>some text <emph>....</emph>.........<p/> </text>
2 - (Four empty p
)
<text><p/>some text<p/> <p/>some text <emph>....</emph>.........<p/> </text>
3 - (Three empty p
)
<text><p/>some text <p/>some text <emph>....</emph>.........<p/> </text>
4 - (p
with content and three empty p
)
<text><p/>some text <p/>some text <p><emph>....</emph></p>.........<p/> </text>
5 - (p
with content two empty p
siblings and other level two empty p
)
<text><p>some text</p> <p/>some text <emph><p/>....<p/></emph>.........<p/> </text>
Results:
1 -
<text><p>some text</p><p>some text <emph>....</emph>.........</p></text>
2 -
<text><p>some text</p><p>some text <emph>....</emph>.........</p></text>
3 -
<text><p>some text </p>some text <emph>....</emph>.........<p></p></text>
4 -
<text><p>some text </p>some text <p><emph>....</emph></p>.........<p></p></text>
5 -
<text><p>some text</p><p>some text <emph><p>....</p></emph>.........</p></text>
Note: Breaking the recursion and following node by node in "serial" way.
EDIT: I think that now it covers every case. Take notice that you can't define with your format when you had odds p
wich of the two (preceding or following) want to enclosed. So, this is "associative" left to rigth.
EDIT 2: Better use of last()
(How did I miss that?)
EDIT 3: Better pattern matching allow to compact code.