**** Merged from MCS ****
[mono-project.git] / mcs / class / System.XML / Mono.Xml.Schema / XsdIdentityState.cs
blobeb57ae1d89f9a5f955cd6fc845c38b44b4b70cb6
1 //
2 // Mono.Xml.Schema.XsdIdentityState.cs
3 //
4 // Author:
5 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C)2003 Atsushi Enomoto
8 //
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:
22 //
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 //
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.
34 using System;
35 using System.Collections;
36 using System.Collections.Specialized;
37 using System.Globalization;
38 using System.Xml;
39 using System.Xml.Schema;
41 namespace Mono.Xml.Schema
43 internal class XsdKeyEntryField
45 XsdKeyEntry entry;
46 XsdIdentityField field;
48 public XsdKeyEntryField (XsdKeyEntry entry, XsdIdentityField field)
50 this.entry = entry;
51 this.field = field;
54 public XsdIdentityField Field {
55 get { return field; }
58 public bool FieldFound;
59 public int FieldLineNumber;
60 public int FieldLinePosition;
61 public bool FieldHasLineInfo;
62 public XsdAnySimpleType FieldType;
64 public object Identity;
65 public bool IsXsiNil;
67 public int FieldFoundDepth;
68 public XsdIdentityPath FieldFoundPath;
70 public bool Consuming;
71 public bool Consumed;
73 // Return false if there is already the same key.
74 public bool SetIdentityField (object identity, bool isXsiNil, XsdAnySimpleType type, XsdValidatingReader reader)
76 IXmlLineInfo li = reader as IXmlLineInfo;
78 FieldFoundDepth = reader.Depth;
79 Identity = identity;
80 IsXsiNil = isXsiNil;
81 FieldFound |= isXsiNil;
82 FieldType = type;
83 Consuming = false;
84 Consumed = true;
85 if (li != null && li.HasLineInfo ()) {
86 FieldHasLineInfo = true;
87 FieldLineNumber = li.LineNumber;
88 FieldLinePosition = li.LinePosition;
91 if (!(this.entry.KeySequence.SourceSchemaIdentity is XmlSchemaKeyref)) {
92 for (int i = 0; i < entry.KeySequence.FinishedEntries.Count; i++) {
93 XsdKeyEntry other = (XsdKeyEntry) entry.KeySequence.FinishedEntries [i];
94 XsdKeyEntryField of = other.KeyFields [field.Index];
95 if (this.entry.CompareIdentity (other))
96 return false;
100 return true;
103 // It might throw Exception including other than XmlSchemaException (ReadTypedValue).
104 internal XsdIdentityPath FieldMatches (ArrayList qnameStack, XsdValidatingReader reader)
106 for (int i = 0; i < field.Paths.Length; i++) {
107 XsdIdentityPath path = field.Paths [i];
108 if (FieldFound && (reader.Depth > this.FieldFoundDepth && this.FieldFoundPath == path))
109 continue;
111 // Only "." hits.
112 if (path.OrderedSteps.Length == 0) {
113 if (reader.Depth == entry.StartDepth)
114 return path;
115 else
116 continue;
118 // It does not hit as yet (too shallow to hit).
119 if (reader.Depth - entry.StartDepth < path.OrderedSteps.Length - 1)
120 continue;
122 bool isAttributePath = false;
123 int iter = path.OrderedSteps.Length;
124 if (path.OrderedSteps [iter-1].IsAttribute) {
125 isAttributePath = true;
126 iter--;
128 if (path.Descendants && reader.Depth < entry.StartDepth + iter)
129 continue;
130 else if (!path.Descendants && reader.Depth != entry.StartDepth + iter)
131 continue;
133 iter--;
135 XsdIdentityStep step;
136 for (; iter >= 0; iter--) {
137 step = path.OrderedSteps [iter];
138 if (step.IsAnyName)
139 continue;
140 XmlQualifiedName qname = (XmlQualifiedName) qnameStack [entry.StartDepth + iter + 1];
141 if (step.NsName != null && qname.Namespace == step.NsName)
142 continue;
143 if ((step.Name == "*" || step.Name == qname.Name) &&
144 step.Namespace == qname.Namespace)
145 continue;
146 else
147 break;
149 if (iter >= 0) // i.e. did not match against the path.
150 continue;
151 if (!isAttributePath)
152 return path;
153 step = path.OrderedSteps [path.OrderedSteps.Length - 1];
154 if (step.IsAnyName || step.NsName != null) {
155 try {
156 while (reader.MoveToNextAttribute ()) {
157 if (reader.NamespaceURI == XmlSchema.InstanceNamespace)
158 continue;
159 if (step.IsAnyName || reader.NamespaceURI == step.NsName) {
160 this.FillAttributeFieldValue (reader);
163 } finally {
164 reader.MoveToElement ();
166 if (this.Identity != null)
167 return path;
168 else
169 continue;
171 if (reader.MoveToAttribute (step.Name, step.Namespace)) {
172 this.FillAttributeFieldValue (reader);
173 reader.MoveToElement ();
174 return path;
176 else
177 continue;
179 return null;
182 private void FillAttributeFieldValue (XsdValidatingReader reader)
184 if (this.FieldFound)
185 throw new XmlSchemaException ("The key value was was already found."
186 + (this.FieldHasLineInfo ?
187 String.Format (CultureInfo.InvariantCulture, " At line {0}, position {1}.", FieldLineNumber, FieldLinePosition) :
188 ""),
189 reader, reader.BaseURI, entry.KeySequence.SourceSchemaIdentity, null);
190 XmlSchemaDatatype dt = reader.SchemaType as XmlSchemaDatatype;
191 XmlSchemaSimpleType st = reader.SchemaType as XmlSchemaSimpleType;
192 if (dt == null && st != null)
193 dt = st.Datatype;
194 object identity = reader.ReadTypedValue ();
195 if (identity == null)
196 identity = reader.Value;
197 if (!this.SetIdentityField (identity, reader.Depth - 1 == reader.XsiNilDepth , dt as XsdAnySimpleType, reader))
198 throw new XmlSchemaException ("Two or more identical field was found.",
199 reader, reader.BaseURI, entry.KeySequence.SourceSchemaIdentity, null);
200 // HACK: This is not logical. Attributes never be cosuming,
201 // so I used it as a temporary mark to sign it is *just* validated now.
202 this.Consuming = true;
203 this.FieldFound = true;
207 internal class XsdKeyEntryFieldCollection : IList
209 ArrayList al = new ArrayList ();
211 #region IList
212 public bool IsReadOnly {
213 get { return false; }
216 object IList.this [int i] {
217 get { return al [i]; }
218 set { al [i] = value; }
221 public XsdKeyEntryField this [int i] {
222 get { return (XsdKeyEntryField) al [i]; }
223 set { al [i] = value; }
226 public void RemoveAt (int i)
228 al.RemoveAt (i);
231 public void Insert (int i, object value)
233 al.Insert (i, value);
236 public void Remove (object value)
238 al.Remove (value);
241 public bool Contains (object value)
243 return al.Contains (value);
246 public void Clear ()
248 al.Clear ();
251 public int IndexOf (object value)
253 return al.IndexOf (value);
256 public int Add (object value)
258 return al.Add (value);
261 public bool IsFixedSize {
262 get { return false; }
265 #endregion
267 #region ICollection
269 public bool IsSynchronized {
270 get { return al.IsSynchronized; }
273 public int Count {
274 get { return al.Count; }
277 public void CopyTo(Array array, int i)
279 al.CopyTo (array, i);
282 public object SyncRoot {
283 get { return al.SyncRoot; }
286 #endregion
288 #region IEnumerable
290 public IEnumerator GetEnumerator ()
292 return ((IEnumerable) al).GetEnumerator ();
295 #endregion
299 // Created per field/key pair, created per selector-matched element.
300 internal class XsdKeyEntry
302 public int StartDepth;
303 public int CurrentStep;
305 public bool ConsumptionTargetIsKey;
307 public int SelectorLineNumber;
308 public int SelectorLinePosition;
309 public bool SelectorHasLineInfo;
311 public XsdKeyEntryFieldCollection KeyFields;
313 public bool KeyRefFound;
314 public int KeyRefSelectorLineNumber;
315 public int KeyRefSelectorLinePosition;
316 public bool KeyRefSelectorHasLineInfo;
318 public XsdKeyTable KeySequence;
319 private bool keyFound = false;
321 public XsdKeyEntry (XsdKeyTable keyseq, XmlReader reader)
323 Init (keyseq, reader);
326 public bool KeyFound {
327 get {
328 if (keyFound)
329 return true;
330 for (int i = 0; i < KeyFields.Count; i++) {
331 XsdKeyEntryField kf = KeyFields [i];
332 if (kf.FieldFound) {
333 keyFound = true;
334 return true;
337 return false;
341 private void Init (XsdKeyTable keyseq, XmlReader reader)
343 KeySequence = keyseq;
344 KeyFields = new XsdKeyEntryFieldCollection ();
345 for (int i = 0; i < keyseq.Selector.Fields.Length; i++)
346 KeyFields.Add (new XsdKeyEntryField (this, keyseq.Selector.Fields [i]));
347 StartDepth = reader.Depth;
348 IXmlLineInfo li = reader as IXmlLineInfo;
349 if (li != null) {
350 if (li.HasLineInfo ()) {
351 this.SelectorHasLineInfo = true;
352 this.SelectorLineNumber = li.LineNumber;
353 this.SelectorLinePosition = li.LinePosition;
358 public bool CompareIdentity (XsdKeyEntry other)
360 for (int i = 0; i < KeyFields.Count; i++) {
361 XsdKeyEntryField f = this.KeyFields [i];
362 XsdKeyEntryField of = other.KeyFields [i];
363 if (f.IsXsiNil && !of.IsXsiNil || !f.IsXsiNil && of.IsXsiNil)
364 return false;
365 if (!XmlSchemaUtil.IsSchemaDatatypeEquals (
366 of.FieldType, of.Identity, f.FieldType, f.Identity))
367 return false; // does not match
369 return true; // matches
372 // In this method, attributes are ignored.
373 // It might throw Exception including non-XmlSchemaException.
374 public void FieldMatches (ArrayList qnameStack, XsdValidatingReader reader)
376 for (int i = 0; i < KeyFields.Count; i++) {
377 XsdKeyEntryField keyField = KeyFields [i];
378 XsdIdentityField fieldDef = keyField.Field;
379 XsdIdentityPath path = keyField.FieldMatches (qnameStack, reader);
380 if (path != null) {
381 if (keyField.FieldFound) {
382 // HACK: This is not logical by nature. Attributes never be cosuming,
383 // so I used it as a temporary mark to sign it is *just* validated now.
384 if (!keyField.Consuming)
385 throw new XmlSchemaException ("Two or more matching field was found.",
386 reader, reader.BaseURI, this.KeySequence.SourceSchemaIdentity, null);
387 else
388 keyField.Consuming = false;
390 if (!keyField.Consumed) {
391 if (reader.XsiNilDepth == reader.Depth && !keyField.SetIdentityField (Guid.Empty, true, XsdAnySimpleType.Instance, reader))
392 throw new XmlSchemaException ("Two or more identical field was found.", reader, reader.BaseURI, KeySequence.SourceSchemaIdentity, null);
393 else {
394 XmlSchemaComplexType ct = reader.SchemaType as XmlSchemaComplexType;
395 if (ct != null &&
396 (ct.ContentType == XmlSchemaContentType.Empty || ct.ContentType == XmlSchemaContentType.ElementOnly) &&
397 reader.SchemaType != XmlSchemaComplexType.AnyType)
398 throw new XmlSchemaException ("Specified schema type is complex type, which is not allowed for identity constraints.", reader, reader.BaseURI, KeySequence.SourceSchemaIdentity, null);
399 keyField.FieldFound = true;
400 keyField.FieldFoundPath = path;
401 keyField.FieldFoundDepth = reader.Depth;
402 keyField.Consuming = true;
403 IXmlLineInfo li = reader as IXmlLineInfo;
404 if (li != null && li.HasLineInfo ()) {
405 keyField.FieldHasLineInfo = true;
406 keyField.FieldLineNumber = li.LineNumber;
407 keyField.FieldLinePosition = li.LinePosition;
409 reader.CurrentKeyFieldConsumers.Add (keyField);