Schematron sample code
This article contains some general Schematron code samples to enable a quick start when writing Schematron validation code in PageSeeder.
For more specific code samples, see:
Basic Schematron
The following is a boilerplate Schematron schema. It has placeholders for different types of validation rules.
<?xml version="1.0"?> <!-- A sample schematron --> <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" title="Rules for [type] documents"> <!-- You can creates as many patterns as you like --> <sch:pattern name="[your name]"> <!-- Rules matchings an element in your document --> <sch:rule context="[your XSLT pattern]"> <!-- Reported as an info if test is true --> <sch:report test="[your xpath test]"> [your message] </sch:report> <!-- Failed assertions are reported as errors --> <sch:assert test="[your xpath]"> [your message] </sch:assert> <!-- Add more <sch:report> or <sch:assert>... --> </sch:rule> <!-- Add more <sch:rule> ... --> </sch:pattern> <!-- Add more <sch:pattern> ... --> </sch:schema>
Example Schema
The following schema is designed to validate some generic best practice rules for PSML.
<?xml version="1.0"?> <!-- This schematron validates a PSML document. The schematron rules can be used to enforce additional constraints required by the application. see https://dev.pageseeder.com/api/data/psml.html --> <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" title="Rules for tutorial documents"> <!-- Set of rules applying to the entire document --> <sch:pattern name="Document"> <sch:rule context="/document"> <!-- Too many sections --> <sch:assert test="count(section) le 20">This document contains over 20 sections. Consider whether to split it into multiple documents. </sch:assert> <!-- No Doc ID --> <sch:report test="not(documentinfo/uri/@docid)"> This document does not have a Doc ID. </sch:report> <!-- No Label --> <sch:report test="not(documentinfo/uri/@labels)"> This document has no label.</sch:report> </sch:rule> </sch:pattern> <!-- Validation rules for document fragments --> <sch:pattern name="Fragments"> <sch:rule context="section/fragment"> <!-- Fragment has no heading --> <sch:assert test="name(*[1]) = 'heading'"> Fragment '<sch:value-of select="@id"/>' has no title.</sch:assert> <!-- Fragment is too long (over 2000 chars)--> <sch:assert test="string-length(string-join(.//text(), '')) le 2000"> Fragment '<sch:value-of select="@id"/>' has over 2000 characters. </sch:assert> </sch:rule> </sch:pattern> </sch:schema>
Check cross-referenced document
A rule to check the content of an xref target document.
<!-- Match cross references --> <sch:rule context="xref|blockxref"> <!-- Check referenced document contains a heading2 --> <sch:assert test="document(@href)//heading[@level='2']"> Document <sch:value-of select="@href"/> has no heading2. </sch:assert> </sch:rule>
Check terms used in a publication
A schematron which when run on a publication root document will check which defined terms are used in the publication. Requires PageSeeder 5.9800 or higher.
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron"> <sch:let name="usage" value="document(concat( 'ps:search?filters=pstype:document&pspublicationid:', /document/documentinfo/publication/@id, '&facets=psinline-definition', '&facetsize=1000&pagesize=1'))" /> <sch:pattern> <sch:title>Usage</sch:title> <sch:rule context="/"> <sch:let name="terms" value="$usage//facets/facet/term" /> <sch:report test="$terms"> The following <sch:value-of select="count($terms)" /> defintions are used in this publication: <sch:value-of select="string-join(sort($terms/@text, 'http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive') ,', ')"/> </sch:report> </sch:rule> </sch:pattern> </sch:schema>
Check documents assigned to me
A schematron to check which documents are assigned to me and have status Complete or In Progress. Requires PageSeeder 5.9811 or higher.
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron"> <sch:let name="self" value="document('ps:self')" /> <sch:let name="memberid" value="$self/member/@id" /> <sch:let name="workflow" value="document('ps:workflow')" /> <sch:pattern> <sch:title>Document</sch:title> <sch:rule context="/document"> <!-- Complete status --> <sch:assert id="COMPLETE_STATUS" flag="info" test="not(@status='Complete' and $workflow/workflow/assignedto[@id=$memberid])"> Document is assigned to you and COMPLETE.</sch:assert> <!-- In Progress status --> <sch:assert id="IN_PROGRESS_STATUS" flag="info" test="not(@status='In Progress' and $workflow/workflow/assignedto[@id=$memberid])"> Document is assigned to you and IN PROGRESS.</sch:assert> </sch:rule> </sch:pattern> </sch:schema>
Debug Schematron code
If you need to debug Schematron and you're not sure what your getting in a variable, you can used code similar to this:
<!-- A separate pattern helps you avoid any side-effect --> <sch:pattern name="DEBUG"> <!-- Have a rule that matches what you want, the document node always matches --> <sch:rule context="/"> <!-- A true() test on a report always matches --> <sch:report test="true()"> <!-- The serialize function serializes the nodes as a string --> <sch:value-of select="serialize($variable-to-check)"/> </sch:report> </sch:rule> </sch:pattern>
Quickfix
A schematron to identify and fix headings that contain markup such as formatting. Requires PageSeeder 6.0 or higher.
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron"> <!-- Patterns --> <sch:include href="../../schema/pattern/heading_no_markup.sch" /> <!-- Properties --> <sch:include href="../../schema/properties.sch" /> </sch:schema>
schema/pattern/heading_no_markup.sch
<sch:pattern id="heading_no_markup" xmlns:sch="http://purl.oclc.org/dsdl/schematron"> <sch:title>No markup in heading</sch:title> <sch:rule context="fragment"> <sch:assert test="not(heading) or heading[not(*)]" subject="heading" properties="fragment remove-markup-from-heading"> Heading should not contain markup </sch:assert> </sch:rule> </sch:pattern>
schema/properties.sch
<sch:properties xmlns:sch="http://purl.oclc.org/dsdl/schematron"> <!-- Allows PageSeeder to identify the fragment and show in interface --> <sch:property id="fragment" ><sch:value-of select="ancestor-or-self::fragment/@id"/></sch:property> <!-- Quick fix --> <sch:property id="remove-markup-from-heading" role="quickfix"> <description>Remove the markup from the heading</description> </sch:property> </sch:properties>
quickfix/remove-markup-from-heading.xsl
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="#all"> <xsl:template match="/fragment"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="copy"/> </xsl:copy> </xsl:template> <xsl:template match="*" mode="copy"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="#current"/> </xsl:copy> </xsl:template> <xsl:template match="heading/*" mode="copy"> <xsl:apply-templates mode="#current"/> </xsl:template> </xsl:stylesheet>