#region Disclaimer / License // Copyright (C) 2009, Kenneth Skovhede // http://www.hexad.dk, opensource@hexad.dk // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Collections.ObjectModel; namespace OSGeo.MapGuide.MaestroAPI.Schema { //TODO: Expand on documentation as this is an important class /// /// Represents a FDO class definition /// public class ClassDefinition : SchemaElement, IFdoSerializable { private List _identity; private List _properties; private Dictionary _ordinalMap; private ClassDefinition() { _ordinalMap = new Dictionary(); _identity = new List(); _properties = new List(); } /// /// Initializes a new instance of the class. /// /// The name. /// The description. public ClassDefinition(string name, string description) : this() { this.Name = name; this.Description = description; } /// /// Gets or sets the base class /// public ClassDefinition BaseClass { get; set; } /// /// Gets the property definition at the specified index /// /// /// public PropertyDefinition this[int index] { get { return _properties[index]; } } /// /// Gets the ordinal of the specified property name /// /// The property name. /// public int GetOrdinal(string name) { if (_ordinalMap.ContainsKey(name)) return _ordinalMap[name]; for (int i = 0; i < this.Properties.Count; i++) { if (this[i].Name.Equals(name)) { _ordinalMap[name] = i; return i; } } throw new ArgumentException(string.Format(MaestroAPI.Strings.ErrorPropertyNotFound, name)); } /// /// Gets or sets whether this is abstract /// public bool IsAbstract { get; set; } /// /// Gets or sets whether this is computed. Computed classes should have its properties /// checked out (and possibly modified) before serving as a basis for a new class definition /// public bool IsComputed { get; set; } /// /// Gets or sets the name of the default geometry property. /// public string DefaultGeometryPropertyName { get; set; } /// /// Gets the identity properties /// public ReadOnlyCollection IdentityProperties { get { return _identity.AsReadOnly(); } } /// /// Removes the assigned identity properties /// public void ClearIdentityProperties() { _identity.Clear(); } /// /// Gets the properties /// public ReadOnlyCollection Properties { get { return _properties.AsReadOnly(); } } /// /// Adds the specified data property, with an option to include it as an identity property /// /// /// public void AddProperty(DataPropertyDefinition prop, bool identity) { if (!_properties.Contains(prop)) _properties.Add(prop); if (identity && !_identity.Contains(prop)) _identity.Add(prop); prop.Parent = this; } /// /// Adds the specified property definition /// /// public void AddProperty(PropertyDefinition prop) { if (!_properties.Contains(prop)) _properties.Add(prop); prop.Parent = this; } /// /// Gets the index of the specified property /// /// /// public int IndexOfProperty(PropertyDefinition prop) { return _properties.IndexOf(prop); } /// /// Removes the property definition of the specified name. If it is a data property /// it is also removed from the identity properties (if it is specified as one) /// /// public void RemoveProperty(string propertyName) { int idx = -1; for (int i = 0; i < _properties.Count; i++) { if (_properties[i].Name == propertyName) { idx = i; break; } } if (idx >= 0) RemovePropertyAt(idx); } /// /// Removes the property definition at the specified index. If it is a data property /// is is also removed from the identity properties (if it is specified as one) /// /// public void RemovePropertyAt(int index) { if (index < _properties.Count) { var prop = _properties[index]; _properties.RemoveAt(index); if (prop.Type == PropertyDefinitionType.Data) { _identity.Remove((DataPropertyDefinition)prop); prop.Parent = null; } } } /// /// Removes the specified property from the properties collection. If it is a data property definition, it is also /// removed from the identity properties collection /// /// /// public bool RemoveProperty(PropertyDefinition prop) { bool removed = _properties.Remove(prop); if (removed && prop.Type == PropertyDefinitionType.Data) { _identity.Remove((DataPropertyDefinition)prop); prop.Parent = null; } return removed; } /// /// Gets the parent schema /// public FeatureSchema Parent { get; internal set; } /// /// Gets a Property Definition by its name /// /// /// The matching property definition. null if none found public PropertyDefinition FindProperty(string name) { foreach (var prop in _properties) { if (prop.Name.Equals(name)) return prop; } return null; } /// /// Gets the qualified name of this class. The qualified name takes the form [Schema Name]:[Class Name] /// public string QualifiedName { get { return this.Parent != null ? this.Parent.Name + ":" + this.Name : this.Name; } } //NOXLATE /// /// Writes the current element's content /// /// /// public void WriteXml(XmlDocument doc, XmlNode currentNode) { XmlElement id = null; var en = Utility.EncodeFDOName(this.Name); if (_identity.Count > 0) { id = doc.CreateElement("xs", "element", XmlNamespaces.XS); //NOXLATE //TODO: May need encoding id.SetAttribute("name", en); //NOXLATE id.SetAttribute("type", this.Parent.Name + ":" + en + "Type"); //NOXLATE id.SetAttribute("abstract", this.IsAbstract.ToString().ToLower()); //NOXLATE id.SetAttribute("substitutionGroup", "gml:_Feature"); //NOXLATE var key = doc.CreateElement("xs", "key", XmlNamespaces.XS); //NOXLATE key.SetAttribute("name", en + "Key"); //NOXLATE var selector = doc.CreateElement("xs", "selector", XmlNamespaces.XS); //NOXLATE selector.SetAttribute("xpath", ".//" + en); //NOXLATE key.AppendChild(selector); foreach (var prop in _identity) { var field = doc.CreateElement("xs", "field", XmlNamespaces.XS); //NOXLATE field.SetAttribute("xpath", prop.Name); //NOXLATE key.AppendChild(field); } id.AppendChild(key); } //Now write class body var ctype = doc.CreateElement("xs", "complexType", XmlNamespaces.XS); //NOXLATE //TODO: This may have been decoded. Should it be re-encoded? ctype.SetAttribute("name", en + "Type"); //NOXLATE ctype.SetAttribute("abstract", this.IsAbstract.ToString().ToLower()); //NOXLATE if (!string.IsNullOrEmpty(this.DefaultGeometryPropertyName)) { var geom = FindProperty(this.DefaultGeometryPropertyName) as GeometricPropertyDefinition; if (geom != null) { ctype.SetAttribute("geometryName", XmlNamespaces.FDO, geom.Name); //NOXLATE } } else { ctype.SetAttribute("hasGeometry", XmlNamespaces.FDO, "false"); //NOXLATE } //Write description node var anno = doc.CreateElement("xs", "annotation", XmlNamespaces.XS); //NOXLATE var docN = doc.CreateElement("xs", "documentation", XmlNamespaces.XS); //NOXLATE docN.InnerText = this.Description; ctype.AppendChild(anno); anno.AppendChild(docN); var cnt = doc.CreateElement("xs", "complexContent", XmlNamespaces.XS); //NOXLATE ctype.AppendChild(cnt); var ext = doc.CreateElement("xs", "extension", XmlNamespaces.XS); //NOXLATE if (this.BaseClass != null) ext.SetAttribute("base", this.BaseClass.QualifiedName); //NOXLATE else ext.SetAttribute("base", "gml:AbstractFeatureType"); //NOXLATE cnt.AppendChild(ext); var seq = doc.CreateElement("xs", "sequence", XmlNamespaces.XS); //NOXLATE ext.AppendChild(seq); foreach (var prop in _properties) { prop.WriteXml(doc, seq); } if (id != null) currentNode.AppendChild(id); currentNode.AppendChild(ctype); } /// /// Set the current element's content from the current XML node /// /// /// public void ReadXml(XmlNode node, XmlNamespaceManager mgr) { var en = Utility.EncodeFDOName(this.Name); var abn = node.Attributes["abstract"]; //NOXLATE if (abn != null) this.IsAbstract = Convert.ToBoolean(abn.Value); //Description var docNode = node.SelectSingleNode("xs:annotation/xs:documentation", mgr); //NOXLATE if (docNode != null) this.Description = docNode.InnerText; //Process properties XmlNodeList propNodes = node.SelectNodes("xs:complexContent/xs:extension/xs:sequence/xs:element", mgr); //NOXLATE if (propNodes.Count == 0) propNodes = node.SelectNodes("xs:sequence/xs:element", mgr); //NOXLATE foreach (XmlNode propNode in propNodes) { var prop = PropertyDefinition.Parse(propNode, mgr); this.AddProperty(prop); } //Set designated geometry property var geom = Utility.GetFdoAttribute(node, "geometryName"); //NOXLATE if (geom != null) this.DefaultGeometryPropertyName = geom.Value; //TODO: Base class //Process identity properties var parent = node.ParentNode; //This is a lower-case coerced xpath query as our encoded name for querying may not be of the correct case var xpath = "xs:element[translate(@name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')=\"" + en.ToLower() + "\"]/xs:key"; //NOXLATE var key = parent.SelectSingleNode(xpath, mgr); if (key != null) { var fields = key.SelectNodes("xs:field", mgr); //NOXLATE foreach (XmlNode f in fields) { var idpropa = f.Attributes["xpath"]; //NOXLATE if (idpropa == null) throw new Exception(string.Format(MaestroAPI.Strings.ErrorBadDocumentExpectedAttribute, "xpath")); var prop = FindProperty(idpropa.Value); if (prop != null && prop.Type == PropertyDefinitionType.Data) _identity.Add((DataPropertyDefinition)prop); } } } /// /// Creates a clone of the specified instance /// /// The instance to clone. /// public static ClassDefinition Clone(ClassDefinition source) { var clone = new ClassDefinition(source.Name, source.Description); foreach (var prop in source.Properties) { var clonedProp = PropertyDefinition.Clone(prop); if (clonedProp.Type == PropertyDefinitionType.Data && source.IdentityProperties.Contains((DataPropertyDefinition)prop)) { clone.AddProperty((DataPropertyDefinition)clonedProp, true); } else { clone.AddProperty(clonedProp); } } clone.DefaultGeometryPropertyName = source.DefaultGeometryPropertyName; clone.IsAbstract = source.IsAbstract; clone.IsComputed = source.IsComputed; if (source.Parent != null) clone.Parent = new FeatureSchema(source.Parent.Name, source.Parent.Description); //TODO: Base Class return clone; } } }