Skip to content
⌘ NSIS Forum Archive

XML plugin

118 posts

Instructor#
1. Not.
2. I'm not plaining this.

But you can use ${xml::CloneNode}


Section
${xml::LoadFile} "file.xml" $0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

${xml::GotoPath} "/" $0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

${xml::CloneNode} $R0
MessageBox MB_OK "xml::CloneNode$\n$$R0=$R0"

${xml::Unload}


${xml::LoadFile} "file2.xml" $0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

${xml::GotoPath} "/" $0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

${xml::InsertBeforeNode} "$R0" $0
MessageBox MB_OK "xml::InsertBeforeNode$\n$$0=$0"

${xml::FreeNode} "$R0" $0
MessageBox MB_OK "xml::FreeNode$\n$$0=$0"

${xml::SaveFile} "file2.xml" $0
MessageBox MB_OK "xml::SaveFile$\n$$0=$0"

${xml::Unload}
SectionEnd
Comperio#edited
I'm trying to create an XML file from scratch using this plugin. Is this possible? If so, can someone post an example of how I might accomplish this?
Comperio#
darn...
I was hoping that I could build an XML file in memory using the plugin and then output the whole thing to a file. (It would be a cool enhancement for sure.)

I guess for now, I'll just write my own FileWrite functions to get me by for now.

Thanks for the feedback!
emmett_m#edited
Hi, I am hoping you can help me, I tried some of the code examples found in \NSIS\Examples\XMLXMLTest.nsi. And tried to apply these examples with what I want with little success, here is what I am trying to achieve.

I have the following xml file (VS.Net config file):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
</configSections>

<appSettings>
<add key="somekey" value="some value"/>
</appSettings>

<connectionStrings>
<add name="ConnString" connectionString="TRYING TO MODIFY THIS TEXT"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>

When the installer runs, I am trying to change the text in the connectionString attribute of the <add> element. I have placed the file in the same directory as my install script, and the script includes the following code:

!include "XML.nsh"

var /global ReplacementString
var /global fhandle

${xml::LoadFile} "appname.exe.config" $fhandle
MessageBox MB_OK "$fhandle" ; always returns -1

${xml::GotoPath} "/configuration/connectionStrings/add" $fhandle
${xml::SetAttribute} "connectionString" "$ReplacementString" $fhandle
${xml::SaveFile} "$INSTDIR\appname.exe.config" $fhandle
${xml::Unload} ; unload plugin

When attempting to open the file it always returns -1, can anybody tell me what the obvious mistake that I am making, and yes the file is in UTF8 format, I have however tried saving the format of the file from Notepad, into different formats in desperation, with no help (as I expected).
emmett_m#
That is interesting, I tried as you suggested to supply the full path to the file, which is the same location as the setup program, and it works. However I thought when you specified a file like:

${xml::LoadFile} "appname.exe.config" $fhandle

That it would use the file from the same directory as the installer, it would seem not. Also I tried the following:

${xml::LoadFile} "$EXEDIR\appname.exe.config" $fhandle

Which seems to be what I should have done. Thanks for your time ;-)
Comm@nder21#
as the plugin is extracted to $PLUGINSDIR i guess it searches the file there, not in $EXEDIR.
emmett_m#
Just in case somebody else comes across this problem, what I really wanted to do was have the .config file wrapped up into the installer, and manipulate that version, to do this I did the following:

Section "MainSection" SEC01
SetOutPath "$INSTDIR"
SetOverwrite ifnewer
File "appname.exe.config" ; reads file into memory and wraps(compresses) into installer
Section

${xml::LoadFile} "appname.exe.config" $fhandle ; modify the config file compressed in installer


This seems to work great.
JDaniels13#
I recently needed to edit existing .NET (Framework 1.1) config files within an NSIS install. Using some of the info here and the XML plugin docs, I was able to put something together that actually appears to work for changing keys in .NET 1.1 config files. Below is what I came up with. (There are some extra message boxes for debugging that should be removed prior to using.) Is there an easier way??!! I was truly surprised at the lack of discussion of this topic. I didn't want to write a .NET program in C# or VB.NET because I wanted it all to be in the NSIS setup, for simplicity's sake.


!include "XML.nsh"

var /global IOResult
var /global NewNodeHandle
var /global CurrentValue
var /global ElementName

!macro EditDotNETConfigFile ConfigFile KeyPath KeyName KeyValue
${xml::LoadFile} ${ConfigFile} $IOResult
${xml::GotoPath} ${KeyPath} $IOResult
IntCmp $IOResult 0 pathLookupSucceeded noAppSettings
pathLookupSucceeded:
MessageBox MB_OK "pathLookupSucceeded for ${KeyPath}"
checkAttributeExistence:
${xml::GetAttribute} "key" $CurrentValue $IOResult
; Did we find an existing instance of our config setting?
IntCmp $IOResult 0 checkKeyAttributeValue toNextElement
checkKeyAttributeValue:
MessageBox MB_OK "checkKeyAttributeValue current value is $CurrentValue vs desired key ${KeyName}"
; OK, found one, is it our desired key? Case-sensitive compare.
StrCmpS $CurrentValue ${KeyName} foundKeyAttribute toNextElement
toNextElement:
MessageBox MB_OK "checkKeyAttributeValue $CurrentValue didn't match ${KeyName}"
; Didn't find the <add key= " attribute name we wanted, check next "<add " element
${xml::NextSiblingElement} "add" $ElementName $IOResult
MessageBox MB_OK "toNextElement name is $ElementName IOResult is $IOResult"
IntCmp $IOResult 0 checkAttributeExistence noExistingEntry

foundKeyAttribute:
${xml::GetAttribute} "value" $CurrentValue $IOResult
MessageBox MB_OK "foundKeyAttribute check value attribute $CurrentValue vs desired key ${KeyValue}"
; Did we find an existing instance of our config setting?
StrCmp $CurrentValue ${KeyValue} valueUnchanged valueChanged
valueChanged:
${xml::SetAttribute} "value" "${KeyValue}" $IOResult
IntCmp $IOResult 0 setAttributeWorked setAttributeFailed
setAttributeWorked:
goto saveConfigFile

noExistingEntry:
MessageBox MB_OK "GetAttribute could not find existing value for ${KeyName}"
${xml::CreateNode} '<add key="${KeyName}" value="${KeyValue}" />' $NewNodeHandle
${xml::InsertAfterNode} $NewNodeHandle $IOResult
goto saveConfigFile
setAttributeFailed:
MessageBox MB_OK "SetAtribute failed for ${KeyName}"
goto cleanUp
noAppSettings:
MessageBox MB_OK "XPath not resolved for ${KeyPath}"
goto cleanUp
saveConfigFile:
${xml::SaveFile} ${ConfigFile} $IOResult
valueUnchanged:
; Just fall thru and unload
cleanUp:
${xml::Unload} ; unload plugin
!macroend

; Called as follows:

!insertmacro EditDotNETConfigFile "c:\InetPub\wwwroot\MyWebSite\Web.config" "configuration/appSettings/add" "connectionString" "MyTestConnString1a"


P.S. I tried the nsisXML plugin but it wanted to do "extra" things to the tags in the .NET config files, so I came back to this XML plugin and it's what I got to work first.
JDaniels13#
A .NET 1.1 config file used in testing

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="@CBR_CBRFILE" value="Initialization.cbr" />
<!-- Environment Setup Type should be "PROD" or "DEV" or "QA" -->
<add key="Site.Environment" value="QA" />
<!-- Environment Setup End -->
<!-- Security Setup -->
<add key="ConnectionString" value="Case sensitive, shouldn't match this" />
<!--Socket Setup (used for refund transaction) -->
<!--add key="SocketPort" value="3805" / -->
<!--add key="SocketTimeout" value="30000" / -->
</appSettings>
</configuration>

So after calling the macro, the file would be like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="@CBR_CBRFILE" value="Initialization.cbr" />
<!-- Environment Setup Type should be "PROD" or "DEV" or "QA" -->
<add key="Site.Environment" value="QA" />
<!-- Environment Setup End -->
<!-- Security Setup -->
<add key="ConnectionString" value="Case sensitive, shouldn't match this" />
<add key="connectionString" value="This is the one that should get matched and changed" />
<!--Socket Setup (used for refund transaction) -->
<!--add key="SocketPort" value="3805" / -->
<!--add key="SocketTimeout" value="30000" / -->
</appSettings>
</configuration>


The problem with .NET config files is that all the elements in the appsettings section are "<add ", so you have to then navigate through each element looking for the attributes "key", to find the node you want, and then once you find it, set the "value" attribute for that node. Thus all the code for navigating within the duplicate-named "<add " elements.

If someone has an easier way, please share it! I didn't see an NSIS plugin for .NET config file editing, is there one I missed?
JDaniels13#
The thing that the nsisXML has going for it is it supports XPath, including the attribute specs in the XPath query. I understand there is a Tiny XPath company to TinyXML, but the XPath functionality is not integrated into the XML plugin.

Here's what I had for the nsisXML plugin version of the .NET 1.1 config file editing macro:

!macro EditDotNETConfigFile2 ConfigFile KeyXPath KeyName KeyValue
SetPluginUnload alwaysoff
nsisXML::create
nsisXML::load ${ConfigFile}
nsisXML::select ${KeyXPath} /* '/main/child[@attrib="value2"]' */
IntCmp $2 0 notFound
; nsisXML::removeChild
nsisXML::createElement "add"
nsisXML::setText " "
nsisXML::appendChild
nsisXML::getAttribute ${KeyName}
DetailPrint "Attribute ${KeyName} is $3"
nsisXML::setAttribute "key" ${KeyName}
nsisXML::setAttribute "value" ${KeyValue}
MessageBox MB_OK "attribute set"
SetPluginUnload manual
nsisXML::save ${ConfigFile}
Goto end
notFound:
MessageBox MB_OK "XPath not resolved"
DetailPrint "XPath not resolved"
end:
!macroend

; Called like this:
!insertmacro EditDotNETConfigFile2 "c:\InetPub\wwwroot\MyWebSite\Web2.config" '/configuration/appSettings/add[@key="connectionString"]' "connectionString" "TestConnString2"

It was very handy to use XPath to go right to the "<add " element node with the "key= " attribute value I wanted. Would have saved much of the navigation code I had to write in the (TinyXML-based) XML plugin.
mtyler#
I have integrated TinyXPath with this plugin

If anyone is interested, I have integrated the TinyXPath library with this plugin. It worked like a charm the very first time (surprised the heck out of me). Here are the steps:

1) Download TinyXPath from


2) Compile the TinyXPath LIB according to the instructions here http://tinyxpath.sourceforge.net/doc/index.html

3) Copy the tinyxpath.lib file along with all the TinyXPath .h (header files), excluding the TinyXML header files, to the XML Plugin source directory.

4) Add
#include "xpath_static.h"
to the top of xml.cpp (XML Plugin source file)

5) Add the following code block to xml.cpp:

extern "C" void __declspec(dllexport) _GotoXPath(HWND hwndParent, int string_size, 
char *variables, stack_t **stacktop)
{
EXDLL_INIT();
{
popstring(szBuf);

nodeTmp = TinyXPath::XNp_xpath_node(node, szBuf);
if(nodeTmp)
{
node=nodeTmp;
pushstring("0");
return;
}
else
{
pushstring("-1");
}
}
}

6) Compile the XML Plugin source to the xml.dll file. Place the new xml.dll file in the NSIS/plugins directory (duh!)

7) Add the following code block to the XML.nsh file located in the NSIS/include directory:


!define xml::GotoXPath `!insertmacro xml::GotoXPath`

!macro xml::GotoXPath _PATH _ERR
xml::_GotoXPath /NOUNLOAD `${_PATH}`
Pop ${_ERR}
!macroend
Viola! You're done. Use
${xml::GotoXPath} "path" $errvar
in your installer script and the current node gets changed to the first node matching the XPath expression. If I have time, or someone else beats me to it, iterative expression matching could easily be added by storing the XPath state (by using the xpath_processor.h rather than xpath_static.h).

Ask if you need any help. Let me know if this helps. And thank you "Instructor" for the Xml Plugin (way better than the rest of them)
JDaniels13#
Thanks for your efforts! I'm not very handy with C++ builds; could you post the resulting XML.dll here?
mtyler#
Here is the new xml.dll file

This site will not allow me to upload a DLL. I've zipped to see if it will accept it. You will need to modify xml.nsh according to my message.

Let me know if it works for you.
mtyler#
PLEASE NOTE:

I hope that I have not overstepped my bounds by providing a modified version of xml.dll to this forum. The modification is based on the latest code base from "Instructor" and TinyXPath, but there are no promises of accuracy, reliability or safety, expressed or implied. Use at your own risk!

It would be nice if "Instructor" integrated this addition into the next build of his XML Plugin, giving it full XPath capabilities.
jeichhorn#
I am trying to use the ${xml::GotoXPath} command added by mtyler.

Here is my test.xml:

<?xml version="1.0" encoding="utf-8" ?>
<test>
<c1>content1</c1>
<c2>
<c3>c3_content1</c3>
<c3>c3_content2</c3>
<c3>c3_content3</c3>
</c2>
</test>
I am trying to select the element /test/c2/c3 with the content 'c2_content2'.

When i use the xpath
${xml::GotoXPath} "//c3[text()='c3_content2']" $0
the element is selected as expected, but when i use
${xml::GotoXPath} "/test/c2/c3[text()='c3_content2']" $0
it does not find the element (result is -1).

Do i use the GotoXPath function in a wrong way in my second example?

Thanks for help.

Jörg
mtyler#
The immediate workaround for your problem is to execute
${xml::RootElement} $1 $0
after loading the document and before conducting any static XPath queries that begin with the root element. Any XPath that is based on the top level element will work, including yours.

The way the TinyXML works is to have a node above the top level element for the document itself. From this node you can get the document name and access XML comments that are outside the scope of the top level element. The XML plugin maintains it state as the current or last node that was read. This enables you to jump around from a relative perspective. But, from a technical perspective, conducting a static XPath query from the document node will not work.

I considered modifying the GotoXPath function to account for this, but I think that it will defeat the purpose of the document node. So use the workaround specified above or use the XPath query Descendant notation along with the root element (even though this is not entirely accurate; you should use xml::RootElement):
//test/c2/c3[text()='c3_content2']
The original GotoPath function looks for a leading slash in the path designation and moves the current node to the root element before it locates the desired element. This works well for this function because it is a simple navigation function. The TinyXPath library is a robust XPath processor that will not benefit from the same logic used by the GotoPath function. Therefore, the suggestions I made above should become the norm.

As usual, I hope that this has helped. Feel free to ask for assistance. I am appreciative of all the great tools and technologies supported by this community.
Instructor#
Changed: Compiled statically without msvcrt.lib, now plugin work on Win95.
Added: Integrated TinyXPath v1.2.4 (thanks mtyler).
Added: ${xml::XPathString} -compute a string XPath expression.
Added: ${xml::XPathNode} -compute a node XPath expression and goes to it.
Added: ${xml::XPathAttribute} -compute an attribute XPath expression and goes to it.
Added: ${xml::CurrentAttribute} -returns name and value of the current attribute.
Updated: TinyXml to v2.5.1
Updated: StackFunc.h to v2.0


"XML" plugin v1.8

Download from Wiki
rtpHarry#
possible bug in system

I almost abandoned this plugin yesterday when I was writing my installer. Now I have found a work around for it and I think its a great plugin again! 😉

The problem arose when I tried to open an xml file for the second time. The first time I loaded the file (through a function attached to a custom page) it worked fine with this code (as I see it used in the forums as well):

  ${xml::LoadFile} "client-list.xml" $0 
The second time I used it was in a Section (for the install files part) and it failed with -1 every time until I saw somebody suggest for a different problem to prepend $EXEDIR to the start which fixed it.

${xml::LoadFile} "$EXEDIR\client-list.xml" $8 
niklasu#
XPathNode and XPathAttribute

Originally posted by Instructor
Added: ${xml::XPathString} -compute a string XPath expression.
Added: ${xml::XPathNode} -compute a node XPath expression and goes to it.
Added: ${xml::XPathAttribute} -compute an attribute XPath expression and goes to it.
I'm trying to use XPathAttribute to change a setting in a web.config file, like this:
${xml::LoadFile} "$INSTDIR\Web.config" $0
${xml::XPathAttribute} "/configuration/appSettings/add[@key='EPsConnection']" $0
${xml::SetAttribute} "value" "some value" $0
${xml::SaveFile} "$INSTDIR\Web.config" $0
${xml::Unload} 
But everytime the xml:XPathAttribute statement executes the installer crashes with the following message:

Microsoft Visual C++ Runtime Library
Assertion failed!

Program: setup.exe
File: tinyxml.cpp
Line: 173

Expression: node->GetDocument() == 0 || node->GetDocument() == this->GetDocument()
(And I get the same error when using XPathNode too...)
What am I doing wrong here?
Instructor#
${xml::LoadFile} "$INSTDIRWeb.config" $0
${xml::RootElement} $0 $1
${xml::XPathAttribute} "/configuration/appSettings/add[@key='EPsConnection']" $0
${xml::SetAttribute} "value" "some value" $0
${xml::SaveFile} "$INSTDIRWeb.config" $0
${xml::Unload}
Commerzpunk#edited
Originally posted by Instructor
1. Not.
2. I'm not plaining this.

But you can use ${xml::CloneNode}


Section
${xml::LoadFile} "file.xml" $0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

${xml::GotoPath} "/" $0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

${xml::CloneNode} $R0
MessageBox MB_OK "xml::CloneNode$\n$$R0=$R0"

${xml::Unload}


${xml::LoadFile} "file2.xml" $0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

${xml::GotoPath} "/" $0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

${xml::InsertBeforeNode} "$R0" $0
MessageBox MB_OK "xml::InsertBeforeNode$\n$$0=$0"

${xml::FreeNode} "$R0" $0
MessageBox MB_OK "xml::FreeNode$\n$$0=$0"

${xml::SaveFile} "file2.xml" $0
MessageBox MB_OK "xml::SaveFile$\n$$0=$0"

${xml::Unload}
SectionEnd

I really tried hard to get this working for me.
The installer always crashed, when I tried to "InsertBeforeNode".

Now, I used this code and changed it, had several hundered crashes.

Here is the working result (for me):


${xml::LoadFile} "file1.xml" $0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

${xml::GotoPath} "Root/I" $0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

${xml::CloneNode} $R0
MessageBox MB_OK "xml::CloneNode$\n$$R0=$R0"

${xml::LoadFile} "file2.xml" $0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

${xml::RootElement} $0 $1 # THATS THE SOLUTION FOR NO CRASHING!
MessageBox MB_OK "xml::RootElement$\n$$0=$0"
MessageBox MB_OK "xml::RootElement$\n$$1=$1"

${xml::GotoPath} "I" $0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

${xml::InsertBeforeNode} "$R0" $0
MessageBox MB_OK "xml::InsertBeforeNode$\n$$0=$0"

${xml::SaveFile} "file2.xml" $0
MessageBox MB_OK "xml::SaveFile$\n$$0=$0"

${xml::Unload}
Messageboxes are for information and debugging only, of course!

Greets,

HA
eafonsof#
Modify &quot;value&quot; attribute.

Hi there, I tried the following commands to modify the attribute named "value". It's not working as I expected. Does anybody know what's missing or wrong?
Thanks in advance.

;Format of config file:
;<?xml version="1.0" encoding="Windows-1252"?>
;<configuration>
; <appSettings>
; <add key="key1" value="value1" />
; <add key="key2" value="value2" />
; <add key="key3" value="value3" />
;
; <!-- Some comments -->
; <add key="key4" value="value4" />
; </appSettings>
;</configuration>

${xml::LoadFile} "$configFile" $0
${xml::RootElement} $0 $1
${xml::XPathAttribute} "/configuration/appSettings/add[@key='${KEY4}']" $0
${xml::SetAttribute} "value" "$newValue" $0
${xml::SaveFile} "$configFile" $0
${xml::Unload}

After this, the attribute "value" is set as an attribute of "<configuration" instead of the corresponding "<add" node.

i.e.
;
;<?xml version="1.0" encoding="Windows-1252"?>
;<configuration value="new_value4">
; <appSettings>
; <add key="key1" value="value1" />
; <add key="key2" value="value2" />
; <add key="key3" value="value3" />
;
; <!-- Some comments -->
; <add key="key4" value="value4" />
; </appSettings>
;</configuration>
Instructor#
${xml::LoadFile} "$configFile" $0
${xml::RootElement} $0 $1
${xml::XPathNode} "/configuration/appSettings/add[@key='${KEY4}']" $0
${xml::SetAttribute} "value" "$newValue" $0
${xml::SaveFile} "$configFile" $0
${xml::Unload}
Instructor#
Updated: TinyXml to v2.5.3
Updated: TinyXPath to v1.3.0


"XML" plugin v1.9

Download from Wiki
DrDan#
Question about the plugin

I'm working on an installer that uses this plug-in. Now, depending on the contents in my XML I need to run an external progam (in fact another NSIS uninstaller) that will read in the XML file and make changes to it.

The steps I am following are:

1) Open the XML file in the main installer
2) Manipulate as required
3) Determine if an external uninstaller is required
4) If an external uninstaller is required, then save the XML file, run the external uninstaller, re-load the XML file and carry on.
5) If no external uninstaller is required, carry on.

My question is, in step 4, should I also unload the XML plug-in (i.e. call ${xml::Unload}) before running the external uninstaller?

Thanks.