//------------------------------------------------------------------------------
//
// 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