Modify an XML

I’d like to be able to modify xml values using NSXMLDocument, but I’m struggling to understand how to do so.

Here’s an example:

What if I want to replace with ?

Thanks to Shane Stanley’s Everyday AppleScriptObjC, I know how to initialize an instance of the xml:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set thePath to "/users/tneison/desktop/test.xml"
its extractFrom:thePath
on extractFrom:thePath
	set theURL to current application's NSURL's fileURLWithPath:thePath
	set aURL to current application's |NSURL|'s fileURLWithPath:thePath -- make NSURL
	set theXMLDoc to current application's NSXMLDocument's alloc()'s initWithContentsOfURL:aURL options:0 |error|:(missing value) -- make XMLDoc
	current application's NSLog("%@", theXMLDoc)
end extractFrom:

I can create a new node using:

set newNode to current application's NSXMLNode's elementWithName:"clip" stringValue:" name=\"A test project\""

but this not is not formated in the same way. The first part of the node is suppose to be an open tag like this:


But instead I get this:


Furthermore, I don’t know the simplest way to replace the old node with the new one. It seems that this should do it but it returns index beyond bounds

set theXMLDocMod to theXMLDoc's replaceChildAtIndex:5 withNode:newNode

Any ideas?

There are several ways, depending on what you want to achieve. You can detach the old node, and add a new one using addChild:, or replace it.


But instead I get this:


[/quote]

You’re confusing the node’s stringValue with an attribute. You need something like this:

set newAttr to current application's NSXMLNode's attributeWithName:"name" stringValue:"A test project"
set newNode to current application's NSXMLNode's elementWithName:"clip" children:(missing value) attributes:{newAttr}

That’s the right command, but wrong target and index. Your is child 0 of the element – it’s not a (direct) element of the document. You need to either walk the node hierarchy or use nodesForXPath:error: to get the node first.

I’m still stuck on replaceChildAtIndex:withNode: you said:


So I tried this:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set thePath to "/users/tneison/desktop/test.xml"
its extractFrom:thePath
on extractFrom:thePath
	set theURL to current application's NSURL's fileURLWithPath:thePath
	set aURL to current application's |NSURL|'s fileURLWithPath:thePath -- make NSURL
	set theXMLDoc to current application's NSXMLDocument's alloc()'s initWithContentsOfURL:aURL options:0 |error|:(missing value) -- make XMLDoc
	set newAttr to current application's NSXMLNode's attributeWithName:"name" stringValue:"A test project"
	set newNode to current application's NSXMLNode's elementWithName:"clip" children:(missing value) attributes:{newAttr}
	set theSpine to theXMLDoc's nodesForXPath:"/fcpxml/library/event/project/sequence/spine" |error|:(missing value)
	set theSpineMod to theSpine's replaceChildAtIndex:0 withNode:newNode
	current application's NSLog("%@", theSpineMod)
end extractFrom:

But it’s giving me:

Am I missing a step?

Post the full error, so we know which selector.

I suspect your problem is that you’re not allowing for the fact that nodesForXPath:error; returns an array.

You’re right. I guess I’d need to coerce theSpine into a NSXMLNode somehow?

No – just use objectAtIndex:0.

I’ve managed to extract the and place the new in but I can’t figure out how to put it all back together.

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set thePath to "/users/tneison/desktop/test.xml"
its extractFrom:thePath
on modifyXML:thePath
	set theURL to current application's NSURL's fileURLWithPath:thePath
	set aURL to current application's |NSURL|'s fileURLWithPath:thePath -- make NSURL
	set theXMLDoc to current application's NSXMLDocument's alloc()'s initWithContentsOfURL:aURL options:0 |error|:(missing value) -- make XMLDoc

	set newAttr to current application's NSXMLNode's attributeWithName:"name" stringValue:"this is a test"--build the attribute
	set newNode to current application's NSXMLNode's elementWithName:"clip" children:(missing value) attributes:{newAttr}--put the attribute in a new node

	set theSpineArray to theXMLDoc's nodesForXPath:"/fcpxml/library/event/project/sequence/spine" |error|:(missing value)--extract the <spine> (the parent of <clip>)
	set theSpine to theSpineArray's objectAtIndex:0--extract the nodes from the array
	theSpine's replaceChildAtIndex:0 withNode:newNode--replace <clip>

	set theClipChildrenArray to theXMLDoc's nodesForXPath:"/fcpxml/library/event/project/sequence/spine/clip/video" |error|:(missing value)-- extract the nodes below <clip>
	theSpine's insertChildren:theClipChildrenArray atIndex:1--restore the nodes below <clip>

	current application's NSLog("%@", theSpine)
end modifyXML:

I’ve tried

theXMLDoc's replaceChildAtIndex:4 withNode:theSpine

But it give me:

How can there only be an index of 0? It looks to me like there is 8.

I’m not clear exactly what you’re after. When you use replaceChildAtIndex::, you replace one item with another – and that includes any elements of the replaced item. But now I’m wondering if all you want to do is change the name attribute of the element. Can you clarify what you want to do?

Yes. Ultimately I probably won’t need to change the structure of the XML tree just the attributes of individual nodes. I’m guessing there is probably a much simpler way to do that.

Yes – get the name attribute of the clip, and change its stringValue.

I do that by navigating the xml tree using NSXMLNode’s childAtIndex:?

Or use XPath. However you like, really.

I think I’ll need to do a bit more reading on how to use Xpath to change attribute values.

Use XPath to find the , then change the stringValue of its name attribute. Something like:

   set theClipArray to theXMLDoc's nodesForXPath:"/fcpxml/library/event/project/sequence/spine/clip" |error|:(missing value)
   set theClip to theClipArray's objectAtIndex:0
set theAttr to theClip's attributeForName:"name"
theAttr's setStringValue:"this is a test"

That is really simple. Thanks!