Search code examples
xpathxmlstarlet

How can I update an XML attribute node with the maximum of its sub-attributes?


Consider the following XML:

<?xml version="1.0"?>
<sportsClass>
    <pupils>
        <pupil name="Adam" highestJump="">
            <jump height="4"/>
            <jump height="1"/>
        </pupil>
        <pupil name="Berta" highestJump="">
            <jump height="4"/>
            <jump height="7"/>
        </pupil>
        <pupil name="Caesar" highestJump="">
            <jump height="1"/>
            <jump height="2"/>
        </pupil>
        <pupil name="Doris" highestJump="">
            <jump height="5"/>
            <jump height="5"/>
        </pupil>
    </pupils>
</sportsClass>

How can I fill the highestJump attribute nodes with the respective maximum height value, using xmlstarlet?


Solution

  • This problem consists of two sub-problems:

    Finding the maximum

    xmlstarlet does not have the max() function, so we have to find a way around:

    cat jumps.xml | \
    xmlstarlet select -t -v "//pupil/jump[not(@height <= following-sibling::jump/@height) and not(@height < preceding-sibling::jump/@height)]/@height"
    

    Note the <= and < – if there are more than one maximum values, only the last one will be taken.

    Result:

    4
    7
    2
    5
    

    Updating the attribute

    Constant value for practice

    cat jumps.xml | xmlstarlet edit --update //pupil/@highestJump -v "Hahahaha"
    

    ...writes Hahahaha to every highestJump attribute.

    Simple XPath

    Take care: The XPath you use for replacing

    • is relative to the selected attribute (so . is the attribute itself)
    • has to be wrapped with eg. string() to have an effect

    So:

    cat jumps.xml | xmlstarlet edit --update //pupil/@highestJump -x "string(../@name)"
    

    ...gives (shortened):

    <pupil name="Adam" highestJump="Adam">
    <pupil name="Berta" highestJump="Berta">
    <pupil name="Caesar" highestJump="Caesar">
    <pupil name="Doris" highestJump="Doris">
    

    Combining the two

    cat jumps.xml | xmlstarlet edit --update //pupil/@highestJump -x "string(../jump[not(@height <= following-sibling::jump/@height) and not(@height < preceding-sibling::jump/@height)]/@height)"
    

    ...gives...

    <?xml version="1.0"?>
    <sportsClass>
      <pupils>
        <pupil name="Adam" highestJump="4">
          <jump height="4"/>
          <jump height="1"/>
        </pupil>
        <pupil name="Berta" highestJump="7">
          <jump height="4"/>
          <jump height="7"/>
        </pupil>
        <pupil name="Caesar" highestJump="2">
          <jump height="1"/>
          <jump height="2"/>
        </pupil>
        <pupil name="Doris" highestJump="5">
          <jump height="5"/>
          <jump height="5"/>
        </pupil>
      </pupils>
    </sportsClass>