@@ -171,7 +171,12 @@ static IPBanConfig()
171171 private IPBanConfig ( XmlDocument doc , IDnsLookup dns = null , IDnsServerList dnsList = null , IHttpRequestMaker httpRequestMaker = null )
172172 {
173173 // deserialize with XmlDocument for fine grained control
174- foreach ( XmlNode node in doc . SelectNodes ( "/configuration/appSettings/add" ) )
174+ var appSettingsNodes = doc . SelectNodes ( "/configuration/appSettings/add" ) ;
175+ if ( appSettingsNodes is null || appSettingsNodes . Count == 0 )
176+ {
177+ throw new InvalidDataException ( "Configuration is missing or has empty /configuration/appSettings element. This element name is case sensitive. Please check your config." ) ;
178+ }
179+ foreach ( XmlNode node in appSettingsNodes )
175180 {
176181 appSettings [ node . Attributes [ "key" ] . Value ] = node . Attributes [ "value" ] . Value ;
177182 }
@@ -761,6 +766,106 @@ public static string ChangeConfigAppSetting(string config, string key, string ne
761766 return doc . OuterXml ;
762767 }
763768
769+ /// <summary>
770+ /// Merge two configurations
771+ /// </summary>
772+ /// <param name="xmlBase">Base configuration</param>
773+ /// <param name="xmlOverride">Override configuration</param>
774+ /// <returns>Merged configuration</returns>
775+ /// <exception cref="ArgumentException">Base xml is null or white space</exception>
776+ public static XmlDocument MergeXml ( string xmlBase , string xmlOverride )
777+ {
778+ if ( string . IsNullOrWhiteSpace ( xmlBase ) )
779+ {
780+ throw new ArgumentException ( "Cannot merge null base xml" ) ;
781+ }
782+
783+ XmlDocument docBase = new ( ) ;
784+ docBase . LoadXml ( xmlBase ) ;
785+
786+ if ( string . IsNullOrWhiteSpace ( xmlOverride ) )
787+ {
788+ return docBase ;
789+ }
790+
791+ XmlDocument docOverride = new ( ) ;
792+ docOverride . LoadXml ( xmlOverride ) ;
793+
794+ XmlNode logFilesOverride = docOverride . SelectSingleNode ( "/configuration/LogFilesToParse/LogFiles" ) ;
795+ XmlNode logFilesBase = docBase . SelectSingleNode ( "/configuration/LogFilesToParse/LogFiles" ) ?? logFilesOverride ;
796+ if ( logFilesBase is not null &&
797+ logFilesOverride is not null &&
798+ logFilesBase != logFilesOverride )
799+ {
800+ foreach ( XmlNode overrideNode in logFilesOverride )
801+ {
802+ if ( overrideNode . NodeType == XmlNodeType . Element )
803+ {
804+ logFilesBase . AppendChild ( docBase . ImportNode ( overrideNode , true ) ) ;
805+ }
806+ }
807+ }
808+
809+ XmlNode expressionsBlockOverride = docOverride . SelectSingleNode ( "/configuration/ExpressionsToBlock/Groups" ) ;
810+ XmlNode expressionsBlockBase = docBase . SelectSingleNode ( "/configuration/ExpressionsToBlock/Groups" ) ?? expressionsBlockOverride ;
811+ if ( expressionsBlockBase is not null &&
812+ expressionsBlockOverride is not null &&
813+ expressionsBlockBase != expressionsBlockOverride )
814+ {
815+ foreach ( XmlNode overrideNode in expressionsBlockOverride )
816+ {
817+ if ( overrideNode . NodeType == XmlNodeType . Element )
818+ {
819+ expressionsBlockBase . AppendChild ( docBase . ImportNode ( overrideNode , true ) ) ;
820+ }
821+ }
822+ }
823+
824+ XmlNode expressionsNotifyOverride = docOverride . SelectSingleNode ( "/configuration/ExpressionsToNotify/Groups" ) ;
825+ XmlNode expressionsNotifyBase = docBase . SelectSingleNode ( "/configuration/ExpressionsToNotify/Groups" ) ?? expressionsNotifyOverride ;
826+ if ( expressionsNotifyBase is not null &&
827+ expressionsNotifyOverride is not null &&
828+ expressionsNotifyBase != expressionsNotifyOverride )
829+ {
830+ foreach ( XmlNode overrideNode in expressionsNotifyOverride )
831+ {
832+ if ( overrideNode . NodeType == XmlNodeType . Element )
833+ {
834+ expressionsNotifyBase . AppendChild ( docBase . ImportNode ( overrideNode , true ) ) ;
835+ }
836+ }
837+ }
838+
839+ XmlNode appSettingsOverride = docOverride . SelectSingleNode ( "/configuration/appSettings" ) ;
840+ XmlNode appSettingsBase = docBase . SelectSingleNode ( "/configuration/appSettings" ) ?? appSettingsOverride ;
841+ if ( appSettingsBase is not null &&
842+ appSettingsOverride is not null &&
843+ appSettingsBase != appSettingsOverride )
844+ {
845+ foreach ( XmlNode overrideNode in appSettingsOverride )
846+ {
847+ if ( overrideNode . NodeType == XmlNodeType . Element )
848+ {
849+ string xpath = $ "/configuration/appSettings/add[@key='{ overrideNode . Attributes [ "key" ] . Value } ']";
850+ XmlNode existing = appSettingsBase . SelectSingleNode ( xpath ) ;
851+ if ( existing is null )
852+ {
853+ // create a new node
854+ appSettingsBase . AppendChild ( docBase . ImportNode ( overrideNode , true ) ) ;
855+ }
856+ else
857+ {
858+ // replace existing node
859+ string overrideValue = overrideNode . Attributes [ "value" ] ? . Value ?? string . Empty ;
860+ existing . Attributes [ "value" ] . Value = overrideValue ;
861+ }
862+ }
863+ }
864+ }
865+
866+ return docBase ;
867+ }
868+
764869 /// <inheritdoc />
765870 public bool IsWhitelisted ( string entry ) => whitelistFilter . IsFiltered ( entry ) ;
766871
0 commit comments