exml

package module
v3.1.1+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2014 License: BSD-2-Clause Imports: 5 Imported by: 3

README

exml Build Status

The exml package provides an intuitive event based XML parsing API which sits on top of a standard Go encoding/xml/Decoder, greatly simplifying the parsing code while retaining the raw speed and low memory overhead of the underlying stream engine, regardless of the size of the input. The module takes care of the complex tasks of maintaining contexts between event handlers allowing you to concentrate on dealing with the actual structure of the XML document.

Installation

HEAD:

go get github.com/lucsky/go-exml

v3.1.1:

go get gopkg.in/lucsky/go-exml.v3

The third version of exml provides compile time callback safety at the cost of an API CHANGE. Ad hoc $text events have been replaced by the specific OnText and OnTextOf event registration methods. Also new in v3.1: custom xml.Decoder support, type attribute readers, typed assignment/appending shortcuts (AssignT and AppendT) and full API documentation. v3.1.1 fixes a major bug causing the handlers stack to become inconsistent when ignoring tags.

v2:

go get gopkg.in/lucsky/go-exml.v2

The second version of exml has a better implementation based on a dynamic handler tree, allowing global events (see example below), having lower memory usage and also being faster.

v1:

go get gopkg.in/lucsky/go-exml.v1

Initial (and naive) implementation based on a flat list of absolute event paths.

Usage

The best way to illustrate how exml makes parsing very easy is to look at actual examples. Consider the following contrived sample document:

<?xml version="1.0"?>
<address-book name="homies">
    <contact>
        <first-name>Tim</first-name>
        <last-name>Cook</last-name>
        <address>Cupertino</address>
    </contact>
    <contact>
        <first-name>Steve</first-name>
        <last-name>Ballmer</last-name>
        <address>Redmond</address>
    </contact>
    <contact>
        <first-name>Mark</first-name>
        <last-name>Zuckerberg</last-name>
        <address>Menlo Park</address>
    </contact>
</address-book>

Here is a way to parse it into an array of contact objects using exml:

package main

import (
    "fmt"
    "os"

    "gopkg.in/lucsky/go-exml.v3"
)

type AddressBook struct {
    Name     string
    Contacts []*Contact
}

type Contact struct {
    FirstName string
    LastName  string
    Address   string
}

func main() {
    reader, _ := os.Open("example.xml")
    defer reader.Close()

    addressBook := AddressBook{}
    decoder := exml.NewDecoder(reader)

    decoder.On("address-book", func(attrs exml.Attrs) {
        addressBook.Name, _ = attrs.Get("name")

        decoder.On("contact", func(attrs exml.Attrs) {
            contact := &Contact{}
            addressBook.Contacts = append(addressBook.Contacts, contact)

            decoder.On("first-name", func(attrs exml.Attrs) {
                decoder.OnText(func(text exml.CharData) {
                    contact.FirstName = string(text)
                })
            })

            decoder.On("last-name", func(attrs exml.Attrs) {
                decoder.OnText(func(text exml.CharData) {
                    contact.LastName = string(text)
                })
            })

            decoder.On("address", func(attrs exml.Attrs) {
                decoder.OnText(func(text exml.CharData) {
                    contact.Address = string(text)
                })
            })
        })
    })

    decoder.Run()

    fmt.Printf("Address book: %s\n", addressBook.Name)
    for _, c := range addressBook.Contacts {
        fmt.Printf("- %s %s @ %s\n", c.FirstName, c.LastName, c.Address)
    }
}

To reduce the amount and depth of event callbacks that you have to write, exml allows you to register handlers on events paths:

decoder.OnTextOf("address-book/contact/first-name", func(text exml.CharData) {
    fmt.Println("First name: ", string(text))
})

// This works too:
decoder.On("address-book/contact", func(attrs exml.Attrs) {
    decoder.OnTextOf("last-name", func(text exml.CharData) {
        fmt.Println("Last name: ", string(text))
    })
})

// And this as well:
decoder.On("address-book/contact/address", func(attrs exml.Attrs) {
    decoder.OnText(func(text exml.CharData) {
        fmt.Println("Address: ", string(text))
    })
})

Finally, since using nodes text content to initialize struct fields is a pretty frequent task, exml provides shortcuts to make it shorter to write. Let's revisit our address book example and use this shortcut:

contact := &Contact{}
decoder.OnTextOf("first-name", exml.Assign(&contact.FirstName))
decoder.OnTextOf("last-name", exml.Assign(&contact.LastName))
decoder.OnTextOf("address", exml.Assign(&contact.Address))

Other assignment shortcuts (AssignBool, AssignFloat, AssignInt and AssignUInt) allow to pick typed values. Another type of shortcuts allow to accumulate text content from various nodes to a single slice:

info := []string{}
decoder.OnTextOf("first-name", decoder.Append(&info))
decoder.OnTextOf("last-name", decoder.Append(&info))
decoder.OnTextOf("address", decoder.Append(&info))

In the same way, there are typed versions of the appending shortcuts (AppendBool, AppendFloat, AppendInt and AppendUInt) which allow to append typed parsed values.

The second version (aka v2) of exml introduced global events which allow to register a top level handler that would be picked up at any level whenever a corresponding XML node is encountered. For example, this snippet would allow to print all text nodes regardless of their depth and parent tag:

decoder := exml.NewDecoder(reader)
decoder.OnText(func(text CharData) {
    fmt.Println(string(text))
})

API

The full API is visible at the exml gopkg.in page.

Benchmarks

The included benchmarks show that exml can be massively faster than standard unmarshaling and the difference would most likely be even greater for bigger inputs.

% go test -bench . -benchmem
OK: 23 passed
PASS
Benchmark_UnmarshalSimple      50000         57156 ns/op        6138 B/op        128 allocs/op
Benchmark_UnmarshalText       100000         22423 ns/op        3452 B/op         61 allocs/op
Benchmark_UnmarshalCDATA      100000         23460 ns/op        3483 B/op         61 allocs/op
Benchmark_UnmarshalMixed      100000         28807 ns/op        4034 B/op         67 allocs/op
Benchmark_DecodeSimple       5000000           388 ns/op          99 B/op          3 allocs/op
Benchmark_DecodeText         5000000           485 ns/op         114 B/op          3 allocs/op
Benchmark_DecodeCDATA        5000000           485 ns/op         114 B/op          3 allocs/op
Benchmark_DecodeMixed        5000000           487 ns/op         114 B/op          3 allocs/op
ok      github.com/lucsky/go-exml   11.194s

Contributors

  • Luc Heinrich (author)
  • Hubert Figuière

License

Code is under the BSD 2 Clause (NetBSD) license.

Documentation

Overview

The exml package provides an intuitive event based XML parsing API which sits on top of a standard Go xml.Decoder, greatly simplifying the parsing code while retaining the raw speed and low memory overhead of the underlying stream engine, regardless of the size of the input. The package takes care of the complex tasks of maintaining contexts between event handlers allowing you to concentrate on dealing with the actual structure of the XML document.

Example
reader := strings.NewReader(EXAMPLE)
decoder := NewDecoder(reader)

addressBook := AddressBook{}

decoder.On("address-book", func(attrs Attrs) {
	addressBook.Name, _ = attrs.Get("name")

	decoder.On("contact", func(attrs Attrs) {
		contact := &Contact{}
		addressBook.Contacts = append(addressBook.Contacts, contact)

		decoder.OnTextOf("first-name", func(text CharData) {
			contact.FirstName = string(text)
		})

		decoder.OnTextOf("last-name", func(text CharData) {
			contact.LastName = string(text)
		})

		decoder.OnTextOf("address", func(text CharData) {
			contact.Address = string(text)
		})
	})
})

decoder.Run()

fmt.Printf("Address book: %s\n", addressBook.Name)
for _, c := range addressBook.Contacts {
	fmt.Println("-", c.FirstName, c.LastName, "@", c.Address)
}
Output:
Address book: homies
- Tim Cook @ Cupertino
- Steve Ballmer @ Redmond
- Mark Zuckerberg @ Menlo Park

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Attrs

type Attrs []xml.Attr

func (Attrs) Get

func (a Attrs) Get(name string) (string, bool)

Get returns the value of the requested attribute and true when the attributes exists, or an empty string and false when it doesn't.

func (Attrs) GetBool

func (a Attrs) GetBool(name string, fallback bool) bool

GetBool returns the value of the requested attribute as a bool when it exists or the passed fallback value when it doesn't. The accepted values corresponds to the one accepted by the strconv.ParseBool() function.

func (Attrs) GetFloat

func (a Attrs) GetFloat(name string, bitsize int, fallback float64) float64

GetFloat returns the value of the requested attribute as a float when it exists or the passed fallback value when it doesn't. The additional parameters and the accepted values corresponds to the one accepted by the strconv.ParseFloat() function.

func (Attrs) GetInt

func (a Attrs) GetInt(name string, base int, bitsize int, fallback int64) int64

GetInt returns the value of the requested attribute as an int when it exists or the passed fallback value when it doesn't. The additional parameters and the accepted values corresponds to the one accepted by the strconv.ParseInt() function.

func (Attrs) GetString

func (a Attrs) GetString(name string, fallback string) string

GetString returns the value of the requested attribute when it exists or the passed fallback value when it doesn't.

func (Attrs) GetUInt

func (a Attrs) GetUInt(name string, base int, bitsize int, fallback uint64) uint64

GetUInt returns the value of the requested attribute as an uint when it exists or the passed fallback value when it doesn't. The additional parameters and the accepted values corresponds to the one accepted by the strconv.ParseUint() function.

type CharData

type CharData xml.CharData

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

A Decoder wraps an xml.Decoder and maintains the various states between the encountered XML nodes during parsing.

func NewCustomDecoder

func NewCustomDecoder(d *xml.Decoder) *Decoder

NewCustomDecoder creates a new exml parser reading from the passed xml.Decoder which is useful when you need to configure the underlying decoder, when you need to handle non-UTF8 xml documents for example.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder creates a new exml parser reading from r.

func (*Decoder) On

func (d *Decoder) On(path string, callback TagCallback)

On registers a handler for a single tag or for a path.

func (*Decoder) OnError

func (d *Decoder) OnError(handler ErrorCallback)

OnError registers a global error handler which will be called whenever the underlying xml.Decoder reports an error.

func (*Decoder) OnText

func (d *Decoder) OnText(callback TextCallback)

OnText registers a handler for the text content of the current tag.

func (*Decoder) OnTextOf

func (d *Decoder) OnTextOf(path string, callback TextCallback)

OnTextOf registers a handler for the text content of a single tag or for the text content at a certain path.

func (*Decoder) Run

func (d *Decoder) Run()

Run starts the parsing process.

type ErrorCallback

type ErrorCallback func(error)

type TagCallback

type TagCallback func(Attrs)

type TextCallback

type TextCallback func(CharData)

func Append

func Append(a *[]string) TextCallback

Append is a helper function which returns a text callback that appends the text content of the current tag to the passed strings slice pointer.

func AppendBool

func AppendBool(a *[]bool, fallback bool) TextCallback

AppendBool is a helper function which returns a text callback that appends the text content of the current tag parsed as a bool to the passed slice pointer. The accepted text values correspond to the one accepted by the strconv.ParseBool() function. The fallback parameter value is used when the parsing of the text content fails.

func AppendFloat

func AppendFloat(a *[]float64, bitsize int, fallback float64) TextCallback

AppendFloat is a helper function which returns a text callback that appends the text content of the current tag parsed as a float to the passed slice pointer. The accepted text values correspond to the one accepted by the strconv.ParseFloat() function. The fallback parameter value is used when the parsing of the text content fails.

func AppendInt

func AppendInt(a *[]int64, base int, bitsize int, fallback int64) TextCallback

AppendInt is a helper function which returns a text callback that appends the text content of the current tag parsed as an int to the passed slice pointer. The accepted text values correspond to the one accepted by the strconv.ParseInt() function. The fallback parameter value is used when the parsing of the text content fails.

func AppendUInt

func AppendUInt(a *[]uint64, base int, bitsize int, fallback uint64) TextCallback

AppendUInt is a helper function which returns a text callback that appends the text content of the current tag parsed as an uint to the passed slice pointer. The accepted text values correspond to the one accepted by the strconv.ParseUint() function. The fallback parameter value is used when the parsing of the text content fails.

func Assign

func Assign(v *string) TextCallback

Assign is a helper function which returns a text callback that assigns the text content of the current tag to the passed variable pointer.

func AssignBool

func AssignBool(v *bool, fallback bool) TextCallback

AssignBool is a helper function which returns a text callback that assigns the text content of the current tag parsed as a bool to the passed variable pointer. The accepted text values correspond to the one accepted by the strconv.ParseBool() function. The fallback parameter value is used when the parsing of the text content fails.

func AssignFloat

func AssignFloat(v *float64, bitsize int, fallback float64) TextCallback

AssignFloat is a helper function which returns a text callback that assigns the text content of the current tag parsed as a float to the passed variable pointer. The accepted text values correspond to the one accepted by the strconv.ParseFloat() function. The fallback parameter value is used when the parsing of the text content fails.

func AssignInt

func AssignInt(v *int64, base int, bitsize int, fallback int64) TextCallback

AssignInt is a helper function which returns a text callback that assigns the text content of the current tag parsed as an int to the passed variable pointer. The accepted text values correspond to the one accepted by the strconv.ParseInt() function. The fallback parameter value is used when the parsing of the text content fails.

func AssignUInt

func AssignUInt(v *uint64, base int, bitsize int, fallback uint64) TextCallback

AssignUInt is a helper function which returns a text callback that assigns the text content of the current tag parsed as an uint to the passed variable pointer. The accepted text values correspond to the one accepted by the strconv.ParseUint() function. The fallback parameter value is used when the parsing of the text content fails.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL