I wrote code with a similar purpose to this once, for the stylesheet used to produce the errata for the XSLT 2.0 and related specs. You might find it gives you some ideas. This contains logic to check that there was no text in the spec "affected" by more than one erratum. The code is as follows (it uses saxon:evaluate because the XML document defining an erratum contains XPath expressions indicating which sections in the base document are "affected").
A key aim here is not to determine whether two XPath expressions select overlapping nodes, but whether N such expressions have any overlaps, where the expressions are not known in advance - and we want to do this without looking for overlaps between all pairs of expressions.
There's an interesting expression here: count($x) != count($x/.). Here ($x/.) is used to force elimination of duplicate nodes from a node sequence, so the test is asking whether elimination of duplicates removes any nodes, i.e. whether $x contains any duplicates.
<!-- The following template checks that there is no element in the source document
that is replaced or deleted by more than one erratum -->
<xsl:template name="check-for-conflicts">
<xsl:variable name="directly-affected-elements"
select="er:eval-all(/er:errata/er:erratum[not(@superseded)]//er:old-text[not(starts-with(@action, 'insert-'))])"/>
<xsl:variable name="all-affected-elements"
select="for $e in $directly-affected-elements return $e/descendant-or-self::*"/>
<xsl:if test="count($all-affected-elements) != count($all-affected-elements/.)">
<!-- we now know there are duplicates, we just need to identify them... -->
<xsl:for-each-group select="$all-affected-elements" group-by="generate-id()">
<xsl:if test="count(current-group()) gt 1">
<xsl:variable name="id" select="(ancestor::*/@id)[last()]"/>
<xsl:variable name="section" select="$spec/key('id',$id)"/>
<xsl:variable name="section-number">
<xsl:number select="$section" level="multiple" count="div1|div2|div3|div4"/>
</xsl:variable>
<xsl:variable name="loc" select="er:location($section, .)"/>
<p style="color:red">
<xsl:text>WARNING: In </xsl:text>
<xsl:value-of select="$section-number, $section/head"/>
<xsl:text> (</xsl:text>
<xsl:value-of select="$loc"/>
<xsl:text>) Element is affected by more than one change</xsl:text>
</p>
</xsl:if>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
<!-- Support function for the check-for-conflicts template.
This function builds a list (retaining duplicates) of all elements
in the source document directly affected by a replacement or deletion.
"Directly affected" means that the element is explicitly selected for replacement
or deletion; the descendants of this element are indirectly affected. -->
<xsl:function name="er:eval-all" as="element()*">
<xsl:param name="in" as="element(er:old-text)*"/>
<xsl:for-each select="$in">
<xsl:variable name="id" select="@ref"/>
<xsl:variable name="section" select="$spec/key('id',$id)"/>
<xsl:variable name="exp" select="@select"/>
<xsl:variable name="nodes" select="$section/saxon:evaluate($exp)"/>
<xsl:sequence select="$nodes"/>
</xsl:for-each>
</xsl:function>