Skip to content
Christian Lück edited this page Oct 16, 2025 · 16 revisions

Citation trees for <pb/>

Citeation trees for <pb/> and other milestone-like markup are complicated. This document is an investigation on options that we have.

For all approaches there's test case in test/john.xml.

approach nice¹ ... reasonable² citation tree levels $ref ... $start+$end returns page correctly
??? probably impossible ??? 1 neither required nor encouraged
--------------------------------------------------- ----------- --------------------------- -------- ---------- ------------------------------------
Simple 1 ❌ (3)
One-level intersection 1
Element construction 1
Two-level intersection ✅❌ 2
Second-level start end 2

Notes

  1. A nice citation tree is one that needs one level and has one member per page. Each nice citation tree is also a reasonable citation tree.
  2. A reasonable citation tree has one member per page on a distinct (the first) level, but may have additional levels.
  3. <pb/> of next page is contained in output

Simple

The most simple approach is to simply select the <pb/> elements:

<refsDecl n="page-simple">
   <citeStructure unit="page" match="//body//pb" use="@n" delim="p."/>
</refsDecl>

This gives us a nice citation tree:

target/bin/xslt.sh -config:saxon.he.xml -xsl:xsl/document.xsl -s:test/john.xml tree=page-simple
  "member": [
    {
      "level": 1,
      "identifier": "p.1",
      "parent": null,
      "citeType": "page",
      "@type": "CitableUnit"
    },
    {
      "level": 1,
      "identifier": "p.2",
      "parent": null,
      "citeType": "page",
      "@type": "CitableUnit"
    },
    {
      "level": 1,
      "identifier": "p.3",
      "parent": null,
      "citeType": "page",
      "@type": "CitableUnit"
    }
  ],

What can we get from specifying ref?

 target/bin/xslt.sh -config:saxon.he.xml -xsl:xsl/document.xsl -s:test/john.xml tree=page-simple ref=p.1
<?xml version="1.0" encoding="UTF-8"?><TEI xmlns="http://www.tei-c.org/ns/1.0"><dts:wrapper xmlns:dts="https://w3id.org/api/dts#"><pb n="1"/></dts:wrapper></TEI>

That's only the milestone-like <pb/>.

What can we get from specifying start through end? It gives us the first page's content.

target/bin/xslt.sh -config:saxon.he.xml -xsl:xsl/document.xsl -s:test/john.xml tree=page-milestones start=p.1 end=p.2
<?xml version="1.0" encoding="UTF-8"?><TEI xmlns="http://www.tei-c.org/ns/1.0"><dts:wrapper xmlns:dts="https://w3id.org/api/dts#"><pb n="1"/>
         
            <head>The book of John</head>The book of John
            
               <milestone unit="theme" xml:id="creation-start"/>
               <l n="1">In the beginning was the Word, and the Word was with God, and the Word was
                  God.</l>In the beginning was the Word, and the Word was with God, and the Word was
                  God.
               <l n="2">He was with God in the beginning.</l>He was with God in the beginning.
               <l n="3">Through him all things were made; without him nothing was made that has been
                  made.</l>Through him all things were made; without him nothing was made that has been
                  made.
               <l n="4">In him was life, and that life was the light of all mankind.</l>In him was life, and that life was the light of all mankind.
               <pb n="2"/></dts:wrapper></TEI>

However, specifying from p.1 to p.2 when we only want p.1 is not obvious. We shouldn't even think of establishing it as a convention.

Thus, let's have a look at expressions, that could get the nodes between two milestone-like elements.

Intersection approach

In order to get the nodes between to empty elements, we can use the intersection pattern:

$start-node/following::node() intersect $end-node/preceding::node()

In order to include the <pb/>, that demarcates the page beginning, we can simple prepend it to the node sequence.

$start-node, $start-node/following::node() intersect $end-node/preceding::node()

Since intersection returns all nodes in kind of exploded fashion, we pass the result to fn:outermost(...):

outermost(($start-node, $start-node/following::node() intersect $end-node/preceding::node()))

BTW, the cut.xsl of DTS Transformations uses a similar technique to select the node ranges for the document endpoint.

One-level intersection

Let's try this approach.

Since the intersective node range must be constructed for each <pb/> (but the last), we als need a for generator expression lie for $pb in //pb return .... Here's our cite structure declaration:

<refsDecl n="page-content-by-intersection">
   <citeStructure unit="page"
       match="for $pb in //pb return (let $next:=$pb/following::pb[1] return outermost(($pb, $pb/following::node() intersect $next/preceding::node())))"
	   use="@n" delim="p."/>
</refsDecl>

Want's going wrong here?

Sequence-flatting is the party pooper! The for ... return ends up in a single sequence, that encompasses all nodes following the p.1 mark.

Element construction

So this could only work, if we could wrap the intersection-based content nodes of each page in wrapper elements.

However, an XQuery-like element construction with element {NAME} {CHILDREN} does not work in XPath:

<refsDecl n="page-content-by-intersection">
  <citeStructure unit="page"
				 match="for $pb in //pb return element {ab} {(let $next:=$pb/following::pb[1] return outermost(($pb, $pb/following::node() intersect $next/preceding::node())))}"
				 use="@n" delim="p."/>
</refsDecl>
target/bin/xslt.sh -config:saxon.he.xml -xsl:xsl/navigation.xsl -s:test/john.xml tree=page-content-xquery-like
Error at char 7 in expression in xsl:evaluate/@xpath on line 176 column 52 of tree.xsl:
  XTDE3160  Static error in XPath expression supplied to xsl:evaluate: Node constructor
  expressions are allowed only in XQuery, not in XPath. Expression: {for $pb in //pb return
  element {ab} {(let $next:=$pb/following::pb[1] return outermost(($pb,
  $pb/following::node() intersect $next/preceding::node())))}}
...

There are two further steps to try with element construction:

  1. By passing the sequence to an XQuery function, which is dynamically loaded with fn:load-xquery-module(...). However, this would introduce external dependencies. So this won't be a self-contained solution.
  2. By passing the sequence to an XSLT stylesheet, which is dynamically loaded with fn:transform(). Since fn:transform(...) allows us to pass an inline stylesheet via the stylesheet-text property of the map that has to be passed into the function, this could be made self-containing!

I haven't tried that yet ...

... because I think other approaches more promising. Moreover, with element construction, we would loose the single most important feature of the DTS transformation: node identity und node tracking.

Two-level intersection

<refsDecl n="page-content-by-intersection-2">
	<citeStructure unit="page-beginning" match="//body//pb" use="@n" delim="p.">
		<citeStructure unit="page-content"
			match="let $pb:=self::pb, $next:=$pb/following::pb[1] return (($pb, $pb/following::node() intersect $next/preceding::node()))"
			use="''" delim=" content"/>
	</citeStructure>
</refsDecl>

This improves the citation tree on level 1, but level 2 still suffers from the same problem as the one-level intersection approach.

Second level start end

Instead of putting all the nodes from the intersection into the second level, we could only put there the first and the last node of this sequence:

<refsDecl n="page-level2-start-end">
    <citeStructure unit="page" match="//body//pb" use="@n" delim="p.">
		<citeStructure unit="page-start" match="self::pb"
			use="''" delim=".start"/>
		<citeStructure unit="page-end" match="self::pb/following::pb[1]/preceding::node()[1]"
			use="''" delim=".end"/>
	</citeStructure>
</refsDecl>

We do not get a nice, but a reasonable citation tree from this:

target/bin/xslt.sh -config:saxon.he.xml -xsl:xsl/navigation.xsl -s:test/john.xml tree=page-level2-start-end
  "member": [
    {
      "level": 1,
      "identifier": "p.1",
      "parent": null,
      "citeType": "page",
      "@type": "CitableUnit"
    },
    {
      "level": 2,
      "identifier": "p.1.start",
      "parent": "p.1",
      "citeType": "page-start",
      "@type": "CitableUnit"
    },
    {
      "level": 2,
      "identifier": "p.1.end",
      "parent": "p.1",
      "citeType": "page-end",
      "@type": "CitableUnit"
    },
    {
      "level": 1,
      "identifier": "p.2",
      "parent": null,
      "citeType": "page",
      "@type": "CitableUnit"
    },
    {
      "level": 2,
      "identifier": "p.2.start",
      "parent": "p.2",
      "citeType": "page-start",
      "@type": "CitableUnit"
    },
    {
      "level": 2,
      "identifier": "p.2.end",
      "parent": "p.2",
      "citeType": "page-end",
      "@type": "CitableUnit"
    },
    {
      "level": 1,
      "identifier": "p.3",
      "parent": null,
      "citeType": "page",
      "@type": "CitableUnit"
    },
    {
      "level": 2,
      "identifier": "p.3.start",
      "parent": "p.3",
      "citeType": "page-start",
      "@type": "CitableUnit"
    }
  ],

We do not expect any good news from querying ref=p.1. But, let's see what we get from p.1 to p.1.end:

target/bin/xslt.sh -config:saxon.he.xml -xsl:xsl/document.xsl -s:test/john.xml tree=page-level2-start-end start=p.1 end=p.1.end

The result is correct:

<?xml version="1.0" encoding="UTF-8"?><TEI xmlns="http://www.tei-c.org/ns/1.0"><dts:wrapper xmlns:dts="https://w3id.org/api/dts#"><pb n="1"/>

<head>The book of John</head>The book of John

<milestone unit="theme" xml:id="creation-start"/>
<l n="1">In the beginning was the Word, and the Word was with God, and the Word was
God.</l>In the beginning was the Word, and the Word was with God, and the Word was
God.
  <l n="2">He was with God in the beginning.</l>He was with God in the beginning.
  <l n="3">Through him all things were made; without him nothing was made that has been
made.</l>Through him all things were made; without him nothing was made that has been
made.
  In him was life, and that life was the light</dts:wrapper></TEI>

How can we further improve this approach? How can we get the page out of the endpoint by specifying ref=p.1?

Breaking the barrier

Breaking the barrier seems only possible by bringing the specification forward again. There are several ways:

  1. add information about the 'situation' in the output of the navigation endpoint which a conforming client can evaluate (HATEOS-based solution)
  2. make the algorithmic part of the specs suitable for milestone-like markup (but keep the set of elements and attributes as is)
  3. specify new attributes (and/or elements) on <citeStructure> and how they are to be processed (TEI-based solution)

The following stays in the border of 1, because telling the client what to do next in the guiding principle of HATEOAS and it's very simple.

OLDER text

It introduces a new term: virtual sub-forrest.

Virtual Sub-Forrest

The algorithm for processing citeStructure is defined by example in SACRCS and in the reference for citeStructure/@match

More important, let's focus on the spec for ref on the document end point:

The string identifier of a single node in the CitationTree for the Resource, used as the root for the sub-tree to be reconstructed.

Let's introduce a new term, which is opposed to sub-tree:

Def: A node in the CitationTree at the level N node has virtual sub-trees as opposed to its (real) sub-tree, when a level N+1 child reconstructs a node outside of the single node's (real) sub-tree reconstructed from the level N node.

Def: The virtual sub-forrest of a level N node in the citation tree encompasses all document nodes starting from the first through the last node in document order which are reconstructed from its first and last level N+1 children in the citetation tree.

Non-normative comment:

The notion of virtual sub-forrest de-couples the hierarchy introduced by a citation tree from the document hierarchy. This is useful for citation trees constructed from milestone-like markup, e.g. TEI's <pb n="42"/>.

1 Refinement of the algorithm

Let's modify this from the specs of the document endpoint:

The string identifier of a single node in the CitationTree for the Resource, used as the root for the sub-tree to be reconstructed.

We could extend this with the underlined phrase:

The string identifier of a single node in the CitationTree for the Resource, used as the root for the sub-tree or the virtual sub-forrest to be reconstructed.

This would turn the Second level start end approach into $ref-✅!

<refsDecl n="page-level2-start-end">
    <citeStructure unit="page" match="//body//pb" use="@n" delim="p.">
		<citeStructure unit="page-start" match="self::pb"
			use="''" delim=".start"/>
		<citeStructure unit="page-end" match="self::pb/following::pb[1]/preceding::node()[1]"
			use="''" delim=".end"/>
	</citeStructure>
</refsDecl>

The document node reconstructed from page-end are virtual sub-trees of the sub-tree constructed from page members. Thus, the virtual sub-forrest of a page member is all the nodes from page start to page end.

  • OLD and REWRITTEN text

If the sub-tree identified by $ref on level N encompasses a single node that has no children (no non-attribute children) and if there are exactly two children members of $ref on level N+1, then and only then, on request with ref=X and X on level N, the document endpoint may return the virtual sub-forrest. The virtual sub-forrest is the sequence of document nodes between the left-most and the right-most nodes (encluding these two) identified by the N+1 level child members.

This would turn the Second level start end approach into $ref-✅!

Isn't this what we want regarding milestone-like markup? We get a reasonable cite structure and a reasonable schema for using it in the document end point using ref. If child members de-mark a bigger portion of the document than the member itself, why not return that bigger portion on ref requests?

virtual sub-forrest: This term carries out the two involved reference systems: The hierarchy of the members in the citation tree is reflected in sub-. The hierarchy of the XML document is reflected in forrest; the resulting node set is a forrest, not a sub-tree (at least in general). There is no sub in that forrest regarding the $referenced node, that's why it's virtual.

Clone this wiki locally