如何在Go语言中基于XML模板动态生成并修改XML文档?
在Go中动态解析并修改SOAP XML模板的实现方案
你已经用xml.Decoder.Token完成了解析环节,但要实现动态替换指定元素值并生成新XML,核心是跟踪当前元素的嵌套路径,在遇到目标元素的文本内容时替换值,最后用xml.Encoder将处理后的Token重新编码输出。下面是完整的实现思路和代码:
核心思路
- 跟踪元素路径:用切片维护当前嵌套的元素栈,判断文本内容属于哪个元素
- 替换逻辑匹配:定义参数映射存储客户端传入的键值对(键为目标元素名,值为替换内容)
- Token流处理:遍历每个Token,根据类型做不同处理:
StartElement:加入路径栈并直接写入输出EndElement:移除路径栈最后一个元素并直接写入输出CharData:判断当前元素是否在替换映射中,是则替换值,否则保留原内容- 其他Token(注释、指令等)直接透传输出
完整代码实现
package main import ( "bytes" "encoding/xml" "fmt" "strings" "testing" ) // 客户端传入的替换参数,键对应wsGetInfosLink下的子元素名 type ReplaceParams map[string]string func modifySOAPTemplate(template []byte, params ReplaceParams) ([]byte, error) { var output bytes.Buffer encoder := xml.NewEncoder(&output) encoder.Indent("", " ") // 保持原模板的缩进风格,可根据需求移除 decoder := xml.NewDecoder(bytes.NewReader(template)) var elementPath []string // 跟踪当前元素的嵌套路径 for { token, err := decoder.Token() if err != nil { if err.Error() == "EOF" { break } return nil, err } switch tt := token.(type) { case xml.StartElement: // 将当前元素加入路径栈 elementPath = append(elementPath, tt.Name.Local) // 写入StartElement到输出 if err := encoder.EncodeToken(tt); err != nil { return nil, err } case xml.EndElement: // 从路径栈移除当前元素 if len(elementPath) > 0 { elementPath = elementPath[:len(elementPath)-1] } // 写入EndElement到输出 if err := encoder.EncodeToken(tt); err != nil { return nil, err } case xml.CharData: trimmedContent := strings.TrimSpace(string(tt)) // 保留原模板的空白缩进(换行、空格) if len(trimmedContent) == 0 { if err := encoder.EncodeToken(tt); err != nil { return nil, err } continue } // 判断当前元素是否需要替换值 if len(elementPath) == 0 { if err := encoder.EncodeToken(tt); err != nil { return nil, err } continue } currentElement := elementPath[len(elementPath)-1] if replacement, ok := params[currentElement]; ok { // 替换为客户端传入的值 newCharData := xml.CharData(replacement) if err := encoder.EncodeToken(newCharData); err != nil { return nil, err } } else { // 无替换需求,保留原内容 if err := encoder.EncodeToken(tt); err != nil { return nil, err } } default: // 透传其他类型Token(如注释、XML指令等) if err := encoder.EncodeToken(token); err != nil { return nil, err } } } // 确保所有内容写入输出缓冲区 if err := encoder.Flush(); err != nil { return nil, err } return output.Bytes(), nil } func TestModifySOAPTemplate(t *testing.T) { // 你的SOAP模板XML xmlTemplate := []byte(`<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.receive.appservice.jcms.hanweb.com"> <soapenv:Header/> <soapenv:Body> <ser:wsGetInfosLink soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <nCataId xsi:type="xsd:int">3</nCataId> <bRef xsi:type="xsd:int">3</bRef> <nStart xsi:type="xsd:int">3</nStart> <nEnd xsi:type="xsd:int">3</nEnd> <bAsc xsi:type="xsd:int">3</bAsc> <strStartCTime xsi:type="xsd:string">gero et</strStartCTime> <strEndCTime xsi:type="xsd:string">sonoras imperio</strEndCTime> <strLoginId xsi:type="xsd:string">quae divum incedo</strLoginId> <strPwd xsi:type="xsd:string">verrantque per auras</strPwd> <strKey xsi:type="xsd:string">per auras</strKey> </ser:wsGetInfosLink> </soapenv:Body> </soapenv:Envelope>`) // 客户端传入的替换参数 params := ReplaceParams{ "nCataId": "10", "bRef": "0", "nStart": "0", "nEnd": "20", "bAsc": "1", "strStartCTime": "2024-01-01 00:00:00", "strEndCTime": "2024-12-31 23:59:59", "strLoginId": "test_user", "strPwd": "test_pwd", "strKey": "test_key", } modifiedXML, err := modifySOAPTemplate(xmlTemplate, params) if err != nil { t.Fatal(err) } fmt.Println(string(modifiedXML)) }
关键细节说明
- 路径跟踪:通过
elementPath切片准确识别当前文本所属的元素,支持多层嵌套的XML结构 - 空白处理:保留原模板的缩进和换行,避免破坏SOAP格式的可读性
- 命名空间兼容:如果需要匹配带命名空间的元素,只需将
tt.Name.Space + ":" + tt.Name.Local作为路径键即可 - Encoder格式化:
encoder.Indent用于保持和原模板一致的缩进风格,不需要格式化时可直接删除该行
内容的提问来源于stack exchange,提问作者Fan
相关产品推荐
相关产品推荐

