A quick tutorial of xpathing and patching

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

Previous topic - Next topic

Pelador

Quote from: LWM on February 03, 2019, 11:00:40 PM
There are a lot of scattered "tutorials" people have written, but they all seem to be missing bits and pieces.  I have found a reference to PatchOperationFindMod on the wiki.  Not many people seem to be using the wiki.  Is it accurate?

No one else seems to mention PatchOperationFindMod.  Does it actually exist?  If so, can anyone explain/show how it's used?

Does anyone want to update the wiki, or is that not a place people use?

--LWM

Syntax is as follows:

<Operation Class="PatchOperationFindMod">
<mods>
    <li>{Name of Mod as per About.xml def}</li>
      </mods>
        <match Class="PatchOperation{...}">
                <xpath>{xpath details}</xpath>
<value>
{vals...}
</value>
        </match>
<nomatch Class="Patchoperation{...}>
                <xpath>{xpath details}</xpath>
<value>
{vals...}
</value>
</nomatch>
</Operation>


The optional <match /> and <nomatch /> are operations you can apply to your "OWN" mod based on whether the named mod is found.

I've found you cannot apply these within a patch sequence or embed a patch sequence into the match and nomatch nodes, so you have to specify multiple patch definitions if you need to do multiple different kinds of changes.

E.g.

<Operation Class="PatchOperationFindMod">
<mods>
<li>VGP Vegetable Garden</li>
      </mods>
        <match Class="PatchOperationReplace">
            <xpath>*/RecipeDef[defName="MakeMSMultiVitamins"]/ingredients</xpath>
            <value>
                    <ingredients>
<li>
<filter>
<thingDefs>
<li>Neutroamine</li>
</thingDefs>
</filter>
<count>1</count>
</li>
<li>
<filter>
<categories>
            <li>FruitFoodRaw</li>
          </categories>
</filter>
<count>5</count>
</li>
</ingredients>
</value>
</match>
    </Operation>

<Operation Class="PatchOperationFindMod">
      <mods>
        <li>VGP Vegetable Garden</li>
      </mods>
        <match Class="PatchOperationReplace">
            <xpath>*/RecipeDef[defName="MakeMSMultiVitamins"]/fixedIngredientFilter</xpath>
            <value>
<fixedIngredientFilter>
<thingDefs>
<li>Neutroamine</li>
</thingDefs>
<categories>
        <li>FruitFoodRaw</li>
      </categories>
</fixedIngredientFilter>
            </value>
        </match>
    </Operation>


LWM

Thank you very much!

Quote from: Pelador on February 03, 2019, 11:54:14 PM
I've found you cannot apply these within a patch sequence or embed a patch sequence into the match and nomatch nodes, so you have to specify multiple patch definitions if you need to do multiple different kinds of changes.

Oh, that's dismaying...I have a handful of places to patch, and a sequence would have been much easier.  But forewarned is forearmed.  And some peoples' alien races have four arms, so it's best to be prepared.

--LWM

LWM

Quote from: Pelador on February 03, 2019, 11:54:14 PM
Syntax is as follows:[...]
I've found you cannot apply these within a patch sequence or embed a patch sequence into the match and nomatch nodes, so you have to specify multiple patch definitions if you need to do multiple different kinds of changes.

Woohoo - I got my test compatibility patch working!

I also tested using a Sequence, and it worked!  So maybe something has changed?

But this does actually patch:


<Patch>
  <Operation Class="PatchOperationFindMod">
    <mods>
      <li>LWM's Deep Storage</li>
    </mods>
    <match Class="PatchOperationSequence">
      <operations>
        <li Class="PatchOperationReplace">
          <xpath>/Defs/ThingDef[defName="LWM_WeaponsLockerLocker"]/label</xpath>
          <value><label>Testing this.</label></value>
        </li>
        <li Class="etc"><actual stuff />
        </li>
      </operations>
    </match>
  </Operation>
</Patch>


Thanks!  I'll go add this detail to the wiki.
--LWM

LWM

I note that https://www.w3schools.com/xml/xpath_syntax.asp specifies that the expression "Defs" in an xpath would 'Select all nodes with the name "[Defs]."'  So it sounds like all xpaths should start with "/Defs" to avoid searching for "Defs" in any depth - am I correct?

--LWM

LWM

Quote from: LWM on February 04, 2019, 12:34:57 PM
I note that https://www.w3schools.com/xml/xpath_syntax.asp specifies that the expression "Defs" in an xpath would 'Select all nodes with the name "[Defs]."'  So it sounds like all xpaths should start with "/Defs" to avoid searching for "Defs" in any depth - am I correct?

Note: I found in testing that /Defs was *slightly* faster than Defs.  Methodology:  https://github.com/lilwhitemouse/RimWorld-PatchSpeedTest

--LWM

Nerdygamer

#80
First time poster, so apologies if I'm out of line somewhere, but I'm having trouble xpathing into ThingDefs with parent names. I'm trying to adjust the blunt armor rating of marine helmets with a PatchOperationReplace. I have a rudimentary understanding of xpathing (thanks in part to this thread) and have a working patch to edit the values of room sizes. The following code works and is meant to serve as proof that I faintly know what I'm doing: <Patch>
<Operation Class = "PatchOperationReplace">
<xpath>Defs/RoomStatDef[defName = "Space"]/scoreStages</xpath>
<value>
    <scoreStages>
[blah blah blah, snipped for space]
</scoreStages>
</value>
</Operation>
</Patch>


However, I cannot figure out how to use a replace command to change the value of a marine helmet's blunt protection. Everything I've tried just crashes the game immediately. I even used http://xmltoolbox.appspot.com/xpath_generator.html to "cheat" and write the code for me (you plug in the code, click on the line you wish to xpath to, and it spits out what you need), and even that doesn't work. My current code, which doesn't work, is as follows: <?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationReplace">
    <xpath>Defs/ThingDef ParentName="ArmorHelmetMakeableBase"[defName = "Apparel_PowerArmorHelmet"]/statBases/ArmorRating_Blunt</xpath>
<value>
<ArmorRating_Blunt>0.4</ArmorRating_Blunt>
</value>
    </Operation>
</patch>


Help, please? I have no idea why my first example works and my second doesn't, but I've had trouble in the past when trying to xpath to an object that started with <ThingDef ParentName = blahblahblah>

LWM

You have an xpath of

<xpath>Defs/ThingDef ParentName="ArmorHelmetMakeableBase"[defName = "Apparel_PowerArmorHelmet"]/statBases/ArmorRating_Blunt</xpath>


But xpath operates on xml.  The node you are looking for is a "ThingDef" node which has an attribute of ParentName="blahblah".  But you don't care about that attribute - you're specifying the node based on the fact that it has a subnode of defName with value Apparel_PowerArmorHelmet.

So use this instead, and I think it will work:


<xpath>/Defs/ThingDef[defName = "Apparel_PowerArmorHelmet"]/statBases/ArmorRating_Blunt</xpath>


--LWM
PS - note that I put a "/" in front of the first Defs - it's sliiiightly faster to do it that way.

Nerdygamer

Unfortunately, that still doesn't work.  Just trying to enable Core and my mod results in the game throwing up the "recovered from incompatible or corrupted mods error".  I have the mod folder, which has an "About" folder and a "Patches" folder.  In the "About" folder, I have an About.xml, which is as follows:
<?xml version="1.0" encoding="utf-8"?>
<ModMetaData>
  <name>Better Marine Helmets</name>
  <author>Nerdygamer1</author>
  <targetVersion>1.0</targetVersion>
  <description>Makes marine helmets competitive when compared to plasteel advanced helmets. Previously, plasteel advanced helmets had more health and better protection against blunt damage (despite the description suggesting otherwise). Now, advanced helmets have been given a gentle increase in both health and blunt protection, at the cost of ten extra plasteel (for a total cost of 50 plasteel; the same cost as a plasteel advanced helmet). Component requirements have not been changed.</description>
</ModMetaData>


In the "Patches" folder is my "Patches_Helmets.xml" which is as follows:
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationReplace">
    <xpath>/Defs/ThingDef[defName = "Apparel_PowerArmorHelmet"]/statBases/ArmorRating_Blunt</xpath>
<value>
<ArmorRating_Blunt>0.4</ArmorRating_Blunt>
</value>
    </Operation>
</patch>


And that's literally everything. The game throws an absolute fit when trying to start up with the mod enabled - checking the box automatically restarts the game, and upon booting up, I immediately get the error message; it's not even the debug log.

LWM

Your XML isn't valid!

You have a lowercase "patch" as your close tag instead of an uppercase.

I use emacs to do my editing, and one of the things it does for me is validate my xml automatically ^.^  I pasted the file in and it told me "Invalid" and gave the "patch" a nice noticeable orange.   I highly recommend a good editor that does that sort of thing for you.

...we've all done stupid things, so don't feel bad there....having an editor that catches (some) stupid mistakes means we can make more subtle ones that drive us up a limestone wall!

--LWM

Nerdygamer

#84
That worked!  Thank you so much for your help; you have no idea how long I spent googling, making minor changes, and testing last night.  I'm no stranger to formatting errors (and that was the first thing I double-checked - making sure I had all my brackets, my slashes, and making sure it was ThingDef), but my mistakes don't usually hang up the game so badly that I can't get a basic idea of what I did wrong from the debug log, and I didn't even think to check that my commands had the same casing :x

I'm looking into emacs right now.

LWM

Emacs has a steeper learning curve than a lot of editors, I am willing to admit, but it's awesome.  If you want something simpler in Windows, look into Notepad++ - I think it does XML?  I'm sure google is your friend ;)

If you really feel like learning something new, there's also vi - which is better is still a hotly contested holy war.   ;D;P

Glad it worked for you!

Pangaea

I have a problem with XPATH, though not for modding purposes, and remembered there was knowledge about it here.

I'm trying to use a tool called xmlstarlet to output the information I need from an XML file. I've gotten far it feels, but the last pieces are missing and the filtering/if-test doesn't work. Are any of you familiar with this tool and syntax? Searching fails me :-(

The general structure is:
redxml/definitions/loot_definitions/loot/loot_entry
redxml/definitions/loot_definitions/loot/loot_entry/@name
redxml/definitions/loot_definitions/loot/loot_entry/@player_level_min
redxml/definitions/loot_definitions/loot/loot_entry/@player_level_max
redxml/definitions/loot_definitions/loot/loot_entry/@quantity_min
redxml/definitions/loot_definitions/loot/loot_entry/@quantity_max
redxml/definitions/loot_definitions/loot/loot_entry/@chance
redxml/definitions/loot_definitions/loot/loot_entry/@respawn_time

With the relevant piece of XML such as this:
<loot name="_monster__Wildhunt minion" player_level_min="0" player_level_max="0" quantity_min="0" quantity_max="2" chance="-1" >
<loot_entry name="Sulfur" player_level_min="0" player_level_max="0" quantity_min="1" quantity_max="1" chance="50" respawn_time="0" />
<loot_entry name="Rotten meat" player_level_min="0" player_level_max="0" quantity_min="1" quantity_max="1" chance="26" respawn_time="0" />
<loot_entry name="Monstrous brain" player_level_min="0" player_level_max="0" quantity_min="1" quantity_max="1" chance="6" respawn_time="0" />
<loot_entry name="Monstrous blood" player_level_min="0" player_level_max="0" quantity_min="1" quantity_max="1" chance="6" respawn_time="0" />
<loot_entry name="Monstrous bone" player_level_min="0" player_level_max="0" quantity_min="1" quantity_max="1" chance="6" respawn_time="0" />
<loot_entry name="Monstrous saliva" player_level_min="0" player_level_max="0" quantity_min="1" quantity_max="1" chance="6" respawn_time="0" />
</loot>


What I want to do is to extract all the monster types that drop a particular type of loot, in this case "Sulfur".

I have tried this command, which just ends up listing all the monster types in the file, instead of applying the if-test and listing the monsters that apply.
xmlstarlet sel -t -v "//redxml/definitions/loot_definitions/loot/@name" -i "//redxml/definitions/loot_definitions/loot/loot_entry[@name='Sulfur']" -nl def_loot_monsters.xml

There is documentation about the tool here, but I'm clearly still doing something wrong: http://xmlstar.sourceforge.net/doc/UG/ch04s01.html

If you are able to help, please offer a hand :)

Pangaea

To follow up on that, as I managed to find the solution eventually, after some trial and error  8)

xmlstarlet sel -t -m "//redxml/definitions/loot_definitions/loot/loot_entry[@name='Monstrous bone']" -s A:T:- "../@name" -v "concat('* [[',../@name, ']] (', @chance, '%)')" -nl def_loot_monsters.xml

That's with a bit more formatting and sorting, but what I had to do was to use -m (for matching) and change things around a little.

Jibbles

When it's all said and done I can get patches to work, but I'm often second guessing that what I'm doing is actually the optimal way or if it could pose unexpected issues.  I've digged through many mods today to see if I could find some good examples.  So many people do things differently.  I ended up copying one method since - it looked right -  that worked well for one mod, but doesn't work properly for the other..   (the issue was actually easy to overlook)

The information is a bit scattered and to be fair there's a lot to cover..  When it comes to patching several things to a single thingDef, I feel like this area should be brushed on a little more with actual examples from the game. Have one simple example, and one complex example. Anyways, this is where I noticed the most inconsistencies with a bunch of mods. 

LWM

The wiki has a bunch of simple examples as well as links to various people's mods with patching "in the wild."  I still refer to it and think it's an excellent resource.