Archive: XML plugin


XML plugin
  XML plugin parses an XML document, and builds from that a Document Object Model (DOM) that can be read, modified and saved. XML plugin is based on TinyXml (2.4.0 RC).

Features:
- Read and/or modified xml files
- Code is fast and lightweight
- Unicode UTF-8 support
- "MSXML.DLL" independent
- Support for condense and non-condense white spaces
- Row and Column tracking


"XML" plugin v1.0


:up: :up: :up: for

"MSXML.DLL" independent 

>
Best Regards and Good Job

Changes:
- TinyXml updated to v2.4.1
- Fixed: output variable in xml::NoChildren
- New: xml::GetText
   Convenience function for easy access to the text inside current element.
- New: xml::SetCDATA
   Turns on or off a CDATA representation of the current element text.
- New: xml::IsCDATA
   Queries whether this represents text using a CDATA section.


"XML" plugin v1.1


next plugin i repack for correct usage of nsis file structure :)


-TinyXml updated to v2.4.2
-Fixed: "CreateNode" appearance of the "\r\n" at the end (thanks Lee Thomason)
-Fixed: Now error appears if current node is TEXT and uses "InsertEndChild"
-Changed: "AttributeByName" -> "GetAttribute" now not only returns value of the attribute, but also goes to it
-New: "GetNodeValue" can be useful in some cases


"XML" plugin v1.2


How would I go about selecting a particular node by it's name? Like how Olivier Marcoux's NSIS XML uses...

nsisXML::select <XPath expression>

Thx!


If you want to "select" the node you need go to it (FirstChildElement, NextSiblingElement ...). Easy for me, when you ask the concrete example, what you trying to do.


Somewhere nested in my XML, I have a set of child nodes who have the same name and share the same parent. The child nodes differ by having different attributes values.

I would like to test if a particular node exists in an XML without having to drill down using FirstChildElement and NextSiblingElement. If the node with the target node name exists, I would make it the current node.

I thought GotoHandle would do this for me but I am wrong in thinking it takes in a node name for a parameter.


I'll think about recursive node search


New: [xml::FindNextElement /NOUNLOAD "[name]" .r0 .r1]
&nbsp;&nbsp;&nbsp;-Search for element (recursive), returns the element name and goes to it.
New: [xml::FindCloseElement /NOUNLOAD]
&nbsp;&nbsp;&nbsp;-Closes search opened by xml::FindNextElement.
New: [xml::ElementPath /NOUNLOAD .r0]
&nbsp;&nbsp;&nbsp;-Returns the current element path.
&nbsp;&nbsp;&nbsp;(e.g. "/a/b/c[3]/d")
New: [xml::GotoPath /NOUNLOAD "[path]" .r0]
&nbsp;&nbsp;&nbsp;-Goes to the specified path.
&nbsp;&nbsp;&nbsp;(e.g. from root "/a/b[2]/c/d", from current element "a/b[2]/c/d",
&nbsp;&nbsp;&nbsp;&nbsp;using last element 'b' "a/b[-1]/c/d")


"XML" plugin v1.3


You da man! That was a fast. I'll give this a shot. Thx


Hey Instructor any plans for UTF-16 support? I could really use that.


DOM builds by TinyXML parser, so UTF-16 is depend on this project. BTW: Where is already have feature request about it.


Ah excellent. Good to know it's on the queue.


Added: now "GotoPath" can accept elements without name (e.g. using any last element "a/[-1]/c/d")


"XML" plugin v1.4


Fixed: Now "ElementPath" and "GotoPath" correctly working with the document beginning. If "ElementPath" returns "" - the current element is the beginning of the document. [xml::GotoPath /NOUNLOAD "" .r0] - go to the document beginning.


"XML" plugin v1.5


Bug in GetAttribute
  Hi,

there seems to be a bug in the GetAttribute Method, it returns
always -1 for the first attribute of a node.

A possible workaround is to check if the FirstAttribute differs from the desired attribute.

Kind Regards,

Rob


Hi,

May be I'm missing something but I don't understand how to replace a text value.

There's a xml::GetText function. How do I do a xml::SetText ?

Thanks in advance

Chag


Originally posted by chagam
Hi,

May be I'm missing something but I don't understand how to replace a text value.

There's a xml::GetText function. How do I do a xml::SetText ?

Thanks in advance

Chag
Did you try the xml::SetNodeValue method ?

Hi,

Yes, I tried. May be a did something wrong.

My xml file looks like this :
<config>
<local>
<host>test</host>
</local>
</config>

I tried :
xml::GotoPath /NOUNLOAD "/config/local/host" .r0
xml::SetNodeValue /NOUNLOAD "1234" .r0

and my new xml file looks like this :
<config>
<local>1234
</local>
</config>

Chag


Name "XMLTest"
OutFile "XMLTest.exe"

Section
xml::LoadFile /NOUNLOAD "test.xml" .r0
MessageBox MB_OK "xml::LoadFile$\n$$0=$0"

xml::GotoPath /NOUNLOAD "/config/local/host" .r0
MessageBox MB_OK "xml::GotoPath$\n$$0=$0"

xml::FirstChild /NOUNLOAD "" .r0 .r1
MessageBox MB_OK "xml::FirstChild$\n$$0=$0$\n$$1=$1"

xml::SetNodeValue /NOUNLOAD "1234"
MessageBox MB_OK "xml::SetNodeValue"

xml::SaveFile "test_saved.xml" .r0
MessageBox MB_OK "xml::SaveFile$\n$$0=$0"
SectionEnd

there seems to be a bug in the GetAttribute Method, it returns
always -1 for the first attribute of a node.
Fixed, thanks.

Fixed: GetAttribute returns -1 for the first attribute.
New: "SetText" -convenience function for easy set the text of the current element.


"XML" plugin v1.6


Hello,

I need to create a new element without text.
I use this code :


xml::LoadFile /NOUNLOAD "test.xml" .r0

xml
::GotoPath /NOUNLOAD "/root" .r0
xml::CreateNode /NOUNLOAD "<histo></histo>" .r0
xml::InsertEndChild /NOUNLOAD "$0" .r0
xml::SaveFile /NOUNLOAD "test.xml" .r0
>
But the new element is with a blank at the end, like this :

>

<
histo />
</root>
If I create the element with text, it's ok.
Have you got an idea?

Thanks

(sorry if my english is not very good)

this is correct xml behaviour.
empty elements have to be <element />, not <element></element>.

last one is no valid xml!


Yes, I know, the problem is the blank after 'histo'.
I have this <histo /> (you see, the blank between 'histo' and '/') and not this <histo/>.

Sorry for my bad explanation :(


W3.org (which pretty much has the final word on markup languages like XML) recommends that a space should be used when declaring empty tags.

So this would be proper XML:
<histo />

NOT this:
<histo/>

See the section tags for empty elements at http://www.w3.org/TR/REC-xml/#sec-starttags for more info.

[edit]
And FWIW, I've always seen empty tags written using a space between the element name and the '/' character...


Ok, I use xmlspy and it deletes the blank, so, I thought it was not normal.
Thanks a lot (and thanks for this perfect plugin)


Updated: TinyXml to v2.4.3
Changed: Now plugin used header "XML.nsh" for custom user variables and
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;better compile errors check.

Update from previous versions:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Insert line in script:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;!include "XML.nsh"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Replace:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xml::LoadFile -> ${xml::LoadFile} ...
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Replace:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.r0 -> $0, .r1 -> $1 ... .R0 -> $R0, .R1 -> $R1 ...

"XML" plugin v1.7


I need to be able to parse two XML documents at the same time (because I'm merging them during an upgrade process).

From looking at the API and the source code, it looks like this XML plugin only supports working on a single XML document at a time, because there is no concept of a file handle being returned by ${xml::LoadFile} and being passed to the other functions. It looks like there is a single, global XML document being edited (TiXmlDocument doc).

So, two questions:

1. Is it possible to edit two XML documents at the same time using this plugin?
2. If not, would you consider enhancing the plugin to use file handles to support editing multiple XML documents at once?

Thanks!


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


FileWrite


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!


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).


I have no problem. Make sure that the file exist and try to specify full path to it.


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 ;-)


as the plugin is extracted to $PLUGINSDIR i guess it searches the file there, not in $EXEDIR.


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.


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.