Sample Code

Some code samples

Validating headings

Headings often require validation to ensure that they will produce consistent tables of contents. They may also be checked for consistency or style.


heading rule

Most rules applying to images should match the <heading> element:

<!-- Matches headings in PSML -->
<sch:rule context="heading">

   <!-- Assertions and reports here ... -->



The message returned by Schematron when reported or when an assertion fail should allow the user to quickly identify the problem so that it can be fixed.

It is generally sufficient to provide the textual content of a <heading> element in order to find it. The @level attribute is also helpful. Additional contextual information such as the fragment or section ID can also be helpful.

For example:

Heading level <sch:value-of select="@level"/> "<sch:value-of select="."/>"
in fragment <sch:value-of select="ancestor::fragment/@id"/>

Can be used to display:

Heading level 2 'Additional information' in fragment 43.

Validating the first heading

All PageSeeder documents have a document title that is used without examining the contents of the file and is set via the URI properties as an alternative to the filename.

Document title vs first heading

The document title is not part of the document content and displayed in relation to other documents (e.g. in a list of documents) or to compute the text content of cross-references. The first heading of the document is part of the document content and is what end-users will appear to be document title. 

When using a built-in PageSeeder document template (e.g. 'default' and 'references'), the first heading of the document will automatically be the same as the document title. However, it is possible to edit the first heading without updating the document title.

Checking the document title matches

A common convention is to require the first heading to match the document title. In that situation, it is easier to have a rule matching the document title rather than the first heading.

<sch:rule context="uri/displaytitle">

  <!-- The first heading of the document -->
  <sch:let name="first-heading" value="(//heading)[1]"/>

  <!-- Document title must match first heading-->
  <sch:assert test=". = $first-heading">The document title  
    '<sch:value-of select="."/>' does not match the first heading 
    '<sch:value-of select="$first-heading"/>.


Or alternatively:

<sch:rule context="heading[not(preceding::heading)]">

  <!-- The document title -->
  <sch:let name="document-title" value="(//uri/displaytitle)[1]"/>

  <!-- Document title must match first heading-->
  <sch:assert test=". = $document-title">The document title  
    '<sch:value-of select="."/>' does not match the first heading 
    '<sch:value-of select="$document-title"/>.



  • Although (//heading)[1] is a valid XPath expression, it cannot be used as a pattern
  • Do NOT use //heading[1] as it will match every first heading in a fragment.
  • //uri@title is an alternative to (//uri/displaytitle)[1]

Checking the first heading

Another common convention is that the first heading should:

  • Appear in the first section of the first fragment
  • Be a level one heading
  • NOT be numbered
  • NOT have prefix
<sch:rule context="heading[not(preceding::heading)]">

  <!-- First heading is level 1 -->
  <sch:assert test="@level = 1">The first heading MUST be level 1 
    but was level <sch:value-of select="@level"/> instead.

  <!-- First heading is in first section of first fragment -->
  <sch:assert test=". = //section[1]/fragment[1]/heading[1]">The first 
  heading MUST be in the first fragment of the first section.

  <!-- First heading is not numbered -->
  <sch:assert test="not(@numbered)">The first heading MUST NOT be numbered.

  <!-- First heading has no prefix -->
  <sch:assert test="not(@prefix)">The first heading MUST NOT have a prefix.



The tests can easily be combined into a single test.

Validating headings in relation to sections

The built-in PSML document templates (e.g. 'default' and 'references') separate documents into two sections:

  • the first section has ID 'title'
  • the second section has ID 'content' or 'references'

In order to build the table of contents without relying on the position of headings, some algorithms use the section to determine whether the headings should be included in the table of contents or not. In that case, it is more appropriate to rely on the containing section rather than the position of the heading.

The test below checks that the first heading belongs to the 'title' section

<sch:rule context="heading[not(preceding::heading)]">

  <!-- First heading is in first section of first fragment -->
  <sch:assert test="ancestor::section/@id = 'title'">The first 
  heading MUST be in the 'title' section.


Validating other headings

Headings which are part of the document content are generally used for the Table of Contents and constraints usually related to:

  • their level (to ensure a proper hierarchy)
  • consistency (e.g. all headings of a specified level are numbered)
  • their style (so that they display appropriately)

Validating the headings hierarchy

One way to ensure consistency in table of contents is to require heading levels to increase by 1 at a time only. For example, a heading level 4 should not be used directly after a heading level 2.

The following rule checks the heading levels never increase by more than 1.

<sch:rule context="heading[preceding::heading]">

   <!-- The immediately preceding heading -->
   <sch:let name="previous" value="preceding::heading[1]"/>

   <!-- Heading level can only increment at most by 1-->
   <sch:assert test="@level - $previous/@level le 1">A heading MUST
    follow a heading that at least equals or 1. But heading level
    <sch:value-of select="@level"/> '<sch:value-of select="."/>' follows
    heading level <sch:value-of select="$previous/@level"/> 
    '<sch:value-of select="$previous"/>'.


Ensuring that headings are numbered

For consistency, it may be useful to ensure that some headings are numbered.

The rule below checks that heading levels 1 to 3 are numbered.

<sch:rule context="heading[preceding::heading][@level lt 4]">

   <!-- Heading level 1 to 3 should be numbered -->
   <sch:assert test="@numbered">Heading levels 1 to 3 MUST
    be numbered. But heading level <sch:value-of select="@level"/> 
   '<sch:value-of select="."/>' is not.


Validating heading styles

In order to produce meaningful Table of Contents that can be formatted easily, some requirements on the style of headings may be required:

  • Minimum/maximum length
  • Illegal characters
  • Leading/trailing spaces
  • Proper capitalization

Checking the character length

The following rule checks that the headings do not exceed a given length.

<sch:rule context="heading">

   <!-- Character-length of heading -->
   <sch:let name="length" value="string-length(.)"/>

   <!-- Heading is between 2 than 99 characters -->
   <sch:assert test="$length gt 1 and $length lt 100">Headings MUST be 
   between 2 and 99 characters long, but heading level 
   <sch:value-of select="@level"/> '<sch:value-of select="."/>' is 
   <sch:value-of select="$length"/> characters long.


Checking for extra spaces

The following rule checks that headings do not include leading or trailing spaces, no consecutive space.

<sch:rule context="heading">

   <!-- Heading is between 2 than 99 characters -->
   <sch:assert test=". = normalize-space(.)">Headings MUST include leading
   or trailing spaces but heading <sch:value-of select="@level"/> 
   '<sch:value-of select="."/>' does.


Checking capitalization

The following rule checks that all headings are capitalized.

<sch:rule context="heading">

   <!-- First letter -->
   <sch:let name="first-letter" value="substring(.,1,1)"/>

   <!-- First letter is upper-case -->
   <sch:assert test="$first-letter = upper-case($first-letter)">Headings 
   MUST be capitalized but heading <sch:value-of select="@level"/> 
   '<sch:value-of select="."/>' is not.


Checking for incorrect punctuation

The following rule checks that headings do not end with a colon or comma.

<sch:rule context="heading">

   <!-- Check of colon/comma -->
   <sch:assert test="not(ends-with(., ':') or ends-with(., ','))">Headings 
   MUST not end with a colon ':' or comma but heading level
   <sch:value-of select="@level"/> '<sch:value-of select="."/>' does.


Created on , last edited on