Old 15th November 2021, 00:45   #1
pocz
Junior Member
 
Join Date: Nov 2021
Posts: 8
Edit attributes in an XML with nsisXML by Wizou

I can't figure it out.
I try to modify an XML, but I get lost when finding the issue why it's not working.
I have a file "foo.xml"
HTML Code:
<global>
    <colors
      lava="orange"
      sky="blue"
    >
    </colors>
</global>
And I try to achieve this:
HTML Code:
<global>
    <colors
      lava="orange"
      sky="cyan"
      earth="brown"
    >
    </colors>
</global>
Let's assume I am not sure if the attributes sky and earth exist in the file, before editing.
This is how my NSIS code looks like:

code:
nsisXML::create
nsisXML::load "foo.xml"
nsisXML::select "/global/colors"
nsisXML::setAttribute "sky" "cyan"
nsisXML::setAttribute "earth" "brown"
nsisXML::save



I haven't found any information in the documentation how to overwrite, add, or remove attributes with their values. Do I have to remove entire nodes, and append them again in the end?
pocz is offline   Reply With Quote
Old 15th November 2021, 01:25   #2
pocz
Junior Member
 
Join Date: Nov 2021
Posts: 8
I think nsisXML by Joel will be the easier solution to my problem.
code:
${nsisXML->removeAttr}
${nsisXML->SetElementAttr}



The simpliest way would be just to add the attributes, and hoping the old ones will be overwritten.
But knowing life, I probably have to check the attribute to see if it exists, then eventually remove it, and then add new attributes:
(Check if "sky" and "earth" exist, if they exist - remove them, create new attributes "sky" and "earth" with new values)
pocz is offline   Reply With Quote
Old 15th November 2021, 10:49   #3
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
The Wizou plugin uses MSXML and MSDN says: "Sets or updates the supplied attribute node on this element"

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 15th November 2021, 13:42   #4
pocz
Junior Member
 
Join Date: Nov 2021
Posts: 8
I got it.
This in nsisXML by Wizou should be written in an other way, so it would work.
code:
nsisXML::create
nsisXML::load "foo.xml"
nsisXML::select "/global/colors"
nsisXML::setAttribute "sky" "cyan"
nsisXML::setAttribute "earth" "brown"
nsisXML::save


But it didn't work for me.
But this below did work, in nsisXML by Joel. Last time updated in 2009.
code:
${nsisXML->OpenXML} "foo.xml"
${nsisXML->SetElementAttr} "/global/colors" "sky" "cyan"
${nsisXML->SetElementAttr} "/global/colors" "earth" "brown"
${nsisXML->Release} "foo.xml"


I didn't have to remove anything. There are no clones of attributes, it just works, and looks simple.

If you have any ideas how the Wizous code should look like, please post your example.
pocz is offline   Reply With Quote
Old 15th November 2021, 18:59   #5
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
Quote:
Originally Posted by pocz View Post
If you have any ideas how the Wizous code should look like, please post your example.
PHP Code:
FileOpen $0 "$temp\test.xml" w
FileWrite $0 '<?xml version="1.0" encoding="ISO-8859-1" ?><global><colors lava="orange" sky="blue"></colors></global>'
FileClose $0

nsisXML::create
DetailPrint $0,$1
nsisXML::load  "$temp\test.xml"
DetailPrint $0,$1
nsisXML::select "/global/colors"
nsisXML::setAttribute "sky" "cyan"
nsisXML::setAttribute "earth" "brown"
strcmp $0 0 +3
nsisXML::save "$temp\test.xml"
nsisXML::release $0

nsexec::ExecToLog 'cmd.exe /C type "$temp\test.xml"'
Pop $0

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 15th November 2021, 19:58   #6
pocz
Junior Member
 
Join Date: Nov 2021
Posts: 8
Ah, yes. I forgot to save the file properly.
nsisXML::save requires an argument.

The downside of the two nsisXML versions is,
that the structure is being rebuilt.
This is the result I was trying to achieve:
HTML Code:
<global>
    <colors
      lava="orange"
      sky="cyan"
      earth="brown"
    >
    </colors>
</global>
But instead I get this:
HTML Code:
<global>
    <colors lava="orange" sky="cyan" earth="brown">
    </colors>
</global>
The problem is, that the XML file has about a hundred of attributes in a single node, and this many attributes in a single line is a bit messy. Anyway, at least other programs can read all the values properly.
Attached Files
File Type: zip Foo.zip (700 Bytes, 141 views)
pocz is offline   Reply With Quote
Old 15th November 2021, 21:58   #7
pocz
Junior Member
 
Join Date: Nov 2021
Posts: 8
Quote:
The problem is, that the XML file has about a hundred of attributes in a single node, and this many attributes in a single line is a bit messy.
Instead in an XML file, the dozens of attributes should have been stored in an INI file. It would be easier to read. ..
pocz is offline   Reply With Quote
Old 15th November 2021, 22:02   #8
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
Quote:
Originally Posted by pocz View Post
The downside of the two nsisXML versions is,
that the structure is being rebuilt.
This will only save some of the whitespace but it is the best you are going to get with MSXML I'm guessing

PHP Code:
FileOpen $0 "$temp\test.xml" w
FileWrite $0 '<?xml version="1.0" encoding="ISO-8859-1" ?>$\r$\n$\r$\n<global><colors lava="orange" sky="blue">$\r$\n$\r$\n</colors>$\r$\n$\r$\n</global>'
FileClose $0

nsisXML::create
DetailPrint $0,$1
strcmp $0 0 +2
System::Call `$0->72(i-1)` ; put_preserveWhiteSpace
nsisXML::load  "$temp\test.xml"
nsisXML::select "/global/colors"
nsisXML::setAttribute "sky" "cyan"
nsisXML::setAttribute "earth" "brown"
strcmp $0 0 +3
nsisXML::save "$temp\test.xml"
nsisXML::release $0

nsexec::ExecToLog 'cmd.exe /C type "$temp\test.xml"'
Pop $0

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 8th July 2022, 17:06   #9
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Hi

I've got a similar issue, so hope this is the right place to post it?

I'm trying to edit one setting in an existing settings.xml file, on upgrades.

This is what the settings file looks like (for sake of anonymity, values obviously aren't literal):

code:
<?xml version="1.0" encoding="UTF-16"?>
<program:settings xmlns:program="http://mysite.com" version="3">
<settings setting1="whatever1" setting2="whatever2" setting3="whatever3"/>
<moresettings setting4="whatever4" setting5="whatever5" setting6="whatever6"/>
<my service="http://mysite.com/service1/"/>
<data>loads more data here</data>
</program:settings>




I want the installer to change one setting as follows:

code:
<my service="http://mynewsite.com/service2/"/>


It should be simple?


I've put Wizou's unicode nsisXML.dll in NSIS\Unicode\Plugins\x86-unicode


Section in installer.nsi

PHP Code:
    ${If} ${FileExists"$SETTINGSDIR\settings.xml"
        
CreateDirectory "$SETTINGSDIR\backup"
        
CopyFiles /SILENT "$SETTINGSDIR\settings.xml" "$SETTINGSDIR\backup\settings.xml"
        
nsisXML::create
        nsisXML
::load "$SETTINGSDIR\settings.xml"
        
StrCpy $"add"
        
nsisXML::select '/program:settings/my'
        
nsisXML::setAttribute "service" "http://mynewsite.com/service2/"
        
nsisXML::save "$SETTINGSDIR\settings.xml"
        
nsisXML::release $0
    
${EndIf} 
The installer crashes after creating the backup, so the script is obviously wrong.

Any help would be greatly appreciated. Thanks!
MookyMook is offline   Reply With Quote
Old 9th July 2022, 10:07   #10
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
I've tried loads of different things from this thread and from the given example files, but the installer always crashes (with no errors).

Does nsisXML not work with UTF-16?

Or is it something to do with the structure or chars in the node names & attribs (colons, http://, slashes, quotes, spaces)?

Or is just that the syntax has to be exact - but mine is always wrong?

Anyone?
MookyMook is offline   Reply With Quote
Old 9th July 2022, 15:26   #11
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
Check if $0 is 0 after nsisXML::load. If it is, it failed to load the XML file.

PHP Code:
RequestExecutionLevel User
Unicode True

Section "Prepare example"
Var /Global SETTINGSDIR
StrCpy $SETTINGSDIR $ExeDir
IfFileExists "$SETTINGSDIR\settings.xml" skip
; Create xml file if it is not already there
FileOpen $0 "$SETTINGSDIR\settings.xml" w
FileWriteUTF16LE /BOM $0 ""
FileWriteUTF16LE $0 '<?xml version="1.0" encoding="UTF-16"?><program><settings dontcare="" /><my service="http://mysite.com/service1/"/></program>'
FileClose $0
skip:
SectionEnd

!include LogicLib.nsh
Section
nsisXML::create
nsisXML::load "$SETTINGSDIR\settings.xml"
${If} $0 P<> 0
    nsisXML::select '/program/my'
    MessageBox mb_ok "$1 should not be 0 here"
    ${If} $1 P<> 0
        System::Call 'KERNEL32::GetTickCount()i.r5' ; Get arbitrary value in $5
        nsisXML::setAttribute "service" "http://mynewsite.com/service2/?something=$5"
        nsisXML::save "$SETTINGSDIR\settings.xml"
    ${EndIf}
    nsisXML::release $0
${Else}
    MessageBox mb_ok "load failed"
    nsisXML::release $1
${EndIf}
SectionEnd
Why it does not like program:settings or its xmlns, I don't know. Probably related to MSXML somehow.

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 9th July 2022, 17:37   #12
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Wow, thank you very much Anders.

We're getting somewhere now. It compiles and doesn't crash.
However, it's effectively creating a <1kb default settings file - which can also be achieved by just deleting settings.xml

So it looks like just the "Prepare example" section is working, but nothing is happening for the nsisXML section.


What I also didn't mention is there's a whole heap more user settings and data in the file which needs to be kept on upgrades.

But that's why I was creating a backup, so at least nothing is lost.

I'm also slightly embarrassed about getting the node/attrib/value wrong in my original script, but I know now. So again, thank you very much.


I'll keep tinkering with it and will let you know how it goes.
MookyMook is offline   Reply With Quote
Old 9th July 2022, 17:52   #13
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
Are you sure you copied the DLL from the binU directory in the .zip? My code works for me, it changes mysite.com/service1/ to mynewsite.com/service2/...

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 9th July 2022, 19:52   #14
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Oh, I think I see what's happening now.
You'll probably hate me for this, but I didn't realise we'd need to write a new temp transitional xml file first.
I thought we could just directly edit the existing settings.xml file.

The truth of the matter is that "http://mysite.com/service1/" is actually a random variable.
It's a user override setting which could be any url.
I want to change it to my new service url - or even just change it to empty, e.g. <my service=""/>

The user can then decide if they want to keep the new service or manually override it again with their own custom url.

So I've no way of knowing what the exact custom user url will be.

Can we specify a wildcard?
Or is there a way to get that exact value first with nsisXML::getAttribute?


Though I have actually tried setting it to the same custom url in both settings.xml and the FileWriteUTF16LE $0 line, but it still doesn't work.

I just get a new settings.xml file - so I'm obviously still doing something wrong.


And yes, I can confirm that binU \ nsisXML.dll is definitely in the "NSIS\Plugins\x86-unicode" folder.

_____________________________________


Also, in this line:

<program><settings dontcare="" /><my service="http://mysite.com/service1/"/></program>

Which "settings" is it referring to from my sample above?

Is it this "settings"?
<program:settings xmlns:name="http://mysite.com" version="3">

Or this "settings"?
<settings setting1="whatever1" setting2="whatever2" setting3="whatever3"/>


The reason being that those aren't the actual names, so I need to adapt it to my script accordingly.


Sorry for the confusion. I really appreciate all the help!
MookyMook is offline   Reply With Quote
Old 9th July 2022, 21:22   #15
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
You can edit a existing xml file directly. I changed my example a bit to reflect this. Until you can get my example to work correctly we can't really move on. By correctly I mean, you can edit the <settings dontcare="xyz"/> part in Notepad and those changes should remain if you run the code again (but you will get a new url part after "?something=" every time you run it).

You can probably read the value but my example does not care what the value is, it just uses the attribute name ("service" in the "my" node).

I was referring to <settings setting1="whatever1" setting2="whatever2" setting3="whatever3"/> but its name does not matter, I just slightly shortened your file.

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 9th July 2022, 23:56   #16
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Thanks again Anders.

Ok, so I don't need the installer to create an xml file.
The program itself will create settings.xml with all the default values - on first run on clean installs.

I just need to check and adjust that one existing setting on upgrades.

The following are already declared/defined in the main nsi script so aren't required for this section:

code:
Unicode True
RequestExecutionLevel Admin
!include "LogicLib.nsh"
Var SETTINGSDIR
!define FILES_PATH




Here's what I've got so far

PHP Code:
Section "name3"
SetOutPath $INSTDIR\Bin
File 
${FILES_PATH}\Bin\some.dll
File 
${FILES_PATH}\Bin\another.dll
WriteRegStr HKEY_CLASSES_ROOT 
"foo" "" "blah"

; If it existsbackup current component settings file first
${If} ${FileExists"$SETTINGSDIR\path\to\name3\settings.xml"
CreateDirectory "$SETTINGSDIR\path\to\name3\backup"
CopyFiles /SILENT "$SETTINGSDIR\path\to\name3\settings.xml" "$SETTINGSDIR\path\to\name3\backup\settings.xml"

Force using new default service on upgrades
nsisXML
::create
nsisXML
::load "$SETTINGSDIR\path\to\name3\settings.xml"
    
${If} $0 P<> 0
        nsisXML
::select '/program/my'
            
${If} $1 P<> 0
            nsisXML
::setAttribute "service" ""
            
nsisXML::save "$SETTINGSDIR\path\to\name3\settings.xml"
            
${EndIf}
        
nsisXML::release $0
        
${Else}
        
nsisXML::release $1
    
${EndIf}
${EndIf}
SectionEnd 
I've also tried the following variations:

code:
nsisXML::select '/program:settings/my'


code:
nsisXML::setAttribute "service" "http://mynewsite.com/service2/"


I've also tried adding your extra lines:

code:
System::Call 'KERNEL32::GetTickCount()i.r5' ; Get arbitrary value in $5
nsisXML::setAttribute "url" "http://mynewsite.com/service2/?id=$5"



Although I'm not sure what this does tbph.
There can't be anything like /?id= after the service url.
Was it just an example to show how an edit should work?


The installer compiles and runs properly.

The settings dir and file (in %AppData%) are not read-only.
The folder has full write permissions etc.

Everything works, except...when I go to the Settings folder,
settings.xml has not been modified and the old custom value for <my service=""/> still exists.

I'm not sure what I'm doing wrong.
MookyMook is offline   Reply With Quote
Old 10th July 2022, 00:38   #17
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
I asked you to get my example working before doing anything else!

If you wish to go on alone; after nsisXML::load, MessageBox $0. After nsisXML::select, MessageBox $1 or $2 or whatever the plug-in readme says it writes to.

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 10th July 2022, 00:59   #18
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Sorry. I couldn't get it working.
As I said, all that happened was a new settings.xml file was created.
I've tried everything you've suggested so far.
I've tried over and over again.

With your modified version, it (rightly) skips the "prepare example" section if settings.xml exists - and if it doesn't exist, I don't need the installer to create a new xml file.

I've read the plugin readme again and there's no ref to MessageBox or similar.

It's driving me crazy :-(

If only it was a simple .ini file instead.
MookyMook is offline   Reply With Quote
Old 10th July 2022, 01:18   #19
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
I know there is no reference to MessageBox, you just need to use it to see the return values. I changed my example, try again.

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 10th July 2022, 08:49   #20
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Thanks for your patience Anders

This is what I get with the revised example:

Quote:
Program Installer

0 should not be 0 here
MookyMook is offline   Reply With Quote
Old 10th July 2022, 09:52   #21
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
nsisXML::select is failing to find the node.

1) Delete the .xml and try again. Inspect the .xml, does the select path look valid?
2) Compile my example and upload the .exe here in a .zip

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 10th July 2022, 11:16   #22
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Sure, I can do that (2), but before I do...

It works if I manually change the <program:settings xmlns:program="http://mysite.com" version="3"> line to just <program>

(and obviously also manually change the closing tag at the end to just </program>)

The message is then "111135688 should not be 0 here"
and the service url value is changed.

However, the <program:settings xmlns:name="http://mysite.com" version="3"> line exists on all installations, so we'd need to somehow change it and get the program itself to recognise the change ?

We can't delete the existing xml file first because it can contain a ton of user data from over the years.

For whatever reason (the colon, the xmlns, the extra params), possibly due to msxml, it's not finding the node.
MookyMook is offline   Reply With Quote
Old 10th July 2022, 13:08   #23
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
It would work if there was some way for the installer to rename
<program:settings xmlns:program="http://mysite.com" version="3"> to just <program>

Then make the change to the "service" value.

Then rename the node back to the original name.

And all whilst keeping all the rest of the existing settings and data.

But something tells me that's not possible?
MookyMook is offline   Reply With Quote
Old 10th July 2022, 15:38   #24
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
I wanted to get my example working first so we did not have to deal with too many issues at once.

I assume there is a way to deal with namespaces but I'm no XML expert. Maybe you could try reading a bit about MSXML on MSDN...

https://docs.microsoft.com/en-us/pre...763742(v=vs.85)
https://docs.microsoft.com/en-us/pre...754539(v=vs.85)

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 10th July 2022, 19:46   #25
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
Thanks again Anders.
Yes, we'll need to do some research and rethink things.
I'll post here if I find a solution involving NSIS.
MookyMook is offline   Reply With Quote
Old 10th July 2022, 20:18   #26
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,505
According to https://docs.microsoft.com/en-us/dot...ace-navigation if you have
Quote:
<e:Envelope xmlns:e="http://schemas.xmlsoap.org/soap/envelope/"> <e:Body><s:Search xmlns:s="http://schemas.microsoft.com/v1/Search">
the xpath expression is "/e:Envelope/e:Body/s:Search".

Notice xmlns:e, where your xml uses xmlns:name instead of xmlns:program or settings or whatever it is supposed to be.

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 10th July 2022, 21:16   #27
MookyMook
Junior Member
 
Join Date: Jul 2022
Posts: 11
So in my example:

code:
<?xml version="1.0" encoding="UTF-16"?>
<program:settings xmlns:program="http://mysite.com" version="3">
<settings setting1="whatever1" setting2="whatever2" setting3="whatever3"/>
<moresettings setting4="whatever4" setting5="whatever5" setting6="whatever6"/>
<my service="http://mysite.com/service1/"/>
<data>loads more data here</data>
</program:settings>



The xpath expression for service="" attrib is...?

"/program:settings/my" ?

I tried that with your script and got "0 should not be 0 here" :-(

Quote:
Originally Posted by Anders View Post
According to https://docs.microsoft.com/en-us/dot...ace-navigation if you have the xpath expression is "/e:Envelope/e:Body/s:Search".

Notice xmlns:e, where your xml uses xmlns:name instead of xmlns:program or settings or whatever it is supposed to be.
Yes, that was originally a mistake on my behalf (in this thread).
The two values are actually the same in the xml:

<program:settings xmlns:program=...
MookyMook is offline   Reply With Quote
Reply
Go Back   Winamp & Shoutcast Forums > Developer Center > NSIS Discussion

Tags
attribute, joel, wizou, xml

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump