Remove confusing trailing dot
[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 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)
110                 {
111                         XsdIdentityPath matchedAttrPath = null;
112
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)
117                                         continue;
118                                 XsdIdentityStep step;
119                                 if (path.IsAttribute) {
120                                         step = path.OrderedSteps [path.OrderedSteps.Length - 1];
121                                         bool match = false;
122                                         if (step.IsAnyName || step.NsName != null) {
123                                                 if (step.IsAnyName || attrNS == step.NsName)
124                                                         match = true;
125                                         }
126                                         else if (step.Name == attrName && step.Namespace == attrNS)
127                                                 match = true;
128                                         if (!match)
129                                                 continue;
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;
134                                 }
135                                 if (FieldFound && (depth > this.FieldFoundDepth && this.FieldFoundPath == path))
136                                         continue; // don't return; other fields might hit errorneously.
137
138                                 // Only "." hits.
139                                 if (path.OrderedSteps.Length == 0) {
140                                         if (depth == entry.StartDepth)
141                                                 return path;
142                                         else
143                                                 continue;
144                                 }
145                                 // It does not hit as yet (too shallow to hit).
146                                 if (depth - entry.StartDepth < path.OrderedSteps.Length - 1)
147                                         continue;
148
149                                 int iter = path.OrderedSteps.Length;
150                                 if (isAttribute)
151                                         iter--;
152                                 if (path.Descendants && depth < entry.StartDepth + iter)
153                                         continue;
154                                 else if (!path.Descendants && depth != entry.StartDepth + iter)
155                                         continue;
156
157                                 iter--;
158
159                                 for (; iter >= 0; iter--) {
160                                         step = path.OrderedSteps [iter];
161                                         if (step.IsCurrent || step.IsAnyName)
162                                                 continue;
163                                         XmlQualifiedName qname = (XmlQualifiedName) qnameStack [entry.StartDepth + iter + (isAttribute ? 0 : 1)];
164                                         if (step.NsName != null && qname.Namespace == step.NsName)
165                                                 continue;
166                                         if ((step.Name == "*" || step.Name == qname.Name) &&
167                                                 step.Namespace == qname.Namespace)
168                                                 continue;
169                                         else
170                                                 break;
171                                 }
172                                 if (iter >= 0)  // i.e. did not match against the path.
173                                         continue;
174
175                                 if (!matchesAttr)
176                                         return path;
177                         }
178                         if (matchedAttrPath != null) {
179                                 this.FillAttributeFieldValue (sender, nameTable, sourceUri, schemaType, nsResolver, attrValue, lineInfo, depth);
180                                 if (this.Identity != null)
181                                         return matchedAttrPath;
182                         }
183                         return null;
184                 }
185
186                 private void FillAttributeFieldValue (object sender, XmlNameTable nameTable, string sourceUri, object schemaType, NSResolver nsResolver, object identity, IXmlLineInfo lineInfo, int depth)
187                 {
188                         if (this.FieldFound)
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) :
192                                                 "")),
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)
197                                 dt = st.Datatype;
198                         try {
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);
208                         }
209                 }
210         }
211
212         internal class XsdKeyEntryFieldCollection : CollectionBase
213         {
214                 public XsdKeyEntryField this [int i] {
215                         get { return (XsdKeyEntryField) List [i]; }
216                         set { List [i] = value; }
217                 }
218
219                 public int Add (XsdKeyEntryField value)
220                 {
221                         return List.Add (value);
222                 }
223         }
224
225         // Created per field/key pair, created per selector-matched element.
226         internal class XsdKeyEntry
227         {
228                 public int StartDepth;
229
230                 public int SelectorLineNumber;
231                 public int SelectorLinePosition;
232                 public bool SelectorHasLineInfo;
233
234                 public XsdKeyEntryFieldCollection KeyFields;
235
236                 public bool KeyRefFound;
237
238                 public XsdKeyTable OwnerSequence;
239                 private bool keyFound = false;
240
241                 public XsdKeyEntry (
242                         XsdKeyTable keyseq, int depth, IXmlLineInfo li)
243                 {
244                         Init (keyseq, depth, li);
245                 }
246
247                 public bool KeyFound {
248                         get {
249                                 if (keyFound)
250                                         return true;
251                                 for (int i = 0; i < KeyFields.Count; i++) {
252                                         XsdKeyEntryField kf = KeyFields [i];
253                                         if (kf.FieldFound) {
254                                                 keyFound = true;
255                                                 return true;
256                                         }
257                                 }
258                                 return false;
259                         }
260                 }
261
262                 private void Init (XsdKeyTable keyseq, int depth, IXmlLineInfo li)
263                 {
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]));
268                         StartDepth = depth;
269                         if (li != null) {
270                                 if (li.HasLineInfo ()) {
271                                         this.SelectorHasLineInfo = true;
272                                         this.SelectorLineNumber = li.LineNumber;
273                                         this.SelectorLinePosition = li.LinePosition;
274                                 }
275                         }
276                 }
277
278                 public bool CompareIdentity (XsdKeyEntry other)
279                 {
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)
284                                         return false;
285                                 if (!XmlSchemaUtil.AreSchemaDatatypeEqual (
286                                         of.FieldType, of.Identity, f.FieldType, f.Identity))
287                                         return false;   // does not match
288                         }
289                         return true;    // matches
290                 }
291
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)
295                 {
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);
299                                 if (path == null)
300                                         continue;
301
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);
308                                         else
309                                                 keyField.Consuming = false;
310                                 }
311                                 if (keyField.Consumed) 
312                                         continue;
313
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;
317                                 if (ct != null && 
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;
329                                 }
330                                 currentKeyFieldConsumers.Add (keyField);
331                         }
332                 }
333         }
334 }