using System; using System.Collections.Generic; using System.Text; using System.Xml; namespace ChadSoft.Utils.Configy { /// /// Merges configuration files /// public static class ConfigurationMerger { #region Variables private static string _XmlVersion = "1.0"; private static string _XmlEncoding = "UTF-8"; private static string _XmlStandalone = null; #endregion #region Methods #endregion #region Static Methods /// /// Merges two documents of the same type /// /// The document containing the "master" template with default values. /// The second document to merge. /// The merged result of the two source documents public static XmlDocument Merge(XmlDocument masterDocument, XmlDocument overrideDocument) { XmlDocument merged = new XmlDocument(); // Create & populate the root node XmlElement rootNode = merged.CreateElement(masterDocument.DocumentElement.Name); switch (rootNode.Name) { case "appSettings": MergeNodes( masterDocument.DocumentElement, overrideDocument.DocumentElement, "//add", "key", ref rootNode); break; case "connectionStrings": MergeNodes( masterDocument.DocumentElement, overrideDocument.DocumentElement, "//add", "name", ref rootNode); break; default: throw new NotSupportedException(String.Format( "Element name {0} is not a supported merge element!", masterDocument.DocumentElement.Name)); } // Add 'em to the document merged.AppendChild(merged.CreateXmlDeclaration(_XmlVersion, _XmlEncoding, _XmlStandalone)); merged.AppendChild(rootNode); return merged; } /// /// Merges two nodes of the same type /// /// The first node to merge. /// The second node to merge. /// The XPath query used to select the nodes being merged. /// The id key. /// The doc. /// The value key. /// /// The merged result of the two source nodes /// internal static bool MergeNodes(XmlNode masterParent, XmlNode overrideParent, string xPath, string idKey, ref XmlElement destination) { XmlDocument doc = destination.OwnerDocument; XmlNode source2Node; XmlElement tmpNode; foreach (XmlNode source1Node in masterParent.SelectNodes(xPath)) { source2Node = overrideParent.SelectSingleNode( String.Format("{0}[@{1}='{2}']", xPath, idKey, source1Node.Attributes[idKey].Value)); if (source2Node != null) { tmpNode = doc.CreateElement(source1Node.Name, source1Node.NamespaceURI); if (MergeAttributes(source1Node as XmlElement, source2Node as XmlElement, ref tmpNode)) destination.AppendChild(tmpNode); } } return true; } public static bool MergeAttributes(XmlElement masterElement, XmlElement overrideElement, ref XmlElement destination) { XmlElement overrideClone = overrideElement.Clone() as XmlElement; // Overwrite any duplicate attributes XmlAttribute tmpAttribute; foreach (XmlAttribute attr in masterElement.Attributes) { // Create a new attribute tmpAttribute = destination.OwnerDocument.CreateAttribute(attr.Name, attr.NamespaceURI); // If the override clone has this attribute, overwrite the master attribute if (overrideClone.HasAttribute(attr.Name, attr.NamespaceURI)) { attr.Value = overrideClone.GetAttribute(attr.Name, attr.NamespaceURI); overrideClone.RemoveAttribute(attr.Name, attr.NamespaceURI); } // Set the value on the new attribute tmpAttribute.Value = attr.Value; // Append the attribute to the destination node destination.Attributes.Append(tmpAttribute); } // Add any additional attributes on the second element that weren't copied over foreach (XmlAttribute attr in overrideClone.Attributes) { destination.Attributes.Append( (XmlAttribute)destination.OwnerDocument.ImportNode(attr, false)); } return true; } #endregion } }