2 // XmlSchemaWriter.cs - DataSet.WriteXmlSchema() support
6 // Atsushi Enomoto <atsushi@ximian.com>
8 // Original WriteXml/WriteXmlSchema authors are:
9 // Ville Palo, Alan Tam, Lluis Sanchez and Eran Domb.
13 // Copyright (C) 2004-05 Novell, Inc (http://www.novell.com)
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:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
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.
36 using System.Collections;
37 using System.Collections.Specialized;
38 using System.Globalization;
44 internal class XmlSchemaWriter
46 const string xmlnsxs = System.Xml.Schema.XmlSchema.Namespace;
48 public static void WriteXmlSchema (DataSet dataset,
51 WriteXmlSchema (dataset, writer, dataset.Tables,
55 public static void WriteXmlSchema (DataSet dataset,
56 XmlWriter writer, DataTableCollection tables,
57 DataRelationCollection relations)
59 new XmlSchemaWriter (dataset, writer,
60 tables, relations).WriteSchema ();
63 public XmlSchemaWriter (DataSet dataset,
64 XmlWriter writer, DataTableCollection tables,
65 DataRelationCollection relations)
70 this.relations = relations;
75 DataTableCollection tables;
76 DataRelationCollection relations;
78 ArrayList globalTypeTables = new ArrayList ();
79 Hashtable additionalNamespaces = new Hashtable ();
81 public string ConstraintPrefix {
82 get { return ds.Namespace != String.Empty ? XmlConstants.TnsPrefix + ':' : String.Empty; }
87 public void WriteSchema ()
89 ListDictionary names = new ListDictionary ();
90 ListDictionary includes = new ListDictionary ();
92 // Add namespaces used in DataSet components (tables, columns, ...)
93 foreach (DataTable dt in tables) {
94 foreach (DataColumn col in dt.Columns)
95 CheckNamespace (col.Prefix, col.Namespace, names, includes);
96 CheckNamespace (dt.Prefix, dt.Namespace, names, includes);
99 w.WriteStartElement ("xs", "schema", xmlnsxs);
100 w.WriteAttributeString ("id", XmlConvert.EncodeLocalName (ds.DataSetName));
102 if (ds.Namespace != String.Empty) {
103 w.WriteAttributeString ("targetNamespace",
105 w.WriteAttributeString (
107 XmlConstants.TnsPrefix,
108 XmlConstants.XmlnsNS,
111 w.WriteAttributeString ("xmlns", ds.Namespace);
113 w.WriteAttributeString ("xmlns", "xs",
114 XmlConstants.XmlnsNS, xmlnsxs);
115 w.WriteAttributeString ("xmlns",
116 XmlConstants.MsdataPrefix,
117 XmlConstants.XmlnsNS,
118 XmlConstants.MsdataNamespace);
120 if (CheckExtendedPropertyExists (tables, relations))
121 w.WriteAttributeString ("xmlns",
122 XmlConstants.MspropPrefix,
123 XmlConstants.XmlnsNS,
124 XmlConstants.MspropNamespace);
126 if (ds.Namespace != String.Empty) {
127 w.WriteAttributeString ("attributeFormDefault", "qualified");
128 w.WriteAttributeString ("elementFormDefault", "qualified");
131 foreach (string prefix in names.Keys)
132 w.WriteAttributeString ("xmlns", prefix,
133 XmlConstants.XmlnsNS,
134 names [prefix] as string);
136 if (includes.Count > 0)
137 w.WriteComment ("ATTENTION: This schema contains references to other imported schemas");
138 foreach (string ns in includes.Keys) {
139 w.WriteStartElement ("xs", "import", xmlnsxs);
140 w.WriteAttributeString ("namespace", ns);
141 w.WriteAttributeString ("schemaLocation", includes [ns] as string);
142 w.WriteEndElement ();
145 WriteDataSetElement ();
147 w.WriteEndElement (); // xs:schema
152 // FIXME: actually there are some cases that this method(ology)
154 private void WriteDataSetElement ()
156 w.WriteStartElement ("xs", "element", xmlnsxs);
157 w.WriteAttributeString ("name", XmlConvert.EncodeLocalName (ds.DataSetName));
158 w.WriteAttributeString (XmlConstants.MsdataPrefix,
159 "IsDataSet", XmlConstants.MsdataNamespace,
162 if (ds.Locale == CultureInfo.CurrentCulture) {
163 w.WriteAttributeString (
164 XmlConstants.MsdataPrefix,
166 XmlConstants.MsdataNamespace,
172 w.WriteAttributeString (
173 XmlConstants.MsdataPrefix,
175 XmlConstants.MsdataNamespace,
179 AddExtendedPropertyAttributes (ds.ExtendedProperties);
181 w.WriteStartElement ("xs", "complexType", xmlnsxs);
182 w.WriteStartElement ("xs", "choice", xmlnsxs);
183 w.WriteAttributeString ("maxOccurs", "unbounded");
185 foreach (DataTable table in tables) {
186 bool isTopLevel = true;
187 foreach (DataRelation rel in table.ParentRelations) {
195 if (ds.Namespace != table.Namespace) {
196 // <xs:element ref="X:y" />
197 w.WriteStartElement ("xs",
200 w.WriteStartAttribute ("ref", String.Empty);
201 w.WriteQualifiedName (XmlConvert.EncodeLocalName (table.TableName), table.Namespace);
202 w.WriteEndAttribute ();
203 w.WriteEndElement ();
206 WriteTableElement (table);
210 w.WriteEndElement (); // choice
211 w.WriteEndElement (); // complexType
213 WriteConstraints (); // DataSet constraints
215 w.WriteEndElement (); // element
218 // Relation based Constraints
220 private void WriteConstraints ()
222 ArrayList names = new ArrayList ();
224 // add all unique constraints.
225 foreach (DataTable table in tables) {
226 foreach (Constraint c in table.Constraints) {
228 c as UniqueConstraint;
230 AddUniqueConstraints (u, names);
234 // Add all foriegn key constraints.
235 if (relations != null)
236 foreach (DataRelation rel in relations)
237 if (rel.ParentKeyConstraint != null &&
238 rel.ChildKeyConstraint != null)
239 AddForeignKeys (rel, names);
242 // Add unique constaraints to the schema.
243 // return hashtable with the names of all XmlSchemaUnique elements we created.
244 private void AddUniqueConstraints (UniqueConstraint uniq,
247 // if column of the constraint is hidden do not write the constraint.
248 foreach (DataColumn column in uniq.Columns)
249 if (column.ColumnMapping == MappingType.Hidden)
250 return; // do nothing
252 w.WriteStartElement ("xs", "unique", xmlnsxs);
253 // if constaraint name do not exist in the hashtable we can use it.
255 if (!names.Contains (uniq.ConstraintName)) {
256 name = uniq.ConstraintName;
257 w.WriteAttributeString ("name", name);
259 // otherwise generate new constraint name for the
260 // XmlSchemaUnique element.
262 name = XmlConvert.EncodeLocalName (uniq.Table.TableName) + "_" + uniq.ConstraintName;
263 w.WriteAttributeString ("name", name);
264 w.WriteAttributeString (
265 XmlConstants.MsdataPrefix,
266 XmlConstants.ConstraintName,
267 XmlConstants.MsdataNamespace,
268 uniq.ConstraintName);
272 if (uniq.IsPrimaryKey) {
273 w.WriteAttributeString (
274 XmlConstants.MsdataPrefix,
275 XmlConstants.PrimaryKey,
276 XmlConstants.MsdataNamespace,
280 AddExtendedPropertyAttributes (uniq.ExtendedProperties);
282 w.WriteStartElement ("xs", "selector",
284 w.WriteAttributeString ("xpath", ".//" +
285 ConstraintPrefix + XmlConvert.EncodeLocalName (uniq.Table.TableName));
286 w.WriteEndElement (); // selector
287 foreach (DataColumn c in uniq.Columns) {
288 w.WriteStartElement ("xs", "field",
290 w.WriteStartAttribute ("xpath", String.Empty);
291 if (c.ColumnMapping == MappingType.Attribute)
293 w.WriteString (ConstraintPrefix);
294 w.WriteString (XmlConvert.EncodeLocalName (c.ColumnName));
295 w.WriteEndAttribute (); // xpath
296 w.WriteEndElement (); // field
299 w.WriteEndElement (); // unique
302 // Add the foriegn keys to the schema.
303 private void AddForeignKeys (DataRelation rel, ArrayList names)
305 // Do nothing if it contains hidden relation
306 foreach (DataColumn col in rel.ParentColumns)
307 if (col.ColumnMapping == MappingType.Hidden)
309 foreach (DataColumn col in rel.ChildColumns)
310 if (col.ColumnMapping == MappingType.Hidden)
313 w.WriteStartElement ("xs", "keyref", xmlnsxs);
314 w.WriteAttributeString ("name", XmlConvert.EncodeLocalName (rel.RelationName));
316 ForeignKeyConstraint fkConst = rel.ChildKeyConstraint;
317 UniqueConstraint uqConst = rel.ParentKeyConstraint;
319 string concatName = XmlConvert.EncodeLocalName (rel.ParentTable.TableName) + "_" + uqConst.ConstraintName;
320 // first try to find the concatenated name. If we didn't find it - use constraint name.
321 if (names.Contains (concatName)) {
322 w.WriteStartAttribute ("refer", String.Empty);
323 w.WriteQualifiedName (concatName, ds.Namespace);
324 w.WriteEndAttribute ();
327 w.WriteStartAttribute ("refer", String.Empty);
328 w.WriteQualifiedName (
329 uqConst.ConstraintName, ds.Namespace);
330 w.WriteEndAttribute ();
334 w.WriteAttributeString (
335 XmlConstants.MsdataPrefix,
336 XmlConstants.IsNested,
337 XmlConstants.MsdataNamespace,
340 AddExtendedPropertyAttributes (uqConst.ExtendedProperties);
342 w.WriteStartElement ("xs", "selector", xmlnsxs);
343 w.WriteAttributeString ("xpath", ".//" +
344 ConstraintPrefix + XmlConvert.EncodeLocalName (rel.ChildTable.TableName));
345 w.WriteEndElement ();
347 foreach (DataColumn c in rel.ChildColumns) {
348 w.WriteStartElement ("xs", "field",
350 w.WriteStartAttribute ("xpath", String.Empty);
351 if (c.ColumnMapping == MappingType.Attribute)
353 w.WriteString (ConstraintPrefix);
354 w.WriteString (XmlConvert.EncodeLocalName (c.ColumnName));
355 w.WriteEndAttribute ();
356 w.WriteEndElement (); // field
359 w.WriteEndElement (); // keyref
362 // ExtendedProperties
364 private bool CheckExtendedPropertyExists (
365 DataTableCollection tables,
366 DataRelationCollection relations)
368 if (ds.ExtendedProperties.Count > 0)
370 foreach (DataTable dt in tables) {
371 if (dt.ExtendedProperties.Count > 0)
373 foreach (DataColumn col in dt.Columns)
374 if (col.ExtendedProperties.Count > 0)
376 foreach (Constraint c in dt.Constraints)
377 if (c.ExtendedProperties.Count > 0)
380 if (relations == null)
382 foreach (DataRelation rel in relations)
383 if (rel.ExtendedProperties.Count > 0)
388 private void AddExtendedPropertyAttributes (PropertyCollection props)
390 // add extended properties to xs:element
391 foreach (DictionaryEntry de in props) {
392 w.WriteStartAttribute (
393 XmlConstants.MspropPrefix,
394 XmlConvert.EncodeName (de.Key.ToString ()),
395 XmlConstants.MspropNamespace);
396 if (de.Value != null)
398 DataSet.WriteObjectXml (de.Value));
399 w.WriteEndAttribute ();
405 private void WriteTableElement (DataTable table)
407 w.WriteStartElement ("xs", "element", xmlnsxs);
408 w.WriteAttributeString ("name", XmlConvert.EncodeLocalName (table.TableName));
410 AddExtendedPropertyAttributes (table.ExtendedProperties);
412 WriteTableType (table);
414 w.WriteEndElement ();
417 private void WriteTableType (DataTable table)
423 DataSet.SplitColumns (table, out atts, out elements, out simple);
425 w.WriteStartElement ("xs", "complexType", xmlnsxs);
427 if (simple != null) {
428 w.WriteStartElement ("xs", "simpleContent", xmlnsxs);
429 // add column name attribute
430 w.WriteAttributeString (
431 XmlConstants.MsdataPrefix,
432 XmlConvert.EncodeLocalName (XmlConstants.ColumnName),
433 XmlConstants.MsdataNamespace,
434 XmlConvert.EncodeLocalName (simple.ColumnName));
436 // add ordinal attribute
437 w.WriteAttributeString (
438 XmlConstants.MsdataPrefix,
439 XmlConstants.Ordinal,
440 XmlConstants.MsdataNamespace,
441 XmlConvert.ToString (simple.Ordinal));
444 w.WriteStartElement ("xs", "extension",
446 w.WriteStartAttribute ("base", String.Empty);
447 WriteQName (MapType (simple.DataType));
448 w.WriteEndAttribute ();
450 WriteTableAttributes (atts);
452 w.WriteEndElement ();
454 WriteTableAttributes (atts);
456 if (elements.Count > 0) {
457 w.WriteStartElement ("xs", "sequence",
459 foreach (DataColumn col in elements)
460 WriteTableTypeParticles (
462 w.WriteEndElement ();
465 foreach (DataRelation rel in table.ChildRelations)
467 WriteChildRelations (rel);
470 w.WriteFullEndElement (); // complexType
473 private void WriteTableTypeParticles (DataColumn col)
475 w.WriteStartElement ("xs", "element",
477 w.WriteAttributeString ("name",
478 XmlConvert.EncodeLocalName (col.ColumnName));
480 if (col.ColumnName != col.Caption && col.Caption != String.Empty)
481 w.WriteAttributeString (
482 XmlConstants.MsdataPrefix,
483 XmlConstants.Caption,
484 XmlConstants.MsdataNamespace,
487 if (col.AutoIncrement == true)
488 w.WriteAttributeString (
489 XmlConstants.MsdataPrefix,
490 XmlConstants.AutoIncrement,
491 XmlConstants.MsdataNamespace,
494 if (col.AutoIncrementSeed != 0) {
495 w.WriteAttributeString (
497 XmlConstants.MsdataPrefix,
498 XmlConstants.AutoIncrementSeed,
499 XmlConstants.MsdataNamespace,
500 XmlConvert.ToString (col.AutoIncrementSeed));
503 if (col.AutoIncrementStep != 1) {
504 w.WriteAttributeString (
506 XmlConstants.MsdataPrefix,
507 XmlConstants.AutoIncrementStep,
508 XmlConstants.MsdataNamespace,
509 XmlConvert.ToString (col.AutoIncrementStep));
512 if (col.DefaultValue.ToString () != String.Empty)
513 w.WriteAttributeString ("default",
514 DataSet.WriteObjectXml (col.DefaultValue));
517 w.WriteAttributeString (
518 XmlConstants.MsdataPrefix,
519 XmlConstants.ReadOnly,
520 XmlConstants.MsdataNamespace,
523 XmlQualifiedName typeQName = null;
524 if (col.MaxLength < 0) {
525 w.WriteStartAttribute ("type", String.Empty);
526 typeQName = MapType (col.DataType);
527 WriteQName (typeQName);
528 w.WriteEndAttribute ();
531 if (typeQName == XmlConstants.QnString
532 && col.DataType != typeof (string)
533 && col.DataType != typeof (char)) {
534 w.WriteStartAttribute (
535 XmlConstants.MsdataPrefix,
536 XmlConstants.DataType,
537 XmlConstants.MsdataNamespace);
538 string runtimeName = col.DataType.AssemblyQualifiedName;
539 w.WriteString (runtimeName);
540 w.WriteEndAttribute ();
544 w.WriteAttributeString ("minOccurs", "0");
546 //writer.WriteAttributeString (XmlConstants.MsdataPrefix,
547 // XmlConstants.Ordinal,
548 // XmlConstants.MsdataNamespace,
549 // col.Ordinal.ToString ());
551 // Write SimpleType if column have MaxLength
552 if (col.MaxLength > -1)
553 WriteSimpleType (col);
555 AddExtendedPropertyAttributes (col.ExtendedProperties);
557 w.WriteEndElement (); // sequence
560 private void WriteChildRelations (DataRelation rel)
562 if (rel.ChildTable.Namespace != ds.Namespace) {
563 w.WriteStartElement ("xs", "element", xmlnsxs);
564 w.WriteStartAttribute ("ref", String.Empty);
565 w.WriteQualifiedName (
566 XmlConvert.EncodeLocalName (rel.ChildTable.TableName),
567 rel.ChildTable.Namespace);
568 w.WriteEndAttribute ();
570 w.WriteStartElement ("xs", "element", xmlnsxs);
571 w.WriteStartAttribute ("type", String.Empty);
572 w.WriteQualifiedName (
573 XmlConvert.EncodeLocalName (rel.ChildTable.TableName),
574 rel.ChildTable.Namespace);
575 w.WriteEndAttribute ();
576 w.WriteEndElement ();
578 globalTypeTables.Add (rel.ChildTable);
579 w.WriteAttributeString ("minOccurs", "0");
580 w.WriteAttributeString ("maxOccurs", "unbounded");
584 private void WriteTableAttributes (ArrayList atts)
586 //Then a list of attributes
587 foreach (DataColumn col in atts) {
588 w.WriteStartElement ("xs", "attribute", xmlnsxs);
590 string name = XmlConvert.EncodeLocalName (col.ColumnName);
591 if (col.Namespace != String.Empty) {
592 w.WriteAttributeString ("form", "qualified");
593 string prefix = col.Prefix == String.Empty ? "app" + additionalNamespaces.Count : col.Prefix;
594 name = prefix + ":" + name;
595 // FIXME: Handle prefix mapping correctly.
596 additionalNamespaces [prefix] = col.Namespace;
598 w.WriteAttributeString ("name", name);
600 AddExtendedPropertyAttributes (
601 col.ExtendedProperties);
604 w.WriteAttributeString (
605 XmlConstants.MsdataPrefix,
606 XmlConstants.ReadOnly,
607 XmlConstants.MsdataNamespace,
610 if (col.MaxLength < 0) {
611 // otherwise simpleType is written later
612 w.WriteStartAttribute ("type", String.Empty);
613 WriteQName (MapType (col.DataType));
614 w.WriteEndAttribute ();
617 if (!col.AllowDBNull)
618 w.WriteAttributeString ("use", "required");
619 if (col.DefaultValue.ToString () != String.Empty)
620 w.WriteAttributeString ("default",
621 DataSet.WriteObjectXml (col.DefaultValue));
623 if (col.MaxLength > -1)
624 WriteSimpleType (col);
626 w.WriteEndElement (); // attribute
630 private void WriteSimpleType (DataColumn col)
632 w.WriteStartElement ("xs", "simpleType", xmlnsxs);
633 w.WriteStartElement ("xs", "restriction", xmlnsxs);
634 w.WriteStartAttribute ("base", String.Empty);
635 WriteQName (MapType (col.DataType));
636 w.WriteEndAttribute ();
638 w.WriteStartElement ("xs", "maxLength", xmlnsxs);
639 w.WriteAttributeString ("value",
640 XmlConvert.ToString (col.MaxLength));
641 w.WriteEndElement (); // maxLength
642 w.WriteEndElement (); // restriction
643 w.WriteEndElement (); // simpleType
646 private void WriteQName (XmlQualifiedName name)
648 w.WriteQualifiedName (name.Name, name.Namespace);
651 private void CheckNamespace (string prefix, string ns, ListDictionary names, ListDictionary includes)
653 if (ns == String.Empty)
655 if (ds.Namespace != ns) {
656 if (names [prefix] != ns) {
657 for (int i = 1; i < int.MaxValue; i++) {
658 string p = "app" + i;
659 if (names [p] == null) {
661 HandleExternalNamespace (p, ns, includes);
669 private void HandleExternalNamespace (string prefix, string ns, ListDictionary includes)
671 if (includes.Contains (ns))
672 return; // nothing to do
673 includes.Add (ns, "_" + prefix + ".xsd");
676 private /*static*/ XmlQualifiedName MapType (Type type)
678 switch (Type.GetTypeCode (type)) {
679 case TypeCode.String: return XmlConstants.QnString;
680 case TypeCode.Int16: return XmlConstants.QnShort;
681 case TypeCode.Int32: return XmlConstants.QnInt;
682 case TypeCode.Int64: return XmlConstants.QnLong;
683 case TypeCode.Boolean: return XmlConstants.QnBoolean;
684 case TypeCode.Byte: return XmlConstants.QnUnsignedByte;
685 //case TypeCode.Char: return XmlConstants.QnChar;
686 case TypeCode.DateTime: return XmlConstants.QnDateTime;
687 case TypeCode.Decimal: return XmlConstants.QnDecimal;
688 case TypeCode.Double: return XmlConstants.QnDouble;
689 case TypeCode.SByte: return XmlConstants.QnSbyte;
690 case TypeCode.Single: return XmlConstants.QnFloat;
691 case TypeCode.UInt16: return XmlConstants.QnUsignedShort;
692 case TypeCode.UInt32: return XmlConstants.QnUnsignedInt;
693 case TypeCode.UInt64: return XmlConstants.QnUnsignedLong;
696 if (typeof (TimeSpan) == type)
697 return XmlConstants.QnDuration;
698 else if (typeof (System.Uri) == type)
699 return XmlConstants.QnUri;
700 else if (typeof (byte[]) == type)
701 return XmlConstants.QnBase64Binary;
702 else if (typeof (XmlQualifiedName) == type)
703 return XmlConstants.QnXmlQualifiedName;
705 return XmlConstants.QnString;