0

I am trying to write a bash script or something similar, that I can use to modify all occurrences in a bunch of xml files with one click. I have 24 xml files, each containing a few dozen lines and with the attribute Torque="SOMENUMBER" inside them about 3-5 times each. I would LIKE to replace those with 1.25 times their value to simply add 25% torque to every motor in this game.

(Doing it this way would be useful, since mods and game patches will overwrite these and I can quickly experiment with other values.)

I came up with the idea to use sed -n -e 's/Torque="\(.*\)"/\1/p' <filename.xml to extract the number, put it in a variable and then I would have no idea how to get it back in there at the right place and also the sed command above outputs all occurrences of Torque at once.

xmlstarlet could probably do it from what I have googled, but it always complains about the xml files containing more than one root element. Maybe I should somehow add a temporary tag <root_temp><\root_temp> around everything somehow, let xmlstarlet run its magic, remove the tags again and save the file? It's been years, since I dabbled with bash scripts, I would also be willing to do it with python, cpp, learn the basics of a new language, I don't care xD

Sample XML:

<_templates>
    <Engine>
        <RUScoutModernEngine BrakesDelay="0.5" />
    </Engine>
</_templates>
<EngineVariants>
    <Engine
        _template="RUScoutModernEngine"
        CriticalDamageThreshold="0.7"
        DamageCapacity="120"
        DamagedConsumptionModifier="1.2"
        EngineResponsiveness="0.35"
        FuelConsumption="1.5"
        Name="ru_scout_modern_engine_0"
        Torque="70000"
        DamagedMinTorqueMultiplier="1.0"
        DamagedMaxTorqueMultiplier="0.6"
        MaxDeltaAngVel="0.01"
    >
        <GameData
            Price="1900"
            UnlockByExploration="false"
            UnlockByRank="1"
        >
            <UiDesc
                PLACE="HOLDER"
            />
        </GameData>
    </Engine>
    <Engine
        BLA="BLA"
        Torque="80000"
        BLA="BLA"
    >
        <GameData
            Price="5500"
            UnlockByExploration="true"
            UnlockByRank="1"
        >
            <UiDesc
                PLACE="HOLDER"
            />
        </GameData>
    </Engine>
    <Engine
        BLA="BLA"
        Torque="76000"
        BLA="BLA"
    >
        <GameData
            BLA="BLA"
        >
            <UiDesc/>
        </GameData>
    </Engine>
</EngineVariants>
2
  • Apart from the missing root node, you also seem to have duplicate attributes in some nodes. – Kusalananda May 4 at 21:41
  • That is unfortunately how this particular game sets up its "xml" files, nothing I can do about that. Although the "BLA BLA" stuff was added by me to filter out unimportant information and make the file more palatable, if that wasn't clear. The <Engine> tags do each contain the same attribute with different values, though I do not think that counts as a duplicate, since it IS a different tag, right? – xOverload86x May 5 at 0:14
0

XML fragments (files with more than one root node) are very difficult to manage, very few tools accept them directly. Adding a wrapper element "by hand" is a good idea; note also the technique described in

https://stackoverflow.com/questions/23571941/making-use-of-an-xml-file-with-more-than-one-root-element/23574398#23574398

Once you have a well-formed document, doing the update you want in xmlstarlet or in Saxon's Gizmo (https://saxonica.com/documentation10/index.html#!gizmo) should be straightforward [self-interest declared]. In Gizmo it's

update //@Torque with .*1.25
4
  • That sounds like a good solution, I find xmlstarlet a bit confusing for now, though. Probably better to try this "Gizmo" approach. Am I correct, that I have to install the whole Saxon Suite or whatever it is to get access to Gizmo? – xOverload86x May 5 at 0:12
  • Yes, the Gizmo tool is just a front end exposing a selection of the transformation capabilities of the Saxon engine. But it's just a JAR file... – Michael Kay May 5 at 17:01
  • Are multi-root XML documents even valid? From my understanding they are not - a document must have no more than one root element – roaima May 5 at 18:38
  • Firstly, I think you mean "well-formed" - validity is something different. This "multi-root" thing is not a well-formed document, but it is a well-formed external general parsed entity, which means it's not supposed to be used on its own, but it can be incorporated into a multi-entity document by reference. – Michael Kay May 6 at 22:02
1

Adding a fake root node around your broken XML is easy enough, modifying the modified XML would be a breeze with xmlstarlet, and deleting the added root node (and the <?xml version="1.0"?> that xmlstarlet adds) once you've modified it is not too hard either:

{ echo '<root>'; cat file.xml; echo '</root>'; }  |
xmlstarlet ed -u '//Engine/@Torque' -x '. * 1.25' |
sed '1d; 2d; $d'

This obviously assumes that the XML that comes out of the { ...; } bit is actually a well-formed XML document (you have duplicated attributes in your example document).


The same thing as above, but using xq (an XML parser wrapping around jq, available as part of yq at https://kislyuk.github.io/yq/):

{ echo '<root>'; cat file.xml; echo '</root>'; }  |
xq -x '.root.EngineVariants.Engine[]."@Torque" |= ( tonumber * 1.25)' |
sed '1d; $d'

The xq output does not contain <?xml version="1.0"?>.


Repeating this for all files whose names match *.xml in a directory, using the xmlstarlet variant of the code above as an example:

tmpfile=$(mktemp)

for pathname in ./*.xml; do
    cp "$pathname" "$tmpfile" &&
    { echo '<root>'; cat "$tmpfile"; echo '</root>'; } |
    xmlstarlet ed -u '//Engine/@Torque' -x '. * 1.25'  |
    sed '1d; 2d; $d' >"$pathname"
done

rm -f "$tmpfile"

Note that the above does an in-place edit on the matched files. You may want to test this on a copy of your files first!

0

I solved the problem by implementing the option with a temporary root node to make it valid and then using Gizmo as suggested. For anyone stumbling upon this later, here is my solution.

I downloaded the java version of Saxon Home Edition, unpacked it in a subfolder called "Saxon" and used the following script successfully.

#!/bin/bash

#remove script and generate it with the following lines (that way I only need
#to include my one .sh file)
rm -f script
echo update //@Torque with .*1.25>script
echo save temp_updated.xml method=xml indent=yes>>script

#we do the following to all xml files in the subfolder "classes/engines/"
for filename in classes/engines/*.xml
do
    #cleanup in case they got leftover, which is important for Gizmo,
    #otherwise I get a thousand "Overwrite File" prompts, which is
    #not conducive to automating the process.
    rm -f temp.xml
    rm -f temp_updated.xml

    #make a new temp file, starting with the temporary root element,
    #append the original file and close the root element
    echo "<xml_temp>">temp.xml
    cat $filename>>temp.xml
    echo "</xml_temp>">>temp.xml

    #call Saxon with the correct codepath, start the Gizmo subroutine or whatever
    #take the temporary file as input and "script" as the commands to be executed
    java -cp "Saxon/saxon-he-10.5.jar:Saxon/jline-2.14.6.jar" net.sf.saxon.Gizmo -s:temp.xml -q:script
    #sed -i for inplace editing of the file
    #the first expression '$d' removes the last line (the closing temp root tag)
    #the second impression removes the first line (Gizmo tags on some kind of xml info
    #and the second line, which contains the starting temp root tag
    sed -i -e '$d' -e 1,2d temp_updated.xml

    #I force overwrite the old file with the new and improved xml
    cp -f temp_updated.xml $filename
    #...and clean up after myself
    rm -f temp.xml
    rm -f temp_updated.xml
    rm -f script
done

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.