非常奇怪的问题:我使用DOM编辑一个xml文件(一个需要与我们交互的应用程序的.exe.config文件),但看到我必须批量添加几个类似的部分,我做了一个函数插入整个所需的块。
调用此功能一次完美无缺。之后使用不同的参数再次调用它会产生异常(请参阅代码下面的说明)。
代码:
// 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;
最初,UpdateConfig
功能是这样完成的:
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');
此代码第一次运行良好,但第二次,它在setAttribute
AddAssemblyBinding
上给出了上述“未知方法”错误。它变得更奇怪......当我删除设置属性为assemblyIdentityNode的三行时,其余代码DID对其他节点运行良好。
我唯一可以想象的是,这些是我在HasNode
函数中查询的节点,以查看该块是否已存在。 DOM不能通过未保存的更改来处理查询吗?所以我编辑了代码以提前进行存在检查并将结果存储在布尔值中,因为我认为问题可能是在修改树上寻找节点。但现在它在尝试将节点嵌套在自身或其自己的子节点上时出错(“msxml3.dll:不允许在其自身下插入节点或其祖先”){{1行。这些错误都没有任何意义。
我似乎更喜欢它。 dependentAssemblyNode.appendChild(bindingRedirectNode);
,在必须添加节点的情况下第二次使用时,也会出现非法嵌套错误。我感觉到某种程度上,该对象在某处神秘地变为null,并且该null被视为处理节点对象的函数中的根节点。
有没有人知道可能导致这种行为的原因是什么?
我正在编辑的XML通常如下所示:
EnsureXPath
(还有更多这些dependentAssembly部分......但这几乎不重要)
答案 0 :(得分:1)
最后,似乎没有办法摆脱这种混乱,我最终只是制作了一个外部应用来进行XML编辑。用于进行xml更改的工具只是提取到程序文件夹,并使用正确的参数在Run和UninstallRun部分中设置。
(需要使用UninstallRun部分,因为要编辑的XML是需要与我们的应用程序集成的外部应用程序的一部分。显然,如果您处于这种情况但需要在自己的程序中进行xml编辑,只需将应用程序解压缩到{tmp}并从那里运行一次应该足够了)
如果有人弄清楚是什么原因导致这个COM混乱失败,请添加另一个答案。然而,从我在创建外部应用程序时遇到的情况来看,它可能与XML树中的名称空间中的变化有关。
答案 1 :(得分:1)
不是解决方案,而是关于该主题的进一步信息。
概述:针对比Windows 7 / Server 2008 R2更新的任何内容的Unicode Inno安装程序应该没问题。对于那些平台和旧版本,我不会依赖在Inno Setup中使用MSXML。我根本没有测试ANSI Inno Setup(我听说经常会有更少的问题)。
详细信息:我也注意到使用MSXML Variant对象的类似问题。我看到的问题通常发生在使用Variants的方法关闭时(因此可能释放资源),并且我收到了DLL崩溃。在专门调用MSXML方法本身时从未出现过任何错误。所以这个问题可能是半PascalScript在处理COM对象时如何引用?我在Windows 10上开发并且没有测试我的安装程序的问题,但只有在部署之后我才开始注意到这种疯狂。这最终让我相信它与平台有关。
Platform Version Works Notes
----------------------------------------------------------------
Windows 10 NT 10.0 YES
Windows Server 2016 NT 10.0 YES
Windows 8.1 NT 6.3 YES
Windows Server 2012 R2 NT 6.3 YES Requires .NET 4.5.2
Windows 8 NT 6.2 ??? Most likely works
Windows Server 2012 NT 6.2 YES Requires .NET 4.5.1
Windows 7 NT 6.1 NO
Windows Server 2008 R2 NT 6.1 NO
Windows Vista NT 6.0 NO
请注意,基于NT 6.1或更早版本的任何内容都是问题开始发生的地方。我无法测试普通的Windows 8,但在这些假设下,我敢打赌它有效。我正在进行相当复杂的XML操作(删除节点,添加节点,修改节点,以及使用方便的XPath方法进行各种XPath查询),因此我相信您可能会看到相同的结果。我不知道为什么需要.NET(对于Server 2012测试),因为MSXML与.NET并没有任何关系,但是在安装这些框架时,可能会更新另一个第三方依赖项(这使得一切都很开心)。即使在应用Windows 7或Server 2008 R2的所有安全更新和修复程序后,问题仍然存在。对于所有其他平台,我在安装后没有执行任何其他更新(除非注意到.NET框架)。所有这些都安装该平台的最新Service Pack / Update。
任何特定版本说明
InnoSetup-5.5.9(u)
Windows 10 Version 1511 (Updated Feb 2016)
Windows 8.1 with Update
Windows 7 with SP1
Windows Vista with SP2
Windows Server 2016
Windows Server 2012 R2 with SP1
Windows Server 2012
Windows Server 2008 R2 with SP1
答案 2 :(得分:0)
我已经看到你的seekNode函数正在为它检查的第一个属性返回。我想你需要删除以下部分:
else
begin
Result:=false;
Exit;
end;
我不知道这是否能解决你的问题。