2 // Mono.Xml.Schema.XsdIdentityState.cs
5 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 // (C)2003 Atsushi Enomoto
9 // These classses represents XML Schema's identity constraints validation state,
10 // created by xs:key, xs:keyref, xs:unique in xs:element.
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Collections;
36 using System.Collections.Specialized;
37 using System.Globalization;
39 using System.Xml.Schema;
42 using NSResolver = System.Xml.IXmlNamespaceResolver;
43 using ValException = System.Xml.Schema.XmlSchemaValidationException;
45 using NSResolver = System.Xml.XmlNamespaceManager;
46 using ValException = System.Xml.Schema.XmlSchemaException;
49 namespace Mono.Xml.Schema
51 internal class XsdKeyEntryField
54 XsdIdentityField field;
56 public XsdKeyEntryField (XsdKeyEntry entry, XsdIdentityField field)
62 public XsdIdentityField Field {
66 public bool FieldFound;
67 public int FieldLineNumber;
68 public int FieldLinePosition;
69 public bool FieldHasLineInfo;
70 public XsdAnySimpleType FieldType;
72 public object Identity;
75 public int FieldFoundDepth;
76 public XsdIdentityPath FieldFoundPath;
78 public bool Consuming;
81 // Return false if there is already the same key.
82 public bool SetIdentityField (object identity, bool isXsiNil, XsdAnySimpleType type, int depth, IXmlLineInfo li)
84 FieldFoundDepth = depth;
87 FieldFound |= isXsiNil;
91 if (li != null && li.HasLineInfo ()) {
92 FieldHasLineInfo = true;
93 FieldLineNumber = li.LineNumber;
94 FieldLinePosition = li.LinePosition;
97 if (!(this.entry.OwnerSequence.SourceSchemaIdentity is XmlSchemaKeyref)) {
98 for (int i = 0; i < entry.OwnerSequence.FinishedEntries.Count; i++) {
99 XsdKeyEntry other = (XsdKeyEntry) entry.OwnerSequence.FinishedEntries [i];
100 if (this.entry.CompareIdentity (other))
108 // if matchesAttr then check attributes; otherwise check elements.
109 internal XsdIdentityPath Matches (bool matchesAttr, object sender, XmlNameTable nameTable, ArrayList qnameStack, string sourceUri, object schemaType, NSResolver nsResolver, IXmlLineInfo lineInfo, int depth, string attrName, string attrNS, object attrValue)
111 XsdIdentityPath matchedAttrPath = null;
113 for (int i = 0; i < field.Paths.Length; i++) {
114 XsdIdentityPath path = field.Paths [i];
115 bool isAttribute = path.IsAttribute;
116 if (matchesAttr != isAttribute)
118 XsdIdentityStep step;
119 if (path.IsAttribute) {
120 step = path.OrderedSteps [path.OrderedSteps.Length - 1];
122 if (step.IsAnyName || step.NsName != null) {
123 if (step.IsAnyName || attrNS == step.NsName)
126 else if (step.Name == attrName && step.Namespace == attrNS)
130 // first -1 is to reduce attr path step, next -1 is to reduce Attribute's depth in XmlReader.
131 if (entry.StartDepth + (path.OrderedSteps.Length - 1) != depth - 1)
132 continue; // matched at different nest level
133 matchedAttrPath = path;
135 if (FieldFound && (depth > this.FieldFoundDepth && this.FieldFoundPath == path))
136 continue; // don't return; other fields might hit errorneously.
139 if (path.OrderedSteps.Length == 0) {
140 if (depth == entry.StartDepth)
145 // It does not hit as yet (too shallow to hit).
146 if (depth - entry.StartDepth < path.OrderedSteps.Length - 1)
149 int iter = path.OrderedSteps.Length;
152 if (path.Descendants && depth < entry.StartDepth + iter)
154 else if (!path.Descendants && depth != entry.StartDepth + iter)
159 for (; iter >= 0; iter--) {
160 step = path.OrderedSteps [iter];
161 if (step.IsCurrent || step.IsAnyName)
163 XmlQualifiedName qname = (XmlQualifiedName) qnameStack [entry.StartDepth + iter + (isAttribute ? 0 : 1)];
164 if (step.NsName != null && qname.Namespace == step.NsName)
166 if ((step.Name == "*" || step.Name == qname.Name) &&
167 step.Namespace == qname.Namespace)
172 if (iter >= 0) // i.e. did not match against the path.
178 if (matchedAttrPath != null) {
179 this.FillAttributeFieldValue (sender, nameTable, sourceUri, schemaType, nsResolver, attrValue, lineInfo, depth);
180 if (this.Identity != null)
181 return matchedAttrPath;
186 private void FillAttributeFieldValue (object sender, XmlNameTable nameTable, string sourceUri, object schemaType, NSResolver nsResolver, object identity, IXmlLineInfo lineInfo, int depth)
189 throw new ValException (String.Format ("The key value was already found as '{0}'{1}.", Identity,
190 (this.FieldHasLineInfo ?
191 String.Format (CultureInfo.InvariantCulture, " at line {0}, position {1}", FieldLineNumber, FieldLinePosition) :
193 sender, sourceUri, entry.OwnerSequence.SourceSchemaIdentity, null);
194 XmlSchemaDatatype dt = schemaType as XmlSchemaDatatype;
195 XmlSchemaSimpleType st = schemaType as XmlSchemaSimpleType;
196 if (dt == null && st != null)
199 if (!this.SetIdentityField (identity, false, dt as XsdAnySimpleType, depth, lineInfo))
200 throw new ValException ("Two or more identical field was found.",
201 sender, sourceUri, entry.OwnerSequence.SourceSchemaIdentity, null);
202 // HACK: This is not logical. Attributes will never be "cosuming",
203 // so I used it as a temporary mark to sign it is validated *just now*.
204 this.Consuming = true;
205 this.FieldFound = true;
206 } catch (Exception ex) {
207 throw new ValException ("Failed to read typed value.", sender, sourceUri, entry.OwnerSequence.SourceSchemaIdentity, ex);
212 internal class XsdKeyEntryFieldCollection : CollectionBase
214 public XsdKeyEntryField this [int i] {
215 get { return (XsdKeyEntryField) List [i]; }
216 set { List [i] = value; }
219 public int Add (XsdKeyEntryField value)
221 return List.Add (value);
225 // Created per field/key pair, created per selector-matched element.
226 internal class XsdKeyEntry
228 public int StartDepth;
230 public int SelectorLineNumber;
231 public int SelectorLinePosition;
232 public bool SelectorHasLineInfo;
234 public XsdKeyEntryFieldCollection KeyFields;
236 public bool KeyRefFound;
238 public XsdKeyTable OwnerSequence;
239 private bool keyFound = false;
242 XsdKeyTable keyseq, int depth, IXmlLineInfo li)
244 Init (keyseq, depth, li);
247 public bool KeyFound {
251 for (int i = 0; i < KeyFields.Count; i++) {
252 XsdKeyEntryField kf = KeyFields [i];
262 private void Init (XsdKeyTable keyseq, int depth, IXmlLineInfo li)
264 OwnerSequence = keyseq;
265 KeyFields = new XsdKeyEntryFieldCollection ();
266 for (int i = 0; i < keyseq.Selector.Fields.Length; i++)
267 KeyFields.Add (new XsdKeyEntryField (this, keyseq.Selector.Fields [i]));
270 if (li.HasLineInfo ()) {
271 this.SelectorHasLineInfo = true;
272 this.SelectorLineNumber = li.LineNumber;
273 this.SelectorLinePosition = li.LinePosition;
278 public bool CompareIdentity (XsdKeyEntry other)
280 for (int i = 0; i < KeyFields.Count; i++) {
281 XsdKeyEntryField f = this.KeyFields [i];
282 XsdKeyEntryField of = other.KeyFields [i];
283 if (f.IsXsiNil && !of.IsXsiNil || !f.IsXsiNil && of.IsXsiNil)
285 if (!XmlSchemaUtil.AreSchemaDatatypeEqual (
286 of.FieldType, of.Identity, f.FieldType, f.Identity))
287 return false; // does not match
289 return true; // matches
292 // In this method, attributes are ignored.
293 // It might throw Exception.
294 public void ProcessMatch (bool isAttribute, ArrayList qnameStack, object sender, XmlNameTable nameTable, string sourceUri, object schemaType, NSResolver nsResolver, IXmlLineInfo li, int depth, string attrName, string attrNS, object attrValue, bool isXsiNil, ArrayList currentKeyFieldConsumers)
296 for (int i = 0; i < KeyFields.Count; i++) {
297 XsdKeyEntryField keyField = KeyFields [i];
298 XsdIdentityPath path = keyField.Matches (isAttribute, sender, nameTable, qnameStack, sourceUri, schemaType, nsResolver, li, depth, attrName, attrNS, attrValue);
302 if (keyField.FieldFound) {
303 // HACK: This is not logical by nature. Attributes never be cosuming,
304 // so I used it as a temporary mark to sign it is *just* validated now.
305 if (!keyField.Consuming)
306 throw new ValException ("Two or more matching field was found.",
307 sender, sourceUri, this.OwnerSequence.SourceSchemaIdentity, null);
309 keyField.Consuming = false;
311 if (keyField.Consumed)
314 if (isXsiNil && !keyField.SetIdentityField (Guid.Empty, true, XsdAnySimpleType.Instance, depth, li))
315 throw new ValException ("Two or more identical field was found.", sender, sourceUri, OwnerSequence.SourceSchemaIdentity, null);
316 XmlSchemaComplexType ct = schemaType as XmlSchemaComplexType;
318 (ct.ContentType == XmlSchemaContentType.Empty || ct.ContentType == XmlSchemaContentType.ElementOnly) &&
319 schemaType != XmlSchemaComplexType.AnyType)
320 throw new ValException ("Specified schema type is complex type, which is not allowed for identity constraints.", sender, sourceUri, OwnerSequence.SourceSchemaIdentity, null);
321 keyField.FieldFound = true;
322 keyField.FieldFoundPath = path;
323 keyField.FieldFoundDepth = depth;
324 keyField.Consuming = true;
325 if (li != null && li.HasLineInfo ()) {
326 keyField.FieldHasLineInfo = true;
327 keyField.FieldLineNumber = li.LineNumber;
328 keyField.FieldLinePosition = li.LinePosition;
330 currentKeyFieldConsumers.Add (keyField);