How to change an XML element in a namespace with M

2020-02-28 18:45发布

问题:

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>

回答1:

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



回答2:

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.