Agregar nodos a xml con DOM en inno Setup - problemas extraños
Problema muy extraño: uso el DOM para editar un archivo xml (un archivo .exe.config para una aplicación que necesita interactuar con la nuestra), pero como tengo que agregar varias secciones similares en forma masiva, hice una función para insertar Todo el bloque necesario.
Llamar a esta función una vez funciona perfectamente. Llamarlo nuevamente con diferentes parámetros justo después da una excepción (vea la explicación debajo del código).
El código:
// Split a string into an array using passed delimeter
procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String);
var
i: Integer;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
if Pos(Separator,Text) > 0 then
begin
Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1);
Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text));
i := i + 1;
end
else
begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
end;
// Ensures an XPath exists, creating nodes if needed
function EnsureXPath(const XmlDoc: Variant; XPath: string): Variant;
var
PathParts: TArrayOfString;
TestNode, CurrentNode, NewNode: Variant;
NodeList: Variant;
i, j: Integer;
found: Boolean;
begin
CurrentNode:=XMLDoc.documentElement;
Explode(PathParts, XPath, '/');
MsgBox('Array length: ' + IntToStr(GetArrayLength(PathParts)), mbInformation, MB_OK);
for i := 0 to GetArrayLength(PathParts) - 1 do
begin
MsgBox('Current path part:'#13#10 + '''' + pathparts[i] + '''', mbInformation, MB_OK);
if pathparts[i] <> '' then
begin
//MsgBox('Current node:'#13#10 + '''' + CurrentNode.NodeName + '''' + #13#10'Current path part:'#13#10 + '''' + PathParts[i] + '''' + #13#10'List length: ' + IntToStr(NodeList.Length), mbInformation, MB_OK);
MsgBox('Current node:'#13#10 + '''' + CurrentNode.NodeName + '''', mbInformation, MB_OK);
MsgBox('Current path part:'#13#10 + '''' + PathParts[i] + '''', mbInformation, MB_OK);
NodeList:= CurrentNode.childNodes;
MsgBox('List length: ' + IntToStr(NodeList.Length), mbInformation, MB_OK);
found:=false;
for j := 0 to NodeList.Length - 1 do
begin
TestNode:=NodeList.Item[j]
if (TestNode.NodeName = pathparts[i]) then
begin
currentNode:= TestNode;
found:=true;
end;
end;
if (not found) then
begin
newNode := XMLDoc.createElement(PathParts[i]);
currentNode.appendChild(newNode);
currentNode:=currentNode.lastChild;
end;
end;
end;
Result:=currentNode;
MsgBox('Last node:'#13#10 + '''' + CurrentNode.NodeName + '''', mbInformation, MB_OK);
end;
// Seeks out a node, returning the node in "resultnode", and whether it was found as Result.
function SeekNode(const ParentNode: Variant; var resultnode: Variant; subNodePath, attrName, attrValue :String; IsFirstCall: Boolean): Boolean;
var
NodesList: Variant;
AttrNode: Variant;
AttrList: Variant;
Attr: Variant;
PathParts, NewPathParts: TArrayOfString;
i, j, truelength: Integer;
currentPath, remainderPath: String;
CallAgain,callResult: Boolean;
begin
Explode(PathParts, subNodePath, '/');
truelength:=GetArrayLength(PathParts);
for i:=0 to GetArrayLength(PathParts) -1 do
begin
if PathParts[i] = '' then
truelength:=truelength-1;
end;
if (truelength <> GetArrayLength(PathParts)) then
begin
SetArrayLength(NewPathParts, truelength);
truelength:=0;
for i:=0 to GetArrayLength(PathParts) -1 do
begin
if PathParts[i] <> '' then
begin
NewPathParts[truelength] := PathParts[i];
truelength:=truelength+1;
end;
end;
end
else
NewPathParts:=PathParts;
CallAgain:=GetArrayLength(NewPathParts)>1;
currentPath:=NewPathParts[0];
remainderPath:='';
for i:=1 to GetArrayLength(NewPathParts) -1 do
begin
if (remainderPath <> '') then
remainderPath:=remainderPath + '/';
remainderPath:=remainderPath + NewPathParts[i];
end;
NodesList:=ParentNode.childNodes;
//MsgBox('Node count for ' + currentPath + ':'#13#10 + '''' + IntToStr(NodesList.length) + '''', mbInformation, MB_OK);
Result:=false;
for i := 0 to NodesList.length - 1 do
begin
attrNode := NodesList.Item[i];
//MsgBox('Current node:'#13#10 + '''' + attrNode.NodeName + ''''#13#10'Current path:'#13#10+ '''' + currentPath + '''', mbInformation, MB_OK);
if (attrNode.NodeName = currentPath) then
begin
if CallAgain then
begin
//MsgBox('Remainder of path:'#13#10 + '''' + remainderPath + '''', mbInformation, MB_OK);
callResult:=SeekNode(attrNode, resultnode, remainderPath, attrName, attrValue, false);
if callResult then
begin
Result:=true;
if IsfirstCall then
resultnode:=attrNode;
exit;
end;
end
else
begin
AttrList:=attrNode.Attributes;
//MsgBox('Node:'#13#10 + '''' + attrNode.NodeName + '''' + #13#10'Attributes count:'#13#10 + '''' + IntToStr(AttrList.Length) + '''', mbInformation, MB_OK);
for j := 0 to AttrList.length - 1 do
begin
Attr:= AttrList.Item[j];
//MsgBox('Node:'#13#10'''' + attrNode.NodeName + ''''#13#10'Attribute:'#13#10'''' + Attr.NodeName + ''''#13#10'Value:'#13#10'''' + Attr.NodeValue + ''''#13#10'To find:'#13#10'''' + AttrValue + '''', mbInformation, MB_OK);
if (Attr.NodeName = attrName) then
begin
if (Attr.NodeValue = attrValue) then
begin
//MsgBox('Attribute found.', mbInformation, MB_OK);
resultnode:=attrNode;
Result:=true;
Exit;
end
else
begin
Result:=false;
Exit;
end;
end;
end;
end;
end;
end;
end;
// Use of SeekNode: Remove node
function removeNode(const ParentNode: Variant; subNodePath, attrName, attrValue :String): Boolean;
var
resultNode: Variant;
begin
Result:=SeekNode(ParentNode, resultNode, subNodePath, attrName, attrValue, true);
if (Result) then
ParentNode.removeChild(resultNode);
end;
// Use of SeekNode: test node existence
function hasNode(const ParentNode: Variant; subNodePath, attrName, attrValue :String): Boolean;
var
resultNode: Variant;
begin
Result:=SeekNode(ParentNode, resultNode, subNodePath, attrName, attrValue, true);
end;
// Adds a single assembly binding block into the xml
procedure AddAssemblyBinding(const XmlDoc: Variant; const ParentNode: Variant; aiName, aiCulture, aiKey, brOld, brNew, cbVer, cbHref: String);
var
dependentAssemblyNode: Variant;
assemblyIdentityNode: Variant;
bindingRedirectNode: Variant;
codeBaseNode: Variant;
publisherPolicyNode: Variant;
begin
// <assemblyIdentity name="ECompas.Runtime" culture="" publicKeyToken="f27ad8cb97726f87" />
// <bindingRedirect oldVersion="3.0.1.0 - 3.0.1.133" newVersion="3.0.1.133" />
// <codeBase version="3.0.1.133" href="[TARGETDIR]Ecompas.Runtime.dll" />
// <publisherPolicy apply="no"/>
dependentAssemblyNode:= XMLDoc.createElement('dependentAssembly');
assemblyIdentityNode:= XMLDoc.createElement('assemblyIdentity');
assemblyIdentityNode.setAttribute('name', aiName);
assemblyIdentityNode.setAttribute('culture', aiCulture);
assemblyIdentityNode.setAttribute('publicKeyToken', aiKey);
dependentAssemblyNode.appendChild(assemblyIdentityNode);
if ((brOld <> '') and (brNew <> '')) then
begin
bindingRedirectNode:= XMLDoc.createElement('bindingRedirect');
bindingRedirectNode.setAttribute('oldVersion', brOld);
bindingRedirectNode.setAttribute('newVersion', brNew);
dependentAssemblyNode.appendChild(bindingRedirectNode);
end;
codeBaseNode:= XMLDoc.createElement('codeBase');
codeBaseNode.setAttribute('version', cbVer);
codeBaseNode.setAttribute('href', cbHref);
dependentAssemblyNode.appendChild(codeBaseNode);
publisherPolicyNode:= XMLDoc.createElement('publisherPolicy');
publisherPolicyNode.setAttribute('apply', 'no');
dependentAssemblyNode.appendChild(publisherPolicyNode);
// Doesn't work? No idea why it gives it an xmlns while its parent already has one.
//dependentAssemblyNode.RemoveAttribute('xmlns');
// It seems the actual variables of the nodes are somehow lost after adding
// them to a parent - so add everything to them in advance!
ParentNode.appendChild(dependentAssemblyNode);
end;
function UpdateConfig(const AFileName, Appdir: string; delete:Boolean): boolean;
var
XMLDoc: Variant;
RootNode, MainNode, AddNode: Variant;
bECompasRuntime, bECompasMetamodel, bECompasDatabaseMS: Boolean;
begin
try
XMLDoc := CreateOleObject('MSXML2.DOMDocument');
except
RaiseException('MSXML is required to complete the post-installation process.'#13#10#13#10'(Error ''' + GetExceptionMessage + ''' occurred)');
end;
XMLDoc.async := False;
XMLDoc.resolveExternals := False;
XMLDoc.load(AFileName);
if XMLDoc.parseError.errorCode <> 0 then
begin
MsgBox('XML processing error:'#13#10 + XMLDoc.parseError.reason, mbInformation, MB_OK);
Result:=False;
exit;
end;
XMLDoc.setProperty('SelectionLanguage', 'XPath');
RootNode:=XMLDoc.documentElement;
if (RootNode.nodeName <> 'configuration') then
begin
MsgBox('XML processing error:'#13#10'Root element ''configuration'' not found.', mbInformation, MB_OK);
Result:=False;
exit;
end;
MainNode:=EnsureXPath(XMLDoc, 'runtime/assemblyBinding');
bECompasRuntime := HasNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Runtime');
bECompasMetamodel := HasNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Metamodel');
bECompasDatabaseMS := HasNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Database.MS');
if (not delete) then
begin
if not bECompasRuntime then
AddAssemblyBinding(XMLDoc, MainNode, 'ECompas.Runtime', '', 'f27ad8cb97726f87', '3.0.1.0 - 3.0.1.133', '3.0.1.133', '3.0.1.133', Appdir + '\Ecompas.Runtime.dll');
if not bECompasMetamodel then
AddAssemblyBinding(XMLDoc, MainNode, 'ECompas.Metamodel', '', 'f27ad8cb97726f87', '3.0.1.0 - 3.0.1.133', '3.0.1.133', '3.0.1.133', Appdir + '\Ecompas.Metamodel.dll');
if not bECompasDatabaseMS then
AddAssemblyBinding(XMLDoc, MainNode, 'ECompas.Database.MS', '', 'f27ad8cb97726f87', '3.0.1.0 - 3.0.1.133', '3.0.1.133', '3.0.1.133', Appdir + '\Ecompas.Database.MS.dll');
end
else
begin
removeNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Runtime');
removeNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Metamodel');
removeNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Database.MS');
end;
MainNode:=EnsureXPath(XMLDoc, 'appSettings');
if (not delete) then
begin
//<add key="logdir" value=".\log" />
if (not HasNode(MainNode,'add','key','logdir')) then
begin
AddNode:= XMLDoc.createElement('add');
AddNode.setAttribute('key', 'logdir');
AddNode.setAttribute('value', '.\log');
MainNode.appendChild(AddNode);
end;
end
else
begin
removeNode(MainNode,'add','key','logdir');
end;
XMLDoc.Save(AFileName);
Result:=true;
end;
Originalmente, elUpdateConfig
La función se hizo así:
if (not HasNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Runtime')) then
AddAssemblyBinding(XMLDoc, MainNode, 'ECompas.Runtime', '', 'f27ad8cb97726f87', '3.0.1.0 - 3.0.1.133', '3.0.1.133', '3.0.1.133', Appdir + '\Ecompas.Runtime.dll');
if (not HasNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Metamodel')) then
AddAssemblyBinding(XMLDoc, MainNode, 'ECompas.Metamodel', '', 'f27ad8cb97726f87', '3.0.1.0 - 3.0.1.133', '3.0.1.133', '3.0.1.133', Appdir + '\Ecompas.Metamodel.dll');
if (not HasNode(MainNode,'dependentAssembly/assemblyIdentity','name','ECompas.Database.MS')) then
AddAssemblyBinding(XMLDoc, MainNode, 'ECompas.Database.MS', '', 'f27ad8cb97726f87', '3.0.1.0 - 3.0.1.133', '3.0.1.133', '3.0.1.133', Appdir + '\Ecompas.Database.MS.dll');
Este código funcionó bien la primera vez, pero la segunda vez, dio lo anterior "Método desconocido"error ensetAttribute
enAddAssemblyBinding
. Se volvió más extraño ... cuando eliminé las tres líneas que configuraban los atributos para assemblyIdentityNode, el resto del código DID se ejecutaba bien para los otros nodos.
Lo único que puedo imaginar está relacionado es que estos son los nodos que pregunto en elHasNode
Función para ver si el bloque ya existe. ¿DOM no puede manejar la consulta a través de cambios no guardados? Así que edité el código para hacer las comprobaciones de existencia por adelantado y almacenar el resultado en Boolean, porque pensé que tal vez el problema era buscar los nodos en un árbol modificado. Pero ahora da un error al tratar de anidar un nodo debajo de sí mismo o de su propio hijo ("msxml3.dll: no se permite insertar un nodo o su antecesor en sí mismo"), sobre eldependentAssemblyNode.appendChild(bindingRedirectNode);
línea. Ninguno de estos errores tiene sentido alguno.
Me parece que me gusta mucho más.EnsureXPath
, cuando se usó por segunda vez en una situación en la que tuvo que agregar nodos, también dio el error de anidación ilegal. Me da la sensación de que, de alguna manera, el objeto se vuelve nulo misteriosamente en algún lugar, y que ese nulo se ve como el nodo raíz en las funciones que manejan objetos de nodo.
¿Alguien tiene alguna idea de qué puede estar causando este comportamiento?
El XML que estoy editando normalmente se ve así:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="AppString1" publicKeyToken="43265234666" culture="neutral"/>
<bindingRedirect oldVersion="1.0.0.0-1.1.99.99" newVersion="1.2.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="AppString2" publicKeyToken="43265234666" culture="neutral"/>
<bindingRedirect oldVersion="1.0.0.0-1.1.99.99" newVersion="1.2.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
(con algunas más de estas secciones dependientes de ensamblaje ... pero eso no importa)