//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // derekdb //------------------------------------------------------------------------------ #if ENABLEDATABINDING using System; using System.Xml; using System.Xml.XPath; using System.Xml.Schema; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; namespace System.Xml.XPath.DataBinding { /// public sealed class XPathDocumentView : IBindingList, ITypedList { ArrayList rows; Shape rowShape; XPathNode ndRoot; XPathDocument document; string xpath; IXmlNamespaceResolver namespaceResolver; IXmlNamespaceResolver xpathResolver; // // Constructors // /// public XPathDocumentView(XPathDocument document) : this(document, (IXmlNamespaceResolver)null) { } /// public XPathDocumentView(XPathDocument document, IXmlNamespaceResolver namespaceResolver) { if (null == document) throw new ArgumentNullException("document"); this.document = document; this.ndRoot = document.Root; if (null == this.ndRoot) throw new ArgumentException("document"); this.namespaceResolver = namespaceResolver; ArrayList rows = new ArrayList(); this.rows = rows; Debug.Assert(XPathNodeType.Root == this.ndRoot.NodeType); XPathNode nd = this.ndRoot.Child; while (null != nd) { if (XPathNodeType.Element == nd.NodeType) rows.Add(nd); nd = nd.Sibling; } DeriveShapeFromRows(); } /// public XPathDocumentView(XPathDocument document, string xpath) : this(document, xpath, null, true) { } /// public XPathDocumentView(XPathDocument document, string xpath, IXmlNamespaceResolver namespaceResolver) : this(document, xpath, namespaceResolver, false) { } /// public XPathDocumentView(XPathDocument document, string xpath, IXmlNamespaceResolver namespaceResolver, bool showPrefixes) { if (null == document) throw new ArgumentNullException("document"); this.xpath = xpath; this.document = document; this.ndRoot = document.Root; if (null == this.ndRoot) throw new ArgumentException("document"); this.ndRoot = document.Root; this.xpathResolver = namespaceResolver; if (showPrefixes) this.namespaceResolver = namespaceResolver; ArrayList rows = new ArrayList(); this.rows = rows; InitFromXPath(this.ndRoot, xpath); } internal XPathDocumentView(XPathNode root, ArrayList rows, Shape rowShape) { this.rows = rows; this.rowShape = rowShape; this.ndRoot = root; } // // public properties /// public XPathDocument Document { get { return this.document; } } /// public String XPath { get { return xpath; } } // // IEnumerable Implementation /// public IEnumerator GetEnumerator() { return new RowEnumerator(this); } // // ICollection implementation /// public int Count { get { return this.rows.Count; } } /// public bool IsSynchronized { get { return false ; } } /// public object SyncRoot { get { return null; } } /// public void CopyTo(Array array, int index) { object o; ArrayList rows = this.rows; for (int i=0; i < rows.Count; i++) o = this[i]; // force creation lazy of row object rows.CopyTo(array, index); } /// /// /// strongly typed version of CopyTo, demanded by Fxcop. /// public void CopyTo(XPathNodeView[] array, int index) { object o; ArrayList rows = this.rows; for (int i=0; i < rows.Count; i++) o = this[i]; // force creation lazy of row object rows.CopyTo(array, index); } // // IList Implementation /// bool IList.IsReadOnly { get { return true; } } /// bool IList.IsFixedSize { get { return true; } } /// bool IList.Contains(object value) { return this.rows.Contains(value); } /// void IList.Remove(object value) { throw new NotSupportedException("IList.Remove"); } /// void IList.RemoveAt(int index) { throw new NotSupportedException("IList.RemoveAt"); } /// void IList.Clear() { throw new NotSupportedException("IList.Clear"); } /// int IList.Add(object value) { throw new NotSupportedException("IList.Add"); } /// void IList.Insert(int index, object value) { throw new NotSupportedException("IList.Insert"); } /// int IList.IndexOf( object value ) { return this.rows.IndexOf(value); } /// object IList.this[int index] { get { object val = this.rows[index]; if (val is XPathNodeView) return val; XPathNodeView xiv = FillRow((XPathNode)val, this.rowShape); this.rows[index] = xiv; return xiv; } set { throw new NotSupportedException("IList.this[]"); } } /// /// /// strongly typed version of Contains, demanded by Fxcop. /// public bool Contains(XPathNodeView value) { return this.rows.Contains(value); } /// /// /// strongly typed version of Add, demanded by Fxcop. /// public int Add(XPathNodeView value) { throw new NotSupportedException("IList.Add"); } /// /// /// strongly typed version of Insert, demanded by Fxcop. /// public void Insert(int index, XPathNodeView value) { throw new NotSupportedException("IList.Insert"); } /// /// /// strongly typed version of IndexOf, demanded by Fxcop. /// public int IndexOf(XPathNodeView value) { return this.rows.IndexOf(value); } /// /// /// strongly typed version of Remove, demanded by Fxcop. /// public void Remove(XPathNodeView value) { throw new NotSupportedException("IList.Remove"); } /// /// /// strongly typed version of Item, demanded by Fxcop. /// public XPathNodeView this[int index] { get { object val = this.rows[index]; XPathNodeView nodeView; nodeView = val as XPathNodeView; if (nodeView != null) { return nodeView; } nodeView = FillRow((XPathNode)val, this.rowShape); this.rows[index] = nodeView; return nodeView; } set { throw new NotSupportedException("IList.this[]"); } } // // IBindingList Implementation /// public bool AllowEdit { get { return false; } } /// public bool AllowAdd { get { return false; } } /// public bool AllowRemove { get { return false; } } /// public bool AllowNew { get { return false; } } /// public object AddNew() { throw new NotSupportedException("IBindingList.AddNew"); } /// public bool SupportsChangeNotification { get { return false; } } /// public event ListChangedEventHandler ListChanged { add { throw new NotSupportedException("IBindingList.ListChanged"); } remove { throw new NotSupportedException("IBindingList.ListChanged"); } } /// public bool SupportsSearching { get { return false; } } /// public bool SupportsSorting { get { return false; } } /// public bool IsSorted { get { return false; } } /// public PropertyDescriptor SortProperty { get { throw new NotSupportedException("IBindingList.SortProperty"); } } /// public ListSortDirection SortDirection { get { throw new NotSupportedException("IBindingList.SortDirection"); } } /// public void AddIndex( PropertyDescriptor descriptor ) { throw new NotSupportedException("IBindingList.AddIndex"); } /// public void ApplySort( PropertyDescriptor descriptor, ListSortDirection direction ) { throw new NotSupportedException("IBindingList.ApplySort"); } /// public int Find(PropertyDescriptor propertyDescriptor, object key) { throw new NotSupportedException("IBindingList.Find"); } /// public void RemoveIndex(PropertyDescriptor propertyDescriptor) { throw new NotSupportedException("IBindingList.RemoveIndex"); } /// public void RemoveSort() { throw new NotSupportedException("IBindingList.RemoveSort"); } // // ITypedList Implementation /// public string GetListName(PropertyDescriptor[] listAccessors) { if( listAccessors == null ) { return this.rowShape.Name; } else { return listAccessors[listAccessors.Length-1].Name; } } /// public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { Shape shape = null; if( listAccessors == null ) { shape = this.rowShape; } else { XPathNodeViewPropertyDescriptor propdesc = listAccessors[listAccessors.Length-1] as XPathNodeViewPropertyDescriptor; if (null != propdesc) shape = propdesc.Shape; } if (null == shape) throw new ArgumentException("listAccessors"); return new PropertyDescriptorCollection(shape.PropertyDescriptors); } // // Internal Implementation internal Shape RowShape { get { return this.rowShape; } } internal void SetRows(ArrayList rows) { Debug.Assert(this.rows == null); this.rows = rows; } XPathNodeView FillRow(XPathNode ndRow, Shape shape) { object[] columns; XPathNode nd; switch (shape.BindingType) { case BindingType.Text: case BindingType.Attribute: columns = new object[1]; columns[0] = ndRow; return new XPathNodeView(this, ndRow, columns); case BindingType.Repeat: columns = new object[1]; nd = TreeNavigationHelper.GetContentChild(ndRow); columns[0] = FillColumn(new ContentIterator(nd, shape), shape); return new XPathNodeView(this, ndRow, columns); case BindingType.Sequence: case BindingType.Choice: case BindingType.All: int subShapesCount = shape.SubShapes.Count; columns = new object[subShapesCount]; if (shape.BindingType == BindingType.Sequence && shape.SubShape(0).BindingType == BindingType.Attribute) { FillAttributes(ndRow, shape, columns); } Shape lastSubShape = (Shape)shape.SubShapes[subShapesCount - 1]; if (lastSubShape.BindingType == BindingType.Text) { //Attributes followed by simpe content or mixed content columns[subShapesCount - 1] = ndRow; return new XPathNodeView(this, ndRow, columns); } else { nd = TreeNavigationHelper.GetContentChild(ndRow); return FillSubRow(new ContentIterator(nd, shape), shape, columns); } default: // should not map to a row #if DEBUG throw new NotSupportedException("Unable to bind row to: "+shape.BindingType.ToString()); #else throw new NotSupportedException(); #endif } } void FillAttributes(XPathNode nd, Shape shape, object[] cols) { int i = 0; while (i < cols.Length) { Shape attrShape = shape.SubShape(i); if (attrShape.BindingType != BindingType.Attribute) break; XmlQualifiedName name = attrShape.AttributeName; XPathNode ndAttr = nd.GetAttribute( name.Name, name.Namespace ); if (null != ndAttr) cols[i] = ndAttr; i++; } } object FillColumn(ContentIterator iter, Shape shape) { object val; switch (shape.BindingType) { case BindingType.Element: val = iter.Node; iter.Next(); break; case BindingType.ElementNested: { ArrayList rows = new ArrayList(); rows.Add(iter.Node); iter.Next(); val = new XPathDocumentView(null, rows, shape.NestedShape); break; } case BindingType.Repeat: { ArrayList rows = new ArrayList(); Shape subShape = shape.SubShape(0); if (subShape.BindingType == BindingType.ElementNested) { Shape nestShape = subShape.NestedShape; XPathDocumentView xivc = new XPathDocumentView(null, null, nestShape); XPathNode nd; while (null != (nd = iter.Node) && subShape.IsParticleMatch(iter.Particle)) { rows.Add(nd); iter.Next(); } xivc.SetRows(rows); val = xivc; } else { XPathDocumentView xivc = new XPathDocumentView(null, null, subShape); XPathNode nd; while (null != (nd = iter.Node) && shape.IsParticleMatch(iter.Particle)) { rows.Add(xivc.FillSubRow(iter, subShape, null)); } xivc.SetRows(rows); val = xivc; } break; } case BindingType.Sequence: case BindingType.Choice: case BindingType.All: { XPathDocumentView docview = new XPathDocumentView(null, null, shape); ArrayList rows = new ArrayList(); rows.Add(docview.FillSubRow(iter, shape, null)); docview.SetRows(rows); val = docview; break; } default: case BindingType.Text: case BindingType.Attribute: throw new NotSupportedException(); } return val; } XPathNodeView FillSubRow(ContentIterator iter, Shape shape, object[] columns) { if (null == columns) { int colCount = shape.SubShapes.Count; if (0 == colCount) colCount = 1; columns = new object[colCount]; } switch (shape.BindingType) { case BindingType.Element: columns[0] = FillColumn(iter, shape); break; case BindingType.Sequence: { int iPrev = -1; int i; while (null != iter.Node) { i = shape.FindMatchingSubShape(iter.Particle); if (i <= iPrev) break; columns[i] = FillColumn(iter, shape.SubShape(i)); iPrev = i; } break; } case BindingType.All: { while (null != iter.Node) { int i = shape.FindMatchingSubShape(iter.Particle); if (-1 == i || null != columns[i]) break; columns[i] = FillColumn(iter, shape.SubShape(i)); } break; } case BindingType.Choice: { int i = shape.FindMatchingSubShape(iter.Particle); if (-1 != i) { columns[i] = FillColumn(iter, shape.SubShape(i)); } break; } case BindingType.Repeat: default: // should not map to a row throw new NotSupportedException(); } return new XPathNodeView(this, null, columns); } // // XPath support // void InitFromXPath(XPathNode ndRoot, string xpath) { XPathStep[] steps = ParseXPath(xpath, this.xpathResolver); ArrayList rows = this.rows; rows.Clear(); PopulateFromXPath(ndRoot, steps, 0); DeriveShapeFromRows(); } void DeriveShapeFromRows() { object schemaInfo = null; for (int i=0; (i= xpath.Length) throw new XmlException(Res.XmlDataBinding_XPathEnd, (string[])null); if ('/' != xpath[pos]) throw new XmlException(Res.XmlDataBinding_XPathRequireSlash, (string[])null); pos++; char ch = xpath[pos]; if (ch == '.') { pos++; // again... } else if ('@' == ch) { if (0 == i) throw new XmlException(Res.XmlDataBinding_XPathAttrNotFirst, (string[])null); pos++; if (pos >= xpath.Length) throw new XmlException(Res.XmlDataBinding_XPathEnd, (string[])null); steps[i].name = ParseQName(xpath, ref pos, xnr); steps[i].type = XPathNodeType.Attribute; i++; if (pos != xpath.Length) throw new XmlException(Res.XmlDataBinding_XPathAttrLast, (string[])null); break; } else { steps[i].name = ParseQName(xpath, ref pos, xnr); steps[i].type = XPathNodeType.Element; i++; if (pos == xpath.Length) break; } } Debug.Assert(i == steps.Length); return steps; } // Parse a QName from the string, and resolve prefix XmlQualifiedName ParseQName(string xpath, ref int pos, IXmlNamespaceResolver xnr) { string nm = ParseName(xpath, ref pos); if (pos < xpath.Length && ':' == xpath[pos]) { pos++; string ns = (null==xnr) ? null : xnr.LookupNamespace(nm); if (null == ns || 0 == ns.Length) throw new XmlException(Res.Sch_UnresolvedPrefix, nm); return new XmlQualifiedName(ParseName(xpath, ref pos), ns); } else { return new XmlQualifiedName(nm); } } // Parse a NCNAME from the string string ParseName(string xpath, ref int pos) { char ch; int start = pos++; while (pos < xpath.Length && '/' != (ch = xpath[pos]) && ':' != ch) pos++; string nm = xpath.Substring(start, pos - start); if (!XmlReader.IsName(nm)) throw new XmlException(Res.Xml_InvalidNameChars, (string[])null); return this.document.NameTable.Add(nm); } // // Helper classes // class ContentIterator { XPathNode node; ContentValidator contentValidator; ValidationState currentState; object currentParticle; public ContentIterator(XPathNode nd, Shape shape) { this.node = nd; XmlSchemaElement xse = shape.XmlSchemaElement; Debug.Assert(null != xse); SchemaElementDecl decl = xse.ElementDecl; Debug.Assert(null != decl); this.contentValidator = decl.ContentValidator; this.currentState = new ValidationState(); this.contentValidator.InitValidation(this.currentState); this.currentState.ProcessContents = XmlSchemaContentProcessing.Strict; if (nd != null) Advance(); } public XPathNode Node { get { return this.node; } } public object Particle { get { return this.currentParticle; } } public bool Next() { if (null != this.node) { this.node = TreeNavigationHelper.GetContentSibling(this.node, XPathNodeType.Element); if (node != null) Advance(); return null != this.node; } return false; } private void Advance() { XPathNode nd = this.node; int errorCode; this.currentParticle = this.contentValidator.ValidateElement(new XmlQualifiedName(nd.LocalName, nd.NamespaceUri), this.currentState, out errorCode); if (null == this.currentParticle || 0 != errorCode) { this.node = null; } } } // Helper class to implement enumerator over rows // We can't just use ArrayList enumerator because // sometims rows may be lazily constructed sealed class RowEnumerator : IEnumerator { XPathDocumentView collection; int pos; internal RowEnumerator(XPathDocumentView collection) { this.collection = collection; this.pos = -1; } public object Current { get { if (this.pos < 0 || this.pos >= this.collection.Count) return null; return this.collection[this.pos]; } } public void Reset() { this.pos = -1; } public bool MoveNext() { this.pos++; int max = this.collection.Count; if (this.pos > max) this.pos = max; return this.pos < max; } } } } #endif