Skip to content

Instantly share code, notes, and snippets.

@mattn
Created August 5, 2011 11:28
Show Gist options
  • Save mattn/1127348 to your computer and use it in GitHub Desktop.
Save mattn/1127348 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"encoding/base64"
"fmt"
"http"
"os"
"reflect"
"strconv"
"time"
"xml"
)
type Array []interface{}
type Struct map[string]interface{}
var xmlSpecial = map[byte]string{
'<': "&lt;",
'>': "&gt;",
'"': "&quot;",
'\'': "&apos;",
'&': "&amp;",
}
func xmlEscape(s string) string {
var b bytes.Buffer
for i := 0; i < len(s); i++ {
c := s[i]
if s, ok := xmlSpecial[c]; ok {
b.WriteString(s)
} else {
b.WriteByte(c)
}
}
return b.String()
}
type valueNode struct {
Type string `xml:"attr"`
Body string `xml:"chardata"`
}
func next(p *xml.Parser) (xml.Name, interface{}, os.Error) {
se, err := nextStart(p)
if err != nil {
return xml.Name{}, nil, err
}
var nv interface{}
var vn valueNode
switch se.Name.Local {
case "string":
if err = p.Unmarshal(&vn, &se); err != nil {
return xml.Name{}, nil, err
}
return xml.Name{}, vn.Body, nil
case "int", "i4":
if err = p.Unmarshal(&vn, &se); err != nil {
return xml.Name{}, nil, err
}
i, err := strconv.Atoi64(vn.Body)
return xml.Name{}, i, err
case "double":
if err = p.Unmarshal(&vn, &se); err != nil {
return xml.Name{}, nil, err
}
f, err := strconv.Atof64(vn.Body)
return xml.Name{}, f, err
case "dateTime.iso8601":
if err = p.Unmarshal(&vn, &se); err != nil {
return xml.Name{}, nil, err
}
t, err := time.Parse("20060102T15:04:05", vn.Body)
if err != nil {
t, err = time.Parse("2006-01-02T15:04:05-07:00", vn.Body)
if err != nil {
t, err = time.Parse("2006-01-02T15:04:05", vn.Body)
}
}
return xml.Name{}, t, err
case "base64":
if err = p.Unmarshal(&vn, &se); err != nil {
return xml.Name{}, nil, err
}
if b, err := base64.StdEncoding.DecodeString(vn.Body); err != nil {
return xml.Name{}, nil, err
} else {
return xml.Name{}, b, nil
}
case "member":
nextStart(p)
return next(p)
case "value":
nextStart(p)
return next(p)
case "name":
nextStart(p)
return next(p)
case "struct":
st := Struct{}
se, err = nextStart(p)
for err == nil && se.Name.Local == "member" {
// name
se, err = nextStart(p)
if err != nil {
break
}
if err = p.Unmarshal(&vn, &se); err != nil {
return xml.Name{}, nil, err
}
name := vn.Body
se, err = nextStart(p)
if err != nil {
break
}
// value
_, value, err := next(p)
if err != nil {
break
}
se, err = nextStart(p)
if err != nil {
break
}
st[name] = value
}
return xml.Name{}, st, nil
case "array":
var ar Array
nextStart(p) // data
nextStart(p) // top of value
for {
_, value, err := next(p)
if err != nil {
break
}
ar = append(ar, value)
}
return xml.Name{}, ar, nil
}
if err = p.Unmarshal(nv, &se); err != nil {
return xml.Name{}, nil, err
}
return se.Name, nv, err
}
func nextStart(p *xml.Parser) (xml.StartElement, os.Error) {
for {
t, err := p.Token()
if err != nil {
return xml.StartElement{}, err
}
switch t := t.(type) {
case xml.StartElement:
return t, nil
}
}
panic("unreachable")
}
func to_xml(v interface{}, typ bool) (s string) {
r := reflect.ValueOf(v)
t := r.Type()
k := t.Kind()
if b, ok := v.([]byte); ok {
return base64.StdEncoding.EncodeToString(b)
}
switch k {
case reflect.Invalid:
panic("unsupported type")
case reflect.Bool:
return fmt.Sprintf("<boolean>%v</boolean>", v)
case reflect.Int,
reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint,
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if typ {
return fmt.Sprintf("<int>%v</int>", v)
}
return fmt.Sprintf("%v", v)
case reflect.Uintptr:
panic("unsupported type")
case reflect.Float32, reflect.Float64:
if typ {
return fmt.Sprintf("<double>%v</double>", v)
}
return fmt.Sprintf("%v", v)
case reflect.Complex64, reflect.Complex128:
panic("unsupported type")
case reflect.Array:
s = "<array><data>"
for n := 0; n < r.Len(); n++ {
s += "<value>"
s += to_xml(r.Index(n).Interface(), typ)
s += "</value>"
}
s += "</data></array>"
return s
case reflect.Chan:
panic("unsupported type")
case reflect.Func:
panic("unsupported type")
case reflect.Interface:
return to_xml(r.Elem(), typ)
case reflect.Map:
s = "<struct>"
for _, key := range r.MapKeys() {
s += "<member>"
s += "<name>" + xmlEscape(key.Interface().(string)) + "</name>"
s += "<value>" + to_xml(r.MapIndex(key).Interface(), typ) + "</value>"
s += "</member>"
}
return s
case reflect.Ptr:
panic("unsupported type")
case reflect.Slice:
panic("unsupported type")
case reflect.String:
if typ {
return fmt.Sprintf("<string>%v</string>", xmlEscape(v.(string)))
}
return xmlEscape(v.(string))
case reflect.Struct:
s = "<struct>"
for n := 0; n < r.NumField(); n++ {
s += "<member>"
s += "<name>" + t.Field(n).Name + "</name>"
s += "<value>" + to_xml(r.FieldByIndex([]int{n}).Interface(), true) + "</value>"
s += "</member>"
}
return s
case reflect.UnsafePointer:
return to_xml(r.Elem(), typ)
}
return
}
func Call(url, name string, args ...interface{}) (v interface{}, e os.Error) {
s := "<methodCall>"
s += "<methodName>" + xmlEscape(name) + "</methodName>"
s += "<params>"
for _, arg := range args {
s += "<param><value>"
s += to_xml(arg, false)
s += "</value></param>"
}
s += "</params></methodCall>"
bs := bytes.NewBuffer([]byte(s))
r, e := http.Post(url, "text/xml", bs)
if e != nil {
return nil, e
}
defer r.Body.Close()
p := xml.NewParser(r.Body)
nextStart(p) // methodResponse
nextStart(p) // params
nextStart(p) // param
nextStart(p) // value
_, v, e = next(p)
return v, e
}
func main() {
res, e := Call(
"http://your-blog.example.com/xmlrpc.php",
"metaWeblog.getRecentPosts",
"blog-id",
"user-id",
"password",
10)
if e != nil {
panic(e.String())
}
for _, p := range res.(Array) {
for k, v := range p.(Struct) {
fmt.Printf("%s=%v\n", k, v)
}
fmt.Println()
}
}
@mattn
Copy link
Author

mattn commented Aug 10, 2011

@mattn foo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment