XmlConfigMerge - Merge config file settings
A class library I developed required various system.serviceModel settings present in the application configuration that uses the library. There needed to be a way to modify the application configuration file during library installation. To make my life easy, I modified the XmlConfigMergeclass by buc to accept XDocument objects as the master configuration. I used this to upgrade the configuration files with XML stored within a library using an Installer class:
Imports System.Configuration.Install
''' <summary>Installer class for all the necessary components for the library.</summary>
<System.ComponentModel.RunInstaller(True)> _
Public Class LibraryInstaller
Inherits Installer
Dim masterConfig As XDocument = _
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IService">
<security mode="None">
<transport clientCredentialType="Windows"
protectionLevel="EncryptAndSign"/>
<message clientCredentialType="Windows"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:81/ServiceEndpoint"
binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_IService"
contract="ServiceReference.IService"
name="NetTcpBinding_IService"/>
</client>
</system.serviceModel>
</configuration>
Public Overrides Sub Commit(savedState As System.Collections.IDictionary)
MyBase.Commit(savedState)
Dim configFileMgr As ConfigFileManager
'expecting a list of configuration files to update with the necessary
'configuration nodes defined in the configuration XDocument variable
Dim configFilesParameter As String = _
Me.Context.Parameters("ConfigurationFiles")
If Not String.IsNullOrWhiteSpace(configFilesParameter) Then
Dim configFiles() As String = configFilesParameter.Split(";"c)
For Each c As String In configFiles
configFileMgr = New ConfigFileManager(masterConfig, c)
configFileMgr.Save()
Next
End If
End Sub
End Class
Now whichever install package we use to install this library, we include the custom actions defined within the library. The necessary XML nodes will be merged into whatever application configuration files are passed to the library Commit method in the ConfigurationFiles parameter.
Here's the modified ConfigFileManager that accepts XDocument as the master configuration:
'XmlConfigMerge - Merge config file settings
'By buc
'http://www.codeproject.com/KB/install/XmlConfigMerge.aspx?display=Print
'Posted: 13 Jun 2004
'Downloaded 2/17/09
'converted to VB by http://www.developerfusion.com/tools/convert/csharp-to-vb/
'Modified 2/2/2012 by Tom Adams to accept XmlDocument as the master configuration
Imports System.Xml
Imports System.IO
Imports System.Text.RegularExpressions
''' <summary>Manages config file reading and writing, and optional merging of two config
''' files.</summary>
<Serializable()> _
Public Class ConfigFileManager
Private Shared _typeParsePattern As New Regex("([^,]+),")
Private m_configPath As String
Private m_xmlDocument As XmlDocument
#Region "Constructors"
''' <summary>Initializes a new instance of the ConfigFileManager class.</summary>
''' <param name="masterConfigPath">The file path to the master configuration to
''' modify.</param>
''' <remarks>The resulting merge will save to the file path specified in the
''' <paramref name="masterConfigPath">masterConfigPath</paramref> argument.</remarks>
Public Sub New(ByVal masterConfigPath As String)
Me.New(masterConfigPath, Nothing)
End Sub
''' <summary>Initializes a new instance of the ConfigFileManager class.</summary>
''' <param name="masterConfigPath">The file path to the master configuration to
''' merge.</param>
''' <param name="mergeFromConfigPath">The file path to the configuration to merge
''' from.</param>
''' <remarks>The resulting merge will save to the file path specified in the
''' <paramref name="masterConfigPath">masterConfigPath</paramref> argument.</remarks>
''' <exception cref="ArgumentException">If
''' <paramref name="mergeFromConfigPath">mergeFromConfigPath</paramref> is specified
''' but does not exist.</exception>
Public Sub New(ByVal masterConfigPath As String, ByVal mergeFromConfigPath As String)
Me.New(masterConfigPath, mergeFromConfigPath, False)
'makeMergeFromConfigPathTheSavePath
End Sub
''' <summary>Initializes a new instance of the ConfigFileManager class.</summary>
''' <param name="masterConfigPath">The file path to the master configuration to
''' merge.</param>
''' <param name="mergeFromConfigPath">The file path to the configuration to merge
''' from.</param>
''' <param name="makeMergeFromConfigPathTheSavePath">If <c>True</c>, then the
''' resulting merge will save to the file path specified in the
''' <paramref name="mergeFromConfigPath">mergeFromConfigPath</paramref> argument.If
''' <c>False</c>, then the resulting merge will save to
''' the file path specified in the
''' <paramref name="masterConfigPath">masterConfigPath</paramref> argument.</param>
''' <exception cref="ArgumentException">If
''' <paramref name="mergeFromConfigPath">mergeFromConfigPath</paramref> is specified
''' but does not exist,
''' and <paramref name="makeMergeFromConfigPathTheSavePath">
''' makeMergeFromConfigPathTheSavePath</paramref> is false.</exception>
Public Sub New(ByVal masterConfigPath As String, ByVal mergeFromConfigPath As String,
ByVal makeMergeFromConfigPathTheSavePath As Boolean)
m_configPath = masterConfigPath
Dim fromLastInstallConfigPath As String = mergeFromConfigPath
If mergeFromConfigPath IsNot Nothing AndAlso _
(Not File.Exists(mergeFromConfigPath)) AndAlso _
Not makeMergeFromConfigPathTheSavePath Then
Throw New ArgumentException( _
String.Format("File '{0}' does not exist", mergeFromConfigPath), _
"mergeFromConfigPath")
End If
m_xmlDocument = New XmlDocument
Try
m_xmlDocument.Load(masterConfigPath)
Catch ex As Exception
Throw New ArgumentException( _
"Could not open '" & masterConfigPath & "'", _
"masterConfigPath", ex)
End Try
If mergeFromConfigPath IsNot Nothing AndAlso _
File.Exists(mergeFromConfigPath) Then
Debug.WriteLine("Merging from " & mergeFromConfigPath)
'Merge approach:
' x Use from-last-install config as base
' x Merge in any non-existing keyvalue pairs from distrib-config
' x Use this merged config, and leave the last-install-config file in place
' for reference
'Merge, preserving any comments from distrib-one only
Dim reInstallConfig As New XmlDocument()
Try
reInstallConfig.Load(mergeFromConfigPath)
Catch ex As Exception
fromLastInstallConfigPath = Nothing
Throw New ArgumentException( _
("Could not read existing config '" & mergeFromConfigPath & "'"), _
"mergeFromConfigPath", ex)
End Try
UpdateExistingElementsAndAttribs(reInstallConfig, m_xmlDocument)
End If
If makeMergeFromConfigPathTheSavePath Then
m_configPath = fromLastInstallConfigPath
End If
End Sub
''' <summary>Initializes a new instance of the ConfigFileManager class.</summary>
''' <param name="masterConfigPath">The master configuration to merge.</param>
''' <param name="mergeFromConfigPath">The file path to the configuration to merge
''' from.</param>
''' <remarks>The resulting merge will save to the file path specified in the
''' <paramref name="mergeFromConfigPath">mergeFromConfigPath</paramref> argument.
''' </remarks>
''' <exception cref="ArgumentNullException">If
''' <paramref name="masterConfigPath">masterConfigPath</paramref> is null.
''' </exception>
Public Sub New(ByVal masterConfigPath As System.Xml.Linq.XDocument, _
ByVal mergeFromConfigPath As String)
Dim xmlDoc As New Xml.XmlDocument
xmlDoc.LoadXml(masterConfigPath.ToString)
Load(xmlDoc, mergeFromConfigPath)
End Sub
''' <summary>Initializes a new instance of the ConfigFileManager class.</summary>
''' <param name="masterConfigPath">The master configuration to merge.</param>
''' <param name="mergeFromConfigPath">The file path to the configuration to merge
''' from.</param>
''' <remarks>The resulting merge will save to the file path specified in the
''' <paramref name="mergeFromConfigPath">mergeFromConfigPath</paramref> argument.
''' </remarks>
''' <exception cref="ArgumentNullException">If
''' <paramref name="masterConfigPath">masterConfigPath</paramref> is null.
''' </exception>
Public Sub New(ByVal masterConfigPath As XmlDocument, _
ByVal mergeFromConfigPath As String)
Load(masterConfigPath, mergeFromConfigPath)
End Sub
#End Region
#Region "Public Methods"
''' <summary>The file path for the configuration.</summary>
''' <returns>The file path for the configuration.</returns>
Public ReadOnly Property ConfigPath() As String
Get
Return m_configPath
End Get
End Property
''' <summary>Get the value for the given xPath.</summary>
''' <param name="xPath">The path to value to return.</param>
''' <returns>The value for the given xPath.</returns>
Public Function GetXPathValue(ByVal xPath As String) As String
Dim node As XmlNode = XmlDocument.SelectSingleNode(xPath)
If node Is Nothing Then
Return Nothing
End If
Return node.InnerText
End Function
''' <summary>Search and replace on one or more specified values as specified by a
''' single xpath expression.</summary>
''' <param name="xPath">The path to the values to replace.</param>
''' <param name="replaceWith">The value to substitute.</param>
''' <returns>A description of the results.</returns>
''' <exception cref="ArgumentNullException">No nodes match xpath-expression and
''' no regexPattern is specified (is null).</exception>
''' <exception cref="ArgumentOutOfRangeException">Can't auto-create the node
''' (is not an appSettings expression).</exception>
Public Function ReplaceXPathValues(ByVal xPath As String, _
ByVal replaceWith As String) As String()
Return ReplaceXPathValues(xPath, replaceWith, Nothing)
End Function
''' <summary>Search and replace on one or more specified values as specified by a
''' single xpath expression.</summary>
''' <param name="xPath">The path to the values to replace.</param>
''' <param name="replaceWith">The value to substitute.</param>
''' <param name="regexPattern">Optionally specify a regex pattern to search within
''' the found values.
''' If a single () group is found within this expression, only this portion is
''' replaced.</param>
''' <returns>A description of the results.</returns>
''' <exception cref="ArgumentNullException">No nodes match xpath-expression and no
''' regexPattern is specified (is null).</exception>
''' <exception cref="ArgumentOutOfRangeException">Can't auto-create the node (is
''' not an appSettings expression) or if
''' regexPattern produces more than 1 replace group.</exception>
Public Function ReplaceXPathValues(ByVal xPath As String, _
ByVal replaceWith As String, _
ByVal regexPattern As Regex) As String()
If String.IsNullOrWhiteSpace(xPath) Then
Throw New ArgumentNullException("xPath", _
"Cannot be null, empty or whitespace.")
End If
If replaceWith Is Nothing Then
Throw New ArgumentNullException("replaceWith", "Cannot be null.")
End If
Dim ret As New ArrayList()
Dim nodes As XmlNodeList = m_xmlDocument.SelectNodes(xPath)
'Check if no existing nodes match
If nodes.Count = 0 Then
If regexPattern IsNot Nothing Then
Return DirectCast(ret.ToArray(GetType(String)), String())
'no error or auto-create attempt when a pattern was specified
End If
'Check special case of fully-qualified appSetting key/value pair
Dim regex As New Regex("/configuration/appSettings/add" & _
"\[\@key=['""](.*)['""]\]/\@value")
Dim match As Match = regex.Match(xPath)
If Not match.Success Then
Throw New ArgumentException( _
String.Format("'{0}' does not match any nodes, and may not be " & _
"auto-created since it is not of form '{1}'", _
xPath, regex.ToString()), "xPath")
'is an error when no pattern specified and can't auto-create
End If
'Auto create this one
Dim key As String = match.Result("$1")
SetKeyValue(key, "")
'Value is set below (unless no regex match)
ret.Add(("add/@key" & " created for key '") + key & "'")
nodes = m_xmlDocument.SelectNodes(xPath)
End If
'Proceed with replacements
Dim replacedOneOrMore As Boolean = False
For Each node As XmlNode In nodes
Dim replText As String = Nothing
If regexPattern IsNot Nothing Then
Dim match As Match = regexPattern.Match(node.InnerText)
If match.Success Then
'Determine if group match applies
Select Case match.Groups.Count
Case 1
replText = regexPattern.Replace(node.InnerText, replaceWith)
Exit Select
Case 2
replText = String.Empty
If match.Groups(2 - 1).Index > 0 Then
replText += node.InnerText.Substring(0, _
match.Groups(2 - 1).Index)
End If
replText += replaceWith
Dim firstPostGroupPos As Integer = _
match.Groups(2 - 1).Index + match.Groups(2 - 1).Length
If node.InnerText.Length > firstPostGroupPos Then
replText += node.InnerText.Substring(firstPostGroupPos)
End If
Exit Select
Case Else
Throw New ArgumentOutOfRangeException( _
String.Format("> 1 regex replace group not " & _
"supported ({0}) for regex expr: '{1}'", _
match.Groups.Count, _
regexPattern.ToString()))
End Select
End If
Else
replText = replaceWith
End If
If replText IsNot Nothing Then
replacedOneOrMore = True
node.InnerText = replText
If TypeOf node Is XmlAttribute Then
Dim keyAttrib As XmlAttribute = _
DirectCast(node, XmlAttribute).OwnerElement.Attributes("key")
If keyAttrib Is Nothing Then
ret.Add(((DirectCast(node, XmlAttribute).OwnerElement.Name & _
"/@") + node.Name & " set to '") + replText & "'")
Else
ret.Add((((DirectCast(node, XmlAttribute).OwnerElement.Name & _
"[@key='") + keyAttrib.InnerText & "']/@") + _
node.Name & " set to '") + replText & "'")
End If
Else
ret.Add((node.Name & " set to '") + replText)
End If
End If
Next
If Not replacedOneOrMore Then
ret.Add(("No values matched replace pattern '" & regexPattern.ToString() & _
"' for '") + xPath & "'")
End If
Return DirectCast(ret.ToArray(GetType(String)), String())
End Function
''' <summary>The merged or modified configuration as an
''' <see cref="XmlDocument">XmlDocument</see>.</summary>
''' <returns>The merged or modified configuration as an
''' <see cref="XmlDocument">XmlDocument</see>.</returns>
Public ReadOnly Property XmlDocument() As XmlDocument
Get
Return m_xmlDocument
End Get
End Property
''' <summary>Saves the merged or modified configuration to the file path in the
''' <see cref="ConfigPath">ConfigPath</see> property.</summary>
Public Overridable Sub Save()
Save(ConfigPath)
End Sub
''' <summary>Saves the merged or modified configuration to disk.</summary>
''' <param name="saveAsname">The file path to save where the configuration will
''' be written.</param>
Public Overridable Sub Save(ByVal saveAsname As String)
'Ensure not set r/o
If File.Exists(saveAsname) Then
ClearSpecifiedFileAttributes(saveAsname, FileAttributes.[ReadOnly])
End If
m_xmlDocument.Save(saveAsname)
End Sub
''' <summary>Gets the value of a key in the appSettings section.</summary>
''' <param name="key">The key whose value will be returned.</param>
''' <returns>The value of the specified key.</returns>
Public Function GetKeyValue(ByVal key As String) As String
If key Is Nothing OrElse key = String.Empty Then
Throw New ApplicationException("Required key is blank or null")
End If
Dim node As XmlNode = GetKeyValueNode(key)
If node Is Nothing Then
Return Nothing
End If
Return node.Attributes("value").InnerText
End Function
''' <summary>Returns a <see cref="Hashtable">HashTable</see> of all key/value pairs
''' in the appSettings section.</summary>
''' <returns>A <see cref="Hashtable">HashTable</see> of all key/value pairs
''' in the appSettings section.</returns>
Public Function GetAllKeyValuePairs() As Hashtable
Dim ret As New Hashtable()
For Each node As XmlNode In XmlDocument.SelectNodes( _
"/configuration/appSettings/add")
ret.Add(node.Attributes("key").InnerText, node.Attributes("value").InnerText)
Next
Return ret
End Function
''' <summary>Sets the value of a key in the appSettings section.</summary>
''' <param name="key">The key whose value will be set.</param>
''' <param name="value">The value to apply to the specified key.</param>
Public Sub SetKeyValue(ByVal key As String, ByVal value As String)
If key Is Nothing OrElse key = String.Empty Then
Throw New ApplicationException("Required key is blank or null")
End If
If value Is Nothing Then
Throw New ApplicationException("Required value is null")
End If
Dim node As XmlNode = GetKeyValueNode(key)
If node Is Nothing Then
Dim top As XmlNode = XmlDocument.SelectSingleNode("/configuration")
If top Is Nothing Then
top = XmlDocument.AppendChild(XmlDocument.CreateElement("configuration"))
End If
Dim app As XmlNode = top.SelectSingleNode("appSettings")
If app Is Nothing Then
app = top.AppendChild(XmlDocument.CreateElement("appSettings"))
End If
node = app.AppendChild(XmlDocument.CreateElement("add"))
node.Attributes.Append(XmlDocument.CreateAttribute("key")).InnerText = key
node.Attributes.Append(XmlDocument.CreateAttribute("value"))
End If
node.Attributes("value").InnerText = value
End Sub
''' <summary>Merge element and attribute values from one xml doc to another.
''' </summary>
''' <param name="fromXdoc">The <see cref="XmlDocument">XmlDocument</see> to merge
''' from.</param>
''' <param name="toXdoc">The <see cref="XmlDocument">XmlDocument</see> to merge to.
''' </param>
''' <remarks>Multiple same-named peer elements, are merged in the ordinal order they
''' appear.</remarks>
Public Shared Sub UpdateExistingElementsAndAttribs(ByVal fromXdoc As XmlDocument, _
ByVal toXdoc As XmlDocument)
UpdateExistingElementsAndAttribsRecurse(fromXdoc.ChildNodes, toXdoc)
End Sub
#End Region
#Region "Private Methods"
Private Sub Load(ByVal masterConfigPath As XmlDocument, _
ByVal mergeFromConfigPath As String)
Dim fromLastInstallConfigPath As String = mergeFromConfigPath
If masterConfigPath Is Nothing Then
Throw New ArgumentNullException("masterConfigPath")
End If
m_xmlDocument = New XmlDocument()
m_xmlDocument.LoadXml(masterConfigPath.OuterXml)
If File.Exists(mergeFromConfigPath) Then
Debug.WriteLine("Merging from " & mergeFromConfigPath)
'Merge approach:
' x Use from-last-install config as base
' x Merge in any non-existing keyvalue pairs from distrib-config
' x Use this merged config, and leave the last-install-config file in place
' for reference
'Merge, preserving any comments from distrib-one only
Dim reInstallConfig As New XmlDocument()
Try
reInstallConfig.Load(mergeFromConfigPath)
Catch ex As Exception
fromLastInstallConfigPath = Nothing
Throw New ArgumentException(("Could not read config '" & _
mergeFromConfigPath & "'"), _
"mergeFromConfigPath", ex)
End Try
UpdateExistingElementsAndAttribs(reInstallConfig, m_xmlDocument)
End If
m_configPath = fromLastInstallConfigPath
End Sub
Private Function GetKeyValueNode(ByVal key As String) As XmlNode
Dim nodes As XmlNodeList = XmlDocument.SelectNodes( _
"/configuration/appSettings/add[@key='" & key & "']")
If nodes.Count = 0 Then
Return Nothing
End If
Return nodes(nodes.Count - 1)
'Return last (to be compat with System.Configuration behavior)
End Function
Private Sub ClearSpecifiedFileAttributes(ByVal path As String, _
ByVal fileAttributes As FileAttributes)
File.SetAttributes(path, File.GetAttributes(path) And _
DirectCast((DirectCast(&H7FFFFFFF, FileAttributes) - fileAttributes), _
FileAttributes))
End Sub
Private Shared Sub UpdateExistingElementsAndAttribsRecurse( _
ByVal fromNodes As XmlNodeList, _
ByVal toParentNode As XmlNode)
Dim iSameElement As Integer = 0
Dim lastElement As XmlNode = Nothing
For Each node As XmlNode In fromNodes
If node.NodeType <> XmlNodeType.Element OrElse _
String.Compare(node.Name, "clear", True) = 0 Then
Continue For
End If
If lastElement IsNot Nothing AndAlso node.Name = lastElement.Name AndAlso _
node.NamespaceURI = lastElement.NamespaceURI Then
iSameElement += 1
Else
iSameElement = 0
End If
lastElement = node
Dim toNode As XmlNode
If node.Attributes("key") IsNot Nothing Then
toNode = SelectSingleNodeMatchingNamespaceURI(toParentNode, node, _
node.Attributes("key"))
ElseIf node.Attributes("name") IsNot Nothing Then
toNode = SelectSingleNodeMatchingNamespaceURI(toParentNode, node, _
node.Attributes("name"))
ElseIf node.Attributes("type") IsNot Nothing Then
toNode = SelectSingleNodeMatchingNamespaceURI(toParentNode, node, _
node.Attributes("type"))
Else
toNode = SelectSingleNodeMatchingNamespaceURI(toParentNode, node, _
iSameElement)
End If
If toNode Is Nothing Then
If node Is Nothing Then
Throw New ApplicationException("node == null")
End If
If node.Name Is Nothing Then
Throw New ApplicationException("node.Name == null")
End If
If toParentNode Is Nothing Then
Throw New ApplicationException("toParentNode == null")
End If
If toParentNode.OwnerDocument Is Nothing Then
Throw New ApplicationException("toParentNode.OwnerDocument == null")
End If
Debug.WriteLine(("app: " & toParentNode.Name & "/") + node.Name)
If node.ParentNode.Name <> toParentNode.Name Then
Throw New ApplicationException( _
("node.ParentNode.Name != toParentNode.Name: " & _
node.ParentNode.Name & " !=") + toParentNode.Name)
End If
Try
toNode = toParentNode.AppendChild( _
toParentNode.OwnerDocument.CreateElement(node.Name))
Catch ex As Exception
Throw New ApplicationException( _
"ex during toNode = toParentNode.AppendChild(: " & ex.Message)
End Try
End If
'Copy element content if any
Dim textEl As XmlNode = GetTextElement(node)
If textEl IsNot Nothing Then
toNode.InnerText = textEl.InnerText
End If
'Copy attribs if any
For Each attrib As XmlAttribute In node.Attributes
Dim toAttrib As XmlAttribute = toNode.Attributes(attrib.Name)
If toAttrib Is Nothing Then
Debug.WriteLine(("attr: " & toNode.Name & "@") + attrib.Name)
toAttrib = toNode.Attributes.Append( _
toNode.OwnerDocument.CreateAttribute(attrib.Name))
End If
toAttrib.InnerText = attrib.InnerText
Next
DirectCast(toNode, XmlElement).IsEmpty = Not toNode.HasChildNodes
'Ensure no endtag when not needed
UpdateExistingElementsAndAttribsRecurse(node.ChildNodes, toNode)
Next
End Sub
Private Shared Function GetTextElement(ByVal node As XmlNode) As XmlNode
For Each subNode As XmlNode In node.ChildNodes
If subNode.NodeType = XmlNodeType.Text Then
Return subNode
End If
Next
Return Nothing
End Function
Private Shared Function SelectSingleNodeMatchingNamespaceURI(ByVal node As XmlNode, _
ByVal nodeName As XmlNode) As XmlNode
Return SelectSingleNodeMatchingNamespaceURI(node, nodeName, Nothing, 0)
End Function
Private Shared Function SelectSingleNodeMatchingNamespaceURI(ByVal node As XmlNode, _
ByVal nodeName As XmlNode, ByVal iSameElement As Integer) As XmlNode
Return SelectSingleNodeMatchingNamespaceURI(node, _
nodeName, _
Nothing, _
iSameElement)
End Function
Private Shared Function SelectSingleNodeMatchingNamespaceURI(ByVal node As XmlNode, _
ByVal nodeName As XmlNode, ByVal keyAttrib As XmlAttribute) As XmlNode
Return SelectSingleNodeMatchingNamespaceURI(node, nodeName, keyAttrib, 0)
End Function
Private Shared Function SelectSingleNodeMatchingNamespaceURI(ByVal node As XmlNode, _
ByVal nodeName As XmlNode, ByVal keyAttrib As XmlAttribute, _
ByVal iSameElement As Integer) As XmlNode
Dim matchNode As XmlNode = Nothing
Dim iNodeNameElements As Integer = 0 - 1
For Each subNode As XmlNode In node.ChildNodes
If subNode.Name <> nodeName.Name OrElse _
subNode.NamespaceURI <> nodeName.NamespaceURI Then
Continue For
End If
iNodeNameElements += 1
If keyAttrib Is Nothing Then
If iNodeNameElements = iSameElement Then
Return subNode
Else
Continue For
End If
End If
If subNode.Attributes(keyAttrib.Name) IsNot Nothing AndAlso _
subNode.Attributes(keyAttrib.Name).InnerText = keyAttrib.InnerText Then
matchNode = subNode
ElseIf keyAttrib IsNot Nothing AndAlso keyAttrib.Name = "type" Then
Dim subNodeMatch As Match = _
_typeParsePattern.Match(subNode.Attributes(keyAttrib.Name).InnerText)
Dim keyAttribMatch As Match = _
_typeParsePattern.Match(keyAttrib.InnerText)
If subNodeMatch.Success AndAlso _
keyAttribMatch.Success AndAlso _
subNodeMatch.Result("$1") = keyAttribMatch.Result("$1") Then
matchNode = subNode
'Have type class match (ignoring assembly-name suffix)
End If
End If
Next
Return matchNode
'return last match if > 1
End Function
#End Region
End Class