Index: doc/schema.html ================================================================== --- doc/schema.html +++ doc/schema.html @@ -2,22 +2,22 @@ tDOM manual: schema
-

NAME

+

NAME

tdom::schema -
Create a schema validation command

-

SYNOPSIS

package require tdom
+  

SYNOPSIS

package require tdom
 
 tdom::schema ?create? cmdName
     
-

DESCRIPTION

This command creates validation commands with a simple API. The +

DESCRIPTION

This command creates validation commands with a simple API. The validation commands have methods to define a schema and are able to validate XML or DOM trees (and to some degree other kind of hierarchical data) against this schema.

Additionally, a validation command may be used as argument to the -validateCmd option of the dom parse and the expat commands to enable validation additional to what they @@ -186,11 +186,11 @@ READY (while preserving the defined grammer). -

Schema definition scripts

Schema definition scripts are ordinary Tcl scripts that are +

Schema definition scripts

Schema definition scripts are ordinary Tcl scripts that are evaluatend in the namespace tdom::schema. The below listed schema definition commands in this tcl namespace allow to define a wide variety of document structures. Every schema definition command establish a validation constraint on the content which has to match or must be optional to render the content as valid. It is a @@ -347,11 +347,11 @@ other definition script command and will raise error, if you call it there. -

Quantity specifier

Serveral schema definition commands expects a quantifier as +

Quantity specifier

Serveral schema definition commands expects a quantifier as one of their arguments, which specifies how often the content particle specified by the command is expected. The valid values for a quant argument are:

!
@@ -392,11 +392,13 @@ must be integers, with n >= 0 and n < m.

If an optional quantifier is not given then it defaults to * in case of the mixed command and to ! for all other commands.

-

Text constraint scripts

The text constraint commands are:

+

Text constraint scripts

Text - parsed character data, as XML calles it - must sometimes + have to be of a certain kind, must comply to some rules etc to be + valid.

The text constraint commands are:

isint
@@ -555,12 +557,48 @@
base64
This text constraint match if text is valid according to RFC 4648.
- -

Exampels

The XML Schema Part 0: Primer Second Edition + +

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.

+ +
+keyspace names list> <constraint script> +
+
Any number of keyspaces are possible. A keyspace is + either active or not. An inside a constraint + script> called keyspace with the same name does + nothing.
+ +

This text constraint commands work with keyspaces:

+ +
+key name> +
+
If the keyspace with the name name> is not + active always matches. If the keyspace is active then + reports error if there is already a key with the value. + Otherwise, stores the value as key in this keyspace and + matches.
+ + +
+keyref 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.
+ +
+ +

Exampels

The XML Schema Part 0: Primer Second Edition (https://www.w3.org/TR/xmlschema-0/) starts with this example schema:

 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 
   <xsd:annotation>

Index: doc/schema.n
==================================================================
--- doc/schema.n
+++ doc/schema.n
@@ -430,10 +430,13 @@
 .PP
 If an optional quantifier is not given then it defaults to * in
 case of the mixed command and to ! for all other commands.
 .SH "Text constraint scripts"
 .PP
+Text - parsed character data, as XML calles it - must sometimes
+have to be of a certain kind, must comply to some rules etc to be
+valid.
 .PP
 The text constraint commands are:
 .TP
 \&\fB\fBisint\fP
 \&\fR
@@ -562,10 +565,38 @@
 references within the document to one ID are possible.
 .TP
 \&\fB\fBbase64\fP
 \&\fRThis text constraint match if text is valid according to
 RFC 4648.
+.SH "Local key constraints"
+.PP
+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.
+.TP
+\&\fB\fBkeyspace\fP \fInames list>\fB \fI\fB
+\&\fRAny number of keyspaces are possible. A keyspace is
+either active or not. An inside a \fIconstraint
+script>\fR called keyspace with the same name does
+nothing.
+.PP
+This text constraint commands work with keyspaces:
+.TP
+\&\fB\fBkey\fP \fIname>\fB
+\&\fRIf the keyspace with the name \fIname>\fR is not
+active always matches. If the keyspace is active then
+reports error if there is already a key with the value.
+Otherwise, stores the value as key in this keyspace and
+matches.
+.TP
+\&\fB\fBkeyref\fP \fIname>\fB
+\&\fRIf the keyspace with the name \fIname>\fR 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 \fIname>\fR. Otherwise it
+matches.
 .SH Exampels
 .PP
 .UR "https://www.w3.org/TR/xmlschema-0/"
 
 .UE

Index: doc/schema.xml
==================================================================
--- doc/schema.xml
+++ doc/schema.xml
@@ -369,11 +369,13 @@
   
 
   
Text constraint scripts -

+

Text - parsed character data, as XML calles it - must sometimes + have to be of a certain kind, must comply to some rules etc to be + valid.

The text constraint commands are:

@@ -511,11 +513,52 @@ This text constraint match if text is valid according to RFC 4648.
- + +
+ 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.

+ + + + keyspace &kt;names list> <constraint script> + Any number of keyspaces are possible. A keyspace is + either active or not. An inside a constraint + script> called keyspace with the same name does + nothing. + + + +

This text constraint commands work with keyspaces:

+ + + + key name> + If the keyspace with the name name> is not + active always matches. If the keyspace is active then + reports error if there is already a key with the value. + Otherwise, stores the value as key in this keyspace and + matches. + + + keyref 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. + + + +
+
Exampels

The XML Schema Part 0: Primer Second Edition (https://www.w3.org/TR/xmlschema-0/) starts with this @@ -603,34 +646,34 @@ element purchaseOrder {ref PurchaseOrderType} element comment {text} defpattern PurchaseOrderType { - element shipTo {ref USAddress} - element billTo {ref USAddress} + element shipTo ! {ref USAddress} + element billTo ! {ref USAddress} element comment ? element items - attribute orderDate + attribute orderDate isodate } defpattern USAddress { element name ! {text} element street ! {text} element city ! {text} element state ! {text} element zip ! {text isNumber} - attribute country ! {text {fixed "US"}} + attribute country ! {fixed "US"} } defelement items { element item * { element product ! {text} - element quntity ! {text {maxExcluse 100}} - element USPrice ! {text isNumber} + element quantity ! {text {maxExcluse 100}} + element USPrice ! {text integer} element comment - element shipDate ? {text isDate} - attribute partNum ! {text {pattern "\d{3}-[A-Z]{2}"}} + element shipDate ? {text isodate} + attribute partNum ! {pattern "\d{3}-[A-Z]{2}"} } } } Index: generic/schema.c ================================================================== --- generic/schema.c +++ generic/schema.c @@ -295,10 +295,14 @@ ); break; case SCHEMA_CTYPE_TEXT: /* content/quant will be allocated, if the cp in fact has * constraints */ + break; + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: + pattern->name = name; break; case SCHEMA_CTYPE_VIRTUAL: case SCHEMA_CTYPE_ANY: /* Do nothing */ break; @@ -312,14 +316,18 @@ ) { fprintf (stderr, "CP %p type: %s\n", pattern, Schema_CP_Type2str[pattern->type]); switch (pattern->type) { + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: + fprintf (stderr, "\tName: '%s'\n", pattern->name); + break; case SCHEMA_CTYPE_NAME: case SCHEMA_CTYPE_PATTERN: fprintf (stderr, "\tName: '%s' Namespace: '%s'\n", - pattern->name,pattern->namespace); + pattern->name, pattern->namespace); if (pattern->flags & FORWARD_PATTERN_DEF) { fprintf (stderr, "\tAnonymously defined NAME\n"); } if (pattern->flags & PLACEHOLDER_PATTERN_DEF) { fprintf (stderr, "\tAs placeholder defined NAME\n"); @@ -488,10 +496,11 @@ sdata->cdata = TMALLOC (Tcl_DString); Tcl_DStringInit (sdata->cdata); Tcl_InitHashTable (&sdata->ids, TCL_STRING_KEYS); sdata->unknownIDrefs = 0; Tcl_InitHashTable (&sdata->idTables, TCL_STRING_KEYS); + Tcl_InitHashTable (&sdata->keySpaces, TCL_STRING_KEYS); return sdata; } static void schemaInstanceDelete ( ClientData clientData @@ -501,10 +510,11 @@ unsigned int i; SchemaValidationStack *down; Tcl_HashEntry *h; Tcl_HashSearch search; SchemaDocKey *dk; + SchemaKeySpace *ks; /* Protect the clientData to be freed inside (even nested) * Tcl_Eval*() calls to avoid invalid mem access and postpone the * cleanup until the Tcl_Eval*() calls are finished (done in * schemaInstanceCmd(). */ @@ -566,10 +576,20 @@ dk = Tcl_GetHashValue (h); Tcl_DeleteHashTable (&dk->ids); FREE (dk); } Tcl_DeleteHashTable (&sdata->idTables); + for (h = Tcl_FirstHashEntry (&sdata->keySpaces, &search); + h != NULL; + h = Tcl_NextHashEntry (&search)) { + ks = Tcl_GetHashValue (h); + if (ks->active) { + Tcl_DeleteHashTable (&ks->ids); + } + FREE (ks); + } + Tcl_DeleteHashTable (&sdata->keySpaces); FREE (sdata); } static void cleanupLastPattern ( @@ -925,10 +945,14 @@ break; case SCHEMA_CTYPE_VIRTUAL: Tcl_Panic ("Virtual constrain in MIXED or CHOICE"); + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: + Tcl_Panic ("Keyspace constrain in MIXED or CHOICE"); + } if (!mayskip && mayMiss (candidate->quants[i])) mayskip = 1; } break; @@ -952,12 +976,39 @@ if (rc == 1) { updateStack (se, cp, ac); return 1; } popStack (sdata); - break; + + case SCHEMA_CTYPE_KEYSPACE_END: + candidate->keySpace->active--; + if (!candidate->keySpace->active) { + if (candidate->keySpace->unknownIDrefs) { + if (!recover (interp, sdata, S("UNKNOWN_KEYREF"))) { + return 0; + } + candidate->keySpace->unknownIDrefs = 0; + } + Tcl_DeleteHashTable (&candidate->keySpace->ids); + } + ac++; + hm = 0; + continue; + + case SCHEMA_CTYPE_KEYSPACE: + if (!candidate->keySpace->active) { + Tcl_InitHashTable (&candidate->keySpace->ids, + TCL_STRING_KEYS); + candidate->keySpace->active = 1; + candidate->keySpace->unknownIDrefs = 0; + } else { + candidate->keySpace->active++; + } + ac++; + hm = 0; + continue; } if (!mayskip && mustMatch (cp->quants[ac], hm)) { if (recover (interp, sdata, S("MISSING_CP"))) { /* Skip the just opened element tag and the following * content of the current. */ @@ -978,10 +1029,12 @@ } return 0; } return -1; + case SCHEMA_CTYPE_KEYSPACE: + case SCHEMA_CTYPE_KEYSPACE_END: case SCHEMA_CTYPE_VIRTUAL: case SCHEMA_CTYPE_CHOICE: case SCHEMA_CTYPE_TEXT: case SCHEMA_CTYPE_ANY: /* Never pushed onto stack */ @@ -1037,10 +1090,16 @@ break; case SCHEMA_CTYPE_VIRTUAL: Tcl_Panic ("Virtual constraint child of INTERLEAVE"); break; + + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: + Tcl_Panic ("Keyspace constraint child of INTERLEAVE"); + break; + } } if (mayskip) break; @@ -1467,10 +1526,34 @@ if (mayMiss (cp->quants[ac])) { ac++; continue; } switch (cp->content[ac]->type) { + case SCHEMA_CTYPE_KEYSPACE_END: + cp->content[ac]->keySpace->active--; + if (!cp->content[ac]->keySpace->active) { + if (cp->content[ac]->keySpace->unknownIDrefs) { + if (!recover (interp, sdata, S("UNKNOWN_KEYREF"))) { + return 0; + } + cp->content[ac]->keySpace->unknownIDrefs = 0; + } + Tcl_DeleteHashTable (&cp->content[ac]->keySpace->ids); + } + break; + + case SCHEMA_CTYPE_KEYSPACE: + if (!cp->content[ac]->keySpace->active) { + Tcl_InitHashTable (&cp->content[ac]->keySpace->ids, + TCL_STRING_KEYS); + cp->content[ac]->keySpace->active = 1; + cp->content[ac]->keySpace->unknownIDrefs = 0; + } else { + cp->content[ac]->keySpace->active++; + } + break; + case SCHEMA_CTYPE_TEXT: if (cp->content[ac]->nc) { if (!checkText (interp, cp->content[ac], "")) { if (recover (interp, sdata, S("MISSING_TEXT"))) { break; @@ -1496,12 +1579,10 @@ } } mayMiss = 1; break; - case SCHEMA_CTYPE_CHOICE: - /* Can't happen */ case SCHEMA_CTYPE_NAME: case SCHEMA_CTYPE_ANY: continue; case SCHEMA_CTYPE_INTERLEAVE: @@ -1511,12 +1592,15 @@ mayMiss = 1; } popStack (sdata); break; + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: case SCHEMA_CTYPE_VIRTUAL: - Tcl_Panic ("Virtual constrain in MIXED or CHOICE"); + case SCHEMA_CTYPE_CHOICE: + Tcl_Panic ("Invalid CTYPE in MIXED or CHOICE"); } if (mayMiss) break; } if (mayMiss) break; @@ -1547,10 +1631,12 @@ ac++; } if (isName) return 1; return -1; + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: case SCHEMA_CTYPE_VIRTUAL: case SCHEMA_CTYPE_CHOICE: case SCHEMA_CTYPE_TEXT: case SCHEMA_CTYPE_ANY: /* Never pushed onto stack */ @@ -1721,10 +1807,11 @@ return 1; } if (!sdata->evalError) { SetResult ("Invalid text content"); } + updateStack (se, cp, ac); return 0; case SCHEMA_CTYPE_CHOICE: if (candidate->flags & MIXED_CONTENT) { updateStack (se, cp, ac); @@ -1759,10 +1846,14 @@ break; case SCHEMA_CTYPE_CHOICE: Tcl_Panic ("MIXED or CHOICE child of MIXED or CHOICE"); + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: + Tcl_Panic ("Keyspace constrain in MIXED or CHOICE"); + } } if (mustMatch (cp->quants[ac], hm)) { SetResult ("Unexpected text content"); return 0; @@ -1784,10 +1875,34 @@ break; case SCHEMA_CTYPE_VIRTUAL: if (!evalVirtual (interp, sdata, candidate)) return 0; break; + + case SCHEMA_CTYPE_KEYSPACE: + if (!cp->content[ac]->keySpace->active) { + Tcl_InitHashTable (&cp->content[ac]->keySpace->ids, + TCL_STRING_KEYS); + cp->content[ac]->keySpace->active = 1; + cp->content[ac]->keySpace->unknownIDrefs = 0; + } else { + cp->content[ac]->keySpace->active++; + } + break; + + case SCHEMA_CTYPE_KEYSPACE_END: + cp->content[ac]->keySpace->active--; + if (!cp->content[ac]->keySpace->active) { + if (cp->content[ac]->keySpace->unknownIDrefs) { + if (!recover (interp, sdata, S("UNKNOWN_KEYREF"))) { + return 0; + } + cp->content[ac]->keySpace->unknownIDrefs = 0; + } + Tcl_DeleteHashTable (&cp->content[ac]->keySpace->ids); + } + break; case SCHEMA_CTYPE_NAME: case SCHEMA_CTYPE_ANY: if (mustMatch (cp->quants[ac], hm)) { SetResult ("Unexpected text content"); @@ -1803,10 +1918,12 @@ return 0; } popStack (sdata); continue; + case SCHEMA_CTYPE_KEYSPACE: + case SCHEMA_CTYPE_KEYSPACE_END: case SCHEMA_CTYPE_VIRTUAL: case SCHEMA_CTYPE_CHOICE: case SCHEMA_CTYPE_TEXT: case SCHEMA_CTYPE_ANY: /* Never pushed onto stack */ @@ -1840,15 +1957,20 @@ break; case SCHEMA_CTYPE_CHOICE: Tcl_Panic ("MIXED or CHOICE child of INTERLEAVE"); + case SCHEMA_CTYPE_KEYSPACE_END: + case SCHEMA_CTYPE_KEYSPACE: + Tcl_Panic ("Keyspace child of INTERLEAVE"); + case SCHEMA_CTYPE_VIRTUAL: break; } } + break; } break; } return 0; } @@ -2423,10 +2545,11 @@ ) { Tcl_HashEntry *h; Tcl_HashSearch search; SchemaDocKey *dk; + SchemaKeySpace *ks; while (sdata->stack) popStack (sdata); sdata->validationState = VALIDATION_READY; sdata->skipDeep = 0; sdata->evalError = 0; @@ -2445,10 +2568,23 @@ Tcl_DeleteHashTable (&dk->ids); Tcl_InitHashTable (&dk->ids, TCL_STRING_KEYS); dk->unknownIDrefs = 0; } } + } + if (sdata->keySpaces.numEntries) { + for (h = Tcl_FirstHashEntry (&sdata->keySpaces, &search); + h != NULL; + h = Tcl_NextHashEntry (&search)) { + ks = Tcl_GetHashValue (h); + if (ks->active && ks->ids.numEntries) { + Tcl_DeleteHashTable (&ks->ids); + Tcl_InitHashTable (&ks->ids, TCL_STRING_KEYS); + } + ks->unknownIDrefs = 0; + ks->active = 0; + } } } static int evalConstraints ( @@ -3644,15 +3780,12 @@ if (objc < 2) { SetResult ("Expected: ?arg? ?arg? ..."); return TCL_ERROR; } - switch (sdata->cp->type) { - case SCHEMA_CTYPE_NAME: - case SCHEMA_CTYPE_PATTERN: - break; - default: + if (sdata->cp->type != SCHEMA_CTYPE_NAME + && sdata->cp->type != SCHEMA_CTYPE_PATTERN) { SetResult ("The \"tcl\" schema definition command is only " "allowed in sequential context (defelement, " "element or defpattern)"); return TCL_ERROR; } @@ -3670,11 +3803,11 @@ addToContent (sdata, pattern, SCHEMA_CQUANT_ONE, 0, 0); return TCL_OK; } static int -domuniquePatternCmd ( +domuniquePatternObjCmd ( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[] ) @@ -3751,10 +3884,75 @@ } kc->next = sdata->cp->domKeys; sdata->cp->domKeys = kc; return TCL_OK; } + +static int +keyspacePatternObjCmd ( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[] + ) +{ + SchemaData *sdata = GETASI; + SchemaCP *pattern; + int nrKeyspaces, i, hnew; + Tcl_Obj *ksObj; + SchemaKeySpace *ks; + Tcl_HashEntry *h; + + CHECK_SI + CHECK_TOPLEVEL + checkNrArgs (3, 3, "Expected: pattern"); + if (sdata->cp->type != SCHEMA_CTYPE_NAME + && sdata->cp->type != SCHEMA_CTYPE_PATTERN) { + SetResult ("The keyspace schema definition command is only " + "allowed in sequential context (defelement, " + "element or defpattern)"); + return TCL_ERROR; + } + if (Tcl_ListObjLength (interp, objv[1], &nrKeyspaces) != TCL_OK) { + SetResult ("The argument must be a valid tcl " + "list"); + return TCL_ERROR; + } + for (i = 0; i < nrKeyspaces; i++) { + Tcl_ListObjIndex (interp, objv[1], i, &ksObj); + h = Tcl_CreateHashEntry (&sdata->keySpaces, + Tcl_GetString (ksObj), &hnew); + if (hnew) { + ks = TMALLOC (SchemaKeySpace); + ks->active = 0; + ks->unknownIDrefs = 0; + Tcl_SetHashValue (h, ks); + } else { + ks = Tcl_GetHashValue (h); + } + pattern = initSchemaCP (SCHEMA_CTYPE_KEYSPACE, + Tcl_GetString (ksObj), NULL); + pattern->keySpace = ks; + REMEMBER_PATTERN (pattern); + addToContent (sdata, pattern, SCHEMA_CQUANT_ONE, 0, 0); + } + sdata->currentEvals++; + if (Tcl_EvalObjEx (interp, objv[2], TCL_EVAL_DIRECT) != TCL_OK) { + return TCL_ERROR; + } + sdata->currentEvals--; + for (i = 0; i < nrKeyspaces; i++) { + Tcl_ListObjIndex (interp, objv[1], i, &ksObj); + h = Tcl_FindHashEntry (&sdata->keySpaces, Tcl_GetString(ksObj)); + pattern = initSchemaCP (SCHEMA_CTYPE_KEYSPACE_END, + Tcl_GetString (ksObj), NULL); + REMEMBER_PATTERN (pattern); + pattern->keySpace = Tcl_GetHashValue (h); + addToContent (sdata, pattern, SCHEMA_CQUANT_ONE, 0, 0); + } + return TCL_OK; +} static int integerImpl ( Tcl_Interp *interp, void *constraintData, @@ -4941,10 +5139,122 @@ sc->constraint = docidrefImpl; sc->constraintData = (void *)dk; } return TCL_OK; } + +static int +keyImpl ( + Tcl_Interp *interp, + void *constraintData, + char *text + ) +{ + SchemaKeySpace *ks = (SchemaKeySpace *) constraintData; + int hnew; + Tcl_HashEntry *h; + + if (!ks->active) return 1; + h = Tcl_CreateHashEntry (&ks->ids, text, &hnew); + if (hnew) { + Tcl_SetHashValue (h, 1); + return 1; + } + if (Tcl_GetHashValue (h) == 0) { + Tcl_SetHashValue (h, 1); + ks->unknownIDrefs--; + return 1; + } else { + /* Duplicate ID value */ + return 0; + } +} + +static int +keyTCObjCmd ( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[] + ) +{ + SchemaData *sdata = GETASI; + SchemaConstraint *sc; + Tcl_HashEntry *h; + int hnew; + SchemaKeySpace *ks; + + CHECK_TI + CHECK_TOPLEVEL + checkNrArgs (2,2,"key_space"); + ADD_CONSTRAINT (sdata, sc) + h = Tcl_CreateHashEntry (&sdata->keySpaces, Tcl_GetString (objv[1]), &hnew); + if (hnew) { + ks = TMALLOC (SchemaKeySpace); + ks->active = 0; + ks->unknownIDrefs = 0; + Tcl_SetHashValue (h, ks); + } else { + ks = Tcl_GetHashValue (h); + } + sc->constraint = keyImpl; + sc->constraintData = (void *) ks; + return TCL_OK; +} + +static int +keyrefImpl ( + Tcl_Interp *interp, + void *constraintData, + char *text + ) +{ + SchemaKeySpace *ks = (SchemaKeySpace *) constraintData; + int hnew; + Tcl_HashEntry *h; + + if (!ks->active) return 1; + h = Tcl_CreateHashEntry (&ks->ids, text, &hnew); + if (hnew) { + Tcl_SetHashValue (h, 0); + ks->unknownIDrefs++; + } + return 1; +} + +static int +keyrefTCObjCmd ( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[] + ) +{ + SchemaData *sdata = GETASI; + SchemaConstraint *sc; + Tcl_HashEntry *h; + int hnew; + SchemaKeySpace *ks; + + CHECK_TI + CHECK_TOPLEVEL + checkNrArgs (2,2,"key_space"); + ADD_CONSTRAINT (sdata, sc) + h = Tcl_CreateHashEntry (&sdata->keySpaces, Tcl_GetString (objv[1]), + &hnew); + if (hnew) { + ks = TMALLOC (SchemaKeySpace); + Tcl_InitHashTable (&ks->ids, TCL_STRING_KEYS); + ks->unknownIDrefs = 0; + Tcl_SetHashValue (h, ks); + } else { + ks = Tcl_GetHashValue (h); + } + sc->constraint = keyrefImpl; + sc->constraintData = (void *)ks; + return TCL_OK; +} static int base64Impl ( Tcl_Interp *interp, void *constraintData, @@ -5040,11 +5350,12 @@ Tcl_CreateObjCommand (interp, "tdom::schema::interleave", AnonPatternObjCmd, (ClientData) 2, NULL); Tcl_CreateObjCommand (interp, "tdom::schema::group", AnonPatternObjCmd, (ClientData) 3, NULL); - /* The "attribute", "nsattribute", "namespace" and "text" definition commands. */ + /* The "attribute", "nsattribute", "namespace" and "text" + * definition commands. */ Tcl_CreateObjCommand (interp, "tdom::schema::attribute", AttributePatternObjCmd, NULL, NULL); Tcl_CreateObjCommand (interp, "tdom::schema::nsattribute", AttributePatternObjCmd, (ClientData) 1, NULL); Tcl_CreateObjCommand (interp, "tdom::schema::namespace", @@ -5056,11 +5367,15 @@ Tcl_CreateObjCommand (interp, "tdom::schema::tcl", VirtualPatternObjCmd, NULL, NULL); /* XPath contraints for DOM validation */ Tcl_CreateObjCommand (interp,"tdom::schema::domunique", - domuniquePatternCmd, NULL, NULL); + domuniquePatternObjCmd, NULL, NULL); + + /* Local key constraints */ + Tcl_CreateObjCommand (interp, "tdom::schema::keyspace", + keyspacePatternObjCmd, NULL, NULL); /* The text constraint commands */ Tcl_CreateObjCommand (interp,"tdom::schema::text::integer", integerTCObjCmd, NULL, NULL); Tcl_CreateObjCommand (interp, "tdom::schema::text::tcl", @@ -5099,9 +5414,13 @@ idTCObjCmd, NULL, NULL); Tcl_CreateObjCommand (interp,"tdom::schema::text::idref", idrefTCObjCmd, NULL, NULL); Tcl_CreateObjCommand (interp,"tdom::schema::text::base64", base64TCObjCmd, NULL, NULL); + Tcl_CreateObjCommand (interp,"tdom::schema::text::key", + keyTCObjCmd, NULL, NULL); + Tcl_CreateObjCommand (interp,"tdom::schema::text::keyref", + keyrefTCObjCmd, NULL, NULL); } #endif /* #ifndef TDOM_NO_SCHEMA */ Index: generic/schema.h ================================================================== --- generic/schema.h +++ generic/schema.h @@ -32,11 +32,13 @@ SCHEMA_CTYPE_NAME, SCHEMA_CTYPE_CHOICE, SCHEMA_CTYPE_INTERLEAVE, SCHEMA_CTYPE_PATTERN, SCHEMA_CTYPE_TEXT, - SCHEMA_CTYPE_VIRTUAL + SCHEMA_CTYPE_VIRTUAL, + SCHEMA_CTYPE_KEYSPACE, + SCHEMA_CTYPE_KEYSPACE_END, } Schema_CP_Type; typedef enum { SCHEMA_CQUANT_ONE, SCHEMA_CQUANT_OPT, @@ -82,10 +84,17 @@ int nrFields; int flags; struct domKeyConstraint *next; } domKeyConstraint; +typedef struct +{ + int active; + Tcl_HashTable ids; + int unknownIDrefs; +} SchemaKeySpace; + typedef struct SchemaCP { Schema_CP_Type type; char *namespace; char *name; @@ -96,10 +105,11 @@ unsigned int nc; SchemaAttr **attrs; unsigned int numAttr; unsigned int numReqAttr; domKeyConstraint *domKeys; + SchemaKeySpace *keySpace; } SchemaCP; typedef struct SchemaValidationStack { SchemaCP *pattern; @@ -120,11 +130,11 @@ typedef struct { Tcl_HashTable ids; int unknownIDrefs; } SchemaDocKey; - + typedef struct SchemaData_ { Tcl_Obj *self; char *start; char *startNamespace; @@ -165,10 +175,11 @@ unsigned int skipDeep; Tcl_DString *cdata; Tcl_HashTable ids; int unknownIDrefs; Tcl_HashTable idTables; + Tcl_HashTable keySpaces; } SchemaData; int schemaInstanceCmd ( ClientData clientData, Index: tests/schema.test ================================================================== --- tests/schema.test +++ tests/schema.test @@ -4578,10 +4578,58 @@ } s delete set result } {s UNKNOWN_ROOT_ELEMENT 1 s UNKNOWN_ROOT_ELEMENT 1 s UNKNOWN_ROOT_ELEMENT 1 s UNKNOWN_ROOT_ELEMENT 1} +test schema-18.4 {reportcmd} { + tdom::schema s + s define { + defelement doc { + element items * { + element item * { + attribute ref {integer} + } + } + } + } + s reportcmd schema-18 + set result [list] + foreach xml { + {} + {} + } { + lappend result [s validate $xml] + } + s delete + set result +} {1 s WRONG_ATTRIBUTE_VALUE s WRONG_ATTRIBUTE_VALUE 1} + +test schema-18.5 {reportcmd} { + tdom::schema s + s define { + defelement doc { + element items * { + element item * { + text {minLength 2} + } + } + } + } + s reportcmd schema-18 + set result [list] + foreach xml { + {1} + {1} + {>12ab} + + } { + lappend result [s validate $xml] + } + s delete + set result +} {s WRONG_VALUE 1 s WRONG_VALUE s MISSING_TEXT 1 1} + proc validatedSAX {g xml {keepEmpties 1}} { set args [list -validateCmd $g] if {!$keepEmpties} { lappend args -ignorewhitespace 1 } @@ -4610,10 +4658,101 @@ set rc [$g domvalidate $doc errMsg] $doc delete return $rc } +test schema-19.1 {keyspace} { + tdom::schema s + s define { + defelement doc { + element items * { + keyspace ref { + element item * { + attribute ref ? { + key ref + } + } + } + } + } + } + set result [list] + foreach xml { + {} + {} + {} + {} + {} + {} + } { + lappend result [s validate $xml] + } + s delete + set result +} {1 0 1 1 1 0} + +test schema-19.2 {keyspace} { + tdom::schema s + s define { + defelement doc { + element items * { + keyspace ref { + element item * { + attribute ref ? { + key ref + } + } + } + } + } + } + s reportcmd schema-18 + set result [list] + foreach xml { + {} + {} + } { + lappend result [s validate $xml] + } + s delete + set result +} {s WRONG_ATTRIBUTE_VALUE s WRONG_ATTRIBUTE_VALUE 1 1} + +test schema-19.3 {keyspace} { + tdom::schema s + s define { + defelement doc { + element items * + } + defelement items { + keyspace my { + element item * + } + } + defelement item { + attribute id ? { + key my + } + attribute ref ? { + keyref my + } + } + } + s reportcmd schema-18 + set result [list] + foreach xml { + {} + {} + {} + {} + } { + lappend result [s validate $xml] + } + s delete + set result +} {1 1 s UNKNOWN_KEYREF 1 s UNKNOWN_KEYREF 1} + test schema-20.1 {domunique} { set schema { prefixns {ns1 http://tdom.org/test} defelement doc { domunique ${::schema-20.1} @ref