func (d *decoder) mustgetc() (b byte, ok bool) {
	if b, ok = d.getc(); !ok {
		if d.err == io.EOF {
			d.err = d.syntaxError("unexpected EOF")
		}
	}
	return
}
Must read a single byte.
If there is no byte to read,
set d.err to SyntaxError("unexpected EOF")
and return ok==false
package xml
import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"strconv"
)
type Tag struct {
	StartName string
	EndName string
	Attr []Attr
	Empty bool
	CData []byte
	Text string
	NoIndent bool
	Tags []*Tag
}
func (t Tag) AttrValue(name string) (value string, exists bool) {
	for _, a := range t.Attr {
		if a.Name == name {
			return a.Value, true
		}
	}
	return "", false
}
func (t *Tag) SetAttrValue(name, value string) {
	for i, a := range t.Attr {
		if a.Name == name {
			if value == "" {
				t.Attr = append(t.Attr[:i], t.Attr[i+1:]...)
			} else {
				a.Value = value
			}
			return
		}
	}
	if value == "" {
		return
	}
	t.Attr = append(t.Attr, Attr{Name: name, Value: value})
}
func NewRoot(items ...*Tag) *Tag {
	return &Tag{
		StartName: "roblox",
		Attr: []Attr{
			Attr{
				Name:  "xmlns:xmime",
				Value: "http://www.w3.org/2005/05/xmlmime",
			},
			Attr{
				Name:  "xmlns:xsi",
				Value: "http://www.w3.org/2001/XMLSchema-instance",
			},
			Attr{
				Name:  "xsi:noNamespaceSchemaLocation",
				Value: "http://www.roblox.com/roblox.xsd",
			},
			Attr{
				Name:  "version",
				Value: "4",
			},
		},
		Tags: items,
	}
}
func NewItem(class, referent string, properties ...*Tag) *Tag {
	return &Tag{
		StartName: "Item",
		Attr: []Attr{
			Attr{Name: "class", Value: class},
			Attr{Name: "referent", Value: referent},
		},
		Tags: []*Tag{
			&Tag{
				StartName: "Properties",
				Tags:      properties,
			},
		},
	}
}
func NewProp(valueType, propName, value string) *Tag {
	return &Tag{
		StartName: valueType,
		Attr: []Attr{
			Attr{Name: "name", Value: propName},
		},
		Text:     value,
		NoIndent: true,
	}
}
type Attr struct {
	Name  string
	Value string
}
type Document struct {
	Prefix string
	Indent string
	Suffix string
	ExcludeRoot bool
	Root *Tag
	Warnings []error
}
type SyntaxError struct {
	Msg  string
	Line int
}
func (e *SyntaxError) Error() string {
	return "XML syntax error on line " + strconv.Itoa(e.Line) + ": " + e.Msg
}
type decoder struct {
	r        io.ByteReader
	buf      bytes.Buffer
	nextByte []byte
	doc      *Document
	n        int64
	err      error
	line     int
}
func (d *decoder) syntaxError(msg string) error {
	return &SyntaxError{Msg: msg, Line: d.line}
}
func (d *decoder) ignoreStartTag(err error) int {
	d.doc.Warnings = append(d.doc.Warnings, err)
	for {
		b, ok := d.mustgetc()
		if !ok {
			return -1
		}
		if b == '>' {
			break
		}
	}
	return 0
}
func (d *decoder) decodeComment() int {
	for {
		if b, ok := d.mustgetc(); !ok {
			return -1
		} else if b == '-' && d.match("->") {
			break
		}
	}
	return 2
}
func (d *decoder) decodeStartTag(tag *Tag) int {
	b, ok := d.getc()
	if !ok {
		return -1
	}
	if b != '<' {
		d.err = d.syntaxError("expected start tag")
		return -1
	}
	if b, ok = d.mustgetc(); !ok {
		return -1
	}
	if b == '/' {
		d.err = d.syntaxError("unexpected end tag")
		return -1
	}
	if b == '!' && d.match("--") {
		return d.decodeComment()
	}
	d.ungetc(b)
	if tag.StartName, ok = d.name(nameTag); !ok {
		return d.ignoreStartTag(d.syntaxError("expected element name after <"))
	}
	tag.Attr = make([]Attr, 0, 4)
	for {
		d.space()
		if b, ok = d.mustgetc(); !ok {
			return -1
		}
		if b == '/' {
			tag.Empty = true
			if b, ok = d.mustgetc(); !ok {
				return -1
			}
			if b != '>' {
				return d.ignoreStartTag(d.syntaxError("expected /> in element"))
			}
			break
		}
		if b == '>' {
			break
		}
		d.ungetc(b)
		n := len(tag.Attr)
		if n >= cap(tag.Attr) {
			nattr := make([]Attr, n, 2*cap(tag.Attr))
			copy(nattr, tag.Attr)
			tag.Attr = nattr
		}
		tag.Attr = tag.Attr[0 : n+1]
		a := &tag.Attr[n]
		if a.Name, ok = d.name(nameAttr); !ok {
			return d.ignoreStartTag(d.syntaxError("expected attribute name in element"))
		}
		d.space()
		if b, ok = d.mustgetc(); !ok {
			return -1
		}
		if b != '=' {
			return d.ignoreStartTag(d.syntaxError("attribute name without = in element"))
		} else {
			d.space()
			data := d.attrval()
			if data == nil {
				return -1
			}
			a.Value = string(data)
		}
	}
	return 1
}
func (d *decoder) decodeCData(tag *Tag) bool {
	tag.CData = nil
	const opener = "<![CDATA["
	for i := 0; i < len(opener); i++ {
		if b, ok := d.getc(); !ok {
			return false
		} else if b != opener[i] {
			d.ungetc(b)
			for j := i - 1; j >= 0; j-- {
				d.ungetc(opener[j])
			}
			return true
		}
	}
	tag.CData = d.text(-1, true)
	if tag.CData == nil {
		return false
	}
	return true
}
func (d *decoder) decodeText(tag *Tag) bool {
	text := d.text(-1, false)
	if text == nil {
		tag.Text = ""
		return false
	}
	tag.Text = string(text)
	return true
}
func (d *decoder) decodeEndTag(tag *Tag) bool {
	b, ok := d.getc()
	if !ok {
		return false
	}
	if b != '<' {
		d.err = d.syntaxError("expected start tag")
		return false
	}
	if b, ok = d.mustgetc(); !ok {
		return false
	}
	if b != '/' {
		d.err = d.syntaxError("expected end tag")
		return false
	}
	if tag.EndName, ok = d.name(nameTag); !ok {
		if d.err == nil {
			d.err = d.syntaxError("expected element name after </")
		}
		return false
	}
	d.space()
	if b, ok = d.mustgetc(); !ok {
		return false
	}
	if b != '>' {
		d.err = d.syntaxError("invalid characters between </" + tag.EndName + " and >")
		return false
	}
	return true
}
func (d *decoder) decodeTag(root bool) (tag *Tag, err error) {
	if d.err != nil {
		return nil, d.err
	}
	tag = new(Tag)
	noindent := false
	nocontent := true
	if root {
		p := d.readSpace()
		if len(p) > 0 {
			d.doc.Prefix = string(p)
		}
	}
	startTagState := d.decodeStartTag(tag)
	if startTagState < 0 {
		return nil, d.err
	}
	if startTagState == 2 {
		return nil, nil
	}
	if root {
		if tag.StartName != "roblox" {
			d.err = d.syntaxError("no roblox tag")
			return nil, d.err
		}
		if v, ok := tag.AttrValue("version"); !ok {
			d.err = d.syntaxError("version attribute not specified")
			return nil, d.err
		} else {
			n, err := strconv.ParseInt(v, 10, 32)
			if err != nil {
				d.err = d.syntaxError("no version number")
				return nil, d.err
			}
			if n < 4 {
				d.err = d.syntaxError("schemaVersionLoading<4")
				return nil, d.err
			}
		}
	}
	if tag.Empty {
		if startTagState == 0 {
			return nil, nil
		}
		return
	}
	if !d.decodeCData(tag) {
		return nil, d.err
	}
	if len(tag.CData) > 0 {
		nocontent = false
	}
	if root {
		ind := d.readSpace()
		if i := bytes.IndexByte(ind, '\n'); i > -1 {
			if !bytes.HasPrefix(ind[i+1:], []byte(d.doc.Prefix)) {
				d.doc.Prefix = ""
			} else {
				d.doc.Indent = string(ind[i+1+len(d.doc.Prefix):])
			}
		}
	} else {
		if d.doc.Prefix != "" || d.doc.Indent != "" {
			if len(d.readSpace()) == 0 {
				noindent = true
			}
		} else {
			d.space()
		}
	}
	if !d.decodeText(tag) {
		return nil, d.err
	}
	if len(tag.Text) > 0 {
		nocontent = false
	}
	for {
		d.space()
		b, ok := d.getc()
		if !ok {
			return nil, d.err
		}
		if b != '<' {
			d.err = d.syntaxError("expected tag")
			return nil, d.err
		}
		if b, ok = d.mustgetc(); !ok {
			return nil, d.err
		}
		if b == '/' {
			d.ungetc('/')
			d.ungetc('<')
			if !d.decodeEndTag(tag) {
				return nil, d.err
			}
			break
		}
		d.ungetc(b)
		d.ungetc('<')
		subtag, err := d.decodeTag(false)
		if err != nil {
			return nil, err
		}
		if subtag != nil {
			tag.Tags = append(tag.Tags, subtag)
		}
	}
	if len(tag.Tags) > 0 {
		nocontent = false
	}
	if !nocontent {
		tag.NoIndent = noindent
	}
	if startTagState == 0 {
		return nil, nil
	}
	return tag, nil
}
func (d *decoder) attrval() []byte {
	b, ok := d.mustgetc()
	if !ok {
		return nil
	}
	if b == '"' {
		return d.text(int(b), false)
	}
	d.err = d.syntaxError("unquoted or missing attribute value in element")
	return nil
}
func (d *decoder) readSpace() []byte {
	d.buf.Reset()
	for {
		b, ok := d.getc()
		if !ok {
			return d.buf.Bytes()
		}
		if !isSpace(b) {
			d.ungetc(b)
			return d.buf.Bytes()
		}
		d.buf.WriteByte(b)
	}
	return d.buf.Bytes()
}
func (d *decoder) space() {
	for {
		b, ok := d.getc()
		if !ok {
			return
		}
		if !isSpace(b) {
			d.ungetc(b)
			return
		}
	}
}
func isSpace(b byte) bool {
	switch b {
	case ' ', '\r', '\n', '\t', '\f':
		return true
	default:
		return false
	}
}
func (d *decoder) getc() (b byte, ok bool) {
	if d.err != nil {
		return 0, false
	}
	if len(d.nextByte) > 0 {
		b, d.nextByte = d.nextByte[len(d.nextByte)-1], d.nextByte[:len(d.nextByte)-1]
	} else {
		b, d.err = d.r.ReadByte()
		if d.err != nil {
			return 0, false
		}
		d.n++
	}
	if b == '\n' {
		d.line++
	}
	return b, true
}