2 // mcs/class/System.Data/System.Xml/XmlDataDocument.cs
4 // Purpose: Provides a W3C XML DOM Document to interact with
5 // relational data in a DataSet
7 // class: XmlDataDocument
8 // assembly: System.Data.dll
9 // namespace: System.Xml
12 // Daniel Morgan <danmorg@sc.rr.com>
13 // Ville Palo <vi64pa@koti.soon.fi>
14 // Atsushi Enomoto <atsushi@ximian.com>
16 // (c)copyright 2002 Daniel Morgan
17 // (c)copyright 2003 Ville Palo
18 // (c)2004 Novell Inc.
20 // XmlDataDocument is included within the Mono Class Library.
24 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
26 // Permission is hereby granted, free of charge, to any person obtaining
27 // a copy of this software and associated documentation files (the
28 // "Software"), to deal in the Software without restriction, including
29 // without limitation the rights to use, copy, modify, merge, publish,
30 // distribute, sublicense, and/or sell copies of the Software, and to
31 // permit persons to whom the Software is furnished to do so, subject to
32 // the following conditions:
34 // The above copyright notice and this permission notice shall be
35 // included in all copies or substantial portions of the Software.
37 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
39 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
41 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
42 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
43 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
50 using System.Xml.XPath;
51 using System.Collections;
52 using System.Globalization;
53 using System.ComponentModel;
58 public class XmlDataDocument : XmlDocument
60 // Should we consider overriding CloneNode() ? By default
61 // base CloneNode() will be invoked and thus no DataRow conflict
62 // would happen, that sounds the best (that means, no mapped
63 // DataRow will be provided).
64 internal class XmlDataElement : XmlElement
68 internal XmlDataElement (DataRow row, string prefix, string localName, string ns, XmlDataDocument doc)
69 : base (prefix, localName, ns, doc)
72 // Embed row ID only when the element is mapped to
75 row.DataElement = this;
76 row.XmlRowID = doc.dataRowID;
77 doc.dataRowIDList.Add (row.XmlRowID);
82 internal DataRow DataRow {
89 private DataSet dataSet;
91 private int dataRowID = 1;
92 private ArrayList dataRowIDList = new ArrayList ();
94 // this keeps whether table change events should be handles
95 private bool raiseDataSetEvents = true;
96 private bool raiseDocumentEvents = true;
98 DataColumnChangeEventHandler columnChanged;
99 DataRowChangeEventHandler rowDeleted;
100 DataRowChangeEventHandler rowChanged;
101 CollectionChangeEventHandler tablesChanged;
106 public XmlDataDocument ()
108 InitDelegateFields ();
110 dataSet = new DataSet();
111 dataSet._xmlDataDocument = this;
112 dataSet.Tables.CollectionChanged += tablesChanged;
114 AddXmlDocumentListeners ();
115 DataSet.EnforceConstraints = false;
118 public XmlDataDocument (DataSet dataset)
121 throw new ArgumentException ("Parameter dataset cannot be null.");
122 if (dataset._xmlDataDocument != null)
123 throw new ArgumentException ("DataSet cannot be associated with two or more XmlDataDocument.");
125 InitDelegateFields ();
127 this.dataSet = dataset;
128 this.dataSet._xmlDataDocument = this;
130 XmlElement docElem = CreateElement (dataSet.Prefix, XmlHelper.Encode (dataSet.DataSetName), dataSet.Namespace);
131 foreach (DataTable dt in dataSet.Tables) {
132 if (dt.ParentRelations.Count > 0)
133 continue; // don't add them here
134 FillNodeRows (docElem, dt, dt.Rows);
137 // This seems required to avoid Load() error when for
138 // example empty DataSet will be filled on Load().
139 if (docElem.ChildNodes.Count > 0)
140 AppendChild (docElem);
142 foreach (DataTable dt in dataSet.Tables) {
143 dt.ColumnChanged += columnChanged;
144 dt.RowDeleted += rowDeleted;
145 dt.RowChanged += rowChanged;
148 AddXmlDocumentListeners ();
151 // bool clone. If we are cloning XmlDataDocument then clone should be true.
152 // FIXME: shouldn't DataSet be mapped to at most one document??
153 private XmlDataDocument (DataSet dataset, bool clone)
155 InitDelegateFields ();
157 this.dataSet = dataset;
158 this.dataSet._xmlDataDocument = this;
160 foreach (DataTable Table in DataSet.Tables) {
162 foreach (DataRow Row in Table.Rows) {
163 Row.XmlRowID = dataRowID;
164 dataRowIDList.Add (dataRowID);
169 AddXmlDocumentListeners ();
171 foreach (DataTable Table in dataSet.Tables) {
172 Table.ColumnChanged += columnChanged;
173 Table.RowDeleted += rowDeleted;
174 Table.RowChanged += rowChanged;
178 #endregion // Constructors
180 #region Public Properties
182 public DataSet DataSet {
188 #endregion // Public Properties
190 #region Public Methods
192 private void FillNodeRows (XmlElement parent, DataTable dt, ICollection rows)
194 foreach (DataRow dr in dt.Rows) {
195 XmlDataElement el = dr.DataElement;
196 FillNodeChildrenFromRow (dr, el);
198 foreach (DataRelation rel in dt.ChildRelations)
199 FillNodeRows (el, rel.ChildTable, dr.GetChildRows (rel));
200 parent.AppendChild (el);
204 public override XmlNode CloneNode (bool deep)
206 XmlDataDocument Document;
208 Document = new XmlDataDocument (DataSet.Copy (), true);
210 Document = new XmlDataDocument (DataSet.Clone (), true);
212 Document.RemoveXmlDocumentListeners ();
214 Document.PreserveWhitespace = PreserveWhitespace;
216 foreach(XmlNode n in ChildNodes)
217 Document.AppendChild (Document.ImportNode (n, deep));
220 Document.AddXmlDocumentListeners ();
225 #region overloaded CreateElement methods
227 public override XmlElement CreateElement(
228 string prefix, string localName, string namespaceURI)
230 DataTable dt = DataSet.Tables [XmlHelper.Decode (localName)];
231 DataRow row = dt != null ? dt.NewRow () : null;
233 return GetElementFromRow (row);
235 return base.CreateElement (prefix, localName, namespaceURI);
238 #endregion // overloaded CreateElement Methods
240 // It is not supported in XmlDataDocument
241 public override XmlEntityReference CreateEntityReference(string name)
243 throw new NotSupportedException ();
246 // It is not supported in XmlDataDocument
247 public override XmlElement GetElementById (string elemId)
249 throw new NotSupportedException ();
252 // get the XmlElement associated with the DataRow
253 public XmlElement GetElementFromRow (DataRow r)
255 return r.DataElement;
258 // get the DataRow associated with the XmlElement
259 public DataRow GetRowFromElement (XmlElement e)
261 XmlDataElement el = e as XmlDataElement;
267 #region overload Load methods
269 public override void Load(Stream inStream) {
270 Load (new XmlTextReader (inStream));
273 public override void Load(string filename) {
274 Load (new XmlTextReader (filename));
277 public override void Load(TextReader txtReader) {
278 Load (new XmlTextReader (txtReader));
281 public override void Load (XmlReader reader)
283 if (DocumentElement != null)
284 throw new InvalidOperationException ("XmlDataDocument does not support multi-time loading. New XmlDadaDocument is always required.");
286 bool OldEC = DataSet.EnforceConstraints;
287 DataSet.EnforceConstraints = false;
288 dataSet.Tables.CollectionChanged -= tablesChanged;
292 DataSet.EnforceConstraints = OldEC;
293 dataSet.Tables.CollectionChanged += tablesChanged;
296 #endregion // overloaded Load methods
297 #endregion // Public Methods
299 #region Protected Methods
301 [MonoTODO ("Create optimized XPathNavigator")]
302 protected override XPathNavigator CreateNavigator(XmlNode node) {
303 return base.CreateNavigator (node);
306 #endregion // Protected Methods
308 #region XmlDocument event handlers
310 private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
312 if (!this.raiseDocumentEvents)
314 if (DataSet.EnforceConstraints)
315 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
318 // Invoked when XmlNode is changed colum is changed
319 private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
321 if (!raiseDocumentEvents)
323 bool escapedRaiseDataSetEvents = raiseDataSetEvents;
324 raiseDataSetEvents = false;
327 if (args.Node == null)
330 DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
335 if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
338 if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText) {
339 DataColumn col = row.Table.Columns [args.Node.ParentNode.Name];
340 row [col] = StringToObject (col.DataType, args.Node.InnerText);
344 raiseDataSetEvents = escapedRaiseDataSetEvents;
348 private void OnNodeRemoving (object sender, XmlNodeChangedEventArgs args)
350 if (!this.raiseDocumentEvents)
352 if (DataSet.EnforceConstraints)
353 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
357 // Invoked when XmlNode is removed
358 private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
360 if (!raiseDocumentEvents)
362 bool escapedRaiseDataSetEvents = raiseDataSetEvents;
363 raiseDataSetEvents = false;
366 if (args.OldParent == null)
369 XmlElement oldParentElem = args.OldParent as XmlElement;
370 if (oldParentElem == null)
373 // detach child row (if exists)
374 XmlElement childElem = args.Node as XmlElement;
375 if (childElem != null) {
376 DataRow childRow = GetRowFromElement (childElem);
377 if (childRow != null)
378 childRow.Table.Rows.Remove (childRow);
381 DataRow row = GetRowFromElement (oldParentElem);
386 row [args.Node.Name] = null;
389 raiseDataSetEvents = escapedRaiseDataSetEvents;
393 private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args)
395 if (!this.raiseDocumentEvents)
397 if (DataSet.EnforceConstraints)
398 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
401 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
403 if (!raiseDocumentEvents)
405 bool escapedRaiseDataSetEvents = raiseDataSetEvents;
406 raiseDataSetEvents = false;
408 // If the parent node is mapped to a DataTable, then
409 // add a DataRow and map the parent element to it.
411 // AND If the child node is mapped to a DataTable, then
412 // 1. if it is mapped to a DataTable and relation, add
413 // a new DataRow and map the child element to it.
414 // 2. if it is mapped to a DataColumn, set the column
415 // value of the parent DataRow as the child
418 if (! (args.NewParent is XmlElement)) {
419 // i.e. adding document element
420 foreach (XmlNode table in args.Node.ChildNodes)
421 CheckDescendantRelationship (table);
425 DataRow row = GetRowFromElement (args.NewParent as XmlElement);
427 // That happens only when adding table to existing DocumentElement (aka DataSet element)
428 if (args.NewParent == DocumentElement)
429 CheckDescendantRelationship (args.Node);
433 XmlAttribute attr = args.Node as XmlAttribute;
434 if (attr != null) { // fill attribute value
435 DataColumn col = row.Table.Columns [XmlHelper.Decode (attr.LocalName)];
437 row [col] = StringToObject (col.DataType, args.Node.Value);
439 DataRow childRow = GetRowFromElement (args.Node as XmlElement);
440 if (childRow != null) {
441 // child might be a table row.
442 // I might be impossible to set parent
443 // since either of them might be detached
444 if (childRow.RowState != DataRowState.Detached && row.RowState != DataRowState.Detached) {
445 FillRelationship (row, childRow, args.NewParent, args.Node);
447 } else if (args.Node.NodeType == XmlNodeType.Element) {
448 // child element might be a column
449 DataColumn col = row.Table.Columns [XmlHelper.Decode (args.Node.LocalName)];
451 row [col] = StringToObject (col.DataType, args.Node.InnerText);
452 } else if (args.Node is XmlCharacterData) {
453 if (args.Node.NodeType != XmlNodeType.Comment) {
454 for (int i = 0; i < row.Table.Columns.Count; i++) {
455 DataColumn col = row.Table.Columns [i];
456 if (col.ColumnMapping == MappingType.SimpleContent)
457 row [col] = StringToObject (col.DataType, args.Node.Value);
463 raiseDataSetEvents = escapedRaiseDataSetEvents;
467 private void CheckDescendantRelationship (XmlNode n)
469 XmlElement el = n as XmlElement;
470 DataRow row = GetRowFromElement (el);
473 row.Table.Rows.Add (row); // attach
474 CheckDescendantRelationship (n, row);
477 private void CheckDescendantRelationship (XmlNode p, DataRow row)
479 foreach (XmlNode n in p.ChildNodes) {
480 XmlElement el = n as XmlElement;
483 DataRow childRow = GetRowFromElement (el);
484 if (childRow == null)
486 childRow.Table.Rows.Add (childRow);
487 FillRelationship (row, childRow, p, el);
491 private void FillRelationship (DataRow row, DataRow childRow, XmlNode parentNode, XmlNode childNode)
493 for (int i = 0; i < childRow.Table.ParentRelations.Count; i++) {
494 DataRelation rel = childRow.Table.ParentRelations [i];
495 if (rel.ParentTable == row.Table) {
496 childRow.SetParentRow (row);
500 CheckDescendantRelationship (childNode, childRow);
502 #endregion // DataSet event handlers
504 #region DataSet event handlers
506 // If DataTable is added or removed from DataSet
507 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
509 if (!raiseDataSetEvents)
511 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
512 raiseDocumentEvents = false;
515 DataTable Table = (DataTable)eventArgs.Element;
516 switch (eventArgs.Action) {
517 case CollectionChangeAction.Add:
518 Table.ColumnChanged += columnChanged;
519 Table.RowDeleted += rowDeleted;
520 Table.RowChanged += rowChanged;
522 case CollectionChangeAction.Remove:
523 Table.ColumnChanged -= columnChanged;
524 Table.RowDeleted -= rowDeleted;
525 Table.RowChanged -= rowChanged;
529 raiseDocumentEvents = escapedRaiseDocumentEvents;
533 // If column has changed
534 private void OnDataTableColumnChanged (object sender,
535 DataColumnChangeEventArgs eventArgs)
537 if (!raiseDataSetEvents)
539 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
540 raiseDocumentEvents = false;
543 DataRow row = eventArgs.Row;
544 XmlElement el = GetElementFromRow (row);
547 DataColumn col = eventArgs.Column;
548 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
549 switch (col.ColumnMapping) {
550 case MappingType.Attribute:
551 el.SetAttribute (XmlHelper.Encode (col.ColumnName), col.Namespace, value);
553 case MappingType.SimpleContent:
554 el.InnerText = value;
556 case MappingType.Element:
558 for (int i = 0; i < el.ChildNodes.Count; i++) {
559 XmlElement c = el.ChildNodes [i] as XmlElement;
560 if (c != null && c.LocalName == XmlHelper.Encode (col.ColumnName) && c.NamespaceURI == col.Namespace) {
567 XmlElement cel = CreateElement (col.Prefix, XmlHelper.Encode (col.ColumnName), col.Namespace);
568 cel.InnerText = value;
569 el.AppendChild (cel);
572 // FIXME: how to handle hidden?
575 raiseDocumentEvents = escapedRaiseDocumentEvents;
579 private void OnDataTableRowDeleted (object sender,
580 DataRowChangeEventArgs eventArgs)
582 if (!raiseDataSetEvents)
584 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
585 raiseDocumentEvents = false;
588 // This code is obsolete XmlDataDocument one
590 XmlElement el = GetElementFromRow (eventArgs.Row);
594 el.ParentNode.RemoveChild (el);
596 raiseDocumentEvents = escapedRaiseDocumentEvents;
600 [MonoTODO ("Need to handle hidden columns? - see comments on each private method")]
601 private void OnDataTableRowChanged (object sender, DataRowChangeEventArgs eventArgs)
603 if (!raiseDataSetEvents)
605 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
606 raiseDocumentEvents = false;
609 switch (eventArgs.Action) {
611 case DataRowAction.Delete:
612 OnDataTableRowDeleted (sender, eventArgs);
615 case DataRowAction.Add:
616 OnDataTableRowAdded (eventArgs);
619 case DataRowAction.Rollback:
620 OnDataTableRowRollback (eventArgs);
626 raiseDocumentEvents = escapedRaiseDocumentEvents;
630 // Added - see FillNodeChildrenFromRow comment
631 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
633 if (!raiseDataSetEvents)
636 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
637 raiseDocumentEvents = false;
640 // Create row element. Row's name same as TableName
641 DataRow row = args.Row;
643 // create document element if it does not exist
644 if (DocumentElement == null)
645 this.AppendChild (CreateElement (XmlHelper.Encode (DataSet.DataSetName)));
647 DataTable table= args.Row.Table;
648 XmlElement element = GetElementFromRow (row);
650 element = CreateElement (table.Prefix, XmlHelper.Encode (table.TableName), table.Namespace);
652 if (element.ChildNodes.Count == 0)
653 FillNodeChildrenFromRow (row, element);
655 if (element.ParentNode == null) {
656 // parent is not always DocumentElement.
657 XmlElement parent = null;
659 if (table.ParentRelations.Count > 0) {
660 for (int i = 0; i < table.ParentRelations.Count; i++) {
661 DataRelation rel = table.ParentRelations [i];
662 DataRow parentRow = row.GetParentRow (rel);
663 if (parentRow == null)
665 parent = GetElementFromRow (parentRow);
669 // The row might be orphan. In such case, the
670 // element is appended to DocumentElement.
672 parent = DocumentElement;
673 parent.AppendChild (element);
676 raiseDocumentEvents = escapedRaiseDocumentEvents;
680 private void FillNodeChildrenFromRow (DataRow row, XmlElement element)
682 DataTable table = row.Table;
683 // fill columns for the row
684 for (int i = 0; i < table.Columns.Count; i++) {
685 DataColumn col = table.Columns [i];
686 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
687 switch (col.ColumnMapping) {
688 case MappingType.Element:
689 XmlElement el = CreateElement (col.Prefix, XmlHelper.Encode (col.ColumnName), col.Namespace);
690 el.InnerText = value;
691 element.AppendChild (el);
693 case MappingType.Attribute:
694 XmlAttribute attr = CreateAttribute (col.Prefix, XmlHelper.Encode (col.ColumnName), col.Namespace);
696 element.SetAttributeNode (attr);
698 case MappingType.SimpleContent:
699 XmlText text = CreateTextNode (value);
700 element.AppendChild (text);
702 // FIXME: how to handle hidden?
708 [MonoTODO ("It does not look complete.")]
709 private void OnDataTableRowRollback (DataRowChangeEventArgs args)
711 if (!raiseDataSetEvents)
713 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
714 raiseDocumentEvents = false;
717 DataRow r = args.Row;
718 XmlElement el = GetElementFromRow (r);
721 DataTable tab = r.Table;
722 ArrayList al = new ArrayList ();
723 foreach (XmlAttribute attr in el.Attributes) {
724 DataColumn col = tab.Columns [XmlHelper.Decode (attr.LocalName)];
730 attr.Value = r [col].ToString ();
733 foreach (XmlAttribute attr in al)
734 el.RemoveAttributeNode (attr);
736 foreach (XmlNode child in el.ChildNodes) {
737 if (child.NodeType == XmlNodeType.Element) {
738 DataColumn col = tab.Columns [XmlHelper.Decode (child.LocalName)];
743 child.InnerText = r [col].ToString ();
747 foreach (XmlNode n in al)
750 raiseDocumentEvents = escapedRaiseDocumentEvents;
754 #endregion // DataSet event handlers
756 #region Private methods
757 private void InitDelegateFields ()
759 columnChanged = new DataColumnChangeEventHandler (OnDataTableColumnChanged);
760 rowDeleted = new DataRowChangeEventHandler (OnDataTableRowDeleted);
761 rowChanged = new DataRowChangeEventHandler (OnDataTableRowChanged);
762 tablesChanged = new CollectionChangeEventHandler (OnDataTableChanged);
765 private void RemoveXmlDocumentListeners ()
767 this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
768 this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
769 this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
770 this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
771 this.NodeRemoving -= new XmlNodeChangedEventHandler (OnNodeRemoving);
772 this.NodeRemoved -= new XmlNodeChangedEventHandler (OnNodeRemoved);
775 private void AddXmlDocumentListeners ()
777 this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
778 this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
779 this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
780 this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
781 this.NodeRemoving += new XmlNodeChangedEventHandler (OnNodeRemoving);
782 this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
785 internal static object StringToObject (Type type, string value)
787 if (value == null || value == String.Empty)
790 switch (Type.GetTypeCode (type)) {
791 case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
792 case TypeCode.Byte: return XmlConvert.ToByte (value);
793 case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
795 case TypeCode.DateTime: return XmlConvert.ToDateTime (value, XmlDateTimeSerializationMode.Unspecified);
797 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
799 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
800 case TypeCode.Double: return XmlConvert.ToDouble (value);
801 case TypeCode.Int16: return XmlConvert.ToInt16 (value);
802 case TypeCode.Int32: return XmlConvert.ToInt32 (value);
803 case TypeCode.Int64: return XmlConvert.ToInt64 (value);
804 case TypeCode.SByte: return XmlConvert.ToSByte (value);
805 case TypeCode.Single: return XmlConvert.ToSingle (value);
806 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
807 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
808 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
811 if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
812 if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
813 if (type == typeof (byte[])) return Convert.FromBase64String (value);
815 return Convert.ChangeType (value, type);
817 #endregion // Private methods