Adding nodes to xml with DOM in inno Setup - strange problems -
very strange problem: use dom edit xml file (a .exe.config file app needs interact ours), seeing have bulk-add several similar sections, made function insert whole needed block.
calling function once works perfectly. calling again different parameters afterwards gives exception (see explanation below code).
the code:
// split string array using passed delimeter procedure explode(var dest: tarrayofstring; text: string; separator: string); var i: integer; begin := 0; repeat setarraylength(dest, i+1); if pos(separator,text) > 0 begin dest[i] := copy(text, 1, pos(separator, text)-1); text := copy(text, pos(separator,text) + length(separator), length(text)); := + 1; end else begin dest[i] := text; text := ''; end; until length(text)=0; end; // ensures 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); := 0 getarraylength(pathparts) - 1 begin msgbox('current path part:'#13#10 + '''' + pathparts[i] + '''', mbinformation, mb_ok); if pathparts[i] <> '' 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; j := 0 nodelist.length - 1 begin testnode:=nodelist.item[j] if (testnode.nodename = pathparts[i]) begin currentnode:= testnode; found:=true; end; end; if (not found) 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 node, returning node in "resultnode", , whether found 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); i:=0 getarraylength(pathparts) -1 begin if pathparts[i] = '' truelength:=truelength-1; end; if (truelength <> getarraylength(pathparts)) begin setarraylength(newpathparts, truelength); truelength:=0; i:=0 getarraylength(pathparts) -1 begin if pathparts[i] <> '' begin newpathparts[truelength] := pathparts[i]; truelength:=truelength+1; end; end; end else newpathparts:=pathparts; callagain:=getarraylength(newpathparts)>1; currentpath:=newpathparts[0]; remainderpath:=''; i:=1 getarraylength(newpathparts) -1 begin if (remainderpath <> '') remainderpath:=remainderpath + '/'; remainderpath:=remainderpath + newpathparts[i]; end; nodeslist:=parentnode.childnodes; //msgbox('node count ' + currentpath + ':'#13#10 + '''' + inttostr(nodeslist.length) + '''', mbinformation, mb_ok); result:=false; := 0 nodeslist.length - 1 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) begin if callagain begin //msgbox('remainder of path:'#13#10 + '''' + remainderpath + '''', mbinformation, mb_ok); callresult:=seeknode(attrnode, resultnode, remainderpath, attrname, attrvalue, false); if callresult begin result:=true; if isfirstcall 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); j := 0 attrlist.length - 1 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) begin if (attr.nodevalue = attrvalue) 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) 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 single assembly binding block 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 <> '') , (brnew <> '')) 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 gives xmlns while parent has one. //dependentassemblynode.removeattribute('xmlns'); // seems actual variables of nodes somehow lost after adding // them parent - add 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 required complete 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 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') 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) begin if not becompasruntime 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 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 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) begin //<add key="logdir" value=".\log" /> if (not hasnode(mainnode,'add','key','logdir')) 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; originally, updateconfig function done this:
if (not hasnode(mainnode,'dependentassembly/assemblyidentity','name','ecompas.runtime')) 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')) 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')) 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'); this code ran fine first time, second time, gave aforementioned "unknown method" error on setattribute in addassemblybinding. got more bizarre... when removed 3 lines setting attributes assemblyidentitynode, rest of code did run fine other nodes.
the thing imagine related these nodes query in hasnode function see if block exists. can dom not handle querying through unsaved changes? edited code existence checks in advance , store result in booleans, because thought maybe problem seeking nodes on modified tree. gives error trying nest node under or own child ("msxml3.dll: inserting node or ancestor under not allowed"), on dependentassemblynode.appendchild(bindingredirectnode); line. neither of these errors makes sense whatsoever.
i seem loads more it. ensurexpath, when used second time in situation had add nodes, gave illegal nesting error. feeling somehow, object mysteriously becomes null somewhere, , that null seen root node in functions handling node objects.
does have clue may causing behaviour?
the xml i'm editing typically looks this:
<?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> (with more of these dependentassembly sections... hardly matters)
in end, there didn't seem way out of mess, , ended making external app xml edits. tool making xml changes extracted program folder, , set in run , uninstallrun sections correct parameters.
(the uninstallrun part needed because xml edit part of external app needed integrated our app. obviously, if you're in situation need xml edits in own program, extracting app {tmp} , running once there should enough)
if ever figures out makes com mess fail, though, please add answer. ran when making external app, though, related change in namespace halfway in xml tree.
Comments
Post a Comment