XPATH surgery - Need help with xpath? post here.

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

Previous topic - Next topic

TeflonJim

#75
Trying to figure out if what I'm trying to do is even possible or not. I'm attempting to merge my fork of Extended Woodworking with its Vegetable Garden patch. To achieve that I (think I) either need to:

1. Only create a set of ThingDefs if a particular mod is present (and loaded)

Or:

2. Remove a small set of ThingDefs if a particular mod is not present

The second approach is working for a bunch of floors the mod injects, it appears to strip out the TerrainDefs well enough. The linked sequence attempts to do this:

https://github.com/indented-automation/ExtendedWoodworking/blob/master/generated/ExtendedWoodworking/Patches/EW-Cleanup-NoVGP.xml

The link uses PathOperationFindMod which has since been substituted for a PatchOperationConditional based on a Def exposed by the dependent mod. I'm going to replace that with ModCheck because it's just really, really disgusting and I've been starring at PatchOperation classes in ilspy for too long now.

Doing this in C# is likely easier, but my fork was only ever intended to maintain the life of a mod I've always used (and certainly didn't originally create). I'm reluctant to move it out of just-a-bit-of-xml at the moment.

If I can figure this bit out I can do (at least) one more "is this sane" pass on the XML / XPath before making it live.

Otherwise, is there a better way of doing this that I don't know about yet?

Thanks for any / all comments in advance :)

All the best!

Chris

Edit: "May" be related to the way PatchOperationSquence works, where all steps in a sequence are applied to a single XmlDocument (first failure aborts patch).

Nightinggale

Quote from: TeflonJim on November 28, 2017, 02:18:04 PM1. Only create a set of ThingDefs if a particular mod is present (and loaded)
I wrote a patch for that. I added it here with comments.

The idea is to use PatchOperationSequence. It's important to realize that a patch will be applied to each xml file in Defs in each mod. This mean you need to apply some xpath, which ensures the add new item will only apply once and not once for each xml file.
In this specific case PatchOperationAdd takes up that job because that operation is needed anyway, but there are multiple ways to do it.
<Patch>
<!-- a sequence works by taking each PatchOperation in operations and execute them one by one -->
<!-- if a PatchOperation in operations fail, the following will not be executed -->
<Operation Class="PatchOperationSequence">
<success>Always</success>
<operations>
<!-- only work if the needed mod is loaded -->
<li Class="ModCheck.isModLoaded">
<modName>A Dog Said...</modName>
<yourMod>MoreMonstergirls B18 Vanilla</yourMod>
</li>
<!-- make sure the mods are loaded in the needed order -->
<!-- delete this one if order doesn't matter -->
<li Class="ModCheck.loadOrder">
<modName>A Dog Said...</modName>
<yourMod>MoreMonstergirls B18 Vanilla</yourMod>
<errorOnFail>true</errorOnFail>
</li>
<!-- test if an xpath exist -->
<!-- PatchOperationAdd, PatchOperationInsert and PatchOperationTest can be used -->
<!-- which one you pick depends on your specific needs -->
<li Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[defName = "BlackHarpy"]/recipes</xpath>
<value>
<li>InstallPegLegAnimal</li>
<li>InstallSimpleProstheticLegAnimal</li>
<li>InstallBionicLegAnimal</li>
<li>InstallSpineAnimalSimple</li>
<li>InstallSpineAnimalBionic</li>
<li>InstallBionicEyeAnimal</li>
<li>InstallAnimalBrainStimulator</li>
<li>InstallBionicAnimalHeart</li>
<li>InstallBionicAnimalLung</li>
<li>InstallBionicAnimalLiver</li>
<li>InstallBionicAnimalKidney</li>
<li>InstallBionicAnimalStomach</li>
<li>InstallNoseAnimalSimple</li>
<li>InstallJawAnimalSimple</li>
<li>InstallJawAnimalBionic</li>
<li>CureInjuryBurnAnimal</li>
<li>CureInjuryCrushAnimal</li>
<li>CureInjuryCrackAnimal</li>
<li>CureInjuryCutAnimal</li>
<li>CureInjurySurgicalCutAnimal</li>
<li>CureInjuryScratchAnimal</li>
<li>CureInjuryBiteAnimal</li>
<li>CureInjuryStabAnimal</li>
<li>CureInjuryGunshotAnimal</li>
<li>CureInjuryShreddedAnimal</li>
<li>CureInjuryBruiseAnimal</li>
</value>
</li>
<!-- insert the new item into Defs. In this case it's a recipe, but it can be anything -->
<li Class="PatchOperationAdd">
<xpath>/Defs</xpath>
<value>
<RecipeDef ParentName="SurgeryFleshAnimal">
<defName>InstallSimpleProstheticWingHarpy</defName>
<label>install simple prosthetic harpy wing</label>
<description>Installs simple prosthetic harpy wing. Requires a min skill of 4 in Medicine.</description>
<workerClass>Recipe_InstallArtificialBodyPart</workerClass>
<jobString>Installing simple prosthetic harpy wing</jobString>
<workAmount>2000</workAmount>
<skillRequirements>
<Medicine>4</Medicine>
</skillRequirements>
<ingredients>
<li>
<filter>
<categories>
<li>Medicine</li>
</categories>
</filter>
</li>
<li>
<filter>
<thingDefs>
<li>SimpleHarpyWing</li>
</thingDefs>
</filter>
</li>
</ingredients>
<fixedIngredientFilter>
<categories>
<li>Medicine</li>
</categories>
<thingDefs>
<li>SimpleHarpyWing</li>
</thingDefs>
</fixedIngredientFilter>
<appliedOnFixedBodyParts>
<li>LeftShoulder</li>
<li>RightShoulder</li>
</appliedOnFixedBodyParts>
<addsHediff>SimpleHarpyWing</addsHediff>
<recipeUsers>
<li>BlackHarpy</li>
<li>Harpy</li>
</recipeUsers>
</RecipeDef>
</value>
</li>
</operations>
</Operation>
</Patch>
ModCheck - boost your patch loading times and include patchmods in your main mod.

TeflonJim

Thank you kindly, I'll follow the same scheme.

Chris

QuantumX

Hello, could i ask for help on this XPATH please.. I cannot seem to get a insert operation to work on the BodyParts_General.xml file

I want to go from this


<BodyPartDef>
<defName>Brain</defName>
<label>brain</label>
<hitPoints>10</hitPoints>
<oldInjuryBaseChance>1</oldInjuryBaseChance>
<skinCovered>false</skinCovered>
<tags>
<li>ConsciousnessSource</li>
</tags>
<dontSuggestAmputation>true</dontSuggestAmputation>
</BodyPartDef>


To this:


<BodyPartDef>
<defName>Brain</defName>
<label>brain</label>
<hitPoints>10</hitPoints>
<oldInjuryBaseChance>1</oldInjuryBaseChance>
<skinCovered>false</skinCovered>
<tags>
<li>ConsciousnessSource</li>
</tags>
<dontSuggestAmputation>true</dontSuggestAmputation>
<spawnThingOnRemoved>IGBrain</spawnThingOnRemoved>
</BodyPartDef>


My Patch Operation Looks Like this;


<Operation Class="PatchOperationInsert">
<xpath>/Defs/BodyPartDef[defName = "Brain"]/specialDesignatorClasses</xpath>
<value>
<spawnThingOnRemoved>IGBrain</spawnThingOnRemoved>
</value>
</Operation>


But i'm getting operation failed, i think its something to do with the /Defs/BodyPartDef part... but not sure, this is only the second time i've ever done xpath, and the first i had alot of help from here.

Nightinggale

Quote from: QuantumX on November 30, 2017, 03:07:42 PM
<xpath>/Defs/BodyPartDef[defName = "Brain"]/specialDesignatorClasses</xpath>
You only mentioned specialDesignatorClasses here, not in the original or the wanted codes. I think you need to remove it and insert directly into BodyPartDef.
ModCheck - boost your patch loading times and include patchmods in your main mod.

QuantumX

#80
So so i have changed to the following;


<Operation Class="PatchOperationInsert">
<xpath>/Defs/BodyPartDef[defName = "Brain"]/</xpath>
<value>
<spawnThingOnRemoved>IGBrain</spawnThingOnRemoved>
</value>
</Operation>


And does not work

Nightinggale

PatchOperationAdd: adds a new child to the element found in xpath
PatchOperationInsert: adds a new sibling to the element found in xpath

You picked the wrong one and now it tries to read <spawnThingOnRemoved> as a Def.
ModCheck - boost your patch loading times and include patchmods in your main mod.

QuantumX

So next change..


So so i have changed to the following;

[code]
<Operation Class="PatchOperationInsert">
<xpath>/Defs/BodyPartDef[defName = "Brain"]/defName</xpath>
<value>
<spawnThingOnRemoved>IGBrain</spawnThingOnRemoved>
</value>
</Operation>


Now upon loading it now WORKS!!!  WhooHoooo....

larSyn

Quick question...Is it better to use /Defs/whateverDef than */whateverDef now?  I've seen a lot of this since the update and wasn't sure which is better.

Nightinggale

Quote from: larSyn on December 01, 2017, 11:57:09 AM
Quick question...Is it better to use /Defs/whateverDef than */whateverDef now?  I've seen a lot of this since the update and wasn't sure which is better.
Good question. I have seen multiple different versions in use as well and assuming they all work as intended, the difference would presumably be speed. In other words the answer would come from benchmarking each one, so that's what I did.





Def/baseline
/Def/+1.4%
*/Def/+6.4%
//+749%
I ran 500 of each and measured several times. The results are fairly consistent and as such trustworthy and I used time for patching core only. I think the numbers speak for themselves.

Maybe it was a quick question, but providing the answer wasn't  :P
ModCheck - boost your patch loading times and include patchmods in your main mod.

kaptain_kavern

#85
Told ya ;-)

As we're explicitly knowing the whole path upfront, we need to use the most precise "query".

But, duh! I never thought about just removing the first "/". But it's logical... Once someone pointed you to it ^^

Thanks for doing the test.

Much appreciate ;-) this precise information need to be spread fast!

Nightinggale

Quote from: kaptain_kavern on December 01, 2017, 02:59:19 PMBut, duh! I never thought about just removing the first "/". But it's logical... Once someone pointed you to it ^^
Same here. I saw such a patch, then I was like "it's different. Will it work?" and indeed it works.

Quote from: kaptain_kavern on December 01, 2017, 02:59:19 PMthis precise information need to be spread fast!
Added to the sticky about xpath performance. I recommend everybody reading it the entire thread, particularly first post and (at the time of writing) the last two posts, both written by me.
ModCheck - boost your patch loading times and include patchmods in your main mod.

larSyn

Thanks, Nightinggale.  Now I know what I'll be doing later. 

o-o-o

I am working on faction mods, and have made good use of some very basic xpath stuff.  Specifically, I have been adding tags to weapons so that custom pawntypes would wield specific vanilla weapons.  All good there so far; all my dudes spawn with their proper weapons. 

While trying to tweak the spawn apparel for the new pawns, I was bothered that my mostly non-violent industrial-tech and Outlander apparel tag pawns wanted to take a lot of their apparel money and buy armor vests.  Instead of forcing a lot of specific apparel on the pawns and leaving no money left over for the armor vests, I decided to use xpath to just modify the armor vests.  Specifically, I wanted to replace one of the list items under Apparel_Various/Apparel_VestPlate/apparel/tags.  I would keep the Military list item, but change the Outlander list item to a custom new tag ("RaidMonkey" in this case).  I can't seem to get the path properly selected, or don't understand the subtleties of nodes vs list items or something.  Using the following code I get an error that my list items do not correspond to any field type in type ApparelProperties.



Quote<Operation Class="PatchOperationReplace">
    <xpath>*/ThingDef[defName = "Apparel_VestPlate"]/apparel/tags</xpath>
    <value>
      <li>Military</li>
      <li>RaidMonkey</li>
      </value>
    </Operation>

I have not found any good examples of replacing list items in patches in the other faction mods I have been looking at, nor did I find an example in this thread that was easy enough for me to translate to my specific case.  The more I see of xpath, the more I like it for modding, so I expect to get a lot of mileage out of replacing or removing list items found in tags or weaponTags.

Nightinggale

You aren't doing it right. There is no list in the source code, hence you should not use a list in xml. The question is how to do it correctly.
  <Operation Class="PatchOperationReplace">
    <xpath>Defs/ThingDef[defName = "Apparel_VestPlate"]/apparel/tags/Military</xpath>
    <value>RaidMonkey</value>
    </Operation>

I think that will work. At least it matches the source code better. The idea is that whatever is in xpath is replaced with value and for replacement it only considers what is after the last "/". This mean since xpath will find an element with tags as parent, the replacement will have tags as parent and as such moving to a different location is not supported, but I can't really think of any case where that would be useful.
ModCheck - boost your patch loading times and include patchmods in your main mod.