XSD and Schematron - Problem using variables

This should cover W3C XML Schema, Relax NG and DTD related problems.
vlester
Posts: 3
Joined: Thu Aug 10, 2006 6:43 pm

XSD and Schematron - Problem using variables

Post by vlester »

I am new to XSD and Schematron and would like to know if anybody has successfully implemented variable using the new ISO Schematron element 'let' (vaiables).

I have a simple XSD and XML example below. When validating the XML I receive the following error:

Description: Failed to compile stylesheet. 1 error detected.
Description: XPath syntax error at char 8 on line 18 in {$theuser='B'}:
Variable $theuser has not been declared

The XSD file:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:sch="http://www.ascc.net/xml/schematron">
<xs:annotation>
<xs:appinfo>
<sch:title>Schematron validation schema for Portal Client</sch:title>
<sch:ns prefix="C" uri="http://example.com/po-schematron" />
</xs:appinfo>
</xs:annotation>
<xs:element name="Client" >
<xs:complexType>
<xs:sequence>
<xs:element name="UserId" type="xs:string" minOccurs="0" >
<xs:annotation>
<xs:appinfo>
<sch:pattern name="UserId Required Fields\">
<sch:rule context="C:UserId ">
<sch:let name="theuser" value="text()"/>
<sch:assert test="$theuser='B'">User is invalid</sch:assert>
<sch:assert test="../C:clientId[text()]">Client Id is required </sch:assert>
<sch:assert test="../C:CompanyName[text()]">CompanyName is required</sch:assert>
</sch:rule>
</sch:pattern>
</xs:appinfo>
</xs:annotation>
</xs:element>
<xs:element name="clientId" type="xs:string" minOccurs="0" />
<xs:element name="CompanyName" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>

</xs:schema>

XML content:
<?xml version="1.0" encoding="UTF-8"?>
<?oxygen SCHSchema="client.xsd"?>
<Client xmlns="http://example.com/po-schematron"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserId></UserId>
<clientId></clientId>
<CompanyName></CompanyName>
</Client>


george
Site Admin
Posts: 2095
Joined: Thu Jan 09, 2003 2:58 pm

Post by george »

Hi,

oXygen supports Schematron 1.5 currently, support for ISO Schematron is planned for the future.

Best Regards,
George
vlester
Posts: 3
Joined: Thu Aug 10, 2006 6:43 pm

Post by vlester »

Do you know if there is any customization that can be done that would address this issue?

Maybe you would give me another suggestion in how to solve the problem we are trying to address: We need to create a rule that contain 'n' number of assertions. The asserts contains similar constraints. We are trying to avoid repetion in the assert expressions. For example:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:sch="http://www.ascc.net/xml/schematron">
<xs:annotation>
<xs:appinfo>
<sch:title>Schematron validation schema for Portal Client</sch:title>
<sch:ns prefix="C" uri="http://example.com/po-schematron" />
</xs:appinfo>
</xs:annotation>
<xs:element name="Client" >
<xs:complexType>
<xs:sequence>
<xs:element name="UserId" type="xs:string" minOccurs="0" >
<xs:annotation>
<xs:appinfo>
<sch:pattern name="UserId Required Fields\">
<sch:rule context="C:UserId ">
<sch:assert test="../C:CompanyName[text()]">CompanyName is required</sch:assert>
<sch:assert test="../C:clientId[text()] and ../C:CompanyName[text()] and ../C:abc[text()]">abc is required </sch:assert>
<sch:assert test="../C:clientId[text()] and ../C:CompanyName[text()] and ../C:def[text()]">def is required </sch:assert>
<sch:assert test="../C:clientId[text()] and ../C:CompanyName[text()] and ../C:xyz[text()]">xyz is required </sch:assert>
</sch:rule>
</sch:pattern>
</xs:appinfo>
</xs:annotation>
</xs:element>
<xs:element name="clientId" type="xs:string" minOccurs="0" />
<xs:element name="CompanyName" type="xs:string" minOccurs="0" />
<xs:element name="abc" type="xs:string" minOccurs="0" />
<xs:element name="def" type="xs:string" minOccurs="0" />
<xs:element name="xyz" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>

</xs:schema>

How can we rewrite this rule so we do not repeat
../C:clientId[text()] and ../C:CompanyName[text()] in each assertion?


In our application we usually have 10(ten) asserts for each rule.

Your help is appreciated.
george
Site Admin
Posts: 2095
Joined: Thu Jan 09, 2003 2:58 pm

Post by george »

Hi,

You can very well use something like:

Code: Select all


<sch:pattern name="UserId Required Fields\">
<sch:rule context="C:UserId ">
<sch:assert test="../C:CompanyName[text()]">CompanyName is required</sch:assert>
<sch:assert test="../C:clientId[text()]">clientId is required </sch:assert>
<sch:assert test="../C:abc[text()]">abc is required </sch:assert>
<sch:assert test="../C:def[text()]">def is required </sch:assert>
<sch:assert test="../C:xyz[text()]">xyz is required </sch:assert>
</sch:rule>
</sch:pattern>
There is no need for repeating an already checked condition.

For a more compact check you can set the XPath level to XSLT 2.0 from Options->Preferences -- XML -- XML Parser -- Schematron XPath Version and then write the conditions like:

Code: Select all


<sch:pattern name="UserId Required Fields">
<sch:rule context="C:UserId ">
<sch:assert test="count(for $i in (../C:UserId, ../C:CompanyName, ../C:clientId) return $i[text()])=3">
UserId, CompanyName and ClientId inside Client are required if the UserId element is specified.</sch:assert>
</sch:rule>
</sch:pattern>
So basically we iterate the sequence with the fields we require and return a field if it has text content, otherwise not, then we check that we get the same number of fields as the required fields number.

Best Regards,
George
vlester
Posts: 3
Joined: Thu Aug 10, 2006 6:43 pm

Post by vlester »

Thank you for your help.
What is your opinion on the following solution:
Create a pattern with two rules. The first rule will contain the main condition that would be required to add to each assert. If the first rule fails then the second rule will not be executed. The only problem here is how do I create rules with the same context in the same pattern? Just a remainder that those rules are very simple realted to what the real scenario would be.
<sch:pattern name="UserId Required Fields">
<sch:rule context="C:UserId ">
<sch:assert test="../C:CompanyName[text()] and ../C:clientId[text()]">Main rule failed </sch:assert>
</sch:rule>
<sch:rule context="C:UserId ">
<sch:assert test="../C:CompanyName[text()]">b CompanyName is required</sch:assert>
<sch:assert test="../C:clientId[text()]">b clientId is required </sch:assert>
<sch:assert test="../C:abc[text()]">b abc is required </sch:assert>
<sch:assert test="../C:def[text()]">b def is required </sch:assert>
<sch:assert test="../C:xyz[text()]">b xyz is required </sch:assert>
</sch:rule>

</sch:pattern>
george
Site Admin
Posts: 2095
Joined: Thu Jan 09, 2003 2:58 pm

Post by george »

If what you want is to apply a set of asserts when some condition is met then move that condition in the rule context like below

Code: Select all


<sch:pattern name="UserId Required Fields">
<sch:rule context="C:UserId[../C:CompanyName[text()] and ../C:clientId[text()]]">
<sch:assert test="../C:CompanyName[text()]">b CompanyName is required</sch:assert>
<sch:assert test="../C:clientId[text()]">b clientId is required </sch:assert>
<sch:assert test="../C:abc[text()]">b abc is required </sch:assert>
<sch:assert test="../C:def[text()]">b def is required </sch:assert>
<sch:assert test="../C:xyz[text()]">b xyz is required </sch:assert>
</sch:rule>
</sch:pattern>
Best Regards,
George
Post Reply