schema(n)
okay so this is schema(n), part 1 of 6, and bestie it is a lot โ but we're gonna get through it together, no cap ๐
โจ NAME โจ
tdom::schema โ Creates a schema validation command ๐
๐ฆ SYNOPSIS ๐ฆ
package require tdom
tdom::schema ?create? cmdName
๐ธ DESCRIPTION ๐ธ
okay so every single time you call this command, it spins up a brand new validation command. That new command comes with methods that let you define a schema, and it can validate XML data for you โ or it can post-validate a tDOM DOM tree (and, to some degree, other kinds of hierarchical data too) against that schema. slay โจ
Also (and this part is lowkey underrated) a validation command can be passed as the argument to the -validateCmd option of the dom parse and the expat commands, so you get validation on top of whatever else those commands are already doing. two jobs, one command, iconic ๐
Here are the methods of the commands you create:
prefixns ?prefixUriList?โ this one controls the prefix (a.k.a. abbreviation) โ namespace URI mapping. Anywhere a schema command method wants a namespace argument, you can hand it the "prefix" instead of the whole namespace URI. If the list maps the same prefix to different namespace URIs, the first one wins. And if that prefix just isn't in the list? Then the namespace argument gets used literally, as a namespace URI. Call it with no argument and it returns the currentprefixUriList. Call it with the empty string and every namespace URI argument is taken literally โ and that's the default. ๐defelement name ?namespace? <definition script>โ defines the elementname(optionally inside thenamespacenamespace, bestie) in the schema. The definition script gets evaluated, and it defines the content model of the element. If you give thenamespaceargument, then anyelementorrefreferences in the definition script that aren't wrapped inside anamespacecommand get resolved in that namespace. And if there's already an element definition for thatname/namespacecombo? the command raises error. no duplicates, we don't do that here ๐defelementtype typename ?namespace? <definition script>โ defines the element typetypename(optionally in thenamespacenamespace) in the schema. If that element type shows up in a definition script via the schema commandelement, the validation engine expects element content matching the content model definition script. ngl, defining (and using) element types only really makes sense if you genuinely have elements with the same name and namespace but different content models โ which, sure, happens. The definition script gets evaluated and defines the content model of the element it's assigned to. If you pass thenamespaceargument, anyelementorrefreferences in the definition script not wrapped inside anamespacecommand are resolved in that namespace. If there's already an elementtype definition for thattypename/namespacecombo, the command raises error. And heads up: the document element of any XML you want to validate cannot be adefelementtype-defined element. ๐ซถdefpattern name ?namespace? <definition script>โ defines a (possibly complex) content particle namedname(optionally in thenamespacenamespace) in the schema, so other definition scripts can grab it with the definition commandref. The definition script gets evaluated and defines the content model of the content particle. If you give thenamespaceargument, anyelementorrefreferences in the definition script that aren't wrapped in anamespacecommand are resolved in that namespace. Already got a pattern definition for thatname/namespacecombo? the command raises error. ๐คdeftexttype name <constraint script>โ defines a bundle of text constraints you can refer to by name while defining constraints on text element or attribute values. If there's already a text type definition with this name, the command raises error. Fun fact: a text type can be referred to before it's actually defined in the schema. And if a referred text type never gets defined anywhere in the schema at all? Then any text will match this type during validation. vibes-based typing, honestly ๐start documentElement ?namespace?โ defines the name and namespace of the root element of the tree you want to validate. If you use this method, the root element must match for validity. If you never callstart, then any element defined bydefelementcan be the root of a valid document. You can callstartseveral times with different arguments over the lifetime of a validation command โ it's flexible like that. But if you call the command with just the empty string (and nonamespaceargument), the validation constraint for the root element gets removed, and any defined element becomes valid as the root of a tree to validate. ๐ฆdefine <definition script>โ defines several elements or patterns, or a whole entire schema, in one single call, by evaluating the definition script. All the schema command methods we've talked about so far (prefixns,defelement,defelementtype,defpattern,deftexttypeandstart) are allowed top level in the definition script. But thedefinemethod itself isn't allowed recursively โ don't try it. ๐event (start|end|text) ?event specific data?โ this method enables validation of hierarchical data against the content constraints of the validation command. the three flavors:start name ?attributes? ?namespace?โ checks whether the current validation state allows the elementnamein thenamespaceto start right here. It raises error if not.endโ checks whether the current innermost open element may end right there in the current state without violating the validation constraints. It raises error if not.text textโ checks whether the current validation state allows the given text content. It raises error if not.
validate ?options? <XML string> ?objVar?โ returns true if the<XML string>is valid, or false otherwise. If validation failed and you passed the optionalobjVarargument, the variable with that name gets set to a validation error message. If the XML string is valid and you passedobjVar, then the variable is left untouched. โจ
The valid options are:
-baseurl <baseURI>โ if-baseurl <baseURI>is specified, the baseURI is used as the base URI of the document. External entities references in the document are resolved relative to this base URI. This base URI also gets stored inside the DOM tree. ๐-externalentitycommand <script>โ if-externalentitycommand <script>is specified, that Tcl script gets called to resolve any external entities of the document. The default is"::tdom::extRefHandler", which is a simple file URL resolver defined by the script part of the package. Set the option value to the empty string and resolving of external entities is disabled. The command that actually gets evaluated is this option followed by three arguments: the base uri, the system identifier of the entity, and the public identifier of the entity. The base uri and the public identifier may be the empty list. The script has to return a Tcl list of three elements. First element of that list signals how the external entity is returned to the processor โ currently the two allowed types are"string"and"channel". Second element has to be the (absolute) base URI of the external entity to be parsed. Third element is the data: either the already-read data from the external entity as a string (in the"string"case), or the name of a Tcl channel (in the"channel"case).
๐จ real talk bestie: if the script returns a Tcl channel, it will not be closed by the processor. You have to close it separately once you don't need it anymore. don't forget, okay? ๐ญ
-paramentityparsing <always|never|notstandalone>โ this option controls whether the parser tries to resolve the external entities (including the external DTD subset) of the document while it's building the DOM tree.-paramentityparsingrequires an argument, and it must be either"always","never", or"notstandalone"."always"means the parser tries to resolve (recursively) all external entities of the XML source โ and this is the default when-paramentityparsingis omitted."never"means only the given XML source gets parsed, and no external entity (including the external subset) will be resolved and parsed."notstandalone"means all external entities get resolved and parsed โ except for documents that explicitly statestandalone="yes"in their XML declaration. ๐-useForeignDTD <boolean>โ if<boolean>is true and the document has no external subset, the parser will call the-externalentitycommandscript with empty values for thesystemIdandpublicIDarguments. Also please note: if the document also has no internal subset, then the-startdoctypedeclcommandand-enddoctypedeclcommandscripts โ if they're set โ are not called. ๐ซ
validatefile ?options? filename ?objVar?โ returns true if the content offilenameis valid, or false otherwise. The file you give it gets fed as a binary stream to expat, so only US-ASCII, ISO-8859-1, UTF-8 or UTF-16 encoded data will work with this method. If validation failed and you passed the optionalobjVarargument, the variable with that name gets set to a validation error message. If the XML data is valid and you passedobjVar, the variable stays untouched. The allowed options and their meaning are the same as for thevalidatemethod; go read that one for the deets. ๐
validatechannel ?options? channel ?objVar?
okay so bestie, validatechannel is literally the vibe check for a Tcl channel. It returns true if the content read from the Tcl channel channel is valid, or false otherwise. ๐
but here's the tea: since data read out of a Tcl channel is UTF-8 encoded, any misleading encoding declaration at the beginning of the data will lead to errors. no cap. If the validation fails and you passed the optional objVar argument, the variable with that name gets set to a validation error message. If the XML data is valid and you passed objVar, the variable will be untouched. The allowed options and their meaning are the same as for the validate method; see there for a description. ๐ซถ
domvalidate domNode ?objVar?
This one is giving "is this tree valid?" energy. It returns true if the first argument is a valid tree, or false otherwise. If validation has failed and you gave the optional objVar argument, the variable with that name is set to a validation error message. If the dom tree is valid and you gave objVar, the variable with that name is set to the empty string. periodt.
reportcmd ?cmd?
This method expects the name of a Tcl command to be called in case of validation error. The command will be called with two arguments appended: the schema command which raises the validation error, and a validation error code. ๐จ
The possible error codes are:
MISSING_ELEMENTMISSING_TEXTUNEXPECTED_ELEMENTUNEXPECTED_ROOT_ELEMENTUNEXPECTED_TEXTUNKNOWN_ROOT_ELEMENTUNKNOWN_ATTRIBUTEMISSING_ATTRIBUTEINVALID_ATTRIBUTE_VALUEDOM_KEYCONSTRAINTDOM_XPATH_BOOLEANINVALID_KEYREFINVALID_VALUEUNKNOWN_GLOBAL_IDUNKNOWN_ID
For more detailed information see section Recovering. iykyk.
delete
This method deletes the validation command. simple as that. ๐
info ?args?
This method bundles methods to query the state of and details about the schema command. it's giving control panel. โจ
validationstateโ This method returns the state of the validation command with respect to validation state. The possible return values and their meanings are:READYโ The validation command is ready to start validationVALIDATINGโ The validation command is in the process of validating input.FINISHEDโ The validation has finished, no further events are expected.
vstateโ This method is a shorter alias forvalidationstate; see there. lowkey convenient.lineโ If the schema command is currently validating, this method returns the line part of the parsing position information, and the empty string in all other cases. If the schema command is currently post-validating a DOM tree, there may be no position information stored at some or all nodes. The empty string is returned in these cases.columnโ If the schema command is currently validating this method returns the column part of the parsing position information, and the empty string in all other cases. If the schema command is currently post-validating a DOM tree, there may be no position information stored at some or all nodes. The empty string is returned in these cases.byteIndexโ If the schema command is currently validating this method returns the byte position of the parsing position information, and the empty string in all other cases. If the schema command is currently post-validating a DOM tree, there may be no position information stored at some or all nodes. The empty string is returned in these cases.domNodeโ If the schema command isn't currently post-validating a DOM tree this method returns the empty string. Otherwise, if the schema command waits for thereportcmdscript to finish while recovering from a validation error it returns the node on which the validation engine is currently looking at in case the node is anELEMENT_NODEor, if not, its parent node. It is recommended that you do not use this method. Or at least leave the DOM tree alone, use it read-only. ๐ซ
bestie, this is a do not touch situation. the manpage literally says it's recommended you don't use it, or at least keep the DOM tree read-only.
nrForwardDefinitionsโ Returns how many elements, element types and ref patterns are referenced that aren't defined so far (summed together).definedElementsโ Returns in no particular order the defined elements in the grammar as list. If an element is namespaced, its list entry will be itself a list with two elements, with the name as first and the namespace as second element.definedElementtypesโ Returns in no particular order the defined element types in the grammar as list. If an element type is namespaced, its list entry will be itself a list with two elements, with the name as first and the namespace as second element.definedPatternsโ Returns in no particular order the defined named pattern in the grammar as list. If a named pattern is namespaced, its list entry will be itself a list with two elements, with the name as first and the namespace as second element.expectedโ Returns in no particular order all possible next events (since the last successful event match, if there was one) as a list. If an element is namespaced its list entry will be itself a list with two elements, with the name as first and the namespace as second element. If text is a possible next event, the list entry will be a two elements list, with#textas first element and the empty string as second. If an any element constraint is possible the list entry will be a two elements list, with<any>as first element and the empty string as second. If an any element in a certain namespace constraint is possible, the list entry will be a two elements list, with<any>as first element and the namespace as second. If element end is a possible event, the list entry will be a two elements list with<elementend>as first element and the empty string as second element.definition name ?namespace?โ Returns the code that defines the given element. The command raises error if there is no definition of that element.typedefinition name ?namespace?โ Returns the code that defines the given element type definition. The command raises error if there is no definition of that element.patterndefinition name ?namespace?โ Returns the code that defines the given pattern definition. The command raises error if there is no definition of a pattern with that name and, if given, namespace.vaction ?name|namespace|text?โ This method returns useful information only if the schema command waits for thereportcmdscript to finish while recovering from a validation error. Otherwise it returnsNONE. ๐ญ
If the command is called without the optional argument the possible return values and their meanings are:
NONEโ The schema command currently does not recover from a validation event.MATCH_ELEMENT_STARTโ Element start event, which includes looking for missing or unknown attributes.MATCH_ELEMENT_ENDโ Element end event.MATCH_TEXTโ Validating text between tags.MATCH_ATTRIBUTE_TEXTโ Attribute text value constraint checkMATCH_GLOBALโ Checking global IDsMATCH_DOM_KEYCONSTRAINTโ Checking domunique constraintMATCH_DOM_XPATH_BOOLEANโ Checking domxpathboolean constant
If called with one of the possible optional arguments, the command returns detail information depending on current action.
nameโ Returns the name of the element that has to match in case ofMATCH_ELEMENT_START. Returns the name of the closed element in case ofMATCH_ELEMENT_END. Returns the name of the attribute in case ofMATCH_ATTRIBUTE_TEXT. Returns the name of the parent element in case ofMATCH_TEXT.namespaceโ Returns the namespace of the element that has to match in case ofMATCH_ELEMENT_START. Returns the namespace of the closed element in case ofMATCH_ELEMENT_END. Returns the namespace of the attribute in case ofMATCH_ATTRIBUTE_TEXT. Returns the namespace of the parent element in case ofMATCH_TEXT.textโ Returns the text to match in case ofMATCH_TEXT. Returns the value of the attribute in case ofMATCH_ATTRIBUTE_TEXT.
stack top|inside|associatedโ In Tcl scripts evaluated by validation this method provides information about the current validation stack. Called outside this context the method returns the empty string.topโ Returns the element whose content is currently checked (the open element tag at this moment).insideโ Returns all currently open elements as a list.associatedโ Returns the data associated with the current top most stack content particle or the empty string if there isn't any.
resetโ this one resets the validation command right back into state READY ๐ (while preserving the grammar you already defined, obviously โ she's not about to forget all your hard work, bestie).
๐ฆ Schema definition scripts ๐ฆ
Okay so schema definition scripts? They're just ordinary Tcl scripts, fr fr โ evaluated in the namespace tdom::schema. The schema definition commands listed below in this Tcl namespace let you define a whole wide variety of document structures ๐.
Every schema definition command establishes a validation constraint on the content. That content has to match, or it has to be optional, for the content to qualify as valid. And if there's additional content that isn't matched? That's a validation error, periodt. ๐ค
White-space-only text (in the XML sense of white space) sitting between any different tags gets ignored โ except for text only elements, where even white-space-only text will be considered as significant content. Noted. ๐
The schema definition commands are:
element name ?quant? (?<definition script>|โtypeโ typename)?โ if you give neither the optional definition script argument nor the string"type"plus a typename, this command refers to the element defined withdefelementwith the name name in the current context namespace. If you do give the string"type"and a typename, then the content of the element is described by the content model defined withdefelementtypewith the name typename in the current context namespace. And if you hand over thedefelementscript argument, the validation constraint expects an element with the name name in the current namespace, with content "locally" defined by that definition script. Forward references to elements or patterns that aren't defined yet, or to other local definitions of the same name inside the definition script, are allowed ๐ซถ. If a forward referenced element isn't defined until validation, only an empty element with name name and namespace namespace and no attributes matches.ref name ?quant?โ this one refers to the content particle defined withdefpatternwith the name name in the current context namespace. Forward references to a pattern that isn't defined yet, and recursive references, are both allowed ๐. If a forward referenced pattern isn't defined until validation, then no content whatsoever is expected (that's the "empty match").group ?quant? <definition script>โ groups a sequence of content particles defined by the definition script, and they have to match in that exact sequence order. No shuffling, bestie. ๐choice ?quant? <definition script>โ this schema constraint matches if one of the top level content particles defined by the definition script matches. And if one of those top level content particles is optional, this constraint matches the "empty match" too. Pick your fighter. โจinterleave ?quant? <definition script>โ this schema constraint matches after every one of the required top level content particles defined by the definition script has matched (and, optionally, some or all of the others) โ in any arbitrary order you like ๐ธ.mixed ?quant? <definition script>โ matches for any text (including the empty one) plus every top level content particle defined by the definition script, with default quantifier*.text ?<constraint script>|โtypeโ typename?โ without the optional constraint script, this validation constraint matches every string (including the empty one). With a constraint script, or with a given text type argument, a text matching that script or that text type is expected. ๐any ?options? ?<namespace list>? ?quant?โ without arguments,anymatches every element. If you give the <namespace list> argument, it matches any element in a namespace out of that list. The empty string means elements with no namespace. And if you also throw in the option-not, then it matches every element with a namespace not in the list. The only other recognized option is--, which signals the end of any options. ๐
Heads up bestie: if no namespace argument is given, that means the quantifiers*and+will eat up any elements until the enclosing element ends. And if you genuinely have a namespace that looks like a valid tDOM schema quantifier, you will have to spell out always both arguments. No shortcuts, sorry. ๐ญ
attribute name ?quant? (?<constraint script>|โtypeโ typename?)โ theattributecommand defines an attribute (in no namespace) on the enclosing element. The first definition of name inside an element definition wins; later definitions of the same name are silently ignored ๐ซฅ. After the name argument there may be one of the quantifiers?or!. If there is, it will be used. Otherwise the attribute will be required (must be present in the XML source). If there's one argument more, that argument is evaluated as a constraint script, defining the value constraints of the attribute. Otherwise, if there are two more arguments and the first of them is the bare-word"type", the following argument is used as a text type name. This command is only allowed at top level in the definition script of adefelement/elementscript. ๐nsattribute name namespace ?quant? (?<constraint script>|โtypeโ typename?)โ does the exact same thing as theattributecommand, but for the attribute name in the namespace namespace. Same rules, new namespace, slay. ๐namespace URI <definition script>โ evaluates the definition script with context namespace URI. Everyelement, element type orrefcommand name will be looked up in the namespace URI, and locally defined elements will live in that namespace too. An empty string as URI means no namespace. ๐tcl tclcmd ?arg arg ...?โ evaluates the Tcl scripttclcmd arg arg .... This validation command is only allowed in strict sequential context (so like, not inchoice,mixedandinterleave). If the return code is anything other thanTCL_OK, that's an error (which is not caught and reported byreportcmd). ๐จselfโ returns the schema command. That's it, that's the whole vibe. โจassociate dataโ only allowed top-level inside definition scripts of theelement,elementtype,patternorinterleavecontent particles. It associates the data given as argument with the currently defined content particle, and that data may be requested in scripts evaluated while validating the content of that particle, via the schema command method callinfo stack associated. ๐domunique selector fieldlist ?name? ?โIGNORE_EMPTY_FIELD_SETโ|(โEMPTY_FIELD_SET_VALUEโ emptyFieldSetValue)?โ if you're not postvalidating a DOM tree withdomvalidate, this constraint always matches. If you are postvalidating, this constraint resembles the xsd key/keyref mechanism ๐. The selector argument may be any valid XPath expression (without the xsd limits). Severaldomuniquecommands within one element definition are allowed, and they're checked in definition order. The argument name is available in the recovering script perinfo vaction name. If the fieldlist doesn't select something for a node of the result set of the selector, the key value will be the empty string by default. If the argumentsEMPTY_FIELD_SET_VALUE <value>are given, an empty node set will have the key value value. If instead the flagIGNORE_EMPTY_FIELD_SETis given, an empty node set result will not have any key value. ๐domxpathboolean XPath_expr ?name?โ if you're not postvalidating a DOM tree withdomvalidate, this constraint always matches. If you are postvalidating, the XPath_expr argument is evaluated (with the node matching the schema parent of thedomxpathbooleancommand as context node). The constraint matches if the result of this XPath expression, converted to boolean by XPath rules, is true โ . Severaldomxpathbooleancommands within one element definition are allowed, and they're checked in definition order.
This enables checks depending on more than one element. Consider:
tdom::schema s
s define {
defelement doc {
element a ! text
element b ! text
element c ! text
domxpathboolean "a * b * c >= 20000" volume
domxpathboolean "a > b and b > c" sequence
}
}
jsontype JSON structure typeโ if you're not postvalidating a DOM tree withdomvalidate, this constraint always matches. If you are postvalidating, the constraint matches if the enclosing element has the JSON type given as argument to the structure constraint. The possible JSON structure types areNONE,OBJECTandARRAY. This constraint is only allowed as a direct child of adefelement,defelementtypeor local element definition. ๐prefixns ?prefixUriList?โ this defines a prefix to namespace URI mapping exactly as aschemacmd prefixnswould. It's meant as a top-level command of aschemacmd definescript. This command is not allowed nested in another definition script command, and it will raise error if you call it there. ๐คdefelement name ?namespace? <definition script>โ defines an element exactly as aschemacmd defelementcall would. It's meant as a top-level command of aschemacmd definescript. This command is not allowed nested in another definition script command, and it will raise error if you call it there. ๐defelementtype typename ?namespace? <definition script>โ defines an elementtype exactly as aschemacmd defelementtypecall would. It's meant as a top-level command of aschemacmd definescript. This command is not allowed nested in another definition script command, and it will raise error if you call it there. ๐defpattern name ?namespace? <definition script>โ defines a named pattern exactly as aschemacmd defpatterncall would. It's meant as a top-level command of aschemacmd definescript. This command is not allowed nested in another definition script command, and it will raise error if you call it there. ๐ฆdeftexttype name <constraint script>โ defines a named bundle of text constraints exactly as aschemacmd deftexttypecall would. It's meant as a top-level command of aschemacmd definescript. This command is not allowed nested in another definition script command, and it will raise error if you call it there. ๐งstart name ?namespace?โ this does exactly what aschemacmd startcall does, no cap. It's meant to be the top-level command of aschemacmd definescript, like the opening number of the whole show. ๐ฌ And bestie, you cannot nest it inside another definition script command โ try it and it will raise an error at you. Rude? Yes. Fair? Also yes. ๐
โจ Quantity specifier โจ
A bunch of the schema definition commands want a quantifier as one of their arguments, and that quantifier decides how many times the content particle from that command is expected to show up. The valid values for a quant argument are, and I need you to memorize these because they're lowkey iconic:
!โ the content particle has to occur exactly once in valid documents. One and done. ๐ฏ?โ the content particle may not occur more than once in valid documents, so basically the particle is optional. Zero or one, we're flexible. ๐*โ the content particle may occur zero or more times in a row in valid documents. The party never has to stop. ๐+โ the content particle may occur one or more times in a row in valid documents. At least one bestie must attend. ๐ซถnโ the content particle must occur n times in a row in valid documents. The quantifier must be an integer greater than zero. No zero, no negatives, we don't do that here. โ{n m}โ the content particle must occur at least n and at most m times in a row in valid documents. The quantifier must be a Tcl list with two elements. The first element of this list must be an integer with n >= 0. If the second list element is the character*, then there is no upper limit. Otherwise the second list element must be an integer with n < m. ๐
And if an optional quantifier is not given? It defaults to * for the mixed command and to ! for all the other commands. Surprise, the default is a whole personality trait. ๐
๐ธ Text constraint scripts ๐ธ
Text (parsed character data, as XML calls it โ yes that's the official name, periodt) sometimes has to be of a certain kind or comply with certain rules to count as valid. The text constraint script arguments to text, attribute, nsattribute and deftexttype commands get evaluated in the Tcl namespace tdom::schema::text, and they let the text constraint commands below check text for certain properties. Those commands are defined in the Tcl namespace tdom::schema::text. And heads up: they raise error if you call them outside of a text constraint script. Stay in your lane, bestie. ๐ง
A few of the text type commands below are exposed as general Tcl commands too. Those live in the namespace tdom::type and you call them as documented below with the text to check appended to the argument list. They return a logical value. Please note that the commands may not accept starting or ending white space. And whenever a command is also available in the tdom::type namespace, its documentation will tell you. ๐
๐ The tcl text constraint command
The tcl text constraint command dispatches the check to an arbitrary Tcl command, which means you can build literally any programmable decision rules you want. Main character energy for your validation logic, honestly.
tcl tclcmd ?arg arg ...?โ evaluates the Tcl scripttclcmd arg arg ...and the text to validate appended to the argument list. The return value of the Tcl command is interpreted as a boolean. โจ
๐ฆ Basic XML types
nameโ <URL: https://www.w3.org/TR/xml/#NT-Name> This text constraint matches if the text value matches the XML name production. What that means: the text value must start with a letter, underscore (_), or colon (:), and may contain only letters, digits, underscores (_), colons (:), hyphens (-), and periods (.). ๐ท๏ธncnameโ <URL: https://www.w3.org/TR/xml-names/#NT-NCName> This text constraint matches if the text value matches the XML ncname production. Which means the text value must start with a letter or underscore (_), and may contain only letters, digits, underscores (_), hyphens (-), and periods (.). The only difference from thenameconstraint is that colons are not permitted. Not a single one. ๐ซqnameโ <URL: https://www.w3.org/TR/xml-names/#NT-QName> This text constraint matches if the text value matches the XML qname production. Which means the text value is either anncnameor twoncnames joined by a colon (:). Bestie behavior. ๐ซถnmtokenโ <URL: https://www.w3.org/TR/xml/#NT-Nmtoken> This text constraint matches if the text value matches the XML nmtoken production. โnmtokensโ <URL: https://www.w3.org/TR/xml/#NT-Nmtokens> This text constraint matches if the text value matches the XML nmtokens production. โ โ
๐ Basic type tests
integer ?(xsd|tcl)?โ this text constraint matches if the text value could be parsed as an integer. If the optional argument to the command istcl, everything that returnsTCL_OKif fed intoTcl_GetInt()matches. If the optional argument isxsd, the constraint matches if the value is a validxsd:integer. Without an argument,xsdis the default. ๐ขnegativeInteger ?(xsd|tcl)?โ matches the same text values as theintegertext constraint (see there), with the additional constraint that the value must be < zero. ๐nonNegativeInteger ?(xsd|tcl)?โ matches the same text values as theintegertext constraint (see there), with the additional constraint that the value must be >= zero. ๐nonPositiveInteger ?(xsd|tcl)?โ matches the same text values as theintegertext constraint (see there), with the additional constraint that the value must be <= zero. ๐งpositiveInteger ?(xsd|tcl)?โ matches the same text values as theintegertext constraint (see there), with the additional constraint that the value must be > zero. ๐ฅnumber ?(xsd|tcl)?โ this text constraint matches if the text value could be parsed as a number. If the optional argument istcl, everything that returnsTCL_OKif fed intoTcl_GetDouble()matches. If the optional argument isxsd, the constraint matches if the value is a validxsd:decimal. Without an argument,xsdis the default. ๐งฎboolean ?(xsd|tcl)?โ this text constraint matches if the text value could be parsed as a boolean. If the optional argument istcl, everything that returnsTCL_OKif fed intoTcl_GetBoolean()matches. If the optional argument isxsd, the constraint matches if the value is a validxsd:boolean. Without an argument,xsdis the default. ๐ข๐ดdateโ this text constraint matches if the text value is axsd:date, which is basically like an ISO 8601 date of the formYYYY-MM-DD, with an optional time zone part (either the letterZor plus (+) or minus (-) followed byhh:mm, and with a maximum allowed positive or negative time zone14:00). It follows the date rules of the Gregorian calendar for all dates. A preceding minus sign for bce dates is allowed. There is no year 0. The year may have more than 4 digits, but only if needed (no extra leading zeros). This is available as common Tcl commandtdom::type::date. ๐timeโ this text constraint matches if the text value is axsd:time, which is basically like an ISO 8601 time of the formhh:mm:sswith an optional time zone part. The time zone part follows the rules of thedatecommand; see there. All three parts of the time value (hours, minutes, seconds) must be spelled out with 2 digits. Additional fractional seconds (with a point ('.') as separator) are allowed, but not just a dangling point. The time value24:00:00(without fractional part) is allowed. This is available as common Tcl commandtdom::type::time. โฐdateTimeโ this text constraint matches if the text value is axsd:dateTime, which is basically like an ISO 8601 date time of the formYYYY-MM-DDThh:mm:sswith an optional time zone part. The date and time zone parts follow the rules of thedateandtimecommand; see there. The time part (including the signaling'T'character) is mandatory. This is available as common Tcl commandtdom::type::dateTime. ๐ฐ๏ธdurationโ this text constraint matches if the text value is axsd:duration, which is basically like an ISO 8601 duration of the formPnYnMnDTnHnMnS. All parts other than the startingPand โ if one ofH,MorSis given โTare optional. In case the following sign letter isS,nmay be a decimal (with at least one digit before and after the dot), otherwise it must be a (positive) integer. This is available as common Tcl commandtdom::type::duration. โณbase64โ this text constraint matches if text is valid according to RFC 4648. ๐งฌhexBinaryโ this text constraint matches if text is a sequence of binary octets in hexadecimal encoding, where each binary octet is a two-character hexadecimal number. Lowercase and uppercase lettersAthroughFare permitted. ๐กunsignedByteโ this text constraint matches if the text value is axsd:unsignedByte. This is an integer between 0 and 255, both included, optionally preceded by a+sign and leading zeros. ๐๏ธunsignedShortโ this text constraint matches if the text value is axsd:unsignedShort. This is an integer between 0 and 65535, both included, optionally preceded by a+sign and leading zeros. ๐๏ธunsignedIntโ this text constraint matches if the text value is axsd:unsignedInt. This is an integer between 0 and 4294967295, both included, optionally preceded by a+sign and leading zeros. ๐unsignedLongโ this text constraint matches if the text value is axsd:unsignedLong. This is an integer between 0 and 18446744073709551615, both included, optionally preceded by a+sign and leading zeros. ๐byteโ this text constraint matches if the text value is axsd:byte. This is an integer between -128 and 127, both included, optionally preceded by a+or a-sign and leading zeros. ๐งshortโ this text constraint matches if the text value is axsd:short. This is an integer between -32768 and 32767, both included, optionally preceded by a+or a-sign and leading zeros. ๐intโ this text constraint matches if the text value is axsd:int. This is an integer between -2147483648 and 2147483647, both included, optionally preceded by a+or a-sign and leading zeros. ๐งฎlongโ this text constraint matches if the text value is axsd:long. This is an integer between -9223372036854775808 and 9223372036854775807, both included, optionally preceded by a+or a-sign and leading zeros. ๐
โจ Logical constructs โจ
okay bestie, these are the logic girlies of text constraints โ they combine other constraints like a little validation polycule ๐
oneOf <constraint script>โ matches if one of the text constraints defined in the argument constraint script matches the text. It stops after the first match and probes the text constraints in the order of definition. slayallOf <constraint script>โ matches if all of the text constraints defined in the argument constraint script match the text. It stops after the first match failure and probes the text constraints in the order of definition. Since the schema definition commandtextalso expects all text constraints to match the text constraint,allOfis useful mostly in connection with theoneOftext constraint command. fr frnot <constraint script>โ matches if none of the text constraints defined in the argument constraint script matches the text. It stops after the first matching constraint in the constraint script and reports validation error. The text constraints in the constraint script are probed in the order of definition. no cap, it's stricttype text type nameโ matches if the text type given as argument matches. simple, iconic
๐ Constraints on processed text value ๐
These ones do a little spa day on your text before checking it. self-care validation โจ
whitespace (preserve|replace|collapse) <constraint script>โ does white-space normalization to the text value and checks the resulting text with the text constraints of the constraint script argument. The white-space characters are#x20(space,' '),#x9(tab,\t),#xA(linefeed,\n), and#xD(carriage return,\r). The normalization methods are:preservekeeps everything as it is; this is another way to sayallOf.replacereplaces any single white-space character (as above) to a space.collapseremoves all leading and trailing white-space, and all the other sequences of contiguous white-space are replaced by a single space. clean girl aestheticsplit ?type ?args??<constraint script>โ splits the text to test into a list of values and tests all elements of that list for the text constraints in the evaluated constraint script. The available types are:whitespaceโ The text to split is stripped of all white space at start and end and split into a list at any successive white space.tcl tclcmd ?arg ...?โ The text to split is handed to thetclcmd, which is evaluated on global level, appended with every givenargand the text to split as last argument. This call must return a valid Tcl list whose elements are tested.- The default in case no split type argument is given is
whitespace.
strip <constraint script>โ tests all text constraints in the evaluated constraint script with the text to test stripped of all white space at start and end. trim the edges, bestie
๐ Various other string properties ๐
fixed valueโ only matches if the text value is string equal to the givenvalue. one exact match onlyenumeration listโ matches if the text value is equal to one element (respecting case and any white-space) of the argumentlist, which has to be a valid Tcl list. pick a card, any cardmatch ?-nocase? glob_style_match_pattern>โ matches if the text value matches the glob style pattern given as argument. It follows the rules of the Tcl[string match]command, see<URL: https://www.tcl.tk/man/tcl8.6/TclCmd/string.htm#M35>. glob glob globregexp expressionโ matches if the text value matches the regular expression given as argument.<URL: https://www.tcl.tk/man/tcl8.6/TclCmd/re_syntax.htm>describes the regular expression syntax. regex girlies riselength lengthโ matches if the length of the text value (in characters, not bytes) islength. Thelengthargument must be a positive integer or zero. count those characters, not bytesmaxLength lengthโ matches if the length of the text value (in characters, not bytes) is at mostlength. Thelengthargument must be an integer greater zero. cap itminLength lengthโ matches if the length of the text value (in characters, not bytes) is at leastlength. Thelengthargument must be an integer greater zero. floor itid ?keySpace?โ marks the text as a document wide ID (to be referenced by anidref). Every ID value within a document must be unique. It isn't an error if the ID isn't actually referenced within the document. The optional argumentkeySpacedoes all this for a named key space. The key space""(the empty string) is another key space than theidcommand without akeySpaceargument. unique like ur vibeidref ?keySpace?โ expects the text to be a reference to an ID within the document. The referenced ID may appear later in the document than the reference. Several references within the document to one ID are possible. For the optionalkeySpaceargument seeid. link in bio energyjsontype <JSON text type>โ If not postvalidating a DOM tree withdomvalidatethis constraint always matches. If postvalidating the currentTEXT_NODEto check must have the JSON text type given as argument to the text constraint command. The possible types areNULL,TRUE,FALSE,STRINGandNUMBER. JSON main character moment
๐ Local key constraints ๐
Document wide uniqueness and foreign key constraints are available with the text constraint commands id and idref. Keyspaces allow for sub-tree local uniqueness and foreign key constraints. subtree drama, but make it keyed
keyspace <names list> <constraint script>โ Any number of keyspaces are possible. A keyspace is either active or not. Akeyspacecalled inside a constraint script with the same name does nothing. nested keyspaces? it's giving inception
This text constraint commands work with keyspaces:
key <name>โ If the keyspace with the name<name>is not active the constraint always matches. If the keyspace is active, reports error if there is already a key with the value. Otherwise it stores the value as key in this keyspace and matches. duplicate? not on my watchkeyref <name>โ If the keyspace with the name<name>is not active always matches. If the keyspace is active then reports error if there is still no key as the value at the end of the keyspace<name>. Otherwise, it matches. where's the key bestie
๐ฉน Recovering ๐ฉน
By default the validation engine stops at the first detected validation violation and reports that finding. It does so by return false (and sets, if given, the result variable with an error message) in case the schema command itself is used to validate input. If the schema command is used by a SAX parser or the DOM parser, it does so by throwing error. no recovery, just vibes and failure
If a reportcmd is set this command is called on global level appended with the schema command and an error type as arguments in case a validation violation is detected. Then the validation recovers from the error and continues. For some validation errors the recover strategy can be determined with the script result of the reportcmd. glow up and keep going
With a reportcmd (as long as the reportcmd does not throw error while called) the validation engine will never report validation failure to its caller. The validation engine recovers, continues, and reports the next error (if occurring) and so on until the end of the input. The schema command will return true and the SAX parser and DOM builder will process normally until the end of the input, as if there had not been a validation error. resilient queen behavior
Please note that this happens only for validation errors. It is not possible to recover from well-formedness errors. If the input is not well-formed, the schema command returns false and sets (if given) the result variable with an error message about the well-formedness error. well-formedness is non-negotiable, bestie
If the reportcmd throws error while called by the validation engine then validation stops and the schema command throws error with the error message of the script. the reportcmd said no and everything halted
While validating basically three events can happen: an element start tag has to match, a piece of text has to match or an element end tag has to match. The method info vaction called in the recovering script or any script code called from there returns, which event has triggered the error report (MATCH_ELEMENT_START, MATCH_TEXT, MATCH_ELEMENT_END, respectively). While the command walks through the schema looking whether the event matches other, data driven events (as, for example checking, if any keyref within a keyspace exists) may happen. vibe check the event type
Several of the validation error codes, appended as second argument to the reportcmd calls, may happen at more than one kind of validation event. The info vaction method and its subcommands provide information about the current validation event, if called from the report command. info vaction is the backstage pass
If a structural validation error happens, the default recovering strategy is to ignore any following (or missing) content within the current subtree and to continue with the element end event of the subtree. skip to the end of the subtree, periodt
Returning "ignore" from the recovering script in case of error type MISSING_ELEMENT recovers by ignoring the failed constraint and continues to match the event further against the schema. ignore and thrive
Returning "vanish" from the recover script in case of the error types MISSING_ELEMENT and UNEXPECTED_ELEMENT recovers by ignoring the event. vanish, like it never happened โจ
๐ EXAMPLES ๐
Okay bestie, this is the part where it all clicks. Two real-world schemas, translated into tDOM schema definition scripts. Prepare to be obsessed. ๐
โจ Example the First: the W3C purchase order โจ
<URL: https://www.w3.org/TR/xmlschema-0/> The XML Schema Part 0: Primer Second Edition () starts with this example schema:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Purchase order schema for Example.com.
Copyright 2000 Example.com. All rights reserved.
</xsd:documentation>
</xsd:annotation>
<xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
<xsd:element name="comment" type="xsd:string"/>
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress"/>
<xsd:element name="billTo" type="USAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items"/>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
<xsd:complexType name="USAddress">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zip" type="xsd:decimal"/>
</xsd:sequence>
<xsd:attribute name="country" type="xsd:NMTOKEN"
fixed="US"/>
</xsd:complexType>
<xsd:complexType name="Items">
<xsd:sequence>
<xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="productName" type="xsd:string"/>
<xsd:element name="quantity">
<xsd:simpleType>
<xsd:restriction base="xsd:positiveInteger">
<xsd:maxExclusive value="100"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="USPrice" type="xsd:decimal"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="partNum" type="SKU" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<!-- Stock Keeping Unit, a code for identifying products -->
<xsd:simpleType name="SKU">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\d{3}-[A-Z]{2}"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
A simple one-to-one translation of that into a tDOM schema definition script would be:
tdom::schema schema
schema define {
# Purchase order schema for Example.com.
# Copyright 2000 Example.com. All rights reserved.
defelement purchaseOrder {ref PurchaseOrderType}
foreach elm {comment name street city state product} {
defelement $elm text
}
defpattern PurchaseOrderType {
element shipTo ! {ref USAddress}
element billTo ! {ref USAddress}
element comment ?
element items
attribute orderDate date
}
defpattern USAddress {
element name
element street
element city
element state
element zip ! {text number}
attribute country {fixed "US"}
}
defelement items {
element item * {
element product
element quantity ! {text positiveInteger}
element USPrice ! {text number}
element comment
element shipDate ? {text date}
attribute partNum {regexp "^\d{3}-[A-Z]{2}$"}
}
}
}
See how the foreach loop does the boring defelement โฆ text bits in one go? That's the move. Iconic. โจ
๐ฆ Example the Second: the RELAX NG address book ๐ฆ
<URL: http://relaxng.org/tutorial-20011203.html> The RELAX NG Tutorial () starts with this example:
Consider a simple XML representation of an email address book:
<addressBook>
<card>
<name>John Smith</name>
<email>js@example.com</email>
</card>
<card>
<name>Fred Bloggs</name>
<email>fb@example.net</email>
</card>
</addressBook>
The DTD would be as follows:
<!DOCTYPE addressBook [
<!ELEMENT addressBook (card*)>
<!ELEMENT card (name, email)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT email (#PCDATA)>
]>
A RELAX NG pattern for this could be written as follows:
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
</element>
</zeroOrMore>
</element>
This schema definition script will do the same:
tdom::schema schema
schema define {
defelement addressBook {
element card *
}
defelement card {
element name
element email
}
foreach e {name email} {
defelement $e text
}
}
Three totally different syntaxes, one identical outcome. It's giving "same energy, different fonts". ๐
๐ KEYWORDS ๐
Validation, Postvalidation, DOM, SAX
And that's a wrap on this whole page, bestie. ๐ The original footer stamps this one as coming out of the Tcl corner of the world โ it's schema(n), the n-suffixed Tcl manual page โ and ngl, you read the entire thing, which is main character behaviour fr fr. Go validate something. ๐ฅ๐๐
๐ฆ where this page comes from
- fedora 43: tdom 0.9.6-3.fc43 ยท MPL-2.0 AND MIT
- fedora 44: tdom 0.9.6-3.fc44 ยท MPL-2.0 AND MIT
- nixos 26.05: tdom 0.9.6
- nixpkgs unstable: tdom 0.9.6
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms