Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change an XML element in a namespace with MSDeploy Parameters.xml file?

I can't change an element in Web.config with MSDeploy. My Parameters.xml file:

<parameterEntry
  kind="XmlFile"
  scope="\\web.config$"
  match="//spring/objects/object[@id='CultureResolver']/@type" />

The relevant section of Web.config:

<spring>
    <objects xmlns="http://www.springframework.net">

        <object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <property name="DefaultCulture" value="en" />
        </object>
    </objects>
</spring>
like image 856
Zane Avatar asked Apr 29 '11 09:04

Zane


People also ask

How do I change a namespace in XML?

Since in this case you have a default namespace defined, you could just remove the default namespace declaration and add a new declaration for your new prefix using the old namespace name, effectively replacing it.

What is URI in XML namespace?

A Uniform Resource Identifier (URI) is a string of characters which identifies an Internet Resource. The most common URI is the Uniform Resource Locator (URL) which identifies an Internet domain address. Another, not so common type of URI is the Uniform Resource Name (URN).

How do I add namespace prefix to XML element?

Adding a namespace prefix to an elementThe XQuery expression in the following SELECT statement adds a namespace prefix to an element. The XQuery expression uses fn:QName to create a QName with a namespace binding. The XQuery let clause creates an empty element with name emp and namespace http://example.com/new .

How does namespace work in XML?

An XML namespace is a collection of names that can be used as element or attribute names in an XML document. The namespace qualifies element names uniquely on the Web in order to avoid conflicts between elements with the same name.


2 Answers

The XPath Trick

Your example was a little tricky (at first). But this XPath query, while much longer, should work:

//spring/*[local-name() = 'objects' and namespace-uri() = 'http://www.springframework.net']/*[@id='CultureResolver' and local-name() = 'object' and namespace-uri() = 'http://www.springframework.net']/@type

I discovered this trick in this answer to the SO question c# - Declare namespaces within XPath expression, but I actually discovered it after I'd already posted this answer (originally).

I've left most of my original answer below and I also added extra sections about how my original answer is wrong.

My Example

I wanted to do this for NLog; here's an example of my NLog.config file:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwExceptions="true">

  ...

  <targets>
    <target name="HipChat" xsi:type="MethodCall" className="DbNecromancer.Logging, DbNecromancer" methodName="SendHipChatRoomNotification">
      <parameter layout="1234567" name="roomId" />
      <parameter layout="blah-blah-blah" name="authToken" />
      <parameter layout="${message-layout}" name="message" />
      <parameter layout="${level}" name="nLogLevel" />
    </target>
  </targets>

  ...
</nlog>

Here's the relevant XPath query from my Parameters.xml file to change the layout attribute of the parameter element with a value of "roomId" for the name attribute:

/nlog/targets/target[@name='HipChat']/parameter[@name='roomId']/@layout

You can confirm that the above XPath fails to match the desired attribute using this free online XPath tester. You can also confirm it fails using Web Deploy itself!

But I was able to get this to work, both in the online tester and via Web Deploy, without using wildcards or removing the namespace declaration in NLog.config. The trick was to add a namespace prefix.

Here are the modified lines in NLog.config:

<nlog:nlog xmlns:nlog="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwExceptions="true">
    ...
</nlog:nlog>

Here's the modified XPath query:

/nlog:nlog/targets/target[@name='HipChat']/parameter[@name='roomId']/@layout

Your Example

I couldn't get your example to work in the online tester by simply adding a prefix where your example is using a namespace (and I don't have a project that uses Spring). The output of the tester when I try to just add a prefix:

ERROR - Failed to evaluate XPath expression: Prefix must resolve to a namespace: spring

However, I was able to get it to work by doing the following (which I think might be 'correct').

Here's the lines in your XML I changed:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        ...
    </spring:objects>

Here's the modified XPath query:

/spring/spring:objects/object[@id='CultureResolver']/@type

Why 'My Example' and 'Your Example' are (Subtly) Wrong

The problem, as I seem to (barely) understand it, is that declaring a namespace without a prefix is very different than declaring one with a prefix.

Declaring a namespace with a prefix simply declares the namespace and its prefix, but it doesn't change the namespace of any elements.

Declaring a namespace without a prefix makes that namespace the default namespace for both the element in which its defined and for all of the child elements of that element.

So, for your example, you could modify the following line in your XML and your original XPath query will work:

    <objects xmlns:spring="http://www.springframework.net">

[Just add the spring prefix to the namespace.]

That's because (or is according to my working hypothesis) that adding the prefix to the namespace declaration without also adding the prefix to the objects element (and all its children) is 'significantly' modifying the XML because it's removing those elements from the namespace.

To correctly add a prefix without modifying the semantics of the XML, your XML should be as follows:

<spring>
    <spring:objects xmlns:spring="http://www.springframework.net">
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

And your XPath query should be like this:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

The only problem is that that doesn't work (in the online tester, and probably not with MSDeploy either).

The above doesn't work because XPath engines require namespaces to be registered (separate from the namespaces declared in the XML document itself). But, as both Web Deploy and the online tester seem to do, any declarations in the root element of the XML document are automatically registered. And that's very useful as I don't know of any way to register namespaces when using Web Deploy parameter transformation.

For a more in-depth explanation see the answers to my related question Do XML namespaces need to be declared in the root element to be matchable by an XPath query?.

Your (Corrected) Example

XML:

<spring xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

XPath query:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

Conclusion

If you don't mind long XPath queries, use the trick at the top. Otherwise, modify your XML namespace declarations so that they include a prefix and they're located in the root element of your XML document.

like image 200
Kenny Evitt Avatar answered Oct 23 '22 21:10

Kenny Evitt


the problem is the namespace declaration on the <objects/> element. Your XPath query doesn't have a match because there is no <objects/> element with an empty namespace (which is what the query is looking for).

Now, specifying XML namespaces in XPath is a tricky issue (in this case it's even impossible), so I'd suggest you use this expression instead:

"//spring/*/*[@id='CultureResolver']/@type"

HTH...

like image 36
mthierba Avatar answered Oct 23 '22 21:10

mthierba