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 of1
. - 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 oftitle
. - The second section has an
@id
value ofcontent
orreferences
.
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>