添加节点与DOM的Inno Setup的为xml - 奇怪的问题(Adding nodes to

2019-09-02 11:47发布

很奇怪的问题:我使用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');

此代码罚款跑了第一次,但第二次,它介绍了上述“ 未知法 ”的错误setAttributeAddAssemblyBinding 。 它得到了更离奇的......当我删除了三行设置属性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部分...但是,这并不重要)

Answer 1:

最后,似乎没有被任何方式摆脱困境,我结束了简单地使外部应用程序做我的XML编辑。 为了使XML变化的工具是简单地提取到程序文件夹,并用正确的参数运行和UninstallRun部分设置。

(需要的UninstallRun部分原因是由于XML来编辑是一个需要与我们的应用程序集成外部应用程序的一部分。很明显,如果你在这种情况下,但需要它的XML编辑在自己的程序,只需提取应用到{} TMP,一旦运行它应该有足够的)

一旦有人计算出什么使这个COM烂摊子失败,不过,请不要添加另一个答案。 从我跑使得外部应用程序时进入,不过,这可能与在XML树命名空间中途改变。



Answer 2:

没有一个解决方案,但关于这个问题的进一步信息。

概述 :统一的Inno安装瞄准任何更新比Windows 7 / Server 2008 R2中应该罚款。 对于那些平台和长大,我也不会依赖于使用MSXML Inno Setup的内部。 我没有测试ANSI Inno Setup的所有(我听说有事情经常少的问题)。

详细信息 :我也注意到了使用MSXML变对象类似的问题。 我看到的问题时,使用的变体的方法是关闭(从而可能释放资源)通常发生后,我收到了DLL崩溃。 从来没有任何错误的时候,特意调用MSXML方法本身。 所以可能这个问题是在其如何处理COM对象引用相关的半PascalScript? 我开发在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或Windows Server 2008 R2后问题仍然存在。 对于所有其他平台我安装后不进行额外的更新(除非像.NET框架的说明)。 他们都被安装了最新的Service Pack /更新该平台。

任何特定版本的笔记

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


Answer 3:

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

else begin Result:=false; Exit; end;

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



文章来源: Adding nodes to xml with DOM in inno Setup - strange problems