XPATH surgery - Need help with xpath? post here.

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

Previous topic - Next topic

Tamias

#45
Thanks for the reply Skully, I really appreciate it. I'm still really new to the concept of xpath, so I'm sorta trying to fill out my xpath "toolbox" by trying to understand what the possibilities are with xpath in the context of Rimworld. I've read quite a bit online about it as a primer, but nearly all the info I've come across is general purpose, so I've been having trouble figuring out exactly what I can and cannot do in certain situations with RimWorld modding specifically.

As far as my second question goes, I think it would help if I used an expanded example with some code for clarity.

Let's say I'm using two mods: mod A and mod B. Both mods add weapons, as well as their own factions that use the weapons from their respective mods. However, I don't like the weapons from mod B, so I want to patch all the faction pawns from mod B to use weapons from mod A instead. My method so far would be a patch that might look something like this:

  <Operation Class="PatchOperationReplace">
    <xpath>/PawnKindDefs/PawnKindDef[defName="ModB_Soldier"]/weaponTags/li[.="ModB_Weapons"]</xpath>
    <value>
      <li>ModA_Weapons</li>
    </value>
  </Operation>

Then, I would repeat the process for every pawn that mod B adds, creating an individual patch for every instance where the weapon tag "ModB_Weapons" appears, and replacing it with "ModA_Weapons". However, the issue is that the tag is peppered throughout all the various PawnKindDef files, and it's a pain in the ass to have to track down and path every single one, when what I'm doing here basicly amounts to a "replace all" operation. I know there has to be a more efficient way to accomplish something like this with xpath patching, I'm just not fluent enough with it to know what that solution is. For this example, would there be a way to write a single "PatchOperationReplace" patch that would simply replace every instance of "ModB_Weapons" with "ModA_Weapons" throughout all the various PawnKindDef files?

skullywag

well, per file if you can make the xpath match what you want itll patch each thing it finds. The issue above is that it will obviously continue to run on ALL files therefore you have to define the pawns name to ensure you are ONLY hitting those pawns from that mod. If thats the only identifier you have theres not much you can do with the vanilla patching system bar what youve already stated.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

Granitecosmos

If
<Defs>
<ThingDef Name="ExampleAbstract" Abstract="True">
</ThingDef>
</Defs>

and
<Defs>
<ThingDef>
<defName>ExampleDef</defName>
</ThingDef>
</Defs>

exists, is it possible to patch ExampleDef's
<ThingDef>
to
<ThingDef ParentName="ExampleAbstract">
and if it is, how do I do it?

skullywag

For people that care:

namespace Verse
{
public class PatchOperationAttributeAdd : PatchOperationAttribute
{
protected string value;

protected override bool ApplyWorker(XmlDocument xml)
{
bool result = false;
foreach (object current in xml.SelectNodes(this.xpath))
{
XmlNode xmlNode = current as XmlNode;
if (xmlNode.Attributes[this.attribute] == null)
{
XmlAttribute xmlAttribute = xmlNode.OwnerDocument.CreateAttribute(this.attribute);
xmlAttribute.Value = this.value;
xmlNode.Attributes.Append(xmlAttribute);
result = true;
}
}
return result;
}
}
}


simple answer:

<li Class="PatchOperationAttributeAdd">
  <xpath>/Defs/ThingDef[defName = "ExampleDef"]</xpath>
  <attribute>ParentName</attribute>
  <value>ExampleAbstract</value>
</li>


Think thats right. :)

There also attributeSet for changing ones that already exist.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

AngleWyrm

Is there a way to throw an error box log text if an xpath fails?
I'd like my add-on mod to issue a message to the user:
"Ancient Magic: failed to find dependency Infused mod; is Infused mod installed?"

<!-- remove two records if they exist -->
<Operation Class="PatchOperationSequence">
    <success>Always</success>
    <operations>
        <li Class="PatchOperationTest">
            <xpath>/Defs/Infused.ChanceDef</xpath> <!-- is there a record in defs called Infused.ChanceDef? -->
            </li>
            <li Class="PatchOperationRemove">
                <xpath>/Defs/Infused.ChanceDef[defName = "Default"]</xpath>
            </li>
            <li Class="PatchOperationRemove">
                <xpath>/Defs/Infused.ChanceDef[defName = "IndustrialAndBelow"]</xpath>
            </li>
        </operations>
    </Operation>
My 5-point rating system: Yay, Kay, Meh, Erm, Bleh

skullywag

Not really an xpath question, this is one for the modding community in general, i dont know of any way within the core system, but others may have done funky stuff with custom operations. Chuck a thread on mods and see if anyone knows of a way.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

AngleWyrm

#51
There's been some talk about using wildcards and how they generally increase game load time through unnecessarily long searches. There hasn't been any objective tools or tests measuring that difference, but that's another story.

There has also been some demonstrations of how the in-game version of xpath is a partial implementation with some unexpected behaviors.

The following expression works as intended in my patch operation:
<xpath>//ThingDef[DefName="Mineshaft"]/recipes</xpath>

This works as a replacement:
<xpath>*/ThingDef[DefName="Mineshaft"]/recipes</xpath>

Can I replace the wildcard ("//" or "*/") with something specific?
(further info on patch file)

My 5-point rating system: Yay, Kay, Meh, Erm, Bleh

skullywag

It depends if you are targetting one def then yes use the wrapper the file your targetted def is in is using.

/Defs/ThingDef
/BuildableDef/ThingDef

Etc

If you are hunting every file and they have different starting wrappers then you can use the wildcard,  // should never be used.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

AngleWyrm

#53

On the subject of using a PatchOperationFindMod function in patch sequences as a test: I made that into a mod (attached, with source), which works just like any other PatchOperationXXX.

The impetus behind making it was that it would enable modders to write patches without the need for C# coding, and in as far as that was the goal it has been met. But there's significant problems with this approach that make it inadvisable: The active mods list in the config file is made of folder names.

Many mods attach version numbers to their mod folder, or go through modder fidgeting to get a better name. They also get renamed by users where those folder names fail to clearly convey their contents. Thus any patch mod that relies on folder names is apt to break, early and often. It's a maintenance nightmare waiting to happen.

BTW, is (mods)/*/About/About.xml/ModMetaData/name visible in xpath space?

[attachment deleted by admin due to age]
My 5-point rating system: Yay, Kay, Meh, Erm, Bleh

AngleWyrm

#54
The xpath union operator '|' that combines multiple searches seems to work (doesn't throw a red error log message), so that it's theoretically possible to generate a node-set result like this:

/somewhere/deeper[Characteristic="whatever"]/recipe | /somewhereElse/depth/is/irrelevant[defName="stuff"]

My understanding of this implementation of xpath (please correct me if I'm wrong) is that it handles it's work on a per-file basis that maintains a concept of current file. If that is a reasonable approximation of the code, then what happens when an xpath with multiple searches ("|") is applied?
My 5-point rating system: Yay, Kay, Meh, Erm, Bleh

kaptain_kavern

From my personal experimentations, using "or" instead of "|", the patch is applied in only on file, the other "or" options were simply not applied (without throwing any errors - Took me time to even noticed it wasn't working for all my "or"). I guess (but have not tested to confirm) it is applied in the file where the first occurrence is found.

Sorry if it's not that clear, I'm struggling with finding the right English words to express myself better.

Sp0nge

Im trying to change the movementspeed of cats here: https://github.com/XenEmpireAdmin/AnimalCollabProjectA17/blob/master/Defs/ThingDefs_Races/Races_Animal_Cats.xml

However im not shure what to write in order to direct my xpath to the correct location. I tried something like:


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

<Operation Class="PatchOperationSequence">
<success>Always</success>
<operations>
<li Class="CombatExtended.PatchOperationFindMod">
<modName>AnimalCollabProj</modName>
</li>
<li Class="PatchOperationAddModExtension">
<xpath>*/ThingDef[defName="ACPLion"]</xpath>
<value>
<li Class="CombatExtended.RacePropertiesExtensionCE">
<bodyShape>QuadrupedLow</bodyShape>
</li>
</value>
</li>
<li Class="PatchOperationReplace">
<xpath>*/ThingDef[defName="ACPLion"]/verbs</xpath>
<value>
<verbs>
<li Class="CombatExtended.VerbPropertiesCE">
<verbClass>CombatExtended.Verb_MeleeAttackCE</verbClass>
</li>
</verbs>
</value>
</li>
<li Class="PatchOperationReplace">
<xpath>*/ThingDef[defName="BigCatThingBase"]/statBases/MoveSpeed</xpath>
<value>
<MoveSpeed>40.3</MoveSpeed>
</value>
</li>
</operations>
</Operation>
</Patch>


I know that my bodyShape and verbClass wont execute, as they are targeting wrongly (I used some copy paste since there is alot of animals to patch so i made a crude xml pr animal to begin with). Now im trying to sort out my code and finish it up.

shadowstitch

Okay, I'm stumped. Which isn't hard, admittedly, but I've tried doing my homework here, reading documentation and forum posts and I cannot figure out what I'm doing wrong. I'm hoping someone with better eyes and a bigger brain can take pity on me and help.

I'm trying to make a very simple change, (for personal preference usage only, not trying to steal anyone's work.)
In Latta/Skullywag's "Better Vents," I discovered that you could change the costlist to a stuffcategories/stufflist and make the vents out of any stuff you want.
In a16 this was easy enough, since all you had to do was make the change in the Buildings_Temperature.xml thingdef. Worked like a charm.
In a17, it's considerably more complicated, because patches. Better Vents now works all its magic on the core xml with a patch, and even though the existing patch is right there in front of me as an example, I can't get my little changes to work. I get red errors in log on load. If I change the core def directly, it appears to work perfectly with no errors, but I'm trying to play by the rules and use patches as they are intended.

What I'm trying to change is this:

I want to take the core Buildings.Temperature.xml, and mess with this thingdef:


<ThingDef ParentName="BuildingBase">
<defName>Vent</defName>
<label>vent</label>
<thingClass>Building_Vent</thingClass>
                ~~~snip~~~


down in its defs, I want to  change:


<costList>
<Steel>30</Steel>
</costList>

to:

    <stuffCategories>
          <li>Metallic</li>
          <li>Woody</li>
  <li>Stony</li>
    </stuffCategories>
    <costStuffCount>30</costStuffCount>


This is what I have in my patch, which doesn't work:


<Operation Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[defName = "Vent"]/stuffCategories</xpath>
<value>
<li>Metallic</li>
         <li>Woody</li>
     <li>Stony</li>
</value>
</Operation>

<Operation Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[defName = "Vent"]/costStuffCount</xpath>
<value> 30 </value>
</Operation>

<Operation Class="PatchOperationRemove">
<xpath>/Defs/ThingDef[defName = "Vent"]/costList</xpath>
</Operation>


I get this error in the log when I try to run it, so I ASSUME my code to snip the costlist is working, but the other 2 add-ons are wrong somehow. I tried a PatchOperationInsert as well, attempting to follow the syntax guidelines, but that didn't work either -- if that's what I need to use, I'm certainly not doing it right.


[BetterVents] Patch operation Verse.PatchOperationAdd(/Defs/ThingDef[defName = "Vent"]/stuffCategories) failed
Verse.Log:Error(String)
Verse.PatchOperation:Complete(String)
Verse.LoadedModManager:LoadAllActiveMods()
Verse.PlayDataLoader:DoPlayLoad()
Verse.PlayDataLoader:LoadAllPlayData(Boolean)
Verse.Root:<Start>m__853()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__851()

[BetterVents] Patch operation Verse.PatchOperationAdd(/Defs/ThingDef[defName = "Vent"]/costStuffCount) failed
Verse.Log:Error(String)
Verse.PatchOperation:Complete(String)
Verse.LoadedModManager:LoadAllActiveMods()
Verse.PlayDataLoader:DoPlayLoad()
Verse.PlayDataLoader:LoadAllPlayData(Boolean)
Verse.Root:<Start>m__853()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__851()


I'm following the same procedure as the existing operations in the patch, and to the best of my understanding, I'm performing the same sort of operations, but mine are not accepted for some reason. Can someone please teach me the error of my ways?

skullywag

You cannot add a value to something that doesnt exist, you need to add stuffCategories and costStuffCount first before you add values to them.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

AngleWyrm

#59
Quote from: shadowstitch on August 21, 2017, 05:03:14 PM
This is what I have in my patch, which doesn't work:

<Operation Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[defName = "Vent"]/stuffCategories</xpath>


Reference/tutorial wiki page

PatchOperationAdd adds a new sub-part to a record, so the xpath search for those operations should only select the whole 'vent' record, and then add parts into it.

<xpath>/Defs/ThingDef[ defName="Vent" ]</xpath>
My 5-point rating system: Yay, Kay, Meh, Erm, Bleh