Vector
Vector

Reputation: 11703

How to access sibling element using Go with etree XML package?

Given an XML document such as this:

<MasterXML>
    <Processes>
        <Params>
            <ParamName>today</ParamName>
            <ParamType>1</ParamType>
            <ParamValue/>
        </Params>
       <Params>
            <ParamName>today</ParamName>
            <ParamType>2</ParamType>
            <ParamValue/>
        </Params>
       <Params>
            <ParamName>today</ParamName>
            <ParamType>3</ParamType>
            <ParamValue/>
        </Params>
    </Processes>
</MasterXML>

Using the beevik/etree for Go package, how can I access <ParamValue/> for each instance of <Params> in the document to populate it with a value when <ParamName> has a particular, specific value.

In the given example, I'd want to populate all <ParamValue/> nodes in all the <Params> nodes with 05/02/2024 when <ParamName> holds the value today.

This code only works on the first instance of the <Params> node in a document containing many instances where <ParamName>== today , although the loop should seemingly access every instance of <Params>:

    for _, elem1 := range doc.FindElements(".//Processes//Params//ParamName") {
    
            if elem1 == nil {
                log.Fatal("Check XPath)
            }
    
            s := elem1.Text()
    
            if s == "today" {
                elem2 := elem1.FindElement("//ParamValue")
                elem2.SetText("05/02/2024")
            }
    }

How can I do this? Why doesn't range doc.FindElements(".//Processes//Params//ParamName") find every instance of <Params>? Should I be using a different approach?

Upvotes: 0

Views: 127

Answers (1)

frankenapps
frankenapps

Reputation: 8221

Your current code does behave in the way you described, because by using the double slash in the XPath you always search the XML tree recursively, meaning that you always find the first occurence from the top.

This also means that FindElements has always worked the way you thought (which can be validated easily by uncommenting the println statement below, or by running the code through your debugger).

In order to actually produce the desired behaviour, your XPath statement should be adjusted:

  • .. moves up one level in the hierarchy (remember that elem1 is already at the ParamName level, so by moving up one level you are now at the Params level
  • a single slash (/) finds the next element at the current level
  • in our case this has to be ParamValue

therefore the correct XPath would be ../ParamValue".

Here is the fixed sample:

package main

import (
    "log"
    "os"
    "strings"

    "github.com/beevik/etree"
)

func main() {
    doc := etree.NewDocument()
    if err := doc.ReadFromFile("master.xml"); err != nil {
        panic(err)
    }

    for _, elem1 := range doc.FindElements(".//Processes//Params//ParamName") {

        if elem1 == nil {
            log.Fatal("Check XPath")
        }

        s := elem1.Text()
        //println(s)

        if strings.TrimSpace(s) == "today" {
            elem2 := elem1.FindElement("../ParamValue")
            elem2.SetText("05/02/2024")
        }
    }

    doc.WriteTo(os.Stdout)
}

This will require master.xml in the same directory as your .go file.

I went with this:

<MasterXML>
    <Processes>
        <Params>
            <ParamName> today </ParamName>
            <ParamType>1</ParamType>
            <ParamValue/>
        </Params>
       <Params>
            <ParamName> tomorrow </ParamName>
            <ParamType>2</ParamType>
            <ParamValue/>
        </Params>
       <Params>
            <ParamName> today </ParamName>
            <ParamType>3</ParamType>
            <ParamValue/>
        </Params>
    </Processes>
</MasterXML>

which produces the desired output:

<MasterXML>
    <Processes>
        <Params>
            <ParamName> today </ParamName>
            <ParamType>1</ParamType>
            <ParamValue>05/02/2024</ParamValue>
        </Params>
       <Params>
            <ParamName> tomorrow </ParamName>
            <ParamType>2</ParamType>
            <ParamValue/>
        </Params>
       <Params>
            <ParamName> today </ParamName>
            <ParamType>3</ParamType>
            <ParamValue>05/02/2024</ParamValue>
        </Params>
    </Processes>
</MasterXML>

Upvotes: 1

Related Questions