Hinzufügen von Knoten zu XML mit DOM in Inno Setup - seltsame Probleme
Sehr seltsames Problem: Ich benutze das DOM, um eine XML-Datei zu bearbeiten (eine .exe.config-Datei für eine App, die mit unserer interagieren muss), aber da ich mehrere ähnliche Abschnitte massenweise hinzufügen muss, habe ich eine Funktion zum Einfügen erstellt der ganze benötigte Block.
Der einmalige Aufruf dieser Funktion funktioniert einwandfrei. Ein erneuter Aufruf mit anderen Parametern gibt eine Ausnahme (siehe Erklärung unter dem Code).
Der Code:
// 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;
Ursprünglich war dasUpdateConfig
Funktion wurde so gemacht:
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');
Dieser Code lief das erste Mal gut, aber das zweite Mal gab es die oben genannten "Unbekannte Methode"Fehler ansetAttribute
imAddAssemblyBinding
. Es wurde bizarrer ... als ich die drei Zeilen entfernte, die die Attribute auf assemblyIdentityNode setzten, lief der Rest des Codes DID gut für die anderen Knoten.
Das einzige, was ich mir vorstellen kann, ist, dass dies die Knoten sind, die ich in der abfrageHasNode
Funktion, um festzustellen, ob der Block bereits vorhanden ist. Kann DOM das Abfragen von nicht gespeicherten Änderungen nicht verarbeiten? Also habe ich den Code bearbeitet, um die Existenzprüfungen im Voraus durchzuführen und das Ergebnis in Booleschen Formaten zu speichern, weil ich dachte, dass das Problem darin bestand, die Knoten in einem geänderten Baum zu suchen. Aber jetzt gibt es einen Fehler beim Versuch, einen Knoten unter sich selbst oder seinem eigenen Kind zu verschachteln ("msxml3.dll: Das Einfügen eines Knotens oder seines Vorfahren unter sich ist nicht erlaubt"), auf derdependentAssemblyNode.appendChild(bindingRedirectNode);
Linie. Keiner dieser Fehler ergibt irgendeinen Sinn.
Ich scheine viel mehr davon zu bekommen.EnsureXPath
Wenn es ein zweites Mal in einer Situation verwendet wird, in der es Knoten hinzufügen musste, wird auch der illegale Verschachtelungsfehler ausgegeben. Ich habe das Gefühl, dass das Objekt auf mysteriöse Weise irgendwo zu Null wird und dass diese Null als der Wurzelknoten in Funktionen angesehen wird, die mit Knotenobjekten umgehen.
Hat jemand eine Ahnung, was dieses Verhalten verursachen kann?
Das XML, das ich bearbeite, sieht normalerweise so aus:
<?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>
(mit einigen weiteren dieser abhängigen Assembly-Abschnitte ... aber das spielt kaum eine Rolle)