1 //------------------------------------------------------------------------------
2 // <copyright file="XmlSchemas.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
8 namespace System.Xml.Serialization {
10 using System.Collections;
11 using System.Collections.Generic;
14 using System.Globalization;
15 using System.ComponentModel;
16 using System.Xml.Serialization;
17 using System.Xml.Schema;
18 using System.Diagnostics;
19 using System.Threading;
20 using System.Security.Permissions;
21 using System.Security.Policy;
22 using System.Security;
25 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas"]/*' />
28 /// <para>[To be supplied.]</para>
30 public class XmlSchemas : CollectionBase, IEnumerable<XmlSchema> {
31 XmlSchemaSet schemaSet;
33 SchemaObjectCache cache; // cached schema top-level items
35 Hashtable mergedSchemas;
36 internal Hashtable delayedSchemas = new Hashtable();
37 bool isCompiled = false;
38 static volatile XmlSchema xsd;
39 static volatile XmlSchema xml;
41 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.this"]/*' />
43 /// <para>[To be supplied.]</para>
45 public XmlSchema this[int index] {
46 get { return (XmlSchema)List[index]; }
47 set { List[index] = value; }
50 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.this1"]/*' />
52 /// <para>[To be supplied.]</para>
54 public XmlSchema this[string ns] {
56 IList values = (IList)SchemaSet.Schemas(ns);
57 if (values.Count == 0)
59 if (values.Count == 1)
60 return (XmlSchema)values[0];
62 throw new InvalidOperationException(Res.GetString(Res.XmlSchemaDuplicateNamespace, ns));
66 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.GetSchemas"]/*' />
68 /// <para>[To be supplied.]</para>
70 public IList GetSchemas(string ns) {
71 return (IList)SchemaSet.Schemas(ns);
74 internal SchemaObjectCache Cache {
77 cache = new SchemaObjectCache();
82 internal Hashtable MergedSchemas {
84 if (mergedSchemas == null)
85 mergedSchemas = new Hashtable();
90 internal Hashtable References {
92 if (references == null)
93 references = new Hashtable();
98 internal XmlSchemaSet SchemaSet {
100 if (schemaSet == null) {
101 schemaSet = new XmlSchemaSet();
102 schemaSet.XmlResolver = null;
103 schemaSet.ValidationEventHandler += new ValidationEventHandler(IgnoreCompileErrors);
108 internal int Add(XmlSchema schema, bool delay) {
110 if (delayedSchemas[schema] == null)
111 delayedSchemas.Add(schema, schema);
119 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Add"]/*' />
121 /// <para>[To be supplied.]</para>
123 public int Add(XmlSchema schema) {
124 if (List.Contains(schema))
125 return List.IndexOf(schema);
126 return List.Add(schema);
129 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Add"]/*' />
131 /// <para>[To be supplied.]</para>
133 public int Add(XmlSchema schema, Uri baseUri) {
134 if (List.Contains(schema))
135 return List.IndexOf(schema);
137 schema.BaseUri = baseUri;
138 return List.Add(schema);
141 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Add1"]/*' />
143 /// <para>[To be supplied.]</para>
145 public void Add(XmlSchemas schemas) {
146 foreach (XmlSchema schema in schemas) {
151 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.AddReference"]/*' />
153 /// <para>[To be supplied.]</para>
155 public void AddReference(XmlSchema schema) {
156 References[schema] = schema;
159 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Insert"]/*' />
161 /// <para>[To be supplied.]</para>
163 public void Insert(int index, XmlSchema schema) {
164 List.Insert(index, schema);
167 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.IndexOf"]/*' />
169 /// <para>[To be supplied.]</para>
171 public int IndexOf(XmlSchema schema) {
172 return List.IndexOf(schema);
175 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Contains"]/*' />
177 /// <para>[To be supplied.]</para>
179 public bool Contains(XmlSchema schema) {
180 return List.Contains(schema);
183 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Contains1"]/*' />
185 /// <para>[To be supplied.]</para>
187 public bool Contains(string targetNamespace) {
188 return SchemaSet.Contains(targetNamespace);
191 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Remove"]/*' />
193 /// <para>[To be supplied.]</para>
195 public void Remove(XmlSchema schema) {
199 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.CopyTo"]/*' />
201 /// <para>[To be supplied.]</para>
203 public void CopyTo(XmlSchema[] array, int index) {
204 List.CopyTo(array, index);
207 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.OnInsert"]/*' />
209 /// <para>[To be supplied.]</para>
211 protected override void OnInsert(int index, object value) {
212 AddName((XmlSchema)value);
215 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.OnRemove"]/*' />
217 /// <para>[To be supplied.]</para>
219 protected override void OnRemove(int index, object value) {
220 RemoveName((XmlSchema)value);
223 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.OnClear"]/*' />
225 /// <para>[To be supplied.]</para>
227 protected override void OnClear() {
231 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.OnSet"]/*' />
233 /// <para>[To be supplied.]</para>
235 protected override void OnSet(int index, object oldValue, object newValue) {
236 RemoveName((XmlSchema)oldValue);
237 AddName((XmlSchema)newValue);
240 void AddName(XmlSchema schema) {
241 if (isCompiled) throw new InvalidOperationException(Res.GetString(Res.XmlSchemaCompiled));
242 if (SchemaSet.Contains(schema))
243 SchemaSet.Reprocess(schema);
246 SchemaSet.Add(schema);
250 void Prepare(XmlSchema schema) {
251 // need to remove illegal <import> externals;
252 ArrayList removes = new ArrayList();
253 string ns = schema.TargetNamespace;
254 foreach (XmlSchemaExternal external in schema.Includes) {
255 if (external is XmlSchemaImport) {
256 if (ns == ((XmlSchemaImport)external).Namespace) {
257 removes.Add(external);
261 foreach(XmlSchemaObject o in removes) {
262 schema.Includes.Remove(o);
266 void RemoveName(XmlSchema schema) {
267 SchemaSet.Remove(schema);
270 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Find"]/*' />
272 /// <para>[To be supplied.]</para>
274 public object Find(XmlQualifiedName name, Type type) {
275 return Find(name, type, true);
277 internal object Find(XmlQualifiedName name, Type type, bool checkCache) {
279 foreach (XmlSchema schema in List) {
283 IList values = (IList)SchemaSet.Schemas(name.Namespace);
284 if (values == null) return null;
286 foreach (XmlSchema schema in values) {
289 XmlSchemaObject ret = null;
290 if (typeof(XmlSchemaType).IsAssignableFrom(type)) {
291 ret = schema.SchemaTypes[name];
292 if (ret == null || !type.IsAssignableFrom(ret.GetType())) {
296 else if (type == typeof(XmlSchemaGroup)) {
297 ret = schema.Groups[name];
299 else if (type == typeof(XmlSchemaAttributeGroup)) {
300 ret = schema.AttributeGroups[name];
302 else if (type == typeof(XmlSchemaElement)) {
303 ret = schema.Elements[name];
305 else if (type == typeof(XmlSchemaAttribute)) {
306 ret = schema.Attributes[name];
308 else if (type == typeof(XmlSchemaNotation)) {
309 ret = schema.Notations[name];
313 // use exception in the place of Debug.Assert to avoid throwing asserts from a server process such as aspnet_ewp.exe
314 throw new InvalidOperationException(Res.GetString(Res.XmlInternalErrorDetails, "XmlSchemas.Find: Invalid object type " + type.FullName));
318 if (ret != null && shareTypes && checkCache && !IsReference(ret))
319 ret = Cache.AddItem(ret, name, this);
327 IEnumerator<XmlSchema> IEnumerable<XmlSchema>.GetEnumerator() {
328 return new XmlSchemaEnumerator(this);
331 internal static void Preprocess(XmlSchema schema) {
332 if (!schema.IsPreprocessed) {
334 XmlNameTable nameTable = new System.Xml.NameTable();
335 Preprocessor prep = new Preprocessor(nameTable, new SchemaNames(nameTable), null);
336 prep.SchemaLocations = new Hashtable();
337 prep.Execute(schema, schema.TargetNamespace, false);
339 catch(XmlSchemaException e) {
340 throw CreateValidationException(e, e.Message);
345 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.IsDataSet"]/*' />
347 /// <para>[To be supplied.]</para>
349 public static bool IsDataSet(XmlSchema schema) {
350 foreach (XmlSchemaObject o in schema.Items) {
351 if (o is XmlSchemaElement) {
352 XmlSchemaElement e = (XmlSchemaElement)o;
353 if (e.UnhandledAttributes != null) {
354 foreach (XmlAttribute a in e.UnhandledAttributes) {
355 if (a.LocalName == "IsDataSet" && a.NamespaceURI == "urn:schemas-microsoft-com:xml-msdata") {
356 // currently the msdata:IsDataSet uses its own format for the boolean values
357 if (a.Value == "True" || a.Value == "true" || a.Value == "1") return true;
366 void Merge(XmlSchema schema) {
367 if (MergedSchemas[schema] != null)
369 IList originals = (IList)SchemaSet.Schemas(schema.TargetNamespace);
370 if (originals != null && originals.Count > 0) {
371 MergedSchemas.Add(schema, schema);
372 Merge(originals, schema);
376 MergedSchemas.Add(schema, schema);
380 void AddImport(IList schemas, string ns) {
381 foreach(XmlSchema s in schemas) {
383 foreach (XmlSchemaExternal external in s.Includes) {
384 if (external is XmlSchemaImport && ((XmlSchemaImport)external).Namespace == ns) {
390 XmlSchemaImport import = new XmlSchemaImport();
391 import.Namespace = ns;
392 s.Includes.Add(import);
397 void Merge(IList originals, XmlSchema schema) {
398 foreach (XmlSchema s in originals) {
404 foreach (XmlSchemaExternal external in schema.Includes) {
405 if (external is XmlSchemaImport) {
406 external.SchemaLocation = null;
407 if (external.Schema != null) {
408 Merge(external.Schema);
411 AddImport(originals, ((XmlSchemaImport)external).Namespace);
415 if (external.Schema == null) {
416 // we do not process includes or redefines by the schemaLocation
417 if (external.SchemaLocation != null) {
418 throw new InvalidOperationException(Res.GetString(Res.XmlSchemaIncludeLocation, this.GetType().Name, external.SchemaLocation));
422 external.SchemaLocation = null;
423 Merge(originals, external.Schema);
428 // bring all included items to the parent schema;
429 bool[] matchedItems = new bool[schema.Items.Count];
431 for (int i = 0; i < schema.Items.Count; i++) {
432 XmlSchemaObject o = schema.Items[i];
433 XmlSchemaObject dest = Find(o, originals);
435 if (!Cache.Match(dest, o, shareTypes)) {
437 Debug.WriteLineIf(DiagnosticsSwitches.XmlSerialization.TraceVerbose, "XmlSerialization::Failed to Merge " + MergeFailedMessage(o, dest, schema.TargetNamespace)
438 + "' Plase Compare hash:\r\n" + Cache.looks[dest] + "\r\n" + Cache.looks[o]);
439 throw new InvalidOperationException(MergeFailedMessage(o, dest, schema.TargetNamespace));
441 matchedItems[i] = true;
445 if (count != schema.Items.Count) {
446 XmlSchema destination = (XmlSchema)originals[0];
447 for (int i = 0; i < schema.Items.Count; i++) {
448 if (!matchedItems[i]) {
449 destination.Items.Add(schema.Items[i]);
452 destination.IsPreprocessed = false;
453 Preprocess(destination);
457 static string ItemName(XmlSchemaObject o) {
458 if (o is XmlSchemaNotation) {
459 return ((XmlSchemaNotation)o).Name;
461 else if (o is XmlSchemaGroup) {
462 return ((XmlSchemaGroup)o).Name;
464 else if (o is XmlSchemaElement) {
465 return ((XmlSchemaElement)o).Name;
467 else if (o is XmlSchemaType) {
468 return ((XmlSchemaType)o).Name;
470 else if (o is XmlSchemaAttributeGroup) {
471 return ((XmlSchemaAttributeGroup)o).Name;
473 else if (o is XmlSchemaAttribute) {
474 return ((XmlSchemaAttribute)o).Name;
479 internal static XmlQualifiedName GetParentName(XmlSchemaObject item) {
480 while (item.Parent != null) {
481 if (item.Parent is XmlSchemaType) {
482 XmlSchemaType type = (XmlSchemaType)item.Parent;
483 if (type.Name != null && type.Name.Length != 0) {
484 return type.QualifiedName;
489 return XmlQualifiedName.Empty;
492 static string GetSchemaItem(XmlSchemaObject o, string ns, string details) {
496 while (o.Parent != null && !(o.Parent is XmlSchema)) {
499 if (ns == null || ns.Length == 0) {
500 XmlSchemaObject tmp = o;
501 while (tmp.Parent != null) {
504 if (tmp is XmlSchema) {
505 ns = ((XmlSchema)tmp).TargetNamespace;
509 if (o is XmlSchemaNotation) {
510 item = Res.GetString(Res.XmlSchemaNamedItem, ns, "notation", ((XmlSchemaNotation)o).Name, details);
512 else if (o is XmlSchemaGroup) {
513 item = Res.GetString(Res.XmlSchemaNamedItem, ns, "group", ((XmlSchemaGroup)o).Name, details);
515 else if (o is XmlSchemaElement) {
516 XmlSchemaElement e = ((XmlSchemaElement)o);
517 if (e.Name == null || e.Name.Length == 0) {
518 XmlQualifiedName parentName = XmlSchemas.GetParentName(o);
519 // Element reference '{0}' declared in schema type '{1}' from namespace '{2}'
520 item = Res.GetString(Res.XmlSchemaElementReference, e.RefName.ToString(), parentName.Name, parentName.Namespace);
523 item = Res.GetString(Res.XmlSchemaNamedItem, ns, "element", e.Name, details);
526 else if (o is XmlSchemaType) {
527 item = Res.GetString(Res.XmlSchemaNamedItem, ns, o.GetType() == typeof(XmlSchemaSimpleType) ? "simpleType" : "complexType", ((XmlSchemaType)o).Name, null);
529 else if (o is XmlSchemaAttributeGroup) {
530 item = Res.GetString(Res.XmlSchemaNamedItem, ns, "attributeGroup", ((XmlSchemaAttributeGroup)o).Name, details);
532 else if (o is XmlSchemaAttribute) {
533 XmlSchemaAttribute a = ((XmlSchemaAttribute)o);
534 if (a.Name == null || a.Name.Length == 0) {
535 XmlQualifiedName parentName = XmlSchemas.GetParentName(o);
536 // Attribure reference '{0}' declared in schema type '{1}' from namespace '{2}'
537 return Res.GetString(Res.XmlSchemaAttributeReference, a.RefName.ToString(), parentName.Name, parentName.Namespace);
540 item = Res.GetString(Res.XmlSchemaNamedItem, ns, "attribute", a.Name, details);
544 else if (o is XmlSchemaContent) {
545 XmlQualifiedName parentName = XmlSchemas.GetParentName(o);
546 // Check content definition of schema type '{0}' from namespace '{1}'. {2}
547 item = Res.GetString(Res.XmlSchemaContentDef, parentName.Name, parentName.Namespace, null);
549 else if (o is XmlSchemaExternal) {
550 string itemType = o is XmlSchemaImport ? "import" : o is XmlSchemaInclude ? "include" : o is XmlSchemaRedefine ? "redefine" : o.GetType().Name;
551 item = Res.GetString(Res.XmlSchemaItem, ns, itemType, details);
553 else if (o is XmlSchema) {
554 item = Res.GetString(Res.XmlSchema, ns, details);
557 item = Res.GetString(Res.XmlSchemaNamedItem, ns, o.GetType().Name, null, details);
563 static string Dump(XmlSchemaObject o) {
564 XmlWriterSettings settings = new XmlWriterSettings();
565 settings.OmitXmlDeclaration = true;
566 settings.Indent = true;
567 XmlSerializer s = new XmlSerializer(o.GetType());
568 StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
569 XmlWriter xmlWriter = XmlWriter.Create(sw, settings);
570 XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
571 ns.Add("xs", XmlSchema.Namespace);
572 s.Serialize(xmlWriter, o, ns);
573 return sw.ToString();
575 static string MergeFailedMessage(XmlSchemaObject src, XmlSchemaObject dest, string ns) {
576 string err = Res.GetString(Res.XmlSerializableMergeItem, ns, GetSchemaItem(src, ns, null));
577 err += "\r\n" + Dump(src);
578 err += "\r\n" + Dump(dest);
582 internal XmlSchemaObject Find(XmlSchemaObject o, IList originals) {
583 string name = ItemName(o);
587 Type type = o.GetType();
589 foreach (XmlSchema s in originals) {
590 foreach(XmlSchemaObject item in s.Items) {
591 if (item.GetType() == type && name == ItemName(item)) {
599 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.IsCompiled"]/*' />
600 public bool IsCompiled {
601 get { return isCompiled; }
604 /// <include file='doc\XmlSchemas.uex' path='docs/doc[@for="XmlSchemas.Compile"]/*' />
605 public void Compile(ValidationEventHandler handler, bool fullCompile) {
609 foreach(XmlSchema s in delayedSchemas.Values)
611 delayedSchemas.Clear();
614 schemaSet = new XmlSchemaSet();
615 schemaSet.XmlResolver = null;
616 schemaSet.ValidationEventHandler += handler;
618 foreach (XmlSchema s in References.Values)
620 int schemaCount = schemaSet.Count;
622 foreach (XmlSchema s in List) {
623 if (!SchemaSet.Contains(s)) {
629 if (!SchemaSet.Contains(XmlSchema.Namespace)) {
630 AddReference(XsdSchema);
631 schemaSet.Add(XsdSchema);
635 if (!SchemaSet.Contains(XmlReservedNs.NsXml)) {
636 AddReference(XmlSchema);
637 schemaSet.Add(XmlSchema);
641 schemaSet.ValidationEventHandler -= handler;
642 isCompiled = schemaSet.IsCompiled && schemaCount == schemaSet.Count;
646 XmlNameTable nameTable = new System.Xml.NameTable();
647 Preprocessor prep = new Preprocessor(nameTable, new SchemaNames(nameTable), null);
648 prep.XmlResolver = null;
649 prep.SchemaLocations = new Hashtable();
650 prep.ChameleonSchemas = new Hashtable();
651 foreach (XmlSchema schema in SchemaSet.Schemas()) {
652 prep.Execute(schema, schema.TargetNamespace, true);
655 catch(XmlSchemaException e) {
656 throw CreateValidationException(e, e.Message);
661 internal static Exception CreateValidationException(XmlSchemaException exception, string message) {
662 XmlSchemaObject source = exception.SourceSchemaObject;
663 if (exception.LineNumber == 0 && exception.LinePosition == 0) {
664 throw new InvalidOperationException(GetSchemaItem(source, null, message), exception);
668 if (source != null) {
669 while (source.Parent != null) {
670 source = source.Parent;
672 if (source is XmlSchema) {
673 ns = ((XmlSchema)source).TargetNamespace;
676 throw new InvalidOperationException(Res.GetString(Res.XmlSchemaSyntaxErrorDetails, ns, message, exception.LineNumber, exception.LinePosition), exception);
680 internal static void IgnoreCompileErrors(object sender, ValidationEventArgs args) {
684 internal static XmlSchema XsdSchema {
687 xsd = CreateFakeXsdSchema(XmlSchema.Namespace, "schema");
693 internal static XmlSchema XmlSchema {
696 xml = XmlSchema.Read(new StringReader(xmlSchema), null);
702 private static XmlSchema CreateFakeXsdSchema(string ns, string name) {
703 /* Create fake xsd schema to fool the XmlSchema.Compiler
704 <xsd:schema targetNamespace="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
705 <xsd:element name="schema">
710 XmlSchema schema = new XmlSchema();
711 schema.TargetNamespace = ns;
712 XmlSchemaElement element = new XmlSchemaElement();
714 XmlSchemaComplexType type = new XmlSchemaComplexType();
715 element.SchemaType = type;
716 schema.Items.Add(element);
720 internal void SetCache(SchemaObjectCache cache, bool shareTypes) {
721 this.shareTypes = shareTypes;
724 cache.GenerateSchemaGraph(this);
728 internal bool IsReference(XmlSchemaObject type) {
729 XmlSchemaObject parent = type;
730 while (parent.Parent != null) {
731 parent = parent.Parent;
733 return References.Contains(parent);
736 internal const string xmlSchema = @"<?xml version='1.0' encoding='UTF-8' ?>
737 <xs:schema targetNamespace='http://www.w3.org/XML/1998/namespace' xmlns:xs='http://www.w3.org/2001/XMLSchema' xml:lang='en'>
738 <xs:attribute name='lang' type='xs:language'/>
739 <xs:attribute name='space'>
741 <xs:restriction base='xs:NCName'>
742 <xs:enumeration value='default'/>
743 <xs:enumeration value='preserve'/>
747 <xs:attribute name='base' type='xs:anyURI'/>
748 <xs:attribute name='id' type='xs:ID' />
749 <xs:attributeGroup name='specialAttrs'>
750 <xs:attribute ref='xml:base'/>
751 <xs:attribute ref='xml:lang'/>
752 <xs:attribute ref='xml:space'/>
757 public class XmlSchemaEnumerator : IEnumerator<XmlSchema>, System.Collections.IEnumerator {
758 private XmlSchemas list;
759 private int idx, end;
761 public XmlSchemaEnumerator(XmlSchemas list) {
764 this.end = list.Count - 1;
767 public void Dispose() {
770 public bool MoveNext() {
771 if (this.idx >= this.end)
778 public XmlSchema Current {
779 get { return this.list[this.idx]; }
782 object System.Collections.IEnumerator.Current {
783 get { return this.list[this.idx]; }
786 void System.Collections.IEnumerator.Reset() {