2005-12-05 Lluis Sanchez Gual <lluis@novell.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Schema / XsdIdentityState.cs
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.
11 //
12 //
13
14 //
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.
33 //
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;
40
41 #if NET_2_0
42 using NSResolver = System.Xml.IXmlNamespaceResolver;
43 using ValException = System.Xml.Schema.XmlSchemaValidationException;
44 #else
45 using NSResolver = System.Xml.XmlNamespaceManager;
46 using ValException = System.Xml.Schema.XmlSchemaException;
47 #endif
48
49 namespace Mono.Xml.Schema
50 {
51         internal class XsdKeyEntryField
52         {
53                 XsdKeyEntry entry;
54                 XsdIdentityField field;
55
56                 public XsdKeyEntryField (XsdKeyEntry entry, XsdIdentityField field)
57                 {
58                         this.entry = entry;
59                         this.field = field;
60                 }
61
62                 public XsdIdentityField Field {
63                         get { return field; }
64                 }
65
66                 public bool FieldFound;
67                 public int FieldLineNumber;
68                 public int FieldLinePosition;
69                 public bool FieldHasLineInfo;
70                 public XsdAnySimpleType FieldType;
71
72                 public object Identity;
73                 public bool IsXsiNil;
74
75                 public int FieldFoundDepth;
76                 public XsdIdentityPath FieldFoundPath;
77
78                 public bool Consuming;
79                 public bool Consumed;
80
81                 // Return false if there is already the same key.
82                 public bool SetIdentityField (object identity, bool isXsiNil, XsdAnySimpleType type, int depth, IXmlLineInfo li)
83                 {
84                         FieldFoundDepth = depth;
85                         Identity = identity;
86                         IsXsiNil = isXsiNil;
87                         FieldFound |= isXsiNil;
88                         FieldType = type;
89                         Consuming = false;
90                         Consumed = true;
91                         if (li != null && li.HasLineInfo ()) {
92                                 FieldHasLineInfo = true;
93                                 FieldLineNumber = li.LineNumber;
94                                 FieldLinePosition = li.LinePosition;
95                         }
96
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))
101                                                 return false;
102                                 }
103                         }
104
105                         return true;
106                 }
107
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)
110                 {
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)
115                                         continue;
116                                 XsdIdentityStep step;
117                                 if (path.IsAttribute) {
118                                         step = path.OrderedSteps [path.OrderedSteps.Length - 1];
119                                         bool match = false;
120                                         if (step.IsAnyName || step.NsName != null) {
121                                                 if (step.IsAnyName || attrNS == step.NsName)
122                                                         match = true;
123                                         }
124                                         else if (step.Name == attrName && step.Namespace == attrNS)
125                                                 match = true;
126                                         if (match) {
127                                                 this.FillAttributeFieldValue (sender, nameTable, sourceUri, schemaType, nsResolver, attrValue, lineInfo, depth);
128                                                 if (this.Identity != null)
129                                                         return path;
130                                         }
131                                 }
132                                 if (FieldFound && (depth > this.FieldFoundDepth && this.FieldFoundPath == path))
133                                         continue;
134
135                                 // Only "." hits.
136                                 if (path.OrderedSteps.Length == 0) {
137                                         if (depth == entry.StartDepth)
138                                                 return path;
139                                         else
140                                                 continue;
141                                 }
142                                 // It does not hit as yet (too shallow to hit).
143                                 if (depth - entry.StartDepth < path.OrderedSteps.Length - 1)
144                                         continue;
145
146                                 int iter = path.OrderedSteps.Length;
147                                 if (isAttribute)
148                                         iter--;
149                                 if (path.Descendants && depth < entry.StartDepth + iter)
150                                         continue;
151                                 else if (!path.Descendants && depth != entry.StartDepth + iter)
152                                         continue;
153
154                                 iter--;
155
156                                 for (; iter >= 0; iter--) {
157                                         step = path.OrderedSteps [iter];
158                                         if (step.IsCurrent || step.IsAnyName)
159                                                 continue;
160                                         XmlQualifiedName qname = (XmlQualifiedName) qnameStack [entry.StartDepth + iter + (isAttribute ? 0 : 1)];
161                                         if (step.NsName != null && qname.Namespace == step.NsName)
162                                                 continue;
163                                         if ((step.Name == "*" || step.Name == qname.Name) &&
164                                                 step.Namespace == qname.Namespace)
165                                                 continue;
166                                         else
167                                                 break;
168                                 }
169                                 if (iter >= 0)  // i.e. did not match against the path.
170                                         continue;
171
172                                 return path;
173                         }
174                         return null;
175                 }
176
177                 private void FillAttributeFieldValue (object sender, XmlNameTable nameTable, string sourceUri, object schemaType, NSResolver nsResolver, object identity, IXmlLineInfo lineInfo, int depth)
178                 {
179                         if (this.FieldFound)
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) :
183                                                 ""),
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)
188                                 dt = st.Datatype;
189                         try {
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);
199                         }
200                 }
201         }
202
203         internal class XsdKeyEntryFieldCollection : CollectionBase
204         {
205                 public XsdKeyEntryField this [int i] {
206                         get { return (XsdKeyEntryField) List [i]; }
207                         set { List [i] = value; }
208                 }
209
210                 public int Add (XsdKeyEntryField value)
211                 {
212                         return List.Add (value);
213                 }
214         }
215
216         // Created per field/key pair, created per selector-matched element.
217         internal class XsdKeyEntry
218         {
219                 public int StartDepth;
220
221                 public int SelectorLineNumber;
222                 public int SelectorLinePosition;
223                 public bool SelectorHasLineInfo;
224
225                 public XsdKeyEntryFieldCollection KeyFields;
226
227                 public bool KeyRefFound;
228
229                 public XsdKeyTable OwnerSequence;
230                 private bool keyFound = false;
231
232                 public XsdKeyEntry (
233                         XsdKeyTable keyseq, int depth, IXmlLineInfo li)
234                 {
235                         Init (keyseq, depth, li);
236                 }
237
238                 public bool KeyFound {
239                         get {
240                                 if (keyFound)
241                                         return true;
242                                 for (int i = 0; i < KeyFields.Count; i++) {
243                                         XsdKeyEntryField kf = KeyFields [i];
244                                         if (kf.FieldFound) {
245                                                 keyFound = true;
246                                                 return true;
247                                         }
248                                 }
249                                 return false;
250                         }
251                 }
252
253                 private void Init (XsdKeyTable keyseq, int depth, IXmlLineInfo li)
254                 {
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]));
259                         StartDepth = depth;
260                         if (li != null) {
261                                 if (li.HasLineInfo ()) {
262                                         this.SelectorHasLineInfo = true;
263                                         this.SelectorLineNumber = li.LineNumber;
264                                         this.SelectorLinePosition = li.LinePosition;
265                                 }
266                         }
267                 }
268
269                 public bool CompareIdentity (XsdKeyEntry other)
270                 {
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)
275                                         return false;
276                                 if (!XmlSchemaUtil.IsSchemaDatatypeEquals (
277                                         of.FieldType, of.Identity, f.FieldType, f.Identity))
278                                         return false;   // does not match
279                         }
280                         return true;    // matches
281                 }
282
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)
286                 {
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);
290                                 if (path == null)
291                                         continue;
292
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);
299                                         else
300                                                 keyField.Consuming = false;
301                                 }
302                                 if (keyField.Consumed) 
303                                         continue;
304
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;
308                                 if (ct != null && 
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;
320                                 }
321                                 currentKeyFieldConsumers.Add (keyField);
322                         }
323                 }
324         }
325 }