2004-11-17 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml.Schema / XmlSchemaInference.cs
1 //
2 // XmlSchemaInference.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C)2004 Novell Inc.
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 #if NET_2_0
32
33 using System;
34 using System.Collections;
35 using System.Xml;
36 using System.Xml.Schema;
37
38 using QName = System.Xml.XmlQualifiedName;
39 using Form = System.Xml.Schema.XmlSchemaForm;
40 using Use = System.Xml.Schema.XmlSchemaUse;
41 using SOMList = System.Xml.Schema.XmlSchemaObjectCollection;
42 using SOMObject = System.Xml.Schema.XmlSchemaObject;
43 using Element = System.Xml.Schema.XmlSchemaElement;
44 using Attr = System.Xml.Schema.XmlSchemaAttribute;
45 using AttrGroup = System.Xml.Schema.XmlSchemaAttributeGroup;
46 using AttrGroupRef = System.Xml.Schema.XmlSchemaAttributeGroupRef;
47 using SimpleType = System.Xml.Schema.XmlSchemaSimpleType;
48 using ComplexType = System.Xml.Schema.XmlSchemaComplexType;
49 using SimpleModel = System.Xml.Schema.XmlSchemaSimpleContent;
50 using SimpleExt = System.Xml.Schema.XmlSchemaSimpleContentExtension;
51 using SimpleRst = System.Xml.Schema.XmlSchemaSimpleContentRestriction;
52 using ComplexModel = System.Xml.Schema.XmlSchemaComplexContent;
53 using ComplexExt = System.Xml.Schema.XmlSchemaComplexContentExtension;
54 using ComplexRst = System.Xml.Schema.XmlSchemaComplexContentRestriction;
55 using SimpleTypeRst = System.Xml.Schema.XmlSchemaSimpleTypeRestriction;
56 using SimpleList = System.Xml.Schema.XmlSchemaSimpleTypeList;
57 using SimpleUnion = System.Xml.Schema.XmlSchemaSimpleTypeUnion;
58 using SchemaFacet = System.Xml.Schema.XmlSchemaFacet;
59 using LengthFacet = System.Xml.Schema.XmlSchemaLengthFacet;
60 using MinLengthFacet = System.Xml.Schema.XmlSchemaMinLengthFacet;
61 using Particle = System.Xml.Schema.XmlSchemaParticle;
62 using Sequence = System.Xml.Schema.XmlSchemaSequence;
63 using Choice = System.Xml.Schema.XmlSchemaChoice;
64
65
66 namespace System.Xml.Schema
67 {
68         public class XmlSchemaInference
69         {
70                 public enum InferenceOption {
71                         Relaxed,
72                         Rstricted
73                 }
74
75                 InferenceOption occurrence = InferenceOption.Rstricted;
76                 InferenceOption typeInference = InferenceOption.Rstricted;
77
78                 public XmlSchemaInference ()
79                 {
80                 }
81
82                 public InferenceOption Occurrence {
83                         get { return occurrence; }
84                         set { occurrence = value; }
85                 }
86
87                 public InferenceOption TypeInference {
88                         get { return TypeInference; }
89                         set { typeInference = value; }
90                 }
91
92                 public XmlSchemaSet InferSchema (XmlReader xmlReader)
93                 {
94                         return InferSchema (xmlReader, new XmlSchemaSet ());
95                 }
96
97                 public XmlSchemaSet InferSchema (XmlReader xmlReader, XmlSchemaSet schemas)
98                 {
99                         return XsdInference.Process (xmlReader, schemas,
100                                 occurrence == InferenceOption.Relaxed,
101                                 typeInference == InferenceOption.Relaxed);
102                 }
103         }
104
105         class XsdInference
106         {
107                 public static XmlSchemaSet Process (XmlReader xmlReader, XmlSchemaSet schemas, bool laxOccurence, bool laxTypeInference)
108                 {
109                         XsdInference impl = new XsdInference (xmlReader,
110                                 schemas, laxOccurence, laxTypeInference);
111                         impl.Run ();
112                         return impl.schemas;
113                 }
114
115                 public const string NamespaceXml = "http://www.w3.org/XML/1998/namespace";
116
117                 public const string NamespaceXmlns = "http://www.w3.org/2000/xmlns/";
118
119                 public const string XdtNamespace = "http://www.w3.org/2003/11/xpath-datatypes";
120
121                 static readonly QName QNameString = new QName ("string",
122                         XmlSchema.Namespace);
123
124                 static readonly QName QNameBoolean = new QName ("boolean",
125                         XmlSchema.Namespace);
126
127                 static readonly QName QNameAnyType = new QName ("anyType",
128                         XmlSchema.Namespace);
129
130                 XmlReader source;
131                 XmlSchemaSet schemas;
132                 bool laxOccurence;
133                 bool laxTypeInference;
134
135                 Hashtable newElements = new Hashtable ();
136                 Hashtable newAttributes = new Hashtable ();
137
138                 private XsdInference (XmlReader xmlReader, XmlSchemaSet schemas, bool laxOccurence, bool laxTypeInference)
139                 {
140                         this.source = xmlReader;
141                         this.schemas = schemas;
142                         this.laxOccurence = laxOccurence;
143                         this.laxTypeInference = laxTypeInference;
144                 }
145
146                 private void Run ()
147                 {
148                         // XmlSchemaSet need to be compiled.
149                         schemas.Compile ();
150
151                         // move to top-level element
152                         source.MoveToContent ();
153                         int depth = source.Depth;
154                         if (source.NodeType != XmlNodeType.Element)
155                                 throw new ArgumentException ("Argument XmlReader content is expected to be an element.");
156
157                         QName qname = new QName (source.LocalName,
158                                 source.NamespaceURI);
159                         Element el = GetGlobalElement (qname);
160                         if (el == null) {
161                                 el = CreateGlobalElement (qname);
162                                 InferElement (el, qname.Namespace, true);
163                         }
164                         else
165                                 InferElement (el, qname.Namespace, false);
166                 }
167
168                 private void IncludeXmlAttributes ()
169                 {
170                         if (schemas.Schemas (NamespaceXml).Count == 0)
171                                 // FIXME: do it from resources.
172                                 schemas.Add (NamespaceXml, "http://www.w3.org/2001/xml.xsd");
173                 }
174
175                 private void InferElement (Element el, string ns, bool isNew)
176                 {
177                         // Quick check for reference to another definition
178                         // (i.e. element ref='...' that should be redirected)
179                         if (el.RefName != QName.Empty) {
180                                 Element body = GetGlobalElement (el.RefName);
181                                 if (body == null) {
182                                         body = CreateElement (el.RefName);
183                                         InferElement (body, ns, true);
184                                 }
185                                 else
186                                         InferElement (body, ns, isNew);
187                                 return;
188                         }
189
190                         // Attributes
191                         if (source.MoveToFirstAttribute ()) {
192                                 InferAttributes (el, ns, isNew);
193                                 source.MoveToElement ();
194                         }
195
196                         // Content
197                         if (source.IsEmptyElement) {
198                                 InferAsEmptyElement (el, ns, isNew);
199                                 source.Read ();
200                                 source.MoveToContent ();
201                         }
202                         else {
203                                 InferContent (el, ns, isNew);
204                                 source.ReadEndElement ();
205                         }
206                         if (el.SchemaType == null &&
207                                 el.SchemaTypeName == QName.Empty)
208                                 el.SchemaTypeName = QNameString;
209                 }
210
211                 #region Attribute Inference
212
213                 private Hashtable CollectAttrTable (SOMList attList)
214                 {
215                         // get attribute definition table.
216                         Hashtable table = new Hashtable ();
217                         foreach (XmlSchemaObject obj in attList) {
218                                 Attr attr = obj as Attr;
219                                 if (attr == null)
220                                         throw Error (obj, String.Format ("Attribute inference only supports direct attribute definition. {0} is not supported.", obj.GetType ()));
221                                 if (attr.RefName != QName.Empty)
222                                         table.Add (attr.RefName, attr);
223                                 else
224                                         table.Add (new QName (attr.Name, ""),
225                                                 attr);
226                         }
227                         return table;
228                 }
229
230                 private void InferAttributes (Element el, string ns, bool isNew)
231                 {
232                         // Now this element is going to have complexType.
233                         // It currently not, then we have to replace it.
234                         ComplexType ct = null;
235                         SOMList attList = null;
236                         Hashtable table = null;
237
238                         do {
239                                 switch (source.NamespaceURI) {
240                                 case NamespaceXml:
241                                         if (schemas.Schemas (
242                                                 NamespaceXml) .Count == 0)
243                                                 IncludeXmlAttributes ();
244                                         break;
245                                 case XmlSchema.InstanceNamespace:
246                                         if (source.LocalName == "nil")
247                                                 el.IsNillable = true;
248                                         // all other xsi:* atts are ignored
249                                         continue;
250                                 case NamespaceXmlns:
251                                         continue;
252                                 }
253                                 if (ct == null) {
254                                         ct = ToComplexType (el);
255                                         attList = GetAttributes (ct);
256                                         table = CollectAttrTable (attList);
257                                 }
258                                 QName attrName = new QName (
259                                         source.LocalName, source.NamespaceURI);
260                                 Attr attr = table [attrName] as Attr;
261                                 if (attr == null) {
262                                         attList.Add (InferNewAttribute (
263                                                 attrName, isNew));
264                                 } else {
265                                         table.Remove (attrName);
266                                         if (attr.RefName != null &&
267                                                 attr.RefName != QName.Empty)
268                                                 continue; // just a reference
269                                         InferMergedAttribute (attr);
270                                 }
271                         } while (source.MoveToNextAttribute ());
272
273                         // mark all attr definitions that did not appear
274                         // as optional.
275                         if (table != null)
276                                 foreach (Attr attr in table.Values)
277                                         attr.Use = Use.Optional;
278                 }
279
280                 private XmlSchemaAttribute InferNewAttribute (
281                         QName attrName, bool isNewTypeDefinition)
282                 {
283                         Attr attr = null;
284                         bool mergedRequired = false;
285                         if (attrName.Namespace.Length > 0) {
286                                 // global attribute; might be already defined.
287                                 attr = GetGlobalAttribute (attrName) as Attr;
288                                 if (attr == null) {
289                                         attr = CreateGlobalAttribute (attrName);
290                                         attr.SchemaTypeName =
291                                                 InferSimpleType (source.Value);
292                                 } else {
293                                         InferMergedAttribute (attr);
294                                         mergedRequired =
295                                                 attr.Use == Use.Required;
296                                 }
297                                 attr = new Attr ();
298                                 attr.RefName = attrName;
299                         } else {
300                                 // local attribute
301                                 attr = new Attr ();
302                                 attr.Name = attrName.Name;
303                                 attr.SchemaTypeName =
304                                         InferSimpleType (source.Value);
305                         }
306                         if (!laxOccurence &&
307                                 (isNewTypeDefinition || mergedRequired))
308                                 attr.Use = Use.Required;
309                         else
310                                 attr.Use = Use.Optional;
311
312                         return attr;
313                 }
314
315                 // validate string value agains attr and 
316                 // if invalid, then relax the type.
317                 private void InferMergedAttribute (Attr attr)
318                 {
319                         attr.SchemaTypeName = InferMergedType (source.Value,
320                                 attr.SchemaTypeName);
321                         attr.SchemaType = null;
322                 }
323
324                 private QName InferMergedType (string value, QName typeName)
325                 {
326                         // examine value against specified type and
327                         // if unacceptable, then return a relaxed type.
328
329                         SimpleType st = XmlSchemaType.GetBuiltInSimpleType (
330                                 typeName);
331                         if (st == null) // non-primitive type => see above.
332                                 return QNameString;
333                         try {
334                                 st.Datatype.ParseValue (value,
335                                         source.NameTable,
336                                         source as IXmlNamespaceResolver);
337                                 // the string value was value
338                                 return typeName;
339                         } catch {
340                                 // The types were incompatible.
341                                 // FIXME: find the base common type
342                                 return QNameString;
343                         }
344                 }
345
346                 private SOMList GetAttributes (ComplexType ct)
347                 {
348                         if (ct.ContentModel == null)
349                                 return ct.Attributes;
350
351                         SimpleModel sc = ct.ContentModel as SimpleModel;
352                         if (sc != null) {
353                                 SimpleExt sce = sc.Content as SimpleExt;
354                                 if (sce != null)
355                                         return sce.Attributes;
356                                 SimpleRst scr = sc.Content as SimpleRst;
357                                 if (scr != null)
358                                         return scr.Attributes;
359                                 else
360                                         throw Error (sc, "Invalid simple content model.");
361                         }
362                         ComplexModel cc = ct.ContentModel as ComplexModel;
363                         if (cc != null) {
364                                 ComplexExt cce = cc.Content as ComplexExt;
365                                 if (cce != null)
366                                         return cce.Attributes;
367                                 ComplexRst ccr = cc.Content as ComplexRst;
368                                 if (ccr != null)
369                                         return ccr.Attributes;
370                                 else
371                                         throw Error (cc, "Invalid simple content model.");
372                         }
373                         throw Error (cc, "Invalid complexType. Should not happen.");
374                 }
375
376                 private ComplexType ToComplexType (Element el)
377                 {
378                         QName name = el.SchemaTypeName;
379                         XmlSchemaType type = el.SchemaType;
380
381                         // 1. element type is complex.
382                         ComplexType ct = type as ComplexType;
383                         if (ct != null)
384                                 return ct;
385
386                         // 2. reference to global complexType.
387                         XmlSchemaType globalType = schemas.GlobalTypes [name]
388                                 as XmlSchemaType;
389                         ct = globalType as ComplexType;
390                         if (ct != null)
391                                 return ct;
392
393                         ct = new ComplexType ();
394                         el.SchemaType = ct;
395                         el.SchemaTypeName = QName.Empty;
396
397                         // 3. base type name is xs:anyType or no specification.
398                         // <xs:complexType />
399                         if (name == QNameAnyType)
400                                 return ct;
401                         else if (type == null && name == QName.Empty)
402                                 return ct;
403
404                         SimpleModel sc = new SimpleModel ();
405                         ct.ContentModel = sc;
406
407                         // 4. type is simpleType
408                         //    -> extension of existing simple type.
409                         SimpleType st = type as SimpleType;
410                         if (st != null) {
411                                 SimpleRst scr = new SimpleRst ();
412                                 scr.BaseType = st;
413                                 sc.Content = scr;
414                                 return ct;
415                         }
416
417                         SimpleExt sce = new SimpleExt ();
418                         sc.Content = sce;
419
420                         // 5. type name points to primitive type
421                         //    -> simple extension of a primitive type
422                         st = XmlSchemaType.GetBuiltInSimpleType (name);
423                         if (st != null) {
424                                 sce.BaseTypeName = name;
425                                 return ct;
426                         }
427
428                         // 6. type name points to global simpleType.
429                         st = globalType as SimpleType;
430                         if (st != null) {
431                                 sce.BaseTypeName = name;
432                                 return ct;
433                         }
434
435                         throw Error (el, "Unexpected schema component that contains simpleTypeName that could not be resolved.");
436                 }
437
438                 #endregion
439
440                 #region Element Type
441
442                 private void InferAsEmptyElement (Element el, string ns,
443                         bool isNew)
444                 {
445                         ComplexType ct = el.SchemaType as ComplexType;
446                         if (ct != null) {
447                                 SimpleModel sm =
448                                         ct.ContentModel as SimpleModel;
449                                 if (sm != null) {
450                                         ToEmptiableSimpleContent (sm, isNew);
451                                         return;
452                                 }
453
454                                 ComplexModel cm = ct.ContentModel
455                                         as ComplexModel;
456                                 if (cm != null) {
457                                         ToEmptiableComplexContent (cm, isNew);
458                                         return;
459                                 }
460
461                                 if (ct.Particle != null)
462                                         ct.Particle.MinOccurs = 0;
463                                 return;
464                         }
465                         SimpleType st = el.SchemaType as SimpleType;
466                         if (st != null) {
467                                 st = MakeBaseTypeAsEmptiable (st);
468                                 switch (st.QualifiedName.Namespace) {
469                                 case XmlSchema.Namespace:
470                                 case XdtNamespace:
471                                         el.SchemaTypeName = st.QualifiedName;
472                                         break;
473                                 default:
474                                         el.SchemaType =st;
475                                         break;
476                                 }
477                         }
478                 }
479
480                 private SimpleType MakeBaseTypeAsEmptiable (SimpleType st)
481                 {
482                         switch (st.QualifiedName.Namespace) {
483                         case XmlSchema.Namespace:
484                         case XdtNamespace:
485                                 // If a primitive type
486                                 return XmlSchemaType.GetBuiltInSimpleType (
487                                         XmlTypeCode.String);
488                         }
489                         SimpleTypeRst str = st.Content as SimpleTypeRst;
490                         if (str != null) {
491                                 ArrayList al = null;
492                                 foreach (SchemaFacet f in str.Facets) {
493                                         if (f is LengthFacet ||
494                                                 f is MinLengthFacet) {
495                                                 if (al == null)
496                                                         al = new ArrayList ();
497                                                 al.Add (f);
498                                         }
499                                 }
500                                 foreach (SchemaFacet f in al)
501                                         str.Facets.Remove (f);
502                                 if (str.BaseType != null)
503                                         str.BaseType =
504                                                 MakeBaseTypeAsEmptiable (st);
505                                 else
506                                         // It might have a reference to an
507                                         // external simple type, but there is
508                                         // no assurance that any of those
509                                         // external types allow an empty
510                                         // string. So just set base type as
511                                         // xs:string.
512                                         str.BaseTypeName = QNameString;
513                         } // union/list can have empty string value.
514
515                         return st;
516                 }
517
518                 private void ToEmptiableSimpleContent (
519                         SimpleModel sm, bool isNew)
520                 {
521                         SimpleExt se = sm.Content as SimpleExt;
522                         if (se != null)
523                                 se.BaseTypeName = QNameString;
524                         else {
525                                 SimpleRst sr = sm.Content
526                                         as SimpleRst;
527                                 if (sr == null)
528                                         throw Error (sm, "Invalid simple content model was passed.");
529                                 sr.BaseTypeName = QNameString;
530                                 sr.BaseType = null;
531                         }
532                 }
533
534                 private void ToEmptiableComplexContent (
535                         ComplexModel cm, bool isNew)
536                 {
537                         ComplexExt ce = cm.Content
538                                 as ComplexExt;
539                         if (ce != null) {
540                                 if (ce.Particle != null)
541                                         ce.Particle.MinOccurs = 0;
542                                 else if (ce.BaseTypeName != null &&
543                                         ce.BaseTypeName != QName.Empty &&
544                                         ce.BaseTypeName != QNameAnyType)
545                                         throw Error (ce, "Complex type content extension has a reference to an external component that is not supported.");
546                         }
547                         else {
548                                 ComplexRst cr = cm.Content
549                                         as ComplexRst;
550                                 if (cr == null)
551                                         throw Error (cm, "Invalid complex content model was passed.");
552                                 if (cr.Particle != null)
553                                         cr.Particle.MinOccurs = 0;
554                                 else if (cr.BaseTypeName != null &&
555                                         cr.BaseTypeName != QName.Empty &&
556                                         cr.BaseTypeName != QNameAnyType)
557                                         throw Error (cr, "Complex type content extension has a reference to an external component that is not supported.");
558                         }
559                 }
560
561                 private void InferContent (Element el, string ns, bool isNew)
562                 {
563                         source.Read ();
564                         source.MoveToContent ();
565                         switch (source.NodeType) {
566                         case XmlNodeType.EndElement:
567                                 InferAsEmptyElement (el, ns, isNew);
568                                 break;
569                         case XmlNodeType.Element:
570                                 InferComplexContent (el, ns, isNew);
571                                 break;
572                         case XmlNodeType.Text:
573                         case XmlNodeType.CDATA:
574                         case XmlNodeType.SignificantWhitespace:
575                                 InferTextContent (el, isNew);
576                                 source.MoveToContent ();
577                                 if (source.NodeType == XmlNodeType.Element)
578                                         goto case XmlNodeType.Element;
579                                 break;
580                         case XmlNodeType.Whitespace:
581                                 InferContent (el, ns, isNew); // skip and retry
582                                 break;
583                         }
584                 }
585
586                 private void InferComplexContent (Element el, string ns,
587                         bool isNew)
588                 {
589                         ComplexType ct = ToComplexType (el);
590                         ToComplexContentType (ct);
591
592                         int position = 0;
593                         bool consumed = false;
594
595                         do {
596                                 switch (source.NodeType) {
597                                 case XmlNodeType.Element:
598                                         Sequence s = PopulateSequence (ct);
599                                         Choice c = s.Items.Count > 0 ?
600                                                 s.Items [0] as Choice :
601                                                 null;
602                                         if (c != null)
603                                                 ProcessLax (c, ns);
604                                         else
605                                                 ProcessSequence (ct, s, ns,
606                                                         ref position,
607                                                         ref consumed,
608                                                         isNew);
609                                         source.MoveToContent ();
610                                         break;
611                                 case XmlNodeType.Text:
612                                 case XmlNodeType.CDATA:
613                                 case XmlNodeType.SignificantWhitespace:
614                                         MarkAsMixed (ct);
615                                         source.ReadString ();
616                                         source.MoveToContent ();
617                                         break;
618                                 case XmlNodeType.EndElement:
619                                         return; // finished
620                                 case XmlNodeType.None:
621                                         throw new NotImplementedException ("Internal Error: Should not happen.");
622                                 }
623                         } while (true);
624                 }
625
626                 private void InferTextContent (Element el, bool isNew)
627                 {
628                         string value = source.ReadString ();
629                         if (el.SchemaType == null) {
630                                 if (el.SchemaTypeName == QName.Empty) {
631                                         // no type information -> infer type
632                                         if (isNew)
633                                                 el.SchemaTypeName =
634                                                         InferSimpleType (
635                                                         value);
636                                         else
637                                                 el.SchemaTypeName =
638                                                         QNameString;
639                                         return;
640                                 }
641                                 switch (el.SchemaTypeName.Namespace) {
642                                 case XmlSchema.Namespace:
643                                 case XdtNamespace:
644                                         // existing primitive type
645                                         el.SchemaTypeName = InferMergedType (
646                                                 value, el.SchemaTypeName);
647                                         break;
648                                 default:
649                                         ComplexType ct = schemas.GlobalTypes [
650                                                 el.SchemaTypeName]
651                                                 as ComplexType;
652                                         // If it is complex, then just set
653                                         // mixed='true' (type cannot be set.)
654                                         // If it is simple, then we cannot
655                                         // make sure that string value is
656                                         // valid. So just set as xs:string.
657                                         if (ct != null)
658                                                 MarkAsMixed (ct);
659                                         else
660                                                 el.SchemaTypeName = QNameString;
661                                         break;
662                                 }
663                                 return;
664                         }
665                         // simpleType
666                         SimpleType st = el.SchemaType as SimpleType;
667                         if (st != null) {
668                                 // If simple, then (described above)
669                                 el.SchemaType = null;
670                                 el.SchemaTypeName = QNameString;
671                                 return;
672                         }
673
674                         // complexType
675                         ComplexType ect = el.SchemaType as ComplexType;
676
677                         SimpleModel sm = ect.ContentModel as SimpleModel;
678                         if (sm == null) {
679                                 // - ComplexContent
680                                 MarkAsMixed (ect);
681                                 return;
682                         }
683
684                         // - SimpleContent
685                         SimpleExt se = sm.Content as SimpleExt;
686                         if (se != null)
687                                 se.BaseTypeName = InferMergedType (value,
688                                         se.BaseTypeName);
689                         SimpleRst sr = sm.Content as SimpleRst;
690                         if (sr != null) {
691                                 sr.BaseTypeName = InferMergedType (value,
692                                         sr.BaseTypeName);
693                                 sr.BaseType = null;
694                         }
695                 }
696
697                 private void MarkAsMixed (ComplexType ct)
698                 {
699                         ComplexModel cm = ct.ContentModel as ComplexModel;
700                         if (cm != null)
701                                 cm.IsMixed = true;
702                         else
703                                 ct.IsMixed = true;
704                 }
705
706                 #endregion
707
708                 #region Particles
709
710                 private void ProcessLax (Choice c, string ns)
711                 {
712                         foreach (Particle p in c.Items) {
713                                 Element el = p as Element;
714                                 if (el == null)
715                                         throw Error (c, String.Format ("Target schema item contains unacceptable particle {0}. Only element is allowed here."));
716                                 if (ElementMatches (el, ns)) {
717                                         InferElement (el, ns, false);
718                                         return;
719                                 }
720                         }
721                         // append a new element particle to lax term.
722                         Element nel = new Element ();
723                         if (source.NamespaceURI == ns)
724                                 nel.Name = source.LocalName;
725                         else
726                                 nel.RefName = new QName (source.LocalName,
727                                         source.NamespaceURI);
728                         InferElement (nel, source.NamespaceURI, true);
729                         c.Items.Add (nel);
730                 }
731
732                 private bool ElementMatches (Element el, string ns)
733                 {
734                         bool matches = false;
735                         if (el.RefName != QName.Empty) {
736                                 if (el.RefName.Name == source.LocalName &&
737                                         el.RefName.Namespace ==
738                                         source.NamespaceURI)
739                                         matches = true;
740                         }
741                         else if (el.Name == source.LocalName &&
742                                 ns == source.NamespaceURI)
743                                         matches = true;
744                         return matches;
745                 }
746
747                 private void ProcessSequence (ComplexType ct, Sequence s,
748                         string ns, ref int position, ref bool consumed,
749                         bool isNew)
750                 {
751                         for (int i = 0; i < position; i++) {
752                                 Element iel = s.Items [i] as Element;
753                                 if (ElementMatches (iel, ns)) {
754                                         // Sequence element type violation
755                                         // might happen (might not, but we
756                                         // cannot backtrack here). So switch
757                                         // to sequence of choice* here.
758                                         ProcessLax (ToSequenceOfChoice (s), ns);
759                                         return;
760                                 }
761                         }
762
763                         if (s.Items.Count <= position) {
764                                 QName name = new QName (source.LocalName,
765                                         source.NamespaceURI);
766                                 Element nel = CreateElement (name);
767                                 InferElement (nel, ns, true);
768                                 if (ns == name.Namespace)
769                                         s.Items.Add (nel);
770                                 else {
771                                         Element re = new Element ();
772                                         re.RefName = name;
773                                         s.Items.Add (re);
774                                 }
775                                 consumed = true;
776                                 return;
777                         }
778                         Element el = s.Items [position] as Element;
779                         if (el == null)
780                                 throw Error (s, String.Format ("Target complex type content sequence has an unacceptable type of particle {0}", s.Items [position]));
781                         bool matches = ElementMatches (el, ns);
782                         if (matches) {
783                                 if (consumed)
784                                         el.MaxOccursString = "unbounded";
785                                 InferElement (el, source.NamespaceURI, false);
786                                 source.MoveToContent ();
787                                 switch (source.NodeType) {
788                                 case XmlNodeType.None:
789                                         if (source.NodeType ==
790                                                 XmlNodeType.Element)
791                                                 goto case XmlNodeType.Element;
792                                         else if (source.NodeType ==
793                                                 XmlNodeType.EndElement)
794                                                 goto case XmlNodeType.EndElement;
795                                         break;
796                                 case XmlNodeType.Element:
797                                         ProcessSequence (ct, s, ns, ref position,
798                                                 ref consumed, isNew);
799                                         break;
800                                 case XmlNodeType.Text:
801                                 case XmlNodeType.CDATA:
802                                 case XmlNodeType.SignificantWhitespace:
803                                         MarkAsMixed (ct);
804                                         source.ReadString ();
805                                         goto case XmlNodeType.None;
806                                 case XmlNodeType.Whitespace:
807                                         source.ReadString ();
808                                         goto case XmlNodeType.None;
809                                 case XmlNodeType.EndElement:
810                                         return;
811                                 default:
812                                         source.Read ();
813                                         break;
814                                 }
815                         }
816                         else {
817                                 if (consumed) {
818                                         position++;
819                                         consumed = false;
820                                         ProcessSequence (ct, s, ns,
821                                                 ref position, ref consumed,
822                                                 isNew);
823                                 }
824                                 else
825                                         ProcessLax (ToSequenceOfChoice (s), ns);
826                         }
827                 }
828
829                 // Note that it does not return the changed sequence.
830                 private Choice ToSequenceOfChoice (Sequence s)
831                 {
832                         Choice c = new Choice ();
833                         if (laxOccurence)
834                                 c.MinOccurs = 0;
835                         c.MaxOccursString = "unbounded";
836                         foreach (Particle p in s.Items)
837                                 c.Items.Add (p);
838                         s.Items.Clear ();
839                         s.Items.Add (c);
840                         return c;
841                 }
842
843                 // It makes complexType not to have Simple content model.
844                 private void ToComplexContentType (ComplexType type)
845                 {
846                         SimpleModel sm = type.ContentModel as SimpleModel;
847                         if (sm == null)
848                                 return;
849
850                         SOMList atts = GetAttributes (type);
851                         foreach (SOMObject o in atts)
852                                 type.Attributes.Add (o);
853                         // FIXME: need to copy AnyAttribute.
854                         // (though not considered right now)
855                         type.ContentModel = null;
856                         type.IsMixed = true;
857                 }
858
859                 private Sequence PopulateSequence (ComplexType ct)
860                 {
861                         Particle p = PopulateParticle (ct);
862                         Sequence s = p as Sequence;
863                         if (s != null)
864                                 return s;
865                         else
866                                 throw Error (ct, String.Format ("Target complexType contains unacceptable type of particle {0}", p));
867                 }
868
869                 private Sequence CreateSequence ()
870                 {
871                         Sequence s = new Sequence ();
872                         if (laxOccurence)
873                                 s.MinOccurs = 0;
874                         return s;
875                 }
876
877                 private Particle PopulateParticle (ComplexType ct)
878                 {
879                         if (ct.ContentModel == null) {
880                                 if (ct.Particle == null)
881                                         ct.Particle = CreateSequence ();
882                                 return ct.Particle;
883                         }
884                         ComplexModel cm = ct.ContentModel as ComplexModel;
885                         if (cm != null) {
886                                 ComplexExt  ce = cm.Content as ComplexExt;
887                                 if (ce != null) {
888                                         if (ce.Particle == null)
889                                                 ce.Particle = CreateSequence ();
890                                         return ce.Particle;
891                                 }
892                                 ComplexRst cr = cm.Content as ComplexRst;
893                                 if (cr != null) {
894                                         if (cr.Particle == null)
895                                                 cr.Particle = CreateSequence ();
896                                         return cr.Particle;
897                                 }
898                         }
899                         throw Error (ct, "Schema inference internal error. The complexType should have been converted to have a complex content.");
900                 }
901
902                 #endregion
903
904                 #region String Value
905
906                 // primitive type inference.
907                 // When running lax type inference, it just returns xs:string.
908                 private QName InferSimpleType (string value)
909                 {
910                         if (laxTypeInference)
911                                 return QNameString;
912
913                         switch (value) {
914                         // 0 and 1 are not infered as byte unlike MS.XSDInfer
915 //                      case "0":
916 //                      case "1":
917                         case "true":
918                         case "false":
919                                 return QNameBoolean;
920                         }
921                         try {
922                                 long dec = XmlConvert.ToInt64 (value);
923                                 if (byte.MinValue <= dec && dec <= byte.MaxValue)
924                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.UnsignedByte).QualifiedName;
925                                 if (sbyte.MinValue <= dec && dec <= sbyte.MaxValue)
926                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Byte).QualifiedName;
927                                 if (ushort.MinValue <= dec && dec <= ushort.MaxValue)
928                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.UnsignedShort).QualifiedName;
929                                 if (short.MinValue <= dec && dec <= short.MaxValue)
930                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Short).QualifiedName;
931                                 if (uint.MinValue <= dec && dec <= uint.MaxValue)
932                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.UnsignedInt).QualifiedName;
933                                 if (int.MinValue <= dec && dec <= int.MaxValue)
934                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Int).QualifiedName;
935                                 return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Long).QualifiedName;
936                         } catch (Exception) {
937                         }
938                         try {
939                                 XmlConvert.ToUInt64 (value);
940                                 return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.UnsignedLong).QualifiedName;
941                         } catch (Exception) {
942                         }
943                         try {
944                                 XmlConvert.ToDecimal (value);
945                                 return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Decimal).QualifiedName;
946                         } catch (Exception) {
947                         }
948                         try {
949                                 double dbl = XmlConvert.ToDouble (value);
950                                 if (float.MinValue <= dbl && dbl <= float.MaxValue)
951                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Float).QualifiedName;
952                                 else
953                                         return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Double).QualifiedName;
954                         } catch (Exception) {
955                         }
956                         try {
957                                 // FIXME: also try DateTimeSerializationMode
958                                 // and gYearMonth
959                                 XmlConvert.ToDateTime (value);
960                                 return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.DateTime).QualifiedName;
961                         } catch (Exception) {
962                         }
963                         try {
964                                 XmlConvert.ToTimeSpan (value);
965                                 return XmlSchemaType.GetBuiltInSimpleType (XmlTypeCode.Duration).QualifiedName;
966                         } catch (Exception) {
967                         }
968
969                         // xs:string
970                         return QNameString;
971                 }
972
973                 #endregion
974
975                 #region Utilities
976
977                 private Element GetGlobalElement (QName name)
978                 {
979                         Element el = newElements [name] as Element;
980                         if (el == null)
981                                 el = schemas.GlobalElements [name] as Element;
982                         return el;
983                 }
984
985                 private Attr GetGlobalAttribute (QName name)
986                 {
987                         Attr a = newElements [name] as Attr;
988                         if (a == null)
989                                 a = schemas.GlobalAttributes [name] as Attr;
990                         return a;
991                 }
992
993                 private Element CreateElement (QName name)
994                 {
995                         Element el = new Element ();
996                         el.Name = name.Name;
997                         return el;
998                 }
999
1000                 private Element CreateGlobalElement (QName name)
1001                 {
1002                         Element el = CreateElement (name);
1003                         XmlSchema schema = PopulateSchema (name.Namespace);
1004                         schema.Items.Add (el);
1005                         newElements.Add (name, el);
1006                         return el;
1007                 }
1008
1009                 private Attr CreateGlobalAttribute (QName name)
1010                 {
1011                         Attr attr = new Attr ();
1012                         XmlSchema schema = PopulateSchema (name.Namespace);
1013                         attr.Name = name.Name;
1014                         schema.Items.Add (attr);
1015                         newAttributes.Add (name, attr);
1016                         return attr;
1017                 }
1018
1019                 // Note that the return value never assures that all the
1020                 // components in the parameter ns must reside in it.
1021                 private XmlSchema PopulateSchema (string ns)
1022                 {
1023                         ICollection list = schemas.Schemas (ns);
1024                         if (list.Count > 0) {
1025                                 IEnumerator e = list.GetEnumerator ();
1026                                 e.MoveNext ();
1027                                 return (XmlSchema) e.Current;
1028                         }
1029                         XmlSchema s = new XmlSchema ();
1030                         if (ns != null && ns.Length > 0)
1031                                 s.TargetNamespace = ns;
1032                         s.ElementFormDefault = Form.Qualified;
1033                         s.AttributeFormDefault = Form.Unqualified;
1034                         schemas.Add (s);
1035                         return s;
1036                 }
1037
1038                 private XmlSchemaInferenceException Error (
1039                         XmlSchemaObject sourceObj,
1040                         string message)
1041                 {
1042                         // This override is mainly for schema component error.
1043                         return Error (sourceObj, false, message);
1044                 }
1045
1046                 private XmlSchemaInferenceException Error (
1047                         XmlSchemaObject sourceObj,
1048                         bool useReader,
1049                         string message)
1050                 {
1051                         string msg = String.Concat (
1052                                 message,
1053                                 sourceObj != null ?
1054                                         String.Format (". Related schema component is {0}",
1055                                                 sourceObj.SourceUri,
1056                                                 sourceObj.LineNumber,
1057                                                 sourceObj.LinePosition) :
1058                                         String.Empty,
1059                                 useReader ?
1060                                         String.Format (". {0}", source.BaseURI) :
1061                                         String.Empty);
1062
1063                         IXmlLineInfo li = source as IXmlLineInfo;
1064                         if (useReader && li != null)
1065                                 return new XmlSchemaInferenceException (
1066                                         msg, null, li.LineNumber,
1067                                         li.LinePosition);
1068                         else
1069                                 return new XmlSchemaInferenceException (msg);
1070                 }
1071
1072                 #endregion
1073         }
1074 }
1075
1076 #endif