A quick tutorial of xpathing and patching

Started by minimurgle, May 24, 2017, 08:27:11 PM

Previous topic - Next topic

minimurgle

With the A17 update we have a wonderful new patching feature to help with mod compatability. It looks to be a brilliant idea, but it requires xpathing. This may be a new idea to some people. I know it was to me.

https://gist.github.com/Zhentar/4a1b71cea45b9337f70b30a21d868782 Zhentar's guide on patching is helpful, but it doesn't tell us how xpathing works and only gives us a few examples. So I'll give a few more examples here, and explain xpathing.

If you are like me you had no idea what xpathing was. Well I have good news, it's actually pretty simple! You can think of it like selecting a path for a texture, or any other folder path. Here's an example:

If you wanted to use a custom texture for your custom gun you would do:
<texPath>Weapons/Ranged/CustomGun</texPath>
Well that's how you'd do it for selecting a texture, but what about xpaths?

Let's say you wanted to change the warm up of the pistol from 0.3 to 5.2. We shouldn't be editing the defs if we can patch it in, so how would we do it? Well if we want to change the warm up of the pistol we can use the class "PatchOperationReplace". Which as the name suggests we can use to replace something, it looks like this

<Patch>
<Operation Class="PatchOperationReplace">
    <xpath>/ThingDefs/ThingDef[defName = "Gun_Pistol"]/verbs/li/warmupTime</xpath>
    <value>
                <WarmupTime>5.2</WarmupTime>
    </value>
</Operation>
</Patch>

This code goes down the designated xpath until it finds the pistols warm up time. It then replaces it with the set value.

Now let me explain exactly how the xpath works. First of all, everything in the xml could be described as a node.
<ThingDef> is a node and so is <defName>, <warmupTime>, <verbs>, and <description>. Every different tag you see is a node. All xpathing is, is just inputting the correct sequence of nodes.



We start with /ThingDefs, you may be wondering what the slash at the beginning means. This selects all root nodes with that name. It is the beginning of the thingdefs for the gun so it is the beginning of our xpath

Next is /Thingdef. This specifies what we want to grab from the thingdefs. But /ThingDef is grabbing every <ThingDef> it can find. This is why we narrow it down with the next part.

Alternitively we could have done //ThingDef, but this is less optimized and slows down the patching process because it selects the root node and all it's children.

After /ThingDef we attach the defname, so it looks like this /ThingDef[defName = "Gun_Pistol". Now instead of trying to search everything with the <ThingDef> tag, it will only look for the one with the <defName> tag of Gun_Pistol.

We have now told the code to look for the correct ThingDef, but it doesn't know where warmupTime is. So we add more nodes to the xpath, now it's /ThingDef[defName = "Gun_Pistol"]/verbs.

The single slash here looks for all the children of our ThingDef for the pistol named "verbs".

If you look at the code for the pistol you'll see that <warmupTime> is indeed under the verbs, but it's nestled in the <li> tag. Our xpath doesn't know this, so we'll just get errors or replace the verbs tag with <warmupTime>, which will cause errors.

So now we add "li" to our path so it looks like this //ThingDef[defName = "Gun_Pistol"]/verbs/li. You might think this is the end, but we still haven't directed it to the correct place. Now it's gonna replace "li" which will cause errors.

So we want to go one step further and make it /ThingDef[defName = "Gun_Pistol"]/verbs/li/warmupTime. Now our code is looking for the ThingDef named Gun_Pistol, then it looks for any children of that def called verbs. After that any children of verbs called "li", and then any children of li called "warmupTime".

That will finally replace the pistols warm up time.


But what if there were more than one child named "li", like in the hediff for blood loss. Then we could do this

<Patch>
<Operation Class="PatchOperationReplace">
   <xpath>/Defs/HediffDef[defName = "BloodLoss"]/stages/li[2]/capMods/li/offset</xpath>
   <value>
               <offset>-0.15</offset>
   </value>
</Operation>
</Patch>



This code will look for the HediffDef named "BloodLoss", it'll then look for a child named stages. Stages has multiple <li> children so I've specified that I want it to look for the second <li> tag. Then once its found the second <li> tag it'll follow the path just like it did with the pistol.


Of course you can have multiple operations in the same patch too. There's also more PatchOperations than just replace. You should check out Zhentar's Guide for those.

If you want more information about the xpath syntax I'd recommend going here: https://www.w3schools.com/xml/xpath_syntax.asp
Here's a tool to help you figure out the xpath: http://xmltoolbox.appspot.com/xpath_generator.html



I hope this helps anyone who had trouble with it.



Thanks to Shinzy and kaptain_kavern for pointing out some optimizations, and thanks to NoImageAvalible for showing the right way to use the optmizations.
Don't mind the questions. I'm probably just confused.

123nick

this is a GREAT Guide! now so many people can use the absolutely LIMITLESS power of xpathing and patching to make their mods amazing! thanks minimurgs  :-*

kaptain_kavern

#2
Many thanks for writing this

minimurgle

Quote from: kaptain_kavern on May 24, 2017, 09:13:35 PM
Many thanks forme writing this

Of course. I figured I couldn't be the only one who didn't know how xpath worked and wanted to make it easier for anyone to learn.
Don't mind the questions. I'm probably just confused.

Ramsis

I stickied the thread. This is just the kind of thing that makes our community amazing.
Ugh... I have SO MANY MESSES TO CLEAN UP. Oh also I slap people around who work on mods <3

"Back off man, I'm a scientist."
- Egon Stetmann


Awoo~

kaptain_kavern


Shinzy

As of very recent findings (Skully will possibly clarify) but when you use xpath and start from the very root of a def,
instead of //blahblah/blah/bluhh use /blahblah/blah/bluhh

as this is much faster when loading the patching stuff, okay? okay!


Shinzy


kaptain_kavern

#9
;D ;D ;D ;D



When targeting particular node you should always try to be the most precise as possible. Imagine your doing a google search or search for a file in your computer. The more you are precise, the more the search will be quick and efficient.

Here it is the same.


<xpath>//ThingDef[defName = "Gun_Pistol"]</xpath>is searching in every nodes! Even child nodes !
So it will be longer and take more computer resources


Whereas <xpath>/ThingDefs/ThingDef[defName = "Gun_Pistol"]</xpath> is searching only in parents nodes



From here

There also is a nice quick tuto

minimurgle

Thanks for all the info. I'll update all the information above to reflect this.
Don't mind the questions. I'm probably just confused.

NoImageAvailable

#11
Quote from: kaptain_kavern on May 26, 2017, 12:02:58 PM
Whereas <xpath>/ThingDef[defName = "Gun_Pistol"]</xpath> is searching only in parents nodes

The above code would actually return an error. You need to target the root node of the file, which for Weapons_Guns.xml is <ThingDefs>. So you'd need to use either <xpath>/ThingDefs/ThingDef[defName = "Gun_Pistol"]</xpath>
or <xpath>*/ThingDef[defName = "Gun_Pistol"]</xpath>
"The power of friendship destroyed the jellyfish."

kaptain_kavern

#12
I was going back here to correct after testing my wrong assertion. I'll correct it ASAP It is corrected above.

Thanks for taking the time for correcting me.

minimurgle

Don't mind the questions. I'm probably just confused.

benniii

Hi,

Is it possible to use the patching-method to change things added from other mods? I did read somewhere that this process is used before adding the mods.

Just for clarification