Skip to main content

 Sample Code

Some code samples

Validating headings

To create a quality Table of Contents, the documents in a collection must use headings consistently. Following are Schematron assertions to validate different characteristics of headings in PSML documents.

Basic – heading rule

Most rules that apply to headings match the <heading> element:

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

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

</sch:rule>

Message

When a message is returned or an assertion fails, the goal is to help the user to quickly identify the problem so it can be addressed.

With headings, this generally means providing the textual content of a <heading> element. The @level attribute can be also helpful and so can additional information such as the value of the @id attribute on the <fragment> or <section> element.

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.

Validate the first heading

All PageSeeder documents have a document title that is created independent of the document contents through the URI properties, as an alternative to the filename. Because the document title can be changed separate to the content, it can be useful to check them against each other.

Document title vs first heading

Whenever documents are listed, it is the document title that is displayed. This includes browsing folders or authoring cross-references

While the built-in PageSeeder document templates, default and references, automatically populate the first heading of the document with the document title, it is possible to edit both separately. 

Check for integrity

To determine whether the convention of document title and first heading match each other, use one of the following:

<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"/>.
  </sch:assert>

</sch:rule>

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"/>.
  </sch:assert>

</sch:rule>
  • 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]

Check the first heading

Common conventions for the use of headings in PageSeeder publications include a mix of the following:

  • A heading should be in the first fragment of the first section.
  • The heading should have a @level value of 1.
  • It might not be numbered.
  • It might not have a 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.
  </sch:assert>

  <!-- 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.
  </sch:assert>

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

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

</sch:rule>

When appropriate, these individual tests can easily be combined into a single test.

Validate headings against sections

The built-in PSML document templates, default and references, separate documents into two sections:

  • The first section has an @id value of title.
  • The second section has an @id value of content or references.

Different development approaches mean that the section where the first heading occurs can affect the Table of Contents generation. In the event of unexpected results in the ToC, the following rule tests which section has the first heading of a document.

<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.
  </sch:assert>

</sch:rule>

Validate other headings

Generating a Table of Contents typically requires that document headings are consistent according to constraints such as the following:

  • The use of levels reflect an implicit or explicit hierarchy.
  • The application of heading numbering is consistent.
  • The predictable expression of heading content.

Following are some rules to help test document headings against these constraints.

Validate hierarchical consistency

Whenever possible, progressively following each level of a document hierarchy promotes structural consistency. This also generates a predictable Table of Contents.

The following rule checks that the heading levels only increment by a single value at a time.

<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"/>'.
 </sch:assert>

</sch:rule>

Ensure headings are numbered

To comply with document conventions might require some headings to be numbered. The following rule checks for numbering on heading levels 1, 2 and 3.

<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.
  </sch:assert>

</sch:rule>

Validate heading content

To produce a meaningful Table of Contents that complies with a target format might require constraints such as the following:

  • Minimum/maximum number of characters.
  • No occurrence of illegal characters.
  • No leading or trailing whitespace characters.
  • A convention for word capitalization.

The following rules can be used to test for these conditions.

Check the number of characters in heading

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.
  </sch:assert>

</sch:rule>

Check for unwanted whitespace characters

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

<sch:rule context="heading">

  <!-- Heading is between 2 and 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.
  </sch:assert>

</sch:rule>

Check 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.
  </sch:assert>

</sch:rule>

Check 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.
  </sch:assert>

</sch:rule>
Created on , last edited on