| /******************************************************************************* | 
 |  * You may amend and distribute as you like, but don't remove this header! | 
 |  * | 
 |  * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | 
 |  * See http://www.codeplex.com/EPPlus for details. | 
 |  * | 
 |  * Copyright (C) 2011  Jan Källman | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 
 |  * See the GNU Lesser General Public License for more details. | 
 |  * | 
 |  * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | 
 |  * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | 
 |  * | 
 |  * All code and executables are provided "as is" with no warranty either express or implied. | 
 |  * The author accepts no liability for any damage or loss of business that this product may cause. | 
 |  * | 
 |  * Code change notes: | 
 |  * | 
 |  * Author							Change						         Date | 
 |  * ****************************************************************************** | 
 |  * Jan Källman		    Initial Release		         2009-10-01 | 
 |  * Jan Källman		    License changed GPL-->LGPL 2011-12-27 | 
 |  * Eyal Seagull       Add "CreateComplexNode"    2012-04-03 | 
 |  * Eyal Seagull       Add "DeleteTopNode"        2012-04-13 | 
 |  *******************************************************************************/ | 
 |  | 
 | using System; | 
 | using System.Collections.Immutable; | 
 | using System.Globalization; | 
 | using System.IO; | 
 | using System.Text; | 
 | using System.Xml; | 
 |  | 
 | namespace AppsheetEpplus; | 
 |  | 
 | /// <summary> | 
 | /// Help class containing XML functions. | 
 | /// Can be Inherited | 
 | /// </summary> | 
 | public abstract class XmlHelper { | 
 |   internal delegate int ChangedEventHandler(StyleBase sender, StyleChangeEventArgs e); | 
 |  | 
 |   internal XmlHelper(XmlNamespaceManager nameSpaceManager) { | 
 |     TopNode = null; | 
 |     NameSpaceManager = nameSpaceManager; | 
 |   } | 
 |  | 
 |   internal XmlHelper(XmlNamespaceManager nameSpaceManager, XmlNode topNode) { | 
 |     TopNode = topNode; | 
 |     NameSpaceManager = nameSpaceManager; | 
 |   } | 
 |  | 
 |   //internal bool ChangedFlag; | 
 |   internal XmlNamespaceManager NameSpaceManager { get; set; } | 
 |  | 
 |   internal XmlNode TopNode { get; set; } | 
 |  | 
 |   /// <summary> | 
 |   /// Schema order list | 
 |   /// </summary> | 
 |   protected virtual ImmutableArray<string> SchemaNodeOrder => ImmutableArray<string>.Empty; | 
 |  | 
 |   internal XmlNode CreateNode(string path) { | 
 |     if (path == "") { | 
 |       return TopNode; | 
 |     } | 
 |     return CreateNode(path, false); | 
 |   } | 
 |  | 
 |   internal XmlNode CreateNode(string path, bool insertFirst) { | 
 |     XmlNode node = TopNode; | 
 |     XmlNode prependNode = null; | 
 |     foreach (string subPath in path.Split('/')) { | 
 |       XmlNode subNode = node.SelectSingleNode(subPath, NameSpaceManager); | 
 |       if (subNode == null) { | 
 |         string nodeName; | 
 |         string nodePrefix; | 
 |  | 
 |         string nameSpaceUri; | 
 |         string[] nameSplit = subPath.Split(':'); | 
 |  | 
 |         if (SchemaNodeOrder.Length > 0 && subPath[0] != '@') { | 
 |           insertFirst = false; | 
 |           prependNode = GetPrependNode(subPath, node); | 
 |         } | 
 |  | 
 |         if (nameSplit.Length > 1) { | 
 |           nodePrefix = nameSplit[0]; | 
 |           if (nodePrefix[0] == '@') { | 
 |             nodePrefix = nodePrefix.Substring(1, nodePrefix.Length - 1); | 
 |           } | 
 |           nameSpaceUri = NameSpaceManager.LookupNamespace(nodePrefix); | 
 |           nodeName = nameSplit[1]; | 
 |         } else { | 
 |           nodePrefix = ""; | 
 |           nameSpaceUri = ""; | 
 |           nodeName = nameSplit[0]; | 
 |         } | 
 |         if (subPath.StartsWith("@")) { | 
 |           XmlAttribute addedAtt = node.OwnerDocument.CreateAttribute( | 
 |               subPath.Substring(1, subPath.Length - 1), | 
 |               nameSpaceUri); //nameSpaceURI | 
 |           node.Attributes.Append(addedAtt); | 
 |         } else { | 
 |           if (nodePrefix == "") { | 
 |             subNode = node.OwnerDocument.CreateElement(nodeName, nameSpaceUri); | 
 |           } else { | 
 |             if (nodePrefix == "" | 
 |                 || (node.OwnerDocument != null | 
 |                         && node.OwnerDocument.DocumentElement != null | 
 |                         && node.OwnerDocument.DocumentElement.NamespaceURI == nameSpaceUri | 
 |                         && node.OwnerDocument.DocumentElement.Prefix == "")) { | 
 |               subNode = node.OwnerDocument.CreateElement(nodeName, nameSpaceUri); | 
 |             } else { | 
 |               subNode = node.OwnerDocument.CreateElement(nodePrefix, nodeName, nameSpaceUri); | 
 |             } | 
 |           } | 
 |           if (prependNode != null) { | 
 |             node.InsertBefore(subNode, prependNode); | 
 |             prependNode = null; | 
 |           } else if (insertFirst) { | 
 |             node.PrependChild(subNode); | 
 |           } else { | 
 |             node.AppendChild(subNode); | 
 |           } | 
 |         } | 
 |       } | 
 |       node = subNode; | 
 |     } | 
 |     return node; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Options to insert a node in the XmlDocument | 
 |   /// </summary> | 
 |   internal enum eNodeInsertOrder { | 
 |     /// <summary> | 
 |     /// Insert as first node of "topNode" | 
 |     /// </summary> | 
 |     First, | 
 |  | 
 |     /// <summary> | 
 |     /// Insert as the last child of "topNode" | 
 |     /// </summary> | 
 |     Last, | 
 |  | 
 |     /// <summary> | 
 |     /// Insert after the "referenceNode" | 
 |     /// </summary> | 
 |     After, | 
 |  | 
 |     /// <summary> | 
 |     /// Insert before the "referenceNode" | 
 |     /// </summary> | 
 |     Before, | 
 |  | 
 |     /// <summary> | 
 |     /// Use the Schema List to insert in the right order. If the Schema list | 
 |     /// is null or empty, consider "Last" as the selected option | 
 |     /// </summary> | 
 |     SchemaOrder, | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Create a complex node. Insert the node according to the <paramref name="path"/> | 
 |   /// using the <paramref name="topNode"/> as the parent | 
 |   /// </summary> | 
 |   /// <param name="topNode"></param> | 
 |   /// <param name="path"></param> | 
 |   /// <returns></returns> | 
 |   internal XmlNode CreateComplexNode(XmlNode topNode, string path) { | 
 |     return CreateComplexNode(topNode, path, eNodeInsertOrder.SchemaOrder, null); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Creates complex XML nodes | 
 |   /// </summary> | 
 |   /// <remarks> | 
 |   /// 1. "d:conditionalFormatting" | 
 |   ///		1.1. Creates/find the first "conditionalFormatting" node | 
 |   /// | 
 |   /// 2. "d:conditionalFormatting/@sqref" | 
 |   ///		2.1. Creates/find the first "conditionalFormatting" node | 
 |   ///		2.2. Creates (if not exists) the @sqref attribute | 
 |   /// | 
 |   /// 3. "d:conditionalFormatting/@id='7'/@sqref='A9:B99'" | 
 |   ///		3.1. Creates/find the first "conditionalFormatting" node | 
 |   ///		3.2. Creates/update its @id attribute to "7" | 
 |   ///		3.3. Creates/update its @sqref attribute to "A9:B99" | 
 |   /// | 
 |   /// 4. "d:conditionalFormatting[@id='7']/@sqref='X1:X5'" | 
 |   ///		4.1. Creates/find the first "conditionalFormatting" node with @id=7 | 
 |   ///		4.2. Creates/update its @sqref attribute to "X1:X5" | 
 |   /// | 
 |   /// 5. "d:conditionalFormatting[@id='7']/@id='8'/@sqref='X1:X5'/d:cfRule/@id='AB'" | 
 |   ///		5.1. Creates/find the first "conditionalFormatting" node with @id=7 | 
 |   ///		5.2. Set its @id attribute to "8" | 
 |   ///		5.2. Creates/update its @sqref attribute and set it to "X1:X5" | 
 |   ///		5.3. Creates/find the first "cfRule" node (inside the node) | 
 |   ///		5.4. Creates/update its @id attribute to "AB" | 
 |   /// | 
 |   /// 6. "d:cfRule/@id=''" | 
 |   ///		6.1. Creates/find the first "cfRule" node | 
 |   ///		6.1. Remove the @id attribute | 
 |   ///	</remarks> | 
 |   /// <param name="topNode"></param> | 
 |   /// <param name="path"></param> | 
 |   /// <param name="nodeInsertOrder"></param> | 
 |   /// <param name="referenceNode"></param> | 
 |   /// <returns>The last node creates/found</returns> | 
 |   internal XmlNode CreateComplexNode( | 
 |       XmlNode topNode, | 
 |       string path, | 
 |       eNodeInsertOrder nodeInsertOrder, | 
 |       XmlNode referenceNode) { | 
 |     // Path is obrigatory | 
 |     if ((path == null) || (path == string.Empty)) { | 
 |       return topNode; | 
 |     } | 
 |  | 
 |     XmlNode node = topNode; | 
 |  | 
 |     //TODO: BUG: when the "path" contains "/" in an attrribue value, it gives an error. | 
 |  | 
 |     // Separate the XPath to Nodes and Attributes | 
 |     foreach (string subPath in path.Split('/')) { | 
 |       // The subPath can be any one of those: | 
 |       // nodeName | 
 |       // x:nodeName | 
 |       // nodeName[find criteria] | 
 |       // x:nodeName[find criteria] | 
 |       // @attribute | 
 |       // @attribute='attribute value' | 
 |  | 
 |       // Check if the subPath has at least one character | 
 |       if (subPath.Length > 0) { | 
 |         // Check if the subPath is an attribute (with or without value) | 
 |         if (subPath.StartsWith("@")) { | 
 |           // @attribute										--> Create attribute | 
 |           // @attribute=''								--> Remove attribute | 
 |           // @attribute='attribute value' --> Create attribute + update value | 
 |           string[] attributeSplit = subPath.Split('='); | 
 |           string attributeName = attributeSplit[0].Substring(1, attributeSplit[0].Length - 1); | 
 |           string attributeValue = null; // Null means no attribute value | 
 |  | 
 |           // Check if we have an attribute value to set | 
 |           if (attributeSplit.Length > 1) { | 
 |             // Remove the ' or " from the attribute value | 
 |             attributeValue = attributeSplit[1].Replace("'", "").Replace("\"", ""); | 
 |           } | 
 |  | 
 |           // Get the attribute (if exists) | 
 |           XmlAttribute attribute = (XmlAttribute)(node.Attributes.GetNamedItem(attributeName)); | 
 |  | 
 |           // Remove the attribute if value is empty (not null) | 
 |           if (attributeValue == string.Empty) { | 
 |             // Only if the attribute exists | 
 |             if (attribute != null) { | 
 |               node.Attributes.Remove(attribute); | 
 |             } | 
 |           } else { | 
 |             // Create the attribue if does not exists | 
 |             if (attribute == null) { | 
 |               // Create the attribute | 
 |               attribute = node.OwnerDocument.CreateAttribute(attributeName); | 
 |  | 
 |               // Add it to the current node | 
 |               node.Attributes.Append(attribute); | 
 |             } | 
 |  | 
 |             // Update the attribute value | 
 |             if (attributeValue != null) { | 
 |               node.Attributes[attributeName].Value = attributeValue; | 
 |             } | 
 |           } | 
 |         } else { | 
 |           // nodeName | 
 |           // x:nodeName | 
 |           // nodeName[find criteria] | 
 |           // x:nodeName[find criteria] | 
 |  | 
 |           // Look for the node (with or without filter criteria) | 
 |           XmlNode subNode = node.SelectSingleNode(subPath, NameSpaceManager); | 
 |  | 
 |           // Check if the node does not exists | 
 |           if (subNode == null) { | 
 |             string nodeName; | 
 |             string nodePrefix; | 
 |             string[] nameSplit = subPath.Split(':'); | 
 |             string nameSpaceUri; | 
 |  | 
 |             // Check if the name has a prefix like "d:nodeName" | 
 |             if (nameSplit.Length > 1) { | 
 |               nodePrefix = nameSplit[0]; | 
 |               nameSpaceUri = NameSpaceManager.LookupNamespace(nodePrefix); | 
 |               nodeName = nameSplit[1]; | 
 |             } else { | 
 |               nodePrefix = string.Empty; | 
 |               nameSpaceUri = string.Empty; | 
 |               nodeName = nameSplit[0]; | 
 |             } | 
 |  | 
 |             // Check if we have a criteria part in the node name | 
 |             if (nodeName.IndexOf("[") > 0) { | 
 |               // remove the criteria from the node name | 
 |               nodeName = nodeName.Substring(0, nodeName.IndexOf("[")); | 
 |             } | 
 |  | 
 |             if (nodePrefix == string.Empty) { | 
 |               subNode = node.OwnerDocument.CreateElement(nodeName, nameSpaceUri); | 
 |             } else { | 
 |               if (node.OwnerDocument != null | 
 |                   && node.OwnerDocument.DocumentElement != null | 
 |                   && node.OwnerDocument.DocumentElement.NamespaceURI == nameSpaceUri | 
 |                   && node.OwnerDocument.DocumentElement.Prefix == string.Empty) { | 
 |                 subNode = node.OwnerDocument.CreateElement(nodeName, nameSpaceUri); | 
 |               } else { | 
 |                 subNode = node.OwnerDocument.CreateElement(nodePrefix, nodeName, nameSpaceUri); | 
 |               } | 
 |             } | 
 |  | 
 |             // Check if we need to use the "SchemaOrder" | 
 |             if (nodeInsertOrder == eNodeInsertOrder.SchemaOrder) { | 
 |               // Check if the Schema Order List is empty | 
 |               if (SchemaNodeOrder.Length == 0) { | 
 |                 // Use the "Insert Last" option when Schema Order List is empty | 
 |                 nodeInsertOrder = eNodeInsertOrder.Last; | 
 |               } else { | 
 |                 // Find the prepend node in order to insert | 
 |                 referenceNode = GetPrependNode(nodeName, node); | 
 |  | 
 |                 if (referenceNode != null) { | 
 |                   nodeInsertOrder = eNodeInsertOrder.Before; | 
 |                 } else { | 
 |                   nodeInsertOrder = eNodeInsertOrder.Last; | 
 |                 } | 
 |               } | 
 |             } | 
 |  | 
 |             switch (nodeInsertOrder) { | 
 |               case eNodeInsertOrder.After: | 
 |                 node.InsertAfter(subNode, referenceNode); | 
 |                 referenceNode = null; | 
 |                 break; | 
 |  | 
 |               case eNodeInsertOrder.Before: | 
 |                 node.InsertBefore(subNode, referenceNode); | 
 |                 referenceNode = null; | 
 |                 break; | 
 |  | 
 |               case eNodeInsertOrder.First: | 
 |                 node.PrependChild(subNode); | 
 |                 break; | 
 |  | 
 |               case eNodeInsertOrder.Last: | 
 |                 node.AppendChild(subNode); | 
 |                 break; | 
 |             } | 
 |           } | 
 |  | 
 |           // Make the newly created node the top node when the rest of the path | 
 |           // is being evaluated. So newly created nodes will be the children of the | 
 |           // one we just created. | 
 |           node = subNode; | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     // Return the last created/found node | 
 |     return node; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// return Prepend node | 
 |   /// </summary> | 
 |   /// <param name="nodeName">name of the node to check</param> | 
 |   /// <param name="node">Topnode to check children</param> | 
 |   /// <returns></returns> | 
 |   private XmlNode GetPrependNode(string nodeName, XmlNode node) { | 
 |     int pos = GetNodePos(nodeName); | 
 |     if (pos < 0) { | 
 |       return null; | 
 |     } | 
 |     XmlNode prependNode = null; | 
 |     foreach (XmlNode childNode in node.ChildNodes) { | 
 |       int childPos = GetNodePos(childNode.Name); | 
 |       if (childPos | 
 |           > -1) //Found? | 
 |       { | 
 |         if (childPos | 
 |             > pos) //Position is before | 
 |         { | 
 |           prependNode = childNode; | 
 |           break; | 
 |         } | 
 |       } | 
 |     } | 
 |     return prependNode; | 
 |   } | 
 |  | 
 |   private int GetNodePos(string nodeName) { | 
 |     int ix = nodeName.IndexOf(":"); | 
 |     if (ix > 0) { | 
 |       nodeName = nodeName.Substring(ix + 1, nodeName.Length - (ix + 1)); | 
 |     } | 
 |     for (int i = 0; i < SchemaNodeOrder.Length; i++) { | 
 |       if (nodeName == SchemaNodeOrder[i]) { | 
 |         return i; | 
 |       } | 
 |     } | 
 |     return -1; | 
 |   } | 
 |  | 
 |   internal void DeleteAllNode(string path) { | 
 |     string[] split = path.Split('/'); | 
 |     XmlNode node = TopNode; | 
 |     foreach (string s in split) { | 
 |       node = node.SelectSingleNode(s, NameSpaceManager); | 
 |       if (node != null) { | 
 |         if (node is XmlAttribute attribute) { | 
 |           attribute.OwnerElement.Attributes.Remove(attribute); | 
 |         } else { | 
 |           node.ParentNode.RemoveChild(node); | 
 |         } | 
 |       } else { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   internal void DeleteNode(string path) { | 
 |     var node = TopNode.SelectSingleNode(path, NameSpaceManager); | 
 |     if (node != null) { | 
 |       if (node is XmlAttribute att) { | 
 |         att.OwnerElement.Attributes.Remove(att); | 
 |       } else { | 
 |         node.ParentNode.RemoveChild(node); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   internal void SetXmlNodeString(string path, string value) { | 
 |     SetXmlNodeString(TopNode, path, value, false, false); | 
 |   } | 
 |  | 
 |   internal void SetXmlNodeString(string path, string value, bool removeIfBlank) { | 
 |     SetXmlNodeString(TopNode, path, value, removeIfBlank, false); | 
 |   } | 
 |  | 
 |   internal void SetXmlNodeString(XmlNode node, string path, string value, bool removeIfBlank) { | 
 |     SetXmlNodeString(node, path, value, removeIfBlank, false); | 
 |   } | 
 |  | 
 |   internal void SetXmlNodeString( | 
 |       XmlNode node, | 
 |       string path, | 
 |       string value, | 
 |       bool removeIfBlank, | 
 |       bool insertFirst) { | 
 |     if (node == null) { | 
 |       return; | 
 |     } | 
 |     if (value == "" && removeIfBlank) { | 
 |       DeleteAllNode(path); | 
 |     } else { | 
 |       XmlNode nameNode = node.SelectSingleNode(path, NameSpaceManager); | 
 |       if (nameNode == null) { | 
 |         CreateNode(path, insertFirst); | 
 |         nameNode = node.SelectSingleNode(path, NameSpaceManager); | 
 |       } | 
 |       //if (nameNode.InnerText != value) HasChanged(); | 
 |       nameNode.InnerText = value; | 
 |     } | 
 |   } | 
 |  | 
 |   internal void SetXmlNodeBool(string path, bool value) { | 
 |     SetXmlNodeString(TopNode, path, value ? "1" : "0", false, false); | 
 |   } | 
 |  | 
 |   internal void SetXmlNodeBool(string path, bool value, bool removeIf) { | 
 |     if (value == removeIf) { | 
 |       var node = TopNode.SelectSingleNode(path, NameSpaceManager); | 
 |       if (node != null) { | 
 |         if (node is XmlAttribute attribute) { | 
 |           var elem = attribute.OwnerElement; | 
 |           elem.ParentNode.RemoveChild(elem); | 
 |         } else { | 
 |           TopNode.RemoveChild(node); | 
 |         } | 
 |       } | 
 |     } else { | 
 |       SetXmlNodeString(TopNode, path, value ? "1" : "0", false, false); | 
 |     } | 
 |   } | 
 |  | 
 |   internal bool ExistNode(string path) { | 
 |     if (TopNode == null || TopNode.SelectSingleNode(path, NameSpaceManager) == null) { | 
 |       return false; | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   internal bool? GetXmlNodeBoolNullable(string path) { | 
 |     var value = GetXmlNodeString(path); | 
 |     if (string.IsNullOrEmpty(value)) { | 
 |       return null; | 
 |     } | 
 |     return GetXmlNodeBool(path); | 
 |   } | 
 |  | 
 |   internal bool GetXmlNodeBool(string path) { | 
 |     return GetXmlNodeBool(path, false); | 
 |   } | 
 |  | 
 |   internal bool GetXmlNodeBool(string path, bool blankValue) { | 
 |     string value = GetXmlNodeString(path); | 
 |     if (value == "1" | 
 |         || value == "-1" | 
 |         || value.Equals("true", StringComparison.InvariantCultureIgnoreCase)) { | 
 |       return true; | 
 |     } | 
 |     if (value == "") { | 
 |       return blankValue; | 
 |     } | 
 |     return false; | 
 |   } | 
 |  | 
 |   internal int GetXmlNodeInt(string path) { | 
 |     if (int.TryParse(GetXmlNodeString(path), out var i)) { | 
 |       return i; | 
 |     } | 
 |     return int.MinValue; | 
 |   } | 
 |  | 
 |   internal int? GetXmlNodeIntNull(string path) { | 
 |     string s = GetXmlNodeString(path); | 
 |     if (s != "" && int.TryParse(s, out var i)) { | 
 |       return i; | 
 |     } | 
 |     return null; | 
 |   } | 
 |  | 
 |   internal decimal GetXmlNodeDecimal(string path) { | 
 |     if (decimal.TryParse( | 
 |         GetXmlNodeString(path), | 
 |         NumberStyles.Any, | 
 |         CultureInfo.InvariantCulture, | 
 |         out var d)) { | 
 |       return d; | 
 |     } | 
 |     return 0; | 
 |   } | 
 |  | 
 |   internal decimal? GetXmlNodeDecimalNull(string path) { | 
 |     if (decimal.TryParse( | 
 |         GetXmlNodeString(path), | 
 |         NumberStyles.Any, | 
 |         CultureInfo.InvariantCulture, | 
 |         out var d)) { | 
 |       return d; | 
 |     } | 
 |     return null; | 
 |   } | 
 |  | 
 |   internal double? GetXmlNodeDoubleNull(string path) { | 
 |     string s = GetXmlNodeString(path); | 
 |     if (s == "") { | 
 |       return null; | 
 |     } | 
 |     if (double.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out var v)) { | 
 |       return v; | 
 |     } | 
 |     return null; | 
 |   } | 
 |  | 
 |   internal double GetXmlNodeDouble(string path) { | 
 |     string s = GetXmlNodeString(path); | 
 |     if (s == "") { | 
 |       return double.NaN; | 
 |     } | 
 |     if (double.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out var v)) { | 
 |       return v; | 
 |     } | 
 |     return double.NaN; | 
 |   } | 
 |  | 
 |   internal string GetXmlNodeString(XmlNode node, string path) { | 
 |     if (node == null) { | 
 |       return ""; | 
 |     } | 
 |  | 
 |     XmlNode nameNode = node.SelectSingleNode(path, NameSpaceManager); | 
 |  | 
 |     if (nameNode != null) { | 
 |       if (nameNode.NodeType == XmlNodeType.Attribute) { | 
 |         return nameNode.Value ?? ""; | 
 |       } | 
 |       return nameNode.InnerText; | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   internal string GetXmlNodeString(string path) { | 
 |     return GetXmlNodeString(TopNode, path); | 
 |   } | 
 |  | 
 |   internal static void LoadXmlSafe(XmlDocument xmlDoc, Stream stream) { | 
 |     XmlReaderSettings settings = new XmlReaderSettings(); | 
 |     //Disable entity parsing (to aviod xmlbombs, External Entity Attacks etc). | 
 |     settings.ProhibitDtd = true; | 
 |     XmlReader reader = XmlReader.Create(stream, settings); | 
 |     xmlDoc.Load(reader); | 
 |   } | 
 |  | 
 |   internal static void LoadXmlSafe(XmlDocument xmlDoc, string xml, Encoding encoding) { | 
 |     var stream = new MemoryStream(encoding.GetBytes(xml)); | 
 |     LoadXmlSafe(xmlDoc, stream); | 
 |   } | 
 | } |