|
//------------------------------------------------------------------------------
// <copyright file="XmlToDatasetMap.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
// <owner current="false" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
namespace System.Data {
using System;
using System.Xml;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
// This is an internal helper class used during Xml load to DataSet/DataDocument.
// XmlToDatasetMap class provides functionality for binding elemants/atributes
// to DataTable / DataColumn
internal sealed class XmlToDatasetMap {
private sealed class XmlNodeIdentety {
public string LocalName;
public string NamespaceURI;
public XmlNodeIdentety(string localName, string namespaceURI) {
this.LocalName = localName;
this.NamespaceURI = namespaceURI;
}
override public int GetHashCode() {
return ((object) LocalName).GetHashCode();
}
override public bool Equals(object obj) {
XmlNodeIdentety id = (XmlNodeIdentety) obj;
return (
(String.Compare(this.LocalName, id.LocalName, StringComparison.OrdinalIgnoreCase) == 0) &&
(String.Compare(this.NamespaceURI, id.NamespaceURI, StringComparison.OrdinalIgnoreCase) == 0)
);
}
}
// This class exist to avoid alocatin of XmlNodeIdentety to every acces to the hash table.
// Unfortunetely XmlNode doesn't export single identety object.
internal sealed class XmlNodeIdHashtable : Hashtable {
private XmlNodeIdentety id = new XmlNodeIdentety(string.Empty, string.Empty);
public XmlNodeIdHashtable(Int32 capacity)
: base(capacity) {}
public object this[XmlNode node] {
get {
id.LocalName = node.LocalName;
id.NamespaceURI = node.NamespaceURI;
return this[id];
}
}
public object this[XmlReader dataReader] {
get {
id.LocalName = dataReader.LocalName;
id.NamespaceURI = dataReader.NamespaceURI;
return this[id];
}
}
public object this[DataTable table] {
get {
id.LocalName = table.EncodedTableName;
id.NamespaceURI = table.Namespace;
return this[id];
}
}
public object this[string name] {
get {
id.LocalName = name;
id.NamespaceURI = String.Empty;
return this[id];
}
}
}
private sealed class TableSchemaInfo {
public DataTable TableSchema;
public XmlNodeIdHashtable ColumnsSchemaMap;
public TableSchemaInfo(DataTable tableSchema) {
this.TableSchema = tableSchema;
this.ColumnsSchemaMap = new XmlNodeIdHashtable(tableSchema.Columns.Count);
}
}
XmlNodeIdHashtable tableSchemaMap; // Holds all the tables information
TableSchemaInfo lastTableSchemaInfo = null;
// Used to infer schema
public XmlToDatasetMap(DataSet dataSet, XmlNameTable nameTable) {
Debug.Assert(dataSet != null, "DataSet can't be null");
Debug.Assert(nameTable != null, "NameTable can't be null");
BuildIdentityMap(dataSet, nameTable);
}
// Used to read data with known schema
public XmlToDatasetMap(XmlNameTable nameTable, DataSet dataSet) {
Debug.Assert(dataSet != null, "DataSet can't be null");
Debug.Assert(nameTable != null, "NameTable can't be null");
BuildIdentityMap(nameTable, dataSet);
}
// Used to infer schema
public XmlToDatasetMap(DataTable dataTable, XmlNameTable nameTable) {
Debug.Assert(dataTable != null, "DataTable can't be null");
Debug.Assert(nameTable != null, "NameTable can't be null");
BuildIdentityMap(dataTable, nameTable);
}
// Used to read data with known schema
public XmlToDatasetMap(XmlNameTable nameTable, DataTable dataTable) {
Debug.Assert(dataTable != null, "DataTable can't be null");
Debug.Assert(nameTable != null, "NameTable can't be null");
BuildIdentityMap(nameTable, dataTable);
}
static internal bool IsMappedColumn(DataColumn c) {
return (c.ColumnMapping != MappingType.Hidden);
}
// Used to infere schema
private TableSchemaInfo AddTableSchema(DataTable table, XmlNameTable nameTable) {
// Microsoft: Because in our case reader already read the document all names that we can meet in the
// document already has an entry in NameTable.
// If in future we will build identity map before reading XML we can replace Get() to Add()
// Microsoft: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
// First case deals with decoded names; Second one with encoded names.
// We decided encoded names in first case (instead of decoding them in second)
// because it save us time in LoadRows(). We have, as usual, more data them schemas
string tableLocalName = nameTable.Get(table.EncodedTableName);
string tableNamespace = nameTable.Get(table.Namespace );
if(tableLocalName == null) {
// because name of this table isn't present in XML we don't need mapping for it.
// Less mapping faster we work.
return null;
}
TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
return tableSchemaInfo;
}
private TableSchemaInfo AddTableSchema(XmlNameTable nameTable, DataTable table) {
// Microsoft:This is the opposite of the previous function:
// we populate the nametable so that the hash comparison can happen as
// object comparison instead of strings.
// Microsoft: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
// First case deals with decoded names; Second one with encoded names.
// We decided encoded names in first case (instead of decoding them in second)
// because it save us time in LoadRows(). We have, as usual, more data them schemas
string _tableLocalName = table.EncodedTableName; // Table name
string tableLocalName = nameTable.Get(_tableLocalName); // Look it up in nametable
if(tableLocalName == null) { // If not found
tableLocalName = nameTable.Add(_tableLocalName); // Add it
}
table.encodedTableName = tableLocalName; // And set it back
string tableNamespace = nameTable.Get(table.Namespace); // Look ip table namespace
if (tableNamespace == null) { // If not found
tableNamespace = nameTable.Add(table.Namespace); // Add it
}
else {
if (table.tableNamespace != null) // Update table namespace
table.tableNamespace = tableNamespace;
}
TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
// Create new table schema info
tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
// And add it to the hashtable
return tableSchemaInfo; // Return it as we have to populate
// Column schema map and Child table
// schema map in it
}
private bool AddColumnSchema(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
string columnLocalName = nameTable.Get(col.EncodedColumnName );
string columnNamespace = nameTable.Get(col.Namespace );
if(columnLocalName == null) {
return false;
}
XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
columns[idColumn] = col;
if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
HandleSpecialColumn(col, nameTable, columns);
}
return true;
}
private bool AddColumnSchema(XmlNameTable nameTable, DataColumn col, XmlNodeIdHashtable columns) {
string _columnLocalName = XmlConvert.EncodeLocalName(col.ColumnName);
string columnLocalName = nameTable.Get(_columnLocalName); // Look it up in a name table
if(columnLocalName == null) { // Not found?
columnLocalName = nameTable.Add(_columnLocalName); // Add it
}
col.encodedColumnName = columnLocalName; // And set it back
string columnNamespace = nameTable.Get(col.Namespace ); // Get column namespace from nametable
if(columnNamespace == null) { // Not found ?
columnNamespace = nameTable.Add(col.Namespace); // Add it
}
else {
if (col._columnUri != null ) // Update namespace
col._columnUri = columnNamespace;
}
// Create XmlNodeIdentety
// for this column
XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
columns[idColumn] = col; // And add it to hashtable
if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
HandleSpecialColumn(col, nameTable, columns);
}
return true;
}
private void BuildIdentityMap(DataSet dataSet, XmlNameTable nameTable) {
this.tableSchemaMap = new XmlNodeIdHashtable(dataSet.Tables.Count);
foreach(DataTable t in dataSet.Tables) {
TableSchemaInfo tableSchemaInfo = AddTableSchema(t, nameTable);
if(tableSchemaInfo != null) {
foreach( DataColumn c in t.Columns ) {
// don't include auto-generated PK, FK and any hidden columns to be part of mapping
if (IsMappedColumn(c)) {
AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
}
}
}
}
}
// This one is used while reading data with preloaded schema
private void BuildIdentityMap(XmlNameTable nameTable, DataSet dataSet) {
this.tableSchemaMap = new XmlNodeIdHashtable(dataSet.Tables.Count);
// This hash table contains
// tables schemas as TableSchemaInfo objects
// These objects holds reference to the table.
// Hash tables with columns schema maps
// and child tables schema maps
string dsNamespace = nameTable.Get(dataSet.Namespace); // Attept to look up DataSet namespace
// in the name table
if (dsNamespace == null) { // Found ?
dsNamespace = nameTable.Add(dataSet.Namespace); // Nope. Add it
}
dataSet.namespaceURI = dsNamespace; // Set a DataSet namespace URI
foreach(DataTable t in dataSet.Tables) { // For each table
TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t);
// Add table schema info to hash table
if(tableSchemaInfo != null) {
foreach( DataColumn c in t.Columns ) { // Add column schema map
// don't include auto-generated PK, FK and any hidden columns to be part of mapping
if (IsMappedColumn(c)) { // If mapped column
AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
} // Add it to the map
}
// Add child nested tables to the schema
foreach( DataRelation r in t.ChildRelations ) { // Do we have a child tables ?
if (r.Nested) { // Is it nested?
// don't include non nested tables
// Handle namespaces and names as usuall
string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
string tableLocalName = nameTable.Get(_tableLocalName);
if(tableLocalName == null) {
tableLocalName = nameTable.Add(_tableLocalName);
}
string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
if(tableNamespace == null) {
tableNamespace = nameTable.Add(r.ChildTable.Namespace);
}
XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
}
}
}
}
}
// Used for inference
private void BuildIdentityMap(DataTable dataTable, XmlNameTable nameTable) {
this.tableSchemaMap = new XmlNodeIdHashtable(1);
TableSchemaInfo tableSchemaInfo = AddTableSchema(dataTable, nameTable);
if(tableSchemaInfo != null) {
foreach( DataColumn c in dataTable.Columns ) {
// don't include auto-generated PK, FK and any hidden columns to be part of mapping
if (IsMappedColumn(c)) {
AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
}
}
}
}
// This one is used while reading data with preloaded schema
private void BuildIdentityMap(XmlNameTable nameTable, DataTable dataTable) {
ArrayList tableList = GetSelfAndDescendants(dataTable); // Get list of tables we're loading
// This includes our table and
// related tables tree
this.tableSchemaMap = new XmlNodeIdHashtable( tableList.Count );
// Create hash table to hold all
// tables to load.
foreach (DataTable t in tableList) { // For each table
TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t );
// Create schema info
if(tableSchemaInfo != null) {
foreach( DataColumn c in t.Columns ) { // Add column information
// don't include auto-generated PK, FK and any hidden columns to be part of mapping
if (IsMappedColumn(c)) {
AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
}
}
foreach( DataRelation r in t.ChildRelations ) { // Add nested tables information
if (r.Nested) { // Is it nested?
// don't include non nested tables
// Handle namespaces and names as usuall
string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
string tableLocalName = nameTable.Get(_tableLocalName);
if(tableLocalName == null) {
tableLocalName = nameTable.Add(_tableLocalName);
}
string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
if(tableNamespace == null) {
tableNamespace = nameTable.Add(r.ChildTable.Namespace);
}
XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
}
}
}
}
}
private ArrayList GetSelfAndDescendants(DataTable dt) { // breadth-first
ArrayList tableList = new ArrayList();
tableList.Add(dt);
int nCounter = 0;
while (nCounter < tableList.Count) {
foreach(DataRelation childRelations in ((DataTable)tableList[nCounter]).ChildRelations) {
if (!tableList.Contains(childRelations.ChildTable))
tableList.Add(childRelations.ChildTable);
}
nCounter++;
}
return tableList;
}
// Used to infer schema and top most node
public object GetColumnSchema(XmlNode node, bool fIgnoreNamespace) {
Debug.Assert(node != null, "Argument validation");
TableSchemaInfo tableSchemaInfo = null;
XmlNode nodeRegion = (node.NodeType == XmlNodeType.Attribute) ? ((XmlAttribute)node).OwnerElement : node.ParentNode;
do {
if(nodeRegion == null || nodeRegion.NodeType != XmlNodeType.Element) {
return null;
}
tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[nodeRegion.LocalName] : tableSchemaMap[nodeRegion]);
nodeRegion = nodeRegion.ParentNode;
} while(tableSchemaInfo == null);
if (fIgnoreNamespace)
return tableSchemaInfo.ColumnsSchemaMap[node.LocalName];
else
return tableSchemaInfo.ColumnsSchemaMap[node];
}
public object GetColumnSchema(DataTable table, XmlReader dataReader, bool fIgnoreNamespace){
if ((lastTableSchemaInfo == null) || (lastTableSchemaInfo.TableSchema != table)) {
lastTableSchemaInfo = (TableSchemaInfo)(fIgnoreNamespace ? tableSchemaMap[table.EncodedTableName] : tableSchemaMap[table]);
}
if (fIgnoreNamespace)
return lastTableSchemaInfo.ColumnsSchemaMap[dataReader.LocalName];
return lastTableSchemaInfo.ColumnsSchemaMap[dataReader];
}
// Used to infer schema
public object GetSchemaForNode(XmlNode node, bool fIgnoreNamespace) {
TableSchemaInfo tableSchemaInfo = null;
if (node.NodeType == XmlNodeType.Element) { // If element
tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
} // Look up table schema info for it
if (tableSchemaInfo != null) { // Got info ?
return tableSchemaInfo.TableSchema; // Yes, Return table
}
return GetColumnSchema(node, fIgnoreNamespace); // Attempt to locate column
}
public DataTable GetTableForNode(XmlReader node, bool fIgnoreNamespace) {
TableSchemaInfo tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
if (tableSchemaInfo != null) {
lastTableSchemaInfo = tableSchemaInfo;
return lastTableSchemaInfo.TableSchema;
}
return null;
}
private void HandleSpecialColumn(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
// if column name starts with xml, we encode it manualy and add it for look up
Debug.Assert(col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase), "column name should start with xml");
string tempColumnName;
if ('x' == col.ColumnName[0]) {
tempColumnName = "_x0078_"; // lower case xml... -> _x0078_ml...
}
else {
tempColumnName = "_x0058_"; // upper case Xml... -> _x0058_ml...
}
tempColumnName += col.ColumnName.Substring(1);
if(nameTable.Get(tempColumnName) == null) {
nameTable.Add(tempColumnName);
}
string columnNamespace = nameTable.Get(col.Namespace);
XmlNodeIdentety idColumn = new XmlNodeIdentety(tempColumnName, columnNamespace);
columns[idColumn] = col;
}
}
}
|