Xmlid uniqueness.sch
It is often convenient to validate XML documents against RELAX NG schemas without using the “DTD Compatability” feature of RELAX NG. (Particularly when using certain wildcard patterns, as this feature can cause a “conflicting ID-types for attribute "id" from namespace "http://www.w3.org/XML/1998/namespace" of element …” error. Personally, I think getting notified of the conflict is good, but it should be a warning, not an error.) However, when DTD Compatability is turned off, the RELAX NG validator no longer tests that the values of your ID attributes (which, in TEI, means your @xml:id attributes) are unique across all IDs in your document.
Here are three slightly different ways to test for unique @xml:id attributes in ISO Schematron.
BTW, in oXygen this feature can be turned on and off in Options > Preferences… > XML > XML Parser > RELAX NG pane by checking or unchecking the Check ID/IDREF box. The easy way to get to it is to filter for the string “IDREF”, which does not appear in any other preference panes. At least this works in oXygen 17.
<?xml version="1.0" encoding="UTF-8"?> <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2" xmlns:sqf="http://www.schematron-quickfix.com/validator/process"> <sch:p>This Schematron schema contains three different ways of doing exactly the same thing: checking that each and every @xml:id attribute in the input has a unique value.</sch:p> <sch:p>I do not know which of these three methods, or some other, is “best”, and I’m not sure there is any way to know. While #2 has the shortest XPath, I think #3 is easier to understand. But if you think #1 or #2 is easier to understand, by all means use it.</sch:p> <sch:p>Note that none of these rules check that the value of @xml:id is a valid identifier, or even that it is present. They only check for uniqueness.</sch:p> <sch:p>Written 2015-10-02 by Syd Bauman. Copyleft.</sch:p> <sch:pattern> <sch:rule context="//@xml:id"> <sch:let name="myID" value="normalize-space(.)"/> <sch:report test=" ../ancestor::*/@xml:id[ normalize-space(.) eq $myID ] | ../preceding::*/@xml:id[ normalize-space(.) eq $myID ] ">#1 The @xml:id "<sch:value-of select="." />" on <<sch:value-of select="name(..)" />> duplicates an @xml:id found earlier in the document</sch:report> </sch:rule> </sch:pattern> <sch:pattern> <sch:rule context="//@xml:id"> <sch:let name="myID" value="normalize-space(.)"/> <sch:report test="//*[ current()/.. >> .]/@xml:id[ normalize-space(.) eq $myID ] ">#2 The @xml:id "<sch:value-of select="." />" on <<sch:value-of select="name(..)" />> duplicates an @xml:id found earlier in the document</sch:report> </sch:rule> </sch:pattern> <sch:pattern> <sch:rule context="//@xml:id"> <sch:let name="myID" value="normalize-space(.)"/> <sch:report test="../(ancestor::*|preceding::*)/@xml:id[ normalize-space(.) eq $myID ] ">#3 The @xml:id "<sch:value-of select="." />" on <<sch:value-of select="name(..)" />> duplicates an @xml:id found earlier in the document</sch:report> </sch:rule> </sch:pattern> </sch:schema>