XPATH surgery - Need help with xpath? post here.

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

Previous topic - Next topic

Mehni

defName

Capital N. XPath is case sensitive.

In regards to */ vs Defs/: Defs/ is a few percent quicker, and thus preferable. RimWorld forces all rootnodes to be Defs anyway, so the * wildcard just slows things down needlessly.

mcduff

Thank you! I knew it would be something ridiculous like that but I couldn't see it for looking.

WereCat88

#137
I am patching a mod that uses thingDef instead of ThingDef in certain places and i need it to use the other one, i tried this:
<li Class="PatchOperationSetName">
<xpath>Defs/thingDef[defName="ExampleDef"]</xpath>
<name>ThingDef</name>
</li>


But it failed
I am The Primal Mammelon

LWM

Quote from: Mehni on February 16, 2019, 05:43:05 AM
In regards to */ vs Defs/: Defs/ is a few percent quicker, and thus preferable. RimWorld forces all rootnodes to be Defs anyway, so the * wildcard just slows things down needlessly.

Wouldn't Defs/ be just about as slow?  /Defs/ should be much quicker, no?  As it starts at the root, instead of searching for a field named Defs anywhere in the xml tree?

--LWM

AileTheAlien

#139
Always use fully-qualified paths if you have them, and put any type of wild-card or searching behaviour as close to the end as possible, to limit the size of tree to search through.

Mehni

Quote from: LWM on February 17, 2019, 09:44:12 AM
Wouldn't Defs/ be just about as slow?  /Defs/ should be much quicker, no?  As it starts at the root, instead of searching for a field named Defs anywhere in the xml tree?

--LWM

apparently not: https://ludeon.com/forums/index.php?topic=32874.msg382089#msg382089

Quote from: WereCat88 on February 17, 2019, 09:07:00 AM
I am patching a mod that uses thingDef instead of ThingDef in certain places and i need it to use the other one, i tried this:
<li Class="PatchOperationSetName">
<xpath>Defs/thingDef[defName="ExampleDef"]</xpath>
<name>ThingDef</name>
</li>


But it failed


I know some people are going to hate me for being lazy, but.. <xpath>Defs/*[defName="ExampleDef"]</xpath>

Only do that if you're certain there are no other types of Defs with that defName. As an example: Pemmican is the defName for both the research project and the ThingDef. Note that will also overwrite any attributes.

LWM

Well THAT's some methodology I'd like to see ^.^

What did they do, restart RimWorld 1000 times each way?

--LWM

Mehni

Verbose logging, probably.

RimWorld loading has changed (quite substantially) since then so I'd like to see a repeat of that benchmark.

WereCat88

Does xpathing using set name delete the attribute of the node being modified?
I am The Primal Mammelon

LWM

Quote from: WereCat88 on February 18, 2019, 07:52:35 AM
Does xpathing using set name delete the attribute of the node being modified?

Depends on how you're modifying it?

If I knew exactly what you were planning, I'd have better idea.

LWM

Quote from: Mehni on February 18, 2019, 03:26:54 AM
Verbose logging, probably.

RimWorld loading has changed (quite substantially) since then so I'd like to see a repeat of that benchmark.

Well, I know what my next mod is going to be!  Excuse me while I read up on best practices for measuring execution time in C#.

--LWM

EagleCall

#146
Hi, I've been quietly lurking a bit but still pretty new and haven't posted before, so I hope this considered within the scope of this thread.

While I think I'm getting a decent handle on how XML modding works, I'm confused about PatchOperationTest and <success>Invert</success>.  I've forked an existing XML mod on Github to fix/tweak/mess with (well, several actually, but there's a specific one in question here), and it uses this method to patch things from other mods (that may or may not be there):

<Operation Class="PatchOperationSequence">
<success>Always</success>
<operations>
<li Class="PatchOperationTest">
<xpath>*/Defs/ThingDef[defName = "name of thing to patch"]</xpath>
<success>Invert</success>
</li>
<li Class="PatchOperationReplace">
<xpath>/Defs/ThingDef[defName = "name of thing to patch"]/path/to/node</xpath>
<value>
<node>patched stuff</node>
</value>
</li>
</operations>
</Operation>


But, from what I understand, that <success>Invert</success> actually cancels the patch if the thing to patch is found, rather than the intended cancel if not found?  And yet no one seems to have had problems with it, so how?  Is it wrong and just not noticed/reported?  I feel sure that must be the case, but to further complicate things, some of the stuff I've found while searching for answers makes the results of going without that line seem broken/erratic.  So how is it actually supposed to work when you are checking that there is a thing there rather than that there isn't?

Basically, I am now very confused and doubting myself here.  And almost feeling like it would be simpler to just switch to PatchOperationConditional match to be sure of things but...  not really.  Plus I don't know if I can nest a conditional within a conditional or how that would work, while I know I can nest a conditional within a sequence, and some situations require that sort of thing.  Ugg...



I'm also very interested in this whole */Defs/ vs. /Defs/ vs. Defs/ question, but that is a minor issue in comparison.

Mehni

Welcome to the forums.

The operation you posted is kinda weird.

There's a Sequence with a <success>Always</success> tag. Nothing surprising so far: a sequence is often expected to "fail" at some point and setting <success>Always</success> will prevent "xpath failed" errors.

The "Test" has an invalid xpath, so this would always fail[1]. Inverting it's success means it'll always be considered successful, which in turn means the "Replace" does get executed. That one may or may not fail, but we'll never know. The <success>Always</success> in the sequence means the error is never reported.

It looks like you found a bug in the sequence. Congratulations, you have a better understanding of XPath than the one who wrote the patch sequence.

___
[1]: What you posted as a side question is actually central to the XPath failure: */Defs isn't a valid XPath for RimWorld's XML structure. * is a wildcard for "all nodes at this point". The way RimWorlds XML structure is set out is that the root (i.e. first) node of all Defs is <Defs>, and the first childnode is <SomeTypeOfDef> (e.g. <ThingDef>).

This structure (and enforcing) of "all rootnodes start with <Defs>" is relatively recent: before A17 or B18, rootnodes could be anything. There where things like a <Drugs> rootnode, a <ThingDefs> rootnode and even a <Beer> rootnode. That's why the * was practical: you didn't need to specify the exact rootnode. Now that's changed to a uniform <Defs> rootnode, the * wildcard isn't required anymore: the exact XML structure is known in advance.

TL;DR: */Defs/ThingDef points effectively points to Defs/Defs/ThingDef, which doesn't exist in RimWorld's XML.

*/ vs Defs/ vs /Defs/ practically only differs in speed.


More on-topic, or more to the point:

I'd use PatchOperationFindMod to support other mods.

LWM

Speed tests for Defs vs /Defs

Ok, I put together a little test that does an xpath patch operation a large number of times, and I found a few interesting results.

I would run one xpath operation, either Defs/ThingDef/etc or /Defs/ThingDef/etc a large number of times, then run the other one, and time them.  I consistently found that whichever I put 2nd in my tests was slower than whatever ran first.  I consistently found that (as I expected from reading the specification) /Defs/... - which is more specific than Defs/... - is slightly faster.

But it's only very very slightly, like <1%.

Using */... was slower, by about 10% on my system.  Which is still not huge, unless you have a slow computer and a LOT of mods.

A few caveats:
I was only running 10000 xpath operations.
I was running the same type of patch over and over - it's possible some internal library caching was speeding up the process?

You can see what I was using here:  https://github.com/lilwhitemouse/RimWorld-PatchSpeedTest

tldr; Use /Defs/...   If you have previously written mods using Defs/... don't go back and change things unless bored.  Prefer not to use */...

Alternate amusing tldr:  Disk space is cheap.  Use the extra "/"

LWM

#149
Re: EagleCall's patch:

You can often just attempt the patch operation directly, without using a sequence.  If the xpath is valid, the patch is done.  If not (e.g., if the node doesn't exist because a specific mod hasn't been loaded), the patch isn't done, and no harm done.

So:

<Operation Class="PatchOperationReplace">
<success>Always</success><!-- Avoid unnecessary errors, don't scare users -->
<xpath>/Defs/ThingDef[defName = "name of thing to patch"]/path/to/node</xpath>
<value>
<node>patched stuff</node>
</value>
</Operation>


This may or may not be applicable to what you are trying, of course, but it's good to remember.

--LWM