A variable with content instead of select holds a temporary tree, a new document you can query like any input (XSLT 1.0's "result tree fragment" could not be), which makes multi-pass transformations easy. xsl:sequence returns items as they are, including existing nodes, while xsl:copy-of creates new ones, so functions should return with xsl:sequence:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:bn="urn:booknest:fn"
exclude-result-prefixes="#all" expand-text="yes">
<xsl:output method="text"/>
<xsl:variable name="lines">
<xsl:for-each select="doc('../s2-15/orders.xml')//order">
<xsl:variable name="price" select="$catalog//book[identifier = current()/@sku]//price"/>
<line sku="{@sku}" amount="{xs:integer(@qty) * xs:decimal($price)}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="catalog" select="/"/>
<xsl:function name="bn:top" as="element(book)">
<xsl:sequence select="$catalog//book[.//price = max($catalog//price)]"/>
</xsl:function>
<xsl:template match="/">
<xsl:text>{count($lines/*)} lines: {sum($lines/line/xs:decimal(@amount))} </xsl:text>
<xsl:variable name="copy" as="element()"><xsl:copy-of select="bn:top()"/></xsl:variable>
<xsl:text>Same node: {bn:top() is //book[2]}, copy: {$copy is //book[2]} </xsl:text>
</xsl:template>
</xsl:stylesheet>Output
8 lines: 289.62 Same node: true, copy: false
$catalog keeps the catalog in reach while the context is an order in another file. Temporary-tree nodes are untyped, so cast @amount; and with an untyped @qty, @qty * $price becomes xs:double (a first draft summed to 289.61999999999995).