Как получить доступ к родственному элементу с помощью Go с XML-пакетом etree?

Учитывая XML-документ, такой как этот:

<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>

Используя пакет beevik/etree для Go, как я могу получить доступ к <ParamValue/> для каждого экземпляра <Params> в документе, чтобы заполнить его значением, когда <ParamName> имеет определенное, конкретное значение.

В данном примере я бы хотел заполнить все узлы <ParamValue/> во всех узлах <Params> датой 02.05.2024, когда <ParamName> имеет значение сегодня.

Этот код работает только с первым экземпляром узла <Params> в документе, содержащем множество экземпляров, где <ParamName>== Today , хотя цикл, по-видимому, должен обращаться к каждому экземпляру <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")
            }
    }

Как я могу это сделать? Почему range doc.FindElements(".//Processes//Params//ParamName") не находит все экземпляры <Params>? Должен ли я использовать другой подход?

🤔 А знаете ли вы, что...
XML позволяет создавать собственные схемы данных и правила валидации, что делает его гибким для различных потребностей.


80
1

Ответ:

Решено

Ваш текущий код ведет себя так, как вы описали, потому что, используя двойную косую черту в XPath, вы всегда выполняете поиск по дереву XML рекурсивно, то есть всегда находите первое вхождение сверху.

Это также означает, что FindElements всегда работал так, как вы думали (что можно легко проверить, раскомментировав оператор println ниже или запустив код через отладчик).

Чтобы действительно добиться желаемого поведения, ваш оператор XPath должен быть скорректирован:

  • .. перемещается на один уровень вверх в иерархии (помните, что elem1 уже находится на уровне ParamName, поэтому, поднявшись на один уровень вверх, вы теперь находитесь на уровне Params
  • одинарная косая черта (/) находит следующий элемент на текущем уровне
  • в нашем случае так и должно быть ParamValue

поэтому правильный XPath будет ../ParamValue".

Вот фиксированный образец:

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)
}

Для этого потребуется master.xml в том же каталоге, что и ваш .go файл.

Я пошел с этим:

<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>

который дает желаемый результат:

<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>