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 (elementPath) check only elements; else only attributes.
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 for (int i = 0; i < field.Paths.Length; i++) {
112 XsdIdentityPath path = field.Paths [i];
113 bool isAttribute = path.IsAttribute;
114 if (matchesAttr != isAttribute)
116 XsdIdentityStep step;
117 if (path.IsAttribute) {
118 step = path.OrderedSteps [path.OrderedSteps.Length - 1];
120 if (step.IsAnyName || step.NsName != null) {
121 if (step.IsAnyName || attrNS == step.NsName)
124 else if (step.Name == attrName && step.Namespace == attrNS)
127 this.FillAttributeFieldValue (sender, nameTable, sourceUri, schemaType, nsResolver, attrValue, lineInfo, depth);
128 if (this.Identity != null)
132 if (FieldFound && (depth > this.FieldFoundDepth && this.FieldFoundPath == path))
136 if (path.OrderedSteps.Length == 0) {
137 if (depth == entry.StartDepth)
142 // It does not hit as yet (too shallow to hit).
143 if (depth - entry.StartDepth < path.OrderedSteps.Length - 1)
146 int iter = path.OrderedSteps.Length;
149 if (path.Descendants && depth < entry.StartDepth + iter)
151 else if (!path.Descendants && depth != entry.StartDepth + iter)
156 for (; iter >= 0; iter--) {
157 step = path.OrderedSteps [iter];
158 if (step.IsCurrent || step.IsAnyName)
160 XmlQualifiedName qname = (XmlQualifiedName) qnameStack [entry.StartDepth + iter + (isAttribute ? 0 : 1)];
161 if (step.NsName != null && qname.Namespace == step.NsName)
163 if ((step.Name == "*" || step.Name == qname.Name) &&
164 step.Namespace == qname.Namespace)
169 if (iter >= 0) // i.e. did not match against the path.
177 private void FillAttributeFieldValue (object sender, XmlNameTable nameTable, string sourceUri, object schemaType, NSResolver nsResolver, object identity, IXmlLineInfo lineInfo, int depth)
180 throw new ValException ("The key value was already found."
181 + (this.FieldHasLineInfo ?
182 String.Format (CultureInfo.InvariantCulture, " At line {0}, position {1}.", FieldLineNumber, FieldLinePosition) :
184 sender, sourceUri, entry.OwnerSequence.SourceSchemaIdentity, null);
185 XmlSchemaDatatype dt = schemaType as XmlSchemaDatatype;
186 XmlSchemaSimpleType st = schemaType as XmlSchemaSimpleType;
187 if (dt == null && st != null)
190 if (!this.SetIdentityField (identity, false, dt as XsdAnySimpleType, depth, lineInfo))
191 throw new ValException ("Two or more identical field was found.",
192 sender, sourceUri, entry.OwnerSequence.SourceSchemaIdentity, null);
193 // HACK: This is not logical. Attributes will never be "cosuming",
194 // so I used it as a temporary mark to sign it is validated *just now*.
195 this.Consuming = true;
196 this.FieldFound = true;
197 } catch (Exception ex) {
198 throw new ValException ("Failed to read typed value.", sender, sourceUri, entry.OwnerSequence.SourceSchemaIdentity, ex);
203 internal class XsdKeyEntryFieldCollection : CollectionBase
205 public XsdKeyEntryField this [int i] {
206 get { return (XsdKeyEntryField) List [i]; }
207 set { List [i] = value; }
210 public int Add (XsdKeyEntryField value)
212 return List.Add (value);
216 // Created per field/key pair, created per selector-matched element.
217 internal class XsdKeyEntry
219 public int StartDepth;
221 public int SelectorLineNumber;
222 public int SelectorLinePosition;
223 public bool SelectorHasLineInfo;
225 public XsdKeyEntryFieldCollection KeyFields;
227 public bool KeyRefFound;
229 public XsdKeyTable OwnerSequence;
230 private bool keyFound = false;
233 XsdKeyTable keyseq, int depth, IXmlLineInfo li)
235 Init (keyseq, depth, li);
238 public bool KeyFound {
242 for (int i = 0; i < KeyFields.Count; i++) {
243 XsdKeyEntryField kf = KeyFields [i];
253 private void Init (XsdKeyTable keyseq, int depth, IXmlLineInfo li)
255 OwnerSequence = keyseq;
256 KeyFields = new XsdKeyEntryFieldCollection ();
257 for (int i = 0; i < keyseq.Selector.Fields.Length; i++)
258 KeyFields.Add (new XsdKeyEntryField (this, keyseq.Selector.Fields [i]));
261 if (li.HasLineInfo ()) {
262 this.SelectorHasLineInfo = true;
263 this.SelectorLineNumber = li.LineNumber;
264 this.SelectorLinePosition = li.LinePosition;
269 public bool CompareIdentity (XsdKeyEntry other)
271 for (int i = 0; i < KeyFields.Count; i++) {
272 XsdKeyEntryField f = this.KeyFields [i];
273 XsdKeyEntryField of = other.KeyFields [i];
274 if (f.IsXsiNil && !of.IsXsiNil || !f.IsXsiNil && of.IsXsiNil)
276 if (!XmlSchemaUtil.IsSchemaDatatypeEquals (
277 of.FieldType, of.Identity, f.FieldType, f.Identity))
278 return false; // does not match
280 return true; // matches
283 // In this method, attributes are ignored.
284 // It might throw Exception.
285 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)
287 for (int i = 0; i < KeyFields.Count; i++) {
288 XsdKeyEntryField keyField = KeyFields [i];
289 XsdIdentityPath path = keyField.Matches (isAttribute, sender, nameTable, qnameStack, sourceUri, schemaType, nsResolver, li, depth, attrName, attrNS, attrValue);
293 if (keyField.FieldFound) {
294 // HACK: This is not logical by nature. Attributes never be cosuming,
295 // so I used it as a temporary mark to sign it is *just* validated now.
296 if (!keyField.Consuming)
297 throw new ValException ("Two or more matching field was found.",
298 sender, sourceUri, this.OwnerSequence.SourceSchemaIdentity, null);
300 keyField.Consuming = false;
302 if (keyField.Consumed)
305 if (isXsiNil && !keyField.SetIdentityField (Guid.Empty, true, XsdAnySimpleType.Instance, depth, li))
306 throw new ValException ("Two or more identical field was found.", sender, sourceUri, OwnerSequence.SourceSchemaIdentity, null);
307 XmlSchemaComplexType ct = schemaType as XmlSchemaComplexType;
309 (ct.ContentType == XmlSchemaContentType.Empty || ct.ContentType == XmlSchemaContentType.ElementOnly) &&
310 schemaType != XmlSchemaComplexType.AnyType)
311 throw new ValException ("Specified schema type is complex type, which is not allowed for identity constraints.", sender, sourceUri, OwnerSequence.SourceSchemaIdentity, null);
312 keyField.FieldFound = true;
313 keyField.FieldFoundPath = path;
314 keyField.FieldFoundDepth = depth;
315 keyField.Consuming = true;
316 if (li != null && li.HasLineInfo ()) {
317 keyField.FieldHasLineInfo = true;
318 keyField.FieldLineNumber = li.LineNumber;
319 keyField.FieldLinePosition = li.LinePosition;
321 currentKeyFieldConsumers.Add (keyField);