2

非常奇怪的问题:我使用 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 的三行时,其余代码对于其他节点运行良好。

我能想象的唯一相关的是,这些是我在HasNode函数中查询的节点,以查看块是否已经存在。DOM 不能通过未保存的更改处理查询吗?所以我编辑了代码以提前进行存在性检查并将结果存储在布尔值中,因为我认为问题可能是在修改后的树上寻找节点。但是现在它给出了一个关于尝试将节点嵌套在自身或它自己的子节点下的错误(“msxml3.dll:不允许在自身下插入节点或其祖先”),就dependentAssemblyNode.appendChild(bindingRedirectNode);行了。这些错误都没有任何意义。

我似乎越来越喜欢它了。EnsureXPath,在必须添加节点的情况下第二次使用时,也会出现非法嵌套错误。我感觉不知何故,该对象在某处神秘地变为空,并且该空被视为处理节点对象的函数中的根节点。

有谁知道可能导致这种行为的原因?

我正在编辑的 XML 通常如下所示:

<?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>

(还有更多这些dependentAssembly 部分......但这并不重要)

4

3 回答 3

1

最后,似乎没有办法摆脱这种混乱,我最终只是简单地制作了一个外部应用程序来进行 XML 编辑。用于进行 xml 更改的工具被简单地提取到程序文件夹中,并在 Run 和 UninstallRun 部分中使用正确的参数进行设置。

(需要 UninstallRun 部分,因为要编辑的 XML 是需要与我们的应用程序集成的外部应用程序的一部分。显然,如果您处于这种情况但需要在您自己的程序中进行 xml 编辑,只需提取应用程序进入 {tmp} 并从那里运行一次就足够了)

但是,如果有人弄清楚是什么导致这个 COM 混乱失败,请添加另一个答案。不过,从我在制作外部应用程序时遇到的情况来看,这可能与 XML 树中命名空间的更改有关。

于 2013-05-21T12:45:35.640 回答
1

不是解决方案,而是有关该主题的更多信息。

概述:针对 Windows 7/Server 2008 R2 之后的任何版本的 Unicode Inno 安装程序应该没问题。对于那些平台和更早的平台,我不会依赖在 Inno Setup 中使用 MSXML。我根本没有测试 ANSI Inno Setup(我听说事情的问题通常较少)。

详细信息:我也注意到使用 MSXML Variant 对象的类似问题。我看到的问题通常发生在使用 Variants 的方法正在关闭时(因此可能释放资源),并且我收到了 DLL 崩溃。在专门调用 MSXML 方法本身时从未出现任何错误。那么这个问题可能与它如何处理 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 并没有任何关系,但可能在安装这些框架时,另一个 3rd 方依赖项会更新(这让一切都很开心)。即使在应用了 Windows 7 或 Server 2008 R2 的所有安全更新和修复程序后,问题仍然存在。对于所有其他平台,我在安装后没有执行任何其他更新(除非像 . NET 框架)。他们都是安装了该平台的最新服务包/更新。

任何特定版本说明

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
于 2017-03-18T01:22:26.647 回答
0

我已经看到您的 seekNode 函数正在返回它检查的第一个属性。我想您需要删除以下部分:

else begin Result:=false; Exit; end;

我不知道这是否解决了你的问题。

于 2014-05-28T13:44:58.080 回答