XmlToDatasetMap.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. //------------------------------------------------------------------------------
  2. // <copyright file="XmlToDatasetMap.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="false">[....]</owner>
  7. // <owner current="false" primary="false">[....]</owner>
  8. //------------------------------------------------------------------------------
  9. namespace System.Data {
  10. using System;
  11. using System.Xml;
  12. using System.Collections;
  13. using System.Diagnostics;
  14. using System.Globalization;
  15. // This is an internal helper class used during Xml load to DataSet/DataDocument.
  16. // XmlToDatasetMap class provides functionality for binding elemants/atributes
  17. // to DataTable / DataColumn
  18. internal sealed class XmlToDatasetMap {
  19. private sealed class XmlNodeIdentety {
  20. public string LocalName;
  21. public string NamespaceURI;
  22. public XmlNodeIdentety(string localName, string namespaceURI) {
  23. this.LocalName = localName;
  24. this.NamespaceURI = namespaceURI;
  25. }
  26. override public int GetHashCode() {
  27. return ((object) LocalName).GetHashCode();
  28. }
  29. override public bool Equals(object obj) {
  30. XmlNodeIdentety id = (XmlNodeIdentety) obj;
  31. return (
  32. (String.Compare(this.LocalName, id.LocalName, StringComparison.OrdinalIgnoreCase) == 0) &&
  33. (String.Compare(this.NamespaceURI, id.NamespaceURI, StringComparison.OrdinalIgnoreCase) == 0)
  34. );
  35. }
  36. }
  37. // This class exist to avoid alocatin of XmlNodeIdentety to every acces to the hash table.
  38. // Unfortunetely XmlNode doesn't export single identety object.
  39. internal sealed class XmlNodeIdHashtable : Hashtable {
  40. private XmlNodeIdentety id = new XmlNodeIdentety(string.Empty, string.Empty);
  41. public XmlNodeIdHashtable(Int32 capacity)
  42. : base(capacity) {}
  43. public object this[XmlNode node] {
  44. get {
  45. id.LocalName = node.LocalName;
  46. id.NamespaceURI = node.NamespaceURI;
  47. return this[id];
  48. }
  49. }
  50. public object this[XmlReader dataReader] {
  51. get {
  52. id.LocalName = dataReader.LocalName;
  53. id.NamespaceURI = dataReader.NamespaceURI;
  54. return this[id];
  55. }
  56. }
  57. public object this[DataTable table] {
  58. get {
  59. id.LocalName = table.EncodedTableName;
  60. id.NamespaceURI = table.Namespace;
  61. return this[id];
  62. }
  63. }
  64. public object this[string name] {
  65. get {
  66. id.LocalName = name;
  67. id.NamespaceURI = String.Empty;
  68. return this[id];
  69. }
  70. }
  71. }
  72. private sealed class TableSchemaInfo {
  73. public DataTable TableSchema;
  74. public XmlNodeIdHashtable ColumnsSchemaMap;
  75. public TableSchemaInfo(DataTable tableSchema) {
  76. this.TableSchema = tableSchema;
  77. this.ColumnsSchemaMap = new XmlNodeIdHashtable(tableSchema.Columns.Count);
  78. }
  79. }
  80. XmlNodeIdHashtable tableSchemaMap; // Holds all the tables information
  81. TableSchemaInfo lastTableSchemaInfo = null;
  82. // Used to infer schema
  83. public XmlToDatasetMap(DataSet dataSet, XmlNameTable nameTable) {
  84. Debug.Assert(dataSet != null, "DataSet can't be null");
  85. Debug.Assert(nameTable != null, "NameTable can't be null");
  86. BuildIdentityMap(dataSet, nameTable);
  87. }
  88. // Used to read data with known schema
  89. public XmlToDatasetMap(XmlNameTable nameTable, DataSet dataSet) {
  90. Debug.Assert(dataSet != null, "DataSet can't be null");
  91. Debug.Assert(nameTable != null, "NameTable can't be null");
  92. BuildIdentityMap(nameTable, dataSet);
  93. }
  94. // Used to infer schema
  95. public XmlToDatasetMap(DataTable dataTable, XmlNameTable nameTable) {
  96. Debug.Assert(dataTable != null, "DataTable can't be null");
  97. Debug.Assert(nameTable != null, "NameTable can't be null");
  98. BuildIdentityMap(dataTable, nameTable);
  99. }
  100. // Used to read data with known schema
  101. public XmlToDatasetMap(XmlNameTable nameTable, DataTable dataTable) {
  102. Debug.Assert(dataTable != null, "DataTable can't be null");
  103. Debug.Assert(nameTable != null, "NameTable can't be null");
  104. BuildIdentityMap(nameTable, dataTable);
  105. }
  106. static internal bool IsMappedColumn(DataColumn c) {
  107. return (c.ColumnMapping != MappingType.Hidden);
  108. }
  109. // Used to infere schema
  110. private TableSchemaInfo AddTableSchema(DataTable table, XmlNameTable nameTable) {
  111. // [....]: Because in our case reader already read the document all names that we can meet in the
  112. // document already has an entry in NameTable.
  113. // If in future we will build identity map before reading XML we can replace Get() to Add()
  114. // [....]: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
  115. // First case deals with decoded names; Second one with encoded names.
  116. // We decided encoded names in first case (instead of decoding them in second)
  117. // because it save us time in LoadRows(). We have, as usual, more data them schemas
  118. string tableLocalName = nameTable.Get(table.EncodedTableName);
  119. string tableNamespace = nameTable.Get(table.Namespace );
  120. if(tableLocalName == null) {
  121. // because name of this table isn't present in XML we don't need mapping for it.
  122. // Less mapping faster we work.
  123. return null;
  124. }
  125. TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
  126. tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
  127. return tableSchemaInfo;
  128. }
  129. private TableSchemaInfo AddTableSchema(XmlNameTable nameTable, DataTable table) {
  130. // [....]:This is the opposite of the previous function:
  131. // we populate the nametable so that the hash comparison can happen as
  132. // object comparison instead of strings.
  133. // [....]: GetIdentity is called from two places: BuildIdentityMap() and LoadRows()
  134. // First case deals with decoded names; Second one with encoded names.
  135. // We decided encoded names in first case (instead of decoding them in second)
  136. // because it save us time in LoadRows(). We have, as usual, more data them schemas
  137. string _tableLocalName = table.EncodedTableName; // Table name
  138. string tableLocalName = nameTable.Get(_tableLocalName); // Look it up in nametable
  139. if(tableLocalName == null) { // If not found
  140. tableLocalName = nameTable.Add(_tableLocalName); // Add it
  141. }
  142. table.encodedTableName = tableLocalName; // And set it back
  143. string tableNamespace = nameTable.Get(table.Namespace); // Look ip table namespace
  144. if (tableNamespace == null) { // If not found
  145. tableNamespace = nameTable.Add(table.Namespace); // Add it
  146. }
  147. else {
  148. if (table.tableNamespace != null) // Update table namespace
  149. table.tableNamespace = tableNamespace;
  150. }
  151. TableSchemaInfo tableSchemaInfo = new TableSchemaInfo(table);
  152. // Create new table schema info
  153. tableSchemaMap[new XmlNodeIdentety(tableLocalName, tableNamespace)] = tableSchemaInfo;
  154. // And add it to the hashtable
  155. return tableSchemaInfo; // Return it as we have to populate
  156. // Column schema map and Child table
  157. // schema map in it
  158. }
  159. private bool AddColumnSchema(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
  160. string columnLocalName = nameTable.Get(col.EncodedColumnName );
  161. string columnNamespace = nameTable.Get(col.Namespace );
  162. if(columnLocalName == null) {
  163. return false;
  164. }
  165. XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
  166. columns[idColumn] = col;
  167. if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
  168. HandleSpecialColumn(col, nameTable, columns);
  169. }
  170. return true;
  171. }
  172. private bool AddColumnSchema(XmlNameTable nameTable, DataColumn col, XmlNodeIdHashtable columns) {
  173. string _columnLocalName = XmlConvert.EncodeLocalName(col.ColumnName);
  174. string columnLocalName = nameTable.Get(_columnLocalName); // Look it up in a name table
  175. if(columnLocalName == null) { // Not found?
  176. columnLocalName = nameTable.Add(_columnLocalName); // Add it
  177. }
  178. col.encodedColumnName = columnLocalName; // And set it back
  179. string columnNamespace = nameTable.Get(col.Namespace ); // Get column namespace from nametable
  180. if(columnNamespace == null) { // Not found ?
  181. columnNamespace = nameTable.Add(col.Namespace); // Add it
  182. }
  183. else {
  184. if (col._columnUri != null ) // Update namespace
  185. col._columnUri = columnNamespace;
  186. }
  187. // Create XmlNodeIdentety
  188. // for this column
  189. XmlNodeIdentety idColumn = new XmlNodeIdentety(columnLocalName, columnNamespace);
  190. columns[idColumn] = col; // And add it to hashtable
  191. if (col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase)) {
  192. HandleSpecialColumn(col, nameTable, columns);
  193. }
  194. return true;
  195. }
  196. private void BuildIdentityMap(DataSet dataSet, XmlNameTable nameTable) {
  197. this.tableSchemaMap = new XmlNodeIdHashtable(dataSet.Tables.Count);
  198. foreach(DataTable t in dataSet.Tables) {
  199. TableSchemaInfo tableSchemaInfo = AddTableSchema(t, nameTable);
  200. if(tableSchemaInfo != null) {
  201. foreach( DataColumn c in t.Columns ) {
  202. // don't include auto-generated PK, FK and any hidden columns to be part of mapping
  203. if (IsMappedColumn(c)) {
  204. AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
  205. }
  206. }
  207. }
  208. }
  209. }
  210. // This one is used while reading data with preloaded schema
  211. private void BuildIdentityMap(XmlNameTable nameTable, DataSet dataSet) {
  212. this.tableSchemaMap = new XmlNodeIdHashtable(dataSet.Tables.Count);
  213. // This hash table contains
  214. // tables schemas as TableSchemaInfo objects
  215. // These objects holds reference to the table.
  216. // Hash tables with columns schema maps
  217. // and child tables schema maps
  218. string dsNamespace = nameTable.Get(dataSet.Namespace); // Attept to look up DataSet namespace
  219. // in the name table
  220. if (dsNamespace == null) { // Found ?
  221. dsNamespace = nameTable.Add(dataSet.Namespace); // Nope. Add it
  222. }
  223. dataSet.namespaceURI = dsNamespace; // Set a DataSet namespace URI
  224. foreach(DataTable t in dataSet.Tables) { // For each table
  225. TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t);
  226. // Add table schema info to hash table
  227. if(tableSchemaInfo != null) {
  228. foreach( DataColumn c in t.Columns ) { // Add column schema map
  229. // don't include auto-generated PK, FK and any hidden columns to be part of mapping
  230. if (IsMappedColumn(c)) { // If mapped column
  231. AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
  232. } // Add it to the map
  233. }
  234. // Add child nested tables to the schema
  235. foreach( DataRelation r in t.ChildRelations ) { // Do we have a child tables ?
  236. if (r.Nested) { // Is it nested?
  237. // don't include non nested tables
  238. // Handle namespaces and names as usuall
  239. string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
  240. string tableLocalName = nameTable.Get(_tableLocalName);
  241. if(tableLocalName == null) {
  242. tableLocalName = nameTable.Add(_tableLocalName);
  243. }
  244. string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
  245. if(tableNamespace == null) {
  246. tableNamespace = nameTable.Add(r.ChildTable.Namespace);
  247. }
  248. XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
  249. tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
  250. }
  251. }
  252. }
  253. }
  254. }
  255. // Used for inference
  256. private void BuildIdentityMap(DataTable dataTable, XmlNameTable nameTable) {
  257. this.tableSchemaMap = new XmlNodeIdHashtable(1);
  258. TableSchemaInfo tableSchemaInfo = AddTableSchema(dataTable, nameTable);
  259. if(tableSchemaInfo != null) {
  260. foreach( DataColumn c in dataTable.Columns ) {
  261. // don't include auto-generated PK, FK and any hidden columns to be part of mapping
  262. if (IsMappedColumn(c)) {
  263. AddColumnSchema(c, nameTable, tableSchemaInfo.ColumnsSchemaMap);
  264. }
  265. }
  266. }
  267. }
  268. // This one is used while reading data with preloaded schema
  269. private void BuildIdentityMap(XmlNameTable nameTable, DataTable dataTable) {
  270. ArrayList tableList = GetSelfAndDescendants(dataTable); // Get list of tables we're loading
  271. // This includes our table and
  272. // related tables tree
  273. this.tableSchemaMap = new XmlNodeIdHashtable( tableList.Count );
  274. // Create hash table to hold all
  275. // tables to load.
  276. foreach (DataTable t in tableList) { // For each table
  277. TableSchemaInfo tableSchemaInfo = AddTableSchema(nameTable, t );
  278. // Create schema info
  279. if(tableSchemaInfo != null) {
  280. foreach( DataColumn c in t.Columns ) { // Add column information
  281. // don't include auto-generated PK, FK and any hidden columns to be part of mapping
  282. if (IsMappedColumn(c)) {
  283. AddColumnSchema(nameTable, c, tableSchemaInfo.ColumnsSchemaMap);
  284. }
  285. }
  286. foreach( DataRelation r in t.ChildRelations ) { // Add nested tables information
  287. if (r.Nested) { // Is it nested?
  288. // don't include non nested tables
  289. // Handle namespaces and names as usuall
  290. string _tableLocalName = XmlConvert.EncodeLocalName(r.ChildTable.TableName);
  291. string tableLocalName = nameTable.Get(_tableLocalName);
  292. if(tableLocalName == null) {
  293. tableLocalName = nameTable.Add(_tableLocalName);
  294. }
  295. string tableNamespace = nameTable.Get(r.ChildTable.Namespace );
  296. if(tableNamespace == null) {
  297. tableNamespace = nameTable.Add(r.ChildTable.Namespace);
  298. }
  299. XmlNodeIdentety idTable = new XmlNodeIdentety(tableLocalName, tableNamespace);
  300. tableSchemaInfo.ColumnsSchemaMap[idTable] = r.ChildTable;
  301. }
  302. }
  303. }
  304. }
  305. }
  306. private ArrayList GetSelfAndDescendants(DataTable dt) { // breadth-first
  307. ArrayList tableList = new ArrayList();
  308. tableList.Add(dt);
  309. int nCounter = 0;
  310. while (nCounter < tableList.Count) {
  311. foreach(DataRelation childRelations in ((DataTable)tableList[nCounter]).ChildRelations) {
  312. if (!tableList.Contains(childRelations.ChildTable))
  313. tableList.Add(childRelations.ChildTable);
  314. }
  315. nCounter++;
  316. }
  317. return tableList;
  318. }
  319. // Used to infer schema and top most node
  320. public object GetColumnSchema(XmlNode node, bool fIgnoreNamespace) {
  321. Debug.Assert(node != null, "Argument validation");
  322. TableSchemaInfo tableSchemaInfo = null;
  323. XmlNode nodeRegion = (node.NodeType == XmlNodeType.Attribute) ? ((XmlAttribute)node).OwnerElement : node.ParentNode;
  324. do {
  325. if(nodeRegion == null || nodeRegion.NodeType != XmlNodeType.Element) {
  326. return null;
  327. }
  328. tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[nodeRegion.LocalName] : tableSchemaMap[nodeRegion]);
  329. nodeRegion = nodeRegion.ParentNode;
  330. } while(tableSchemaInfo == null);
  331. if (fIgnoreNamespace)
  332. return tableSchemaInfo.ColumnsSchemaMap[node.LocalName];
  333. else
  334. return tableSchemaInfo.ColumnsSchemaMap[node];
  335. }
  336. public object GetColumnSchema(DataTable table, XmlReader dataReader, bool fIgnoreNamespace){
  337. if ((lastTableSchemaInfo == null) || (lastTableSchemaInfo.TableSchema != table)) {
  338. lastTableSchemaInfo = (TableSchemaInfo)(fIgnoreNamespace ? tableSchemaMap[table.EncodedTableName] : tableSchemaMap[table]);
  339. }
  340. if (fIgnoreNamespace)
  341. return lastTableSchemaInfo.ColumnsSchemaMap[dataReader.LocalName];
  342. return lastTableSchemaInfo.ColumnsSchemaMap[dataReader];
  343. }
  344. // Used to infer schema
  345. public object GetSchemaForNode(XmlNode node, bool fIgnoreNamespace) {
  346. TableSchemaInfo tableSchemaInfo = null;
  347. if (node.NodeType == XmlNodeType.Element) { // If element
  348. tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
  349. } // Look up table schema info for it
  350. if (tableSchemaInfo != null) { // Got info ?
  351. return tableSchemaInfo.TableSchema; // Yes, Return table
  352. }
  353. return GetColumnSchema(node, fIgnoreNamespace); // Attempt to locate column
  354. }
  355. public DataTable GetTableForNode(XmlReader node, bool fIgnoreNamespace) {
  356. TableSchemaInfo tableSchemaInfo = (TableSchemaInfo) (fIgnoreNamespace ? tableSchemaMap[node.LocalName] : tableSchemaMap[node]);
  357. if (tableSchemaInfo != null) {
  358. lastTableSchemaInfo = tableSchemaInfo;
  359. return lastTableSchemaInfo.TableSchema;
  360. }
  361. return null;
  362. }
  363. private void HandleSpecialColumn(DataColumn col, XmlNameTable nameTable, XmlNodeIdHashtable columns) {
  364. // if column name starts with xml, we encode it manualy and add it for look up
  365. Debug.Assert(col.ColumnName.StartsWith("xml", StringComparison.OrdinalIgnoreCase), "column name should start with xml");
  366. string tempColumnName;
  367. if ('x' == col.ColumnName[0]) {
  368. tempColumnName = "_x0078_"; // lower case xml... -> _x0078_ml...
  369. }
  370. else {
  371. tempColumnName = "_x0058_"; // upper case Xml... -> _x0058_ml...
  372. }
  373. tempColumnName += col.ColumnName.Substring(1);
  374. if(nameTable.Get(tempColumnName) == null) {
  375. nameTable.Add(tempColumnName);
  376. }
  377. string columnNamespace = nameTable.Get(col.Namespace);
  378. XmlNodeIdentety idColumn = new XmlNodeIdentety(tempColumnName, columnNamespace);
  379. columns[idColumn] = col;
  380. }
  381. }
  382. }