---
title: "How to validate documents using Schematron"
source: https://dev.pageseeder.com/get_started/tutorials/how_to_validate_film_document_by_using_schematron.html
last_updated: 2026-09-14T15:59:05+10:00
tokens: ~2794
---

# How to validate documents using Schematron

| Property | Value |
| --- | --- |
| Skills required | Schematron |
| Time required (minutes) | 30 |
| Intended audience | Developer |
| Difficulty | Easy |
| Category | Document |

## Objective

By the end of this tutorial you will be able to use [Schematron](/reference/sample_code/schematron.md) to validate film documents. This includes learning how to define validation rules and implementing them on PageSeeder.

## Prerequisites

This tutorial assumes that:

- You have [administrator](/reference/glossary/administrator.md) access to a [PageSeeder server](/reference/glossary/pageseeder_server.md) with at least one [group](/reference/glossary/group.md).

## Tutorial

All the files for this tutorial can be found on [Github](https://github.com/pageseeder/pageseeder-tutorials/tree/master/validate_with_schematron).

The film document includes four sections which are:

- title;
- properties list;
- film summary;
- film poster.

### Validation rules

Here is a list validation rules. For code detail please see the [code example](/get_started/tutorials/how_to_validate_film_document_by_using_schematron.md) at the bottom of the page.

#### Properties

| Property name | Rule |
| --- | --- |
| Year | Must be a number greater than 1900 and less than next year |
| Classification | Must be one of “M, PG, G, R18+, ... ” |
| Country | Must not be empty |
| Director | Must not be empty |
| Genre | Must not be empty.  Further, check value in value set. |
| Producer | Must not be empty |
| Writer | Must not be empty |
| Actor | Must not be empty |

#### Fragments

| Fragment name | Rule |
| --- | --- |
| Film summary | Must contain at least one paragraph (`<para>`) element |
| Film poster | Must not be empty and the link must be resolved |

### Upload film documents

Download `films.zip` from Github;

Go to your group on the PageSeeder server and click the **Upload a document** icon;

Create a new folder under 'documents' called 'films' and make sure it is selected;

Drag and drop your .zip file into the page or click **Browse files** to select it;

Click the **Unzip icon** next to the file in PageSeeder;

Click **Continue** and then **Continue** again to confirm.

### Create a Schematron

In the PageSeeder group, enable [developer view](/reference/glossary/developer_view.md);

Go to the **Document** **types** page;

Once on the document type page, click **Create document type...** to create a new document type called 'film';

Then in the row for 'film' type, click **create** in the 'Schematron' column.

![create\_schematron.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/create_schematron.webp)

### Edit the Schematron

Edit the Schematron configuration file and click **Save**. See the **[code examples](/get_started/tutorials/how_to_validate_film_document_by_using_schematron.md)** below for code to enter.

![edit\_schematron\_for\_validation.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/edit_schematron_for_validation.webp)

### Run the Schematron

Go back to the 'film' document folder and validate all the documents.

![document\_collection\_validation.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/document_collection_validation.webp)

Alternatively, open one document and validate this document by clicking on the blue navigation bar.

![Single\_document\_validation.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/single_document_validation.webp)

### Make content invalid

Edit the content fragment and make the content invalid to test validation. For example, change Year to 'abc' and save it.

![edit\_properties.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/edit_properties.webp)

### View the validation error

After validating the document, you will then see the validation error message. Clicking on **details...** will allow you to see the rule.

![error\_message\_for\_single\_doc\_with\_detail.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/error_message_for_single_doc_with_detail.webp)

When validating an entire folder, you will see a pop-up window displaying the error message. For more details, click on **Validation Report**. This will display the summary.

![error\_message\_for\_folder\_validation.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/error_message_for_folder_validation.webp)

 

![error\_message\_for\_folder\_validation\_with\_detail.png](/content/images/tutorials/how_to_validate_film_document_by_using_schematron_files/error_message_for_folder_validation_with_detail.webp)

## Code example

#### Validate properties

```xml

 <!--
    Set of rules applying to the document properties
  -->
  <sch:pattern name="Properties">

    <!-- Classification -->
    <sch:rule context="property[@name='classification']">
      <sch:assert test="contains('M PG G R18+', @value)">
      Fragment '<sch:value-of select="../@id"/>' - classification '<sch:value-of select="@value"/>' must be G, M, PG or R18+.</sch:assert>
    </sch:rule>
   
    <!-- Year-->
    <sch:rule context="property[@name='year']">
       <sch:assert test="number(@value) le number(year-from-date(current-date())) and number(@value) gt 1900">
         Fragment '<sch:value-of select="../@id"/>' - year '<sch:value-of select="@value"/>' is not valid.
       </sch:assert>
    </sch:rule>

    <!-- Genre -->
    <sch:rule context="property[@name='genre']">
       <sch:assert test="@count and count(value)">
         Fragment '<sch:value-of select="../@id"/>' - genre should be specified at least one genre.
       </sch:assert>
     </sch:rule>
   
     <!-- Director -->
    <sch:rule context="property[@name='director']">
       <sch:assert test="@count and count(value)">
         Fragment '<sch:value-of select="../@id"/>' - director should be specified at least one name.
       </sch:assert>
     </sch:rule>
    
     <!-- Writer -->
    <sch:rule context="property[@name='writer']">
       <sch:assert test="@count and count(value)">
         Fragment '<sch:value-of select="../@id"/>' - writer should be specified at least one name.
       </sch:assert>
     </sch:rule>
   
    <!-- Producer -->
    <sch:rule context="property[@name='producer']">
       <sch:assert test="@count and count(value)">
         Fragment '<sch:value-of select="../@id"/>' -  producer should be specified at least one name.
       </sch:assert>
     </sch:rule>
   
    <!-- Actor -->
    <sch:rule context="property[@name='actor']">
       <sch:assert test="@count and count(value)">
         Fragment '<sch:value-of select="../@id"/>' -  actor should be specified at least one name.
       </sch:assert>
     </sch:rule>
   
    <!-- Country -->
    <sch:rule context="property[@name='country']">
       <sch:assert test="@value">
         Fragment '<sch:value-of select="../@id"/>' -  country should be specified at least one country.
       </sch:assert>
     </sch:rule>
  </sch:pattern>
```

You can further check the value of 'genre' by using an external value set and easily add more values into the value set in future. Another advantage is the validation code does not need to be changed. Please see details at [validate by using external code list](/get_started/tutorials/how_to_validate_film_document_by_using_schematron.md) below.

#### Validate summary

```xml

<sch:pattern name="summary">
     <sch:rule context="section[@id='summary']">
       <sch:assert test="count(descendant::para)">
         Documdent '<sch:value-of select="../@id"/>' :  miss film summary
       </sch:assert>
    </sch:rule>
  </sch:pattern>​
```

#### Validate image (film poster)

```xml

  <sch:pattern name="Image">
 
    <!-- Image exists -->
    <sch:rule context="section[@id='image']">      
       <sch:assert test="count(descendant::image) and descendant::image/@src">
         Document '<sch:value-of select="../@id"/>' :  miss film poster.
       </sch:assert>
    </sch:rule>
    
    <!-- Image resolved -->
    <sch:rule context="image">      
       <sch:assert test="not(@unresolved) and @uriid">
         Fragment '<sch:value-of select="../@id"/>' -  image at <sch:value-of select="@src"/> is unresolved.
       </sch:assert>
    </sch:rule>
  </sch:pattern>
```

## Using external code list

### Define code list file

Create a PSML file (e.g. `film_codes.psml`) as below and upload it to the PageSeeder group.

```xml

<document level="portable">
<section id="title">
  <fragment id="1">
    <heading level="1">film codes</heading>
  </fragment>
</section>
<section id="rules">
  <fragment id="classification-codes">
    <heading level="2">classification-codes</heading>
    <list>
      <item>P</item>
      <item>PG</item>
      <item>M</item>
      <item>R18+</item>
    </list>
  </fragment>
  <fragment id="genre-codes">
    <heading level="2">genre-codes</heading>
    <list>
      <item>Action</item>
      <item>Drama</item>
      <item>Thriller</item>
      <item>Romance</item>
      <item>Comedy</item>
    </list>
  </fragment>
</section>
</document>

```

### Declare the reference to the external code list file in schematron

```xml

<sch:pattern name="Properties">
    <sch:let name="URI" value="'/ps/films/tutorial/documents/film_codes.psml'"/>
    <sch:let name="code-list-document" value="document($URI)" />
...
</sch:pattern>
```

> **Note:** Please confirm the URI of your code list file and replace the sample file path above (the folder path can be found by expanding the details at the top of the document view page).

### Use the value in code list to check document content

Property contains single value:

```xml

 <!-- Classification -->
    <sch:rule context="property[@name='classification']"> 
      <sch:let name="classification-list" value="$code-list-document//fragment[@id='classification-codes']"/>
      <sch:assert test="$classification-list//item = @value">
      Fragment '<sch:value-of select="../@id"/>' - classification '<sch:value-of select="@value"/>' is not valid. Matching values are <sch:value-of select="$classification-list/list"/>
      </sch:assert>
    </sch:rule>
```

Property contains multiple values:

```xml

<!-- Genre -->
    <sch:rule context="property[@name='genre']">
       <sch:assert test="@count and count(value)">
         Fragment '<sch:value-of select="../@id"/>' -  genre should be specified at least one genre.
       </sch:assert>
     </sch:rule>
    <sch:rule context="property[@name='genre']/value">
       <sch:let name="genre-list" value="$code-list-document//fragment[@id='genre-codes']"/>
       <sch:assert test="$genre-list//item=current()">
         Fragment '<sch:value-of select="../@id"/>' -  genre '<sch:value-of select="current()"/>' is not valid.
         </sch:assert>
     </sch:rule>​
```

## Reference

[schematron sample code](https://dev.pageseeder.com/reference/sample_code/schematron.html)

[Schematron official website](https://www.schematron.com/)

[Validating Code Lists with Schematron](https://www.oreilly.com/)

