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 will 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 that 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 via 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 template, 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>

Note

  • 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 could be 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 may not be numbered,
  • it may 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>

Note

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 will test 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 will promote structural consistency. This will also generate a predictable Table of Contents.

The following rule checks 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 may 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

In order to produce a meaningful Table of Contents that complies with a target format may require constraints such as the following:

  • minimum/maximum number of characters,
  • no occurrence of illegal characters,
  • no leading or trailing white space characters,
  • a convention for word capitalization.

The rules below 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 white space 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