Reputation: 81
I have a xml template like this:
<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>
I need generate same format xml document , change wsGetInfosLink
children element value to some value receive from client.Because xml template have many formats.I must parse xml and generate in dynamic way,How can i achieve it in golang?
Now I can parse xml template in dynamic way use xml.Encoder.Token
,but I don't konw how to motify value and generate new xml document.
func TestOperation_DoRequest(t *testing.T) {
xmlStr := ""
var xmlTemplate bytes.Buffer
xmlTemplate.Write([]byte(xmlStr))
decoder := xml.NewDecoder(&xmlTemplate)
root, err := decoder.Token()
if err != nil {
t.Fatal(err)
}
for t := root; err == nil; t, err = decoder.Token() {
switch t.(type) {
case xml.StartElement:
token := t.(xml.StartElement)
fmt.Println("len len len", len(token.Attr))
if len(token.Attr) > 0 {
for _, attr := range token.Attr {
attrName := attr.Name.Local
attrSpace := attr.Name.Space
attrValue := attr.Value
fmt.Printf("attrSpace:%s attrName:%s attrValue:%s\n", attrSpace, attrName, attrValue)
}
}
name := token.Name.Local
space := token.Name.Space
fmt.Printf("Token space:%s name:%s\n", space, name)
break
case xml.EndElement:
token := t.(xml.EndElement)
//element := t.(xml.EndElement)
name := token.Name.Local
fmt.Printf("end element name:%s\n", name)
break
case xml.CharData:
token := t.(xml.CharData)
content := string([]byte(token))
fmt.Printf("---value %s\n", content)
break
default:
break
}
}
}
Upvotes: 1
Views: 1647
Reputation: 38233
The usual approach to parse some XML, modify its values, and then generate new XML with the modifications is to define a type that matches the XML's structure, then, using xml.Unmarshal
you decode the XML into a value of that type, modify that value, and then use xml.Marshal
to encode that modified value back into XML.
var data = []byte(`<person>
<name>John Doe</name>
</person>`)
type Person struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
}
func main() {
person := new(Person)
// unmashal xml data into person
if err := xml.Unmarshal(data, person); err != nil {
panic(err)
}
// modify person
person.Name = "Jane Doe"
// marshal modified person back into xml
b, err := xml.Marshal(person)
if err != nil {
panic(err)
}
fmt.Pritnln(string(b))
}
Now with SOAP's XML and its prefixes this becomes a bit more complicated.
As far as I know the encoding/xml
package does not provide direct support for XML prefixes and the discussion on issue #9519 seems to corroborate that. I recommend you read through that and possibly reconsider the tool you're gonna use to solve the problem.
That said, in the already mentioned discussion there is a suggestion to use two separate types, one for unmarshaling and one for marshaling the XML data, that way you may be able to achieve your goal, if you like that idea the examples there are fairly simple and you should be able to use them to guide you to the full implementation.
Besides the two-type solution one other approach comes to mind but, while it works for this small piece of SOAP data, I do not have any real experience with SOAP to be able to say whether this is gonna be enough to work with an actual SOAP API.
The idea here is to utilize the xml.Marshaler
and xml.MarshalerAttr
interfaces. Every XML element that has a prefix, e.g. <soapenv:Envelope>...
needs a matching type that implements the Marshaler interface and every attribute that has a prefix, e.g. soapenv:encodingStyle="...
needs to be declared as a struct field with a type that implements the MarshalerAttr interface.
Example element with prefix:
type Envelope struct {
XMLName xml.Name `xml:"Envelope"`
// ...
}
// MarhsalXML implements the xml.Marshaler interface.
func (env *Envelope) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
start.Name.Local = "soapenv:" + start.Name.Local
return e.EncodeElement(*env, start)
}
Example attribute with prefix:
type WSGetInfosLink struct {
XMLName xml.Name `xml:"wsGetInfosLink"`
EncodingStyle SoapenvAttr `xml:"encodingStyle,attr"`
// ...
}
type SoapenvAttr string
// MarshalXMLAttr implements the xml.MarshalerAttr interface.
func (a SoapenvAttr) MarshalXMLAttr(n xml.Name) (xml.Attr, error) {
return xml.Attr{
Name: xml.Name{Local: "soapenv:" + n.Local},
Value: string(a),
}, nil
}
You can find a more complete example here: https://play.golang.org/p/4wba60hBv7I
Upvotes: 1