DataSetMappper.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DataSetMapper.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">Microsoft</owner>
  6. // <owner current="true" primary="false">Microsoft</owner>
  7. //------------------------------------------------------------------------------
  8. #pragma warning disable 618 // ignore obsolete warning about XmlDataDocument
  9. namespace System.Xml {
  10. using System.Collections;
  11. using System.Data;
  12. using System.Diagnostics;
  13. //
  14. // Maps XML nodes to schema
  15. //
  16. // With the exception of some functions (the most important is SearchMatchingTableSchema) all functions expect that each region rowElem is already associated
  17. // w/ it's DataRow (basically the test to determine a rowElem is based on a != null associated DataRow). As a result of this, some functions will NOT work properly
  18. // when they are used on a tree for which rowElem's are not associated w/ a DataRow.
  19. //
  20. internal sealed class DataSetMapper {
  21. Hashtable tableSchemaMap; // maps an string (currently this is localName:nsURI) to a DataTable. Used to quickly find if a bound-elem matches any data-table metadata..
  22. Hashtable columnSchemaMap; // maps a string (table localName:nsURI) to a Hashtable. The 2nd hastable (the one that is stored as data in columnSchemaMap, maps a string to a DataColumn.
  23. XmlDataDocument doc; // The document this mapper is related to
  24. DataSet dataSet; // The dataset this mapper is related to
  25. internal const string strReservedXmlns = "http://www.w3.org/2000/xmlns/";
  26. internal DataSetMapper() {
  27. Debug.Assert( this.dataSet == null );
  28. this.tableSchemaMap = new Hashtable();
  29. this.columnSchemaMap = new Hashtable();
  30. }
  31. internal void SetupMapping( XmlDataDocument xd, DataSet ds ) {
  32. // If are already mapped, forget about our current mapping and re-do it again.
  33. if ( IsMapped() ) {
  34. this.tableSchemaMap = new Hashtable();
  35. this.columnSchemaMap = new Hashtable();
  36. }
  37. doc = xd;
  38. dataSet = ds;
  39. foreach( DataTable t in dataSet.Tables ) {
  40. AddTableSchema( t );
  41. foreach( DataColumn c in t.Columns ) {
  42. // don't include auto-generated PK & FK to be part of mapping
  43. if ( ! IsNotMapped(c) ) {
  44. AddColumnSchema( c );
  45. }
  46. }
  47. }
  48. }
  49. internal bool IsMapped() {
  50. return dataSet != null;
  51. }
  52. internal DataTable SearchMatchingTableSchema( string localName, string namespaceURI ) {
  53. object tid = GetIdentity( localName, namespaceURI );
  54. return (DataTable)(tableSchemaMap[ tid ]);
  55. }
  56. // SearchMatchingTableSchema function works only when the elem has not been bound to a DataRow. If you want to get the table associated w/ an element after
  57. // it has been associated w/ a DataRow use GetTableSchemaForElement function.
  58. // rowElem is the parent region rowElem or null if there is no parent region (in case elem is a row elem, then rowElem will be the parent region; if elem is not
  59. // mapped to a DataRow, then rowElem is the region elem is part of)
  60. //
  61. // Those are the rules for determing if elem is a row element:
  62. // 1. node is an element (already meet, since elem is of type XmlElement)
  63. // 2. If the node is already associated w/ a DataRow, then the node is a row element - not applicable, b/c this function is intended to be called on a
  64. // to find out if the node s/b associated w/ a DataRow (see XmlDataDocument.LoadRows)
  65. // 3. If the node localName/ns matches a DataTable then
  66. // 3.1 Take the parent region DataTable (in our case rowElem.Row.DataTable)
  67. // 3.2 If no parent region, then the node is associated w/ a DataTable
  68. // 3.3 If there is a parent region
  69. // 3.3.1 If the node has no elem children and no attr other than namespace declaration, and the node can match
  70. // a column from the parent region table, then the node is NOT associated w/ a DataTable (it is a potential DataColumn in the parent region)
  71. // 3.3.2 Else the node is a row-element (and associated w/ a DataTable / DataRow )
  72. //
  73. internal DataTable SearchMatchingTableSchema( XmlBoundElement rowElem, XmlBoundElement elem ) {
  74. Debug.Assert( elem != null );
  75. DataTable t = SearchMatchingTableSchema( elem.LocalName, elem.NamespaceURI );
  76. if ( t == null )
  77. return null;
  78. if ( rowElem == null )
  79. return t;
  80. // Currently we expect we map things from top of the tree to the bottom
  81. Debug.Assert( rowElem.Row != null );
  82. DataColumn col = GetColumnSchemaForNode( rowElem, elem );
  83. if ( col == null )
  84. return t;
  85. foreach ( XmlAttribute a in elem.Attributes ) {
  86. #if DEBUG
  87. // Some sanity check to catch errors like namespace attributes have the right localName/namespace value, but a wrong atomized namespace value
  88. if ( a.LocalName == "xmlns" ) {
  89. Debug.Assert( a.Prefix != null && a.Prefix.Length == 0 );
  90. Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
  91. }
  92. if ( a.Prefix == "xmlns" ) {
  93. Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
  94. }
  95. if ( a.NamespaceURI == strReservedXmlns )
  96. Debug.Assert( (object)a.NamespaceURI == (object)strReservedXmlns );
  97. #endif
  98. // No namespace attribute found, so elem cannot be a potential DataColumn, therefore is a row-elem
  99. if ( (object)(a.NamespaceURI) != (object)strReservedXmlns )
  100. return t;
  101. }
  102. for ( XmlNode n = elem.FirstChild; n != null; n = n.NextSibling ) {
  103. if ( n.NodeType == XmlNodeType.Element ) {
  104. // elem has an element child, so elem cannot be a potential DataColumn, therefore is a row-elem
  105. return t;
  106. }
  107. }
  108. // Node is a potential DataColumn in rowElem region
  109. return null;
  110. }
  111. internal DataColumn GetColumnSchemaForNode( XmlBoundElement rowElem, XmlNode node ) {
  112. //
  113. Debug.Assert( rowElem != null );
  114. // The caller must make sure that node is not a row-element
  115. Debug.Assert( (node is XmlBoundElement) ? ((XmlBoundElement)node).Row == null : true );
  116. object tid = GetIdentity( rowElem.LocalName, rowElem.NamespaceURI );
  117. object cid = GetIdentity( node.LocalName, node.NamespaceURI );
  118. Hashtable columns = (Hashtable) columnSchemaMap[ tid ];
  119. if ( columns != null ) {
  120. DataColumn col = (DataColumn)(columns[ cid ]);
  121. if ( col == null )
  122. return null;
  123. MappingType mt = col.ColumnMapping;
  124. if ( node.NodeType == XmlNodeType.Attribute && mt == MappingType.Attribute )
  125. return col;
  126. if ( node.NodeType == XmlNodeType.Element && mt == MappingType.Element )
  127. return col;
  128. // node's (localName, ns) matches a column, but the MappingType is different (i.e. node is elem, MT is attr)
  129. return null;
  130. }
  131. return null;
  132. }
  133. internal DataTable GetTableSchemaForElement( XmlElement elem ) {
  134. //
  135. XmlBoundElement be = elem as XmlBoundElement;
  136. if ( be == null )
  137. return null;
  138. return GetTableSchemaForElement( be );
  139. }
  140. internal DataTable GetTableSchemaForElement( XmlBoundElement be ) {
  141. // if bound to a row, must be a table.
  142. DataRow row = be.Row;
  143. if ( row != null )
  144. return row.Table;
  145. return null;
  146. }
  147. internal static bool IsNotMapped( DataColumn c ) {
  148. return c.ColumnMapping == MappingType.Hidden;
  149. }
  150. // ATTENTION: GetRowFromElement( XmlElement ) and GetRowFromElement( XmlBoundElement ) should have the same functionality and side effects.
  151. // See this code fragment for why:
  152. // XmlBoundElement be = ...;
  153. // XmlElement e = be;
  154. // GetRowFromElement( be ); // Calls GetRowFromElement( XmlBoundElement )
  155. // GetRowFromElement( e ); // Calls GetRowFromElement( XmlElement ), in spite of e beeing an instance of XmlBoundElement
  156. internal DataRow GetRowFromElement( XmlElement e ) {
  157. XmlBoundElement be = e as XmlBoundElement;
  158. if ( be != null )
  159. return be.Row;
  160. return null;
  161. }
  162. internal DataRow GetRowFromElement( XmlBoundElement be ) {
  163. return be.Row;
  164. }
  165. // Get the row-elem associatd w/ the region node is in.
  166. // If node is in a region not mapped (like document element node) the function returns false and sets elem to null)
  167. // This function does not work if the region is not associated w/ a DataRow (it uses DataRow association to know what is the row element associated w/ the region)
  168. internal bool GetRegion( XmlNode node, out XmlBoundElement rowElem ) {
  169. while ( node != null ) {
  170. XmlBoundElement be = node as XmlBoundElement;
  171. // Break if found a region
  172. if ( be != null && GetRowFromElement( be ) != null ) {
  173. rowElem = be;
  174. return true;
  175. }
  176. if ( node.NodeType == XmlNodeType.Attribute )
  177. node = ((XmlAttribute)node).OwnerElement;
  178. else
  179. node = node.ParentNode;
  180. }
  181. rowElem = null;
  182. return false;
  183. }
  184. internal bool IsRegionRadical( XmlBoundElement rowElem ) {
  185. // You must pass a row element (which s/b associated w/ a DataRow)
  186. Debug.Assert( rowElem.Row != null );
  187. if ( rowElem.ElementState == ElementState.Defoliated )
  188. return true;
  189. DataTable table = GetTableSchemaForElement( rowElem );
  190. DataColumnCollection columns = table.Columns;
  191. int iColumn = 0;
  192. // check column attributes...
  193. int cAttrs = rowElem.Attributes.Count;
  194. for ( int iAttr = 0; iAttr < cAttrs; iAttr++ ) {
  195. XmlAttribute attr = rowElem.Attributes[iAttr];
  196. // only specified attributes are radical
  197. if ( !attr.Specified )
  198. return false;
  199. // only mapped attrs are valid
  200. DataColumn schema = GetColumnSchemaForNode( rowElem, attr );
  201. if ( schema == null ) {
  202. //Console.WriteLine("Region has unmapped attribute");
  203. return false;
  204. }
  205. // check to see if column is in order
  206. if ( !IsNextColumn( columns, ref iColumn, schema ) ) {
  207. //Console.WriteLine("Region has attribute columns out of order or duplicate");
  208. return false;
  209. }
  210. // must have exactly one text node (XmlNodeType.Text) child
  211. //
  212. XmlNode fc = attr.FirstChild;
  213. if ( fc == null || fc.NodeType != XmlNodeType.Text || fc.NextSibling != null ) {
  214. //Console.WriteLine("column element has other than a single child text node");
  215. return false;
  216. }
  217. }
  218. // check column elements
  219. iColumn = 0;
  220. XmlNode n = rowElem.FirstChild;
  221. for ( ; n != null; n = n.NextSibling ) {
  222. // only elements can exist in radically structured data
  223. if ( n.NodeType != XmlNodeType.Element ) {
  224. //Console.WriteLine("Region has non-element child");
  225. return false;
  226. }
  227. XmlElement e = n as XmlElement;
  228. // only checking for column mappings in this loop
  229. if ( GetRowFromElement( e ) != null )
  230. break;
  231. // element's must have schema to be radically structured
  232. DataColumn schema = GetColumnSchemaForNode( rowElem, e );
  233. if ( schema == null ) {
  234. //Console.WriteLine("Region has unmapped child element");
  235. return false;
  236. }
  237. // check to see if column is in order
  238. if ( !IsNextColumn( columns, ref iColumn, schema ) ) {
  239. //Console.WriteLine("Region has element columns out of order or duplicate");
  240. return false;
  241. }
  242. // must have no attributes
  243. if ( e.HasAttributes )
  244. return false;
  245. // must have exactly one text node child
  246. XmlNode fc = e.FirstChild;
  247. if ( fc == null || fc.NodeType != XmlNodeType.Text || fc.NextSibling != null ) {
  248. //Console.WriteLine("column element has other than a single child text node");
  249. return false;
  250. }
  251. }
  252. // check for remaining sub-regions
  253. for (; n != null; n = n.NextSibling ) {
  254. // only elements can exist in radically structured data
  255. if ( n.NodeType != XmlNodeType.Element ) {
  256. //Console.WriteLine("Region has non-element child");
  257. return false;
  258. }
  259. // element's must be regions in order to be radially structured
  260. DataRow row = GetRowFromElement( (XmlElement)n );
  261. if ( row == null ) {
  262. //Console.WriteLine("Region has unmapped element");
  263. return false;
  264. }
  265. }
  266. return true;
  267. }
  268. private void AddTableSchema( DataTable table ) {
  269. object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
  270. tableSchemaMap[ idTable ] = table;
  271. }
  272. private void AddColumnSchema( DataColumn col ) {
  273. DataTable table = col.Table;
  274. object idTable = GetIdentity( table.EncodedTableName, table.Namespace );
  275. object idColumn = GetIdentity( col.EncodedColumnName, col.Namespace );
  276. Hashtable columns = (Hashtable) columnSchemaMap[ idTable ];
  277. if ( columns == null ) {
  278. columns = new Hashtable();
  279. columnSchemaMap[ idTable ] = columns;
  280. }
  281. columns[ idColumn ] = col;
  282. }
  283. private static object GetIdentity( string localName, string namespaceURI ) {
  284. // we need access to XmlName to make this faster
  285. return localName+":"+namespaceURI;
  286. }
  287. private bool IsNextColumn( DataColumnCollection columns, ref int iColumn, DataColumn col ) {
  288. for ( ; iColumn < columns.Count; iColumn++ ) {
  289. if ( columns[iColumn] == col ) {
  290. iColumn++; // advance before we return...
  291. return true;
  292. }
  293. }
  294. return false;
  295. }
  296. }
  297. }