I missed the part in which it <TextContent>turns into <HtmlContent>, but <VisualContent id="n" />becomes <img data-id="n" />, because this question is quite difficult without these distracting factors.
, . - , :
<?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="prevByName" match="TextContent/*" use="generate-id(preceding-sibling::*[not(name()=name(current()))][1])" />
<xsl:template match="/">
<Article><TextContent>
<xsl:for-each select="Article/TextContent/*[generate-id()=generate-id(key('prevByName', generate-id(preceding-sibling::*[not(name()=name(current()))][1]))[1])]">
<xsl:variable name="myGroup" select="key('prevByName', generate-id(preceding-sibling::*[not(name()=name(current()))][1]))" />
<xsl:choose>
<xsl:when test="count($myGroup) > 1">
<ul>
<xsl:for-each select="$myGroup">
<li><xsl:copy-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</TextContent></Article>
</xsl:template>
</xsl:stylesheet>
:
<?xml version="1.0" encoding="utf-8"?>
<Article>
<TextContent>
<p>lorem</p>
<ul>
<li>
<VisualContent id="1"/>
</li>
<li>
<VisualContent id="2"/>
</li>
<li>
<VisualContent id="3"/>
</li>
</ul>
<p>ipsum</p>
<VisualContent id="4"/>
<p>dolor</p>
<VisualContent id="5"/>
</TextContent>
</Article>
EDIT:
, "VisualContent":
<?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="prevByName" match="TextContent/*" use="generate-id(preceding-sibling::*[not(name()=name(current()))][1])" />
<xsl:template match="/Article/TextContent">
<Article><TextContent>
<xsl:apply-templates select="*"/>
</TextContent></Article>
</xsl:template>
<xsl:template match="TextContent/*[not(self::VisualContent)]">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="VisualContent[generate-id()=generate-id(key('prevByName', generate-id(preceding-sibling::*[not(name()=name(current()))][1]))[1])]">
<xsl:variable name="myGroup" select="key('prevByName', generate-id(preceding-sibling::*[not(name()=name(current()))][1]))" />
<xsl:choose>
<xsl:when test="count($myGroup) > 1">
<ul>
<xsl:for-each select="$myGroup">
<li><xsl:copy-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
, - , , , ...