XPATH surgery - Need help with xpath? post here.

Started by skullywag, June 02, 2017, 04:26:23 AM

Previous topic - Next topic

Distman

So, i'm just a dabbler in the mystical arts of xpath.
Simple operations i have no problem with, but when messing with defs with a lot of lists and children my mind explodes.

What i want to do is patch a bodypart in a body with an added <Groups> node.
path is (i think): BodyDef/Human/corePart/parts/li/Neck/parts/li/Head/parts/li/Nose/groups
Defined in Bodies_Humanlike.xml

Should look something like this? Havn't tried it yet...
<Operation Class="PatchOperationAdd">
<xpath>*/BodyDef[defName="Human"]/corePart/parts/li[.="Neck"]/parts/li[.="Head"]/parts/li[.="Nose"]/groups</xpath>
<value>
<li>Face</li>
</value>
</Operation>


I guess my question is; can i use wildcards to reach the node easier?
I see a lot of talk about *text(), but not fully understand how it works. (Just shooting from the hip here...)
<Operation Class="PatchOperationAdd">
<xpath>*/BodyDef[defName="Human"]/*[text("Nose")]/groups</xpath>
<value>
<li>Face</li>
</value>
</Operation>


Thanks in advance!
/Distman

Mehni

Defs/BodyDef[defName="Human"]/corePart/parts/li[def="Neck"]/parts/li[def="Head"]/parts/li[def="Nose"]/groups

Defs/BodyDef[defName="Human"]/corePart/parts//li[def="Nose"]/groups

* is a wildcard that only skips a single node. // is a wildcard that looks in all nodes. Note that // is a LOT slower for general searches. In this case, since you're already a few nodes deep, the performance cost doesn't have that much impact.

For using text, the syntax is text() = "sometext". See also https://gist.github.com/Lanilor/e36af29ce5ce725b9b29768c63b00ef2#some-more-selectors and whatever other documentation you can find.

Xpath isn't a mystical art; it's a well documented standard that's been around since 1999. The more you read and learn about it, the more the mysticism disappears.

Distman

Thanks for the insight and link.
I had to do some more detective work since the parts are not children of def, rather the lists themselves.

Might be a quicker and easier way to solve this, but here is what i got if anybody else wanna patch bodies:
Defs/BodyDef[defName="Human"]/corePart/parts//li[def="Neck"]/ancestor:parts//li[def="Head"]/ancestor:parts//li[def="Nose"]/ancestor:groups

/Distman

Quote from: Mehni on January 31, 2019, 04:53:18 AM
Defs/BodyDef[defName="Human"]/corePart/parts/li[def="Neck"]/parts/li[def="Head"]/parts/li[def="Nose"]/groups

Defs/BodyDef[defName="Human"]/corePart/parts//li[def="Nose"]/groups

* is a wildcard that only skips a single node. // is a wildcard that looks in all nodes. Note that // is a LOT slower for general searches. In this case, since you're already a few nodes deep, the performance cost doesn't have that much impact.

For using text, the syntax is text() = "sometext". See also https://gist.github.com/Lanilor/e36af29ce5ce725b9b29768c63b00ef2#some-more-selectors and whatever other documentation you can find.

Xpath isn't a mystical art; it's a well documented standard that's been around since 1999. The more you read and learn about it, the more the mysticism disappears.

blohod

I'm trying to patch factiondefs so that they use my animal, donkeys, for their caravans as a pack animal. The patch is failing and I can't work out why.


<Operation Class="PatchOperationAdd">
  <xpath>*/FactionDef[defName="TribeBase"]/pawnGroupMakers/li[5]/carriers</xpath>
  <value>
<Donkey>6</Donkey>
  </value>
</Operation>

Pelador

Quote from: blohod on February 01, 2019, 08:37:36 PM
I'm trying to patch factiondefs so that they use my animal, donkeys, for their caravans as a pack animal. The patch is failing and I can't work out why.


<Operation Class="PatchOperationAdd">
  <xpath>*/FactionDef[defName="TribeBase"]/pawnGroupMakers/li[5]/carriers</xpath>
  <value>
<Donkey>6</Donkey>
  </value>
</Operation>


You are patching a parent abstract so will need to use [@Name="TribeBase"].

To potentially search for all carriers despite their list order and also in case other mods add other trader groups you could also use the open-ended pathing of "//" to select any occurrence below the current node rather than targeting a specific node numerically. Shouldn't effect performance too drastically at the level you intend to patch. Though it makes an assumption that donkeys should be added to all carriers definitions that occur. Otherwise, using the literal numercial reference it would be sensible to check that the carrier node exists for the listing you're referencing and apply a conditional test as part of a patch sequence to ensure that the addition doesn't fall over due to the absence of the carrier node not being in it, should the listing order change due to other modding additions/changes.

"A" solution in simplified form could then be:


<Operation Class="PatchOperationAdd">
<xpath>*/FactionDef[@Name="TribeBase"]/pawnGroupMakers//carriers</xpath>
<value>
<Donkey>6</Donkey>
</value>
        </Operation>


otherwise with the specific node:


    <Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>
      <li Class="PatchOperationTest">
            <xpath>*/FactionDef[@Name = "Tribebase"]/pawnGroupMakers/li[5]/carriers</xpath>
      </li>
<li Class="PatchOperationAdd">
<xpath>*/FactionDef[@Name="TribeBase"]/pawnGroupMakers/li[5]/carriers</xpath>
<value>
<Donkey>6</Donkey>
</value>
         </li>
         </operations>
        </Operation>


This won't show you an error with the success is always true set, but then it won't upset players either in the case it fails, but the sequence won't attempt to incorrectly patch the carriers node with your addition if it does not exist, then causing an error without the test. (You can then toggle/comment out the success part for your own debugging purposes of course). This can remedially remove some compatibility issues with other mods just in case someone does attempt to mod and change the ordering for the definitions. (Which would be naughty of course. ;) )
It'll be self-evident to the player if he doesn't see donkeys on tribal traders to then know there's a potential issue with the patching and perhaps his definitions and use of others mods. This might be over-engineering for purposes, but it does at least add one catch to a potential issue. I'll let you decide the merits/relevance of its use.

(Also ensure you are putting the patch file into a mod "Patches" folder with the XML tags of <Patch></Patch>)

Mat1az

Hello, guys. May you help me with my problem?

I'm try to modify a Gun_Autopistol stats after it's been patched by Combat Extended, and catch a problem here. This code works as intended:


<li Class="PatchOperationReplace">
<xpath>/Defs/ThingDef[defName = "Gun_Autopistol"]/verbs/li[@Class = "CombatExtended.VerbPropertiesCE"]</xpath>
<value>
<defaultProjectile>Bullet_9mmPistol_FMJ</defaultProjectile>
</value>
</li>

<li Class="PatchOperationReplace">
<xpath>/Defs/ThingDef[defName = "Gun_Autopistol"]/verbs/li[@Class = "CombatExtended.VerbPropertiesCE"]/warmupTime</xpath>
<value>
<warmupTime>0.5</warmupTime>
</value>
</li>

<li Class="PatchOperationReplace">
<xpath>/Defs/ThingDef[defName = "Gun_Autopistol"]/verbs/li[@Class = "CombatExtended.VerbPropertiesCE"]/range</xpath>
<value>
<range>16</range>
</value>
</li>


And with following code game itself and savegame load without problems, but then I trying to spawn Autopistol throught devconsole, I'm catch System.MissingMethodException.

Problematic code:

<li Class="PatchOperationReplace">
<xpath>/Defs/ThingDef[defName = "Gun_Autopistol"]/verbs/li[@Class = "CombatExtended.VerbPropertiesCE"]</xpath>
<value>
<li>
<defaultProjectile>Bullet_9mmPistol_FMJ</defaultProjectile>
<warmupTime>0.5</warmupTime>
<range>16</range>
</li>
</value>
</li>


Error log:
Could not instantiate Verb (directOwner=CompEquippable(parent=Gun_Autopistol109808 at=(-1000, -1000, -1000))): System.MissingMethodException: Cannot create an abstract class 'Verse.Verb'.
  at System.Activator.CheckAbstractType (System.Type type) [0x00000] in <filename unknown>:0
  at System.Activator.CreateInstance (System.Type type, Boolean nonPublic) [0x00000] in <filename unknown>:0
  at System.Activator.CreateInstance (System.Type type) [0x00000] in <filename unknown>:0
  at Verse.VerbTracker.<InitVerbsFromZero>m__1 (System.Type type, System.String id) [0x00000] in <filename unknown>:0
  at Verse.VerbTracker.InitVerbs (System.Func`3 creator) [0x00000] in <filename unknown>:0
Verse.Log:Error(String, Boolean)
Verse.VerbTracker:InitVerbs(Func`3)
Verse.VerbTracker:InitVerbsFromZero()
Verse.VerbTracker:get_PrimaryVerb()
Verse.CompEquippable:get_PrimaryVerb()
CombatExtended.CompFireModes:get_Verb()
CombatExtended.CompFireModes:InitAvailableFireModes()
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:ExecuteWhenFinished(Action)
CombatExtended.CompFireModes:Initialize(CompProperties)
Verse.ThingWithComps:InitializeComps()
Verse.ThingWithComps:PostMake()
Verse.ThingMaker:MakeThing(ThingDef, ThingDef)
Verse.DebugThingPlaceHelper:DebugSpawn(ThingDef, IntVec3, Int32, Boolean)
Verse.<DoListingItems_MapTools>c__AnonStorey38:<>m__0()
Verse.DebugTool:DebugToolOnGUI()
Verse.DebugTools:DebugToolsOnGUI()
RimWorld.UIRoot_Play:UIRootOnGUI()
Verse.Root:OnGUI()


Besides, this code successfully changes at least a "warmupTime" - I can see this ingame in Autopistol description:
https://imgur.com/a/5Vgi4YJ

Principally, I may use first code as well, but I'd would like to use as most short code as it possible, of course.

LWM

#126
Quote from: Pelador on February 02, 2019, 09:55:19 AM
[/code]
    <Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>
      <li Class="PatchOperationTest">
            <xpath>*/FactionDef[@Name = "Tribebase"]/pawnGroupMakers/li[5]/carriers</xpath>
      </li>
   <li Class="PatchOperationAdd">
      <xpath>*/FactionDef[@Name="TribeBase"]/pawnGroupMakers/li[5]/carriers</xpath>
      <value>
          <Donkey>6</Donkey>
      </value>
         </li>
         </operations>
        </Operation>
[/code]

I believe you could also do this?  Which should run faster (starts with /Defs, instead of searching all nodes for "FactionDef"), be more robust (if something in the xml changes ever ><), and do it all in one step:


<Operation Class="PatchOperationAdd">
  <success>Always</success>
  <xpath>/Defs/FactionDef[@Name="TribeBase"]/pawnGroupMakers/li/carriers</xpath>
  <value>
    <Donkey>6</Donkey>
  </value>
</Operation>


Heck...why can't non-tribals use donkeys too?  We use them now in Grand Canyon tours in the US!  Just take out the entire [@Name="..."] identifier in the <xpath> and it will patch everyone in one go!

As I wrote it, if - for some reason - someone wiped out tribals, because success is always, your mod would silently fail.  I might tend to leave it out, just so there's warning of an incompatibility, but in this case might leave it in.

Hope that helps,

--LWM

PS - industrial factions too, plz?  ^.^

LWM

Quote from: Mat1az on February 05, 2019, 03:20:41 PM
Hello, guys. May you help me with my problem?
Your first code works, your second code doesn't?

Does this work?


<li Class="PatchOperationReplace">
<xpath>/Defs/ThingDef[defName = "Gun_Autopistol"]/verbs/li[@Class = "CombatExtended.VerbPropertiesCE"]</xpath>
<value>
<li Class="CombatExtended.VerbPropertiesCE">
<defaultProjectile>Bullet_9mmPistol_FMJ</defaultProjectile>
<warmupTime>0.5</warmupTime>
<range>16</range>
</li>
</value>
</li>


--LWM

Mat1az

Quote from: LWM on February 06, 2019, 11:25:49 AM
Does this work?

Nope. I've tried this already - game loaded without errors, but stats ingame didn't altered.

LWM

Quote from: Mat1az on February 06, 2019, 04:13:30 PM
Nope. I've tried this already - game loaded without errors, but stats ingame didn't altered.
Ah, apologies.  The code you posted didn't have the Class="..." in the <li>, so I thought that might be the problem.

Pelador

Verbs are a fickle characteristic of a thingdef, you cannot change them on an item in a dynamic way IG. Once an item is made it will retain the verbs it has at the time of its creation. You will probably find that the xpath to change the CE has worked, but you may not see these change on an existing item. If you created a new one however you may then see these new values.

mcduff

Hi, I'm slowly losing my mind over something which feels like it should be incredibly simple and yet does not work.

It's just an experiment to see if I can get pawns to be able to wear parkas over armor, for obvious reasons. All I'm doing is taking out "shell" from the layers so they appear on the middle layer only.

If I edit the code directly it works, but I can't seem to make a patch that does it.

  <Operation Class="PatchOperationReplace">
    <xpath>Defs/ThingDef[defname = "Apparel_PlateArmor"]/apparel/layers</xpath>
    <value>
      <layers>
        <li>Middle</li>
      </layers>
    </value>
  </Operation>


This errors out - what obvious, simple thing am I missing??

Pelador

Quote from: mcduff on February 15, 2019, 02:05:57 PM
Hi, I'm slowly losing my mind over something which feels like it should be incredibly simple and yet does not work.

It's just an experiment to see if I can get pawns to be able to wear parkas over armor, for obvious reasons. All I'm doing is taking out "shell" from the layers so they appear on the middle layer only.

If I edit the code directly it works, but I can't seem to make a patch that does it.

  <Operation Class="PatchOperationReplace">
    <xpath>Defs/ThingDef[defname = "Apparel_PlateArmor"]/apparel/layers</xpath>
    <value>
      <layers>
        <li>Middle</li>
      </layers>
    </value>
  </Operation>


This errors out - what obvious, simple thing am I missing??

You have your patch file in a mod "Patches" folder with the following XML tags?

<?xml version="1.0" encoding="utf-8" ?>
<Patch>
...
</Patch>


Otherwise, it might be a really simple thing like not having the root path in front of the Defs in your xpath definition:

So  "<xpath>/Defs/ThingDef[.........."

I tend to use the more open-ended method of "<xpath>*/ThingDef[......."

The rest of the xpath syntax looks ok to me. If not any of the above things, be interested to know then what the error message is.

mcduff

I've tried Def, /Def, */, and anything else I can think of.

This is what shows up in the error log. It doesn't seem helpful.

[One-Layer Armour] Patch operation Verse.PatchOperationReplace(*/ThingDef[defname = "Apparel_PlateArmor"]/apparel/layers) failed
file: /Users/xxxx/Library/Application Support/Steam/steamapps/common/RimWorld/RimWorldMac.app/Mods/Armourpatch/Patches/patch.xml
Verse.Log:Error(String, Boolean)
Verse.PatchOperation:Complete(String)
Verse.LoadedModManager:ClearCachedPatches()
Verse.LoadedModManager:LoadAllActiveMods()
Verse.PlayDataLoader:DoPlayLoad()
Verse.PlayDataLoader:LoadAllPlayData(Boolean)
Verse.Root:<Start>m__1()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__1()


mcduff

This is my full "patch.xml" file

<?xml version="1.0" encoding="utf-8"?>

<Patch>

  <Operation Class="PatchOperationReplace">
    <xpath>*/ThingDef[defname = "Apparel_PlateArmor"]/apparel/layers</xpath>
    <value>
      <layers>
        <li>Middle</li>
      </layers>
    </value>
  </Operation>
 
    <Operation Class="PatchOperationReplace">
    <xpath>*/ThingDef[defname = "Apparel_PowerArmor"]/apparel/layers</xpath>
    <value>
      <layers>
        <li>Middle</li>
      </layers>
    </value>
  </Operation>
 

</Patch>