xm

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 16, 2022 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const (
	Block  = TagKind(iota) // block level indentation (default)
	Inline                 // inline tag
)

Accepted values for TagKind:

View Source
const (
	IndentTabs    = IndentStyle(0)  // each block level starts on a new line, indented with one '\t' per level
	Indent2Spaces = IndentStyle(2)  // each block level starts on a new line, indented with 2 spaces per level
	Indent4Spaces = IndentStyle(4)  // each block level starts on a new line, indented with 4 spaces per level
	IndentNone    = IndentStyle(-1) // no new lines, no indentation
)
View Source
const AttrQuotationMark = '\''
View Source
const (
	PreserveInlineWhitespace = PrinterFlags(1 << iota)
)

Variables

View Source
var ErrEmptyAttribute = errors.New("xml: empty sttribute")

Functions

func Attr

func Attr[T any](key string, val T) func(AttrWriter)

func Attrs

func Attrs[M ~map[string]T, T any](m M) func(AttrWriter)

Attrs takes a generic map[string]T and turns it into a functor for writing attributes that can be passed to TagWriter.

func ScrambleFunc

func ScrambleFunc(s string, f func(byte) bool) []byte

ScrambleFunc is a generic string scrambler that replaces codeunits matched by f with xml character references.

func Tag

func Tag(name string, args ...any) func(TagWriter)

Types

type AttrMarshaler

type AttrMarshaler interface {
	MarshalXAttr() (RawAttr, bool)
}

type AttrPrinter

type AttrPrinter interface {
	// Attr adds key='val' pairs to a previously opened tag. Notice that Attr works
	// only immediately after opening the tag, once the opening tag is finalized,
	// calling Attr will panic. See description of OTag() for more details.
	Attr(key string, val RawAttr)
}

AttrPrinter is an interface for writing XML tag attributes.

type AttrWriter

type AttrWriter interface {
	// Attr writes key='val' pair into tag's attributes. Accepted val types are:
	//
	//   - RawAttr, a special version of []byte that is written as-is:
	//   - string, gets scrambled with the ScrambleAttr() function
	//   - nils, or pointer types resolving to nil empty attribute
	//   - types supporting AttrMarshaler interface are resolved as t.MarshalXAttr()
	//   - types supporting encoding.TextMarshaler are marshaled into text, then scrambled with ScrambleAttr()
	//   - boolean types are resolved to 'true' or 'false'
	//   - integer types are resolved to their decimal representation
	//   - floating point types are converted to strings with strconv.FormatFloat using fmt='g' and prec=-1
	//   - all other types will panic with ErrUnsupportedType
	Attr(string, any)

	// OptAttr works similar to Attr, but it will skip writing the whole key='val'
	// pair if val is empty:
	//
	//   - RawAttr is considered empty if its length is zero
	//   - strings are considered empty if their length is zero
	//   - nils and pointer types resolving to nil are considered empty
	//   - types that support AttrMarshaler are considered empty if the bool part returned by t.MarshalXAttr() is false
	//   - types that support encoding.TextMarshaler are considered empty if v.MarshalText() returns empty byte slice
	//   - floating/integer/boolean types are never considered empty
	//   - all other types will panic with ErrUnsupportedType
	OptAttr(string, any)

	// Attrs writes map[string]any as attributes. The keys in the map are treated as
	// attribute keys. These are written raw without any scrambling or validation,
	// make sure you don't pass maps with keys that don't conform to xml attribute
	// key syntax.
	Attrs(map[string]any)
}

AttrWriter is an interface for writing XML attributes.

type ContMarshaler

type ContMarshaler interface {
	MarshalXCont(w Printer)
}

type ContPrinter

type ContPrinter interface {
	// Content places non-tagged content into the output. For indentation purposes,
	// this content is considered as inline. Typically, this would be some text
	// between or inside other tags, but you can also use this for emiting commends,
	// cdata, and processing instructions.
	Content(RawCont)

	// Linebreak can be used for inserting '\n' linebreaks after tags that have
	// line break semantics, like <br/> tags in html.
	Linebreak()

	StopInline()
}

ContPrinter is an interface for writing content between XML tags.

type ContWriter

type ContWriter interface {
	Content(...any)
}

ContWriter is an interface for writing content between tags.

type DeclPrinter

type DeclPrinter interface {
	// BOM writes UTF-8 byte order mask.
	BOM()

	// XmlDecl writes XmlDecl at the top of the file.
	XmlDecl()
}

DeclPrinter handles generation of top matter in the XML document.

type ErrUnsupportedType

type ErrUnsupportedType struct {
	reflect.Type
}

UnsupportedTypeError is returned when Marshal encounters a type that cannot be converted into XML.

func (ErrUnsupportedType) Error

func (e ErrUnsupportedType) Error() string

type IndentStyle

type IndentStyle int

IndentStyle specifies the indentation in the XML document.

type Printer

type Printer interface {
	DeclPrinter
	AttrPrinter
	ContPrinter
	TagPrinter
}

Printer combines DeclPrinter, AttrPrinter, ContPrinter, and TagPrinter interfaces into one providing the complete support for XML syntax.

Example
buf := bytes.Buffer{}

p := NewPrinter(Indent2Spaces,
	func(s []byte) { buf.Write(s) },
	func(n string) TagKind {
		if n == "em" || n == "strong" {
			return Inline
		} else {
			return Block
		}
	})

p.OTag("root")
p.Attr("key", RawAttr("val"))

p.OTag("div")
p.CTag()

p.OTag("h1")
p.Content(RawCont("heading"))
p.CTag() // p
p.OTag("p")
p.Content(RawCont("paragraph"))
p.CTag() // p
p.OTag("p")
p.CTag() // p

// content block
p.Content(RawCont("Hello "))
p.OTag("em")
p.Content(RawCont("World!"))
p.CTag()

// content block
p.OTag("strong")
p.Content(RawCont("Hello"))
p.CTag()
p.Content(RawCont(" World!"))

p.OTag("p")
p.OTag("strong")
p.Content(RawCont("bold"))
p.CTag()
p.CTag() // p

p.OTag("div")
p.OTag("div")
p.CTag()
p.CTag()

p.OTag("p")
// note: this can be turned off with the PreserveInlineWhitespace flag
p.Content(RawCont("line breaks must\nalign nicely with additional\nindentation that matches parent's\nblock level"))
p.CTag()

p.CTag() // root

fmt.Println(buf.String())
Output:

<root key='val'>
  <div/>
  <h1>heading</h1>
  <p>paragraph</p>
  <p/>
  Hello <em>World!</em><strong>Hello</strong> World!
  <p><strong>bold</strong></p>
  <div>
    <div/>
  </div>
  <p>line breaks must
    align nicely with additional
    indentation that matches parent's
    block level</p>
</root>

func NewPrinter

func NewPrinter(indenter IndentStyle, putter func([]byte), tagger func(string) TagKind) Printer

NewPrinter creates a new Printer for writing XML files.

The tagger parameter is a callback that allows to customize indentation for certain tags. If tagger is nil, then all the tags will be treated as block level tags.

type PrinterFlags

type PrinterFlags uint

type RawAttr

type RawAttr []byte

RawAttr is used when writing XML attribute values to indicate that a value can be written without any further processing (bypasses ScrambleAttr)

func ScrambleAttr

func ScrambleAttr(s string) RawAttr

ScrambleAttr is a scrambler for attribute values.

type RawCont

type RawCont []byte

RawCont is used when writing XML content to indicate that it does not need any further processing (bypasses ScrambleCont).

func ScrambleCont

func ScrambleCont(s string) RawCont

ScrambleCont is a scrambler for content.

type TagKind

type TagKind int

TagKind is used to customize the behavior of tags when styling the XML document's appearance with automatic indentation.

type TagPrinter

type TagPrinter interface {
	// OTag starts a new open tag:
	//
	//  <name
	//
	// The name parameter is written verbatim, make sure all symbols in it
	// conform to XML standards.
	//
	// Once a tag is open, you can add attributes to it with the Attr command:
	//
	//    <name key='val' key2='val2'
	//
	// After writing the attributes, you can call CTag, which immediately closes
	// the tag:
	//
	//    <name key='val' key2='val2'/>
	//
	// Or you can start writing child content with the Content(...) call:
	//
	//    <name key='val' key2='val2'>...
	//
	// Or you can start writing subtags with the another OTag()/CTag() call:
	//
	//    <name key='val' key2='val2'><subtag/> ...
	//
	// All Content() and child OTag()/CTag() calls may be repeated and
	// interleaved. Once done, call CTag() to finalize the tag:
	//
	//    <name key='val' key2='val2'> ... child content and subtags ... </name>
	//
	// Once done with the tag, a matching CTag() call must be envoked.
	//
	OTag(name string)

	// CTag closes the tag that was previously opened with the OTag() call.
	//
	// If there was no content written after opening the tag, you will get
	//
	//    <tag optional='attributes' />
	//
	// If some content was written, you will get it wrapped in an open/close tag
	// pair:
	//
	//    <tag optional='attributes'> ... content ... </tag>
	//
	// If you want to have open/close pair with empty content, then make a dummy
	// empty content call after opening the tag: Content(nil):
	//
	//    <tag optional='attributes'></tag>
	//
	CTag()
}

type TagWriter

type TagWriter interface {
	// Tag writes an XML tag with attributes and content from args, where accepted arguments are:
	//
	//   - map[string]any - is written into tag attributes
	//   - func(AttrWriter) - is written into tag attribute
	//   - Attrs(map[string]T) - is processed into attributes, wrapped as func(AttrWriter)
	//   - all types supported by ContWriter - written into tag content
	Tag(string, ...any)
}

TagWriter is an interface for writing XML tags with optional attributes and content.

type Writer

type Writer interface {
	ContWriter
	TagWriter
}

Writer combines AttrWriter and TagWriter

Example
buf := strings.Builder{}
p := NewPrinter(Indent2Spaces,
	func(s []byte) { buf.Write(s) },
	func(n string) TagKind {
		if n == "em" || n == "strong" {
			return Inline
		} else {
			return Block
		}
	})
w := NewWriter(p)
p.XmlDecl()

attrs := map[string]any{
	"k1": "v1",
	"k2": 42,
	"k3": 3.14,
}

notanymap := map[string]string{
	"s1": "v1",
	"s2": "v2",
	"s3": "v3",
}

w.Tag("root",
	Attr("key", "value"), Attr("bool", true), Attr("int", 42),

	// block tag indenting
	Tag("div"),
	Tag("div", Tag("subdiv", Tag("subsubdiv"))),

	// indentation is handled differently for block and inline tags
	Tag("em"),
	Tag("em", Tag("em", Tag("em"))),

	// simple content
	Tag("h1", "String Content"),

	// plain content between tags
	"Plain text content with automatic\ncharacter reference scrambling\nthat also supports aligned wrapping",

	// inline child tags within content
	Tag("p", "String content with ", Tag("strong", "inline formatting"), " is handled as expected"),

	// block child tags within content
	Tag("p", "String content with", Tag("div", "block subtags"), "is handled differently"),

	// declarative attributes
	Tag("div", Attr("key", "val"), "automatically sorts between attributes and content", attrs),
	Tag("div", Attrs(notanymap), "maps that are not `map[string]any` must be wrapped with Attrs(mymap)"),

	// functional attributes
	Tag("div", "functional and subfunctional attribute writing", func(attrs AttrWriter) {
		attrs.Attr("k", "v")
		attrs.Attr("subfunc", func(sub AttrWriter) { sub.Attr("k2", "subfunc") })
	}),

	// funcional content
	func(sub Writer) {
		sub.Tag("p", "functional content writing")
		sub.Tag("p", func(subsub Writer) { subsub.Content("can be nested") })
	},

	// low level printing
	Tag("div", func(p Printer) {
		p.Content(nil) // start new line
		p.Linebreak()
		p.Content(RawCont("direct raw writing with higher performance"))
		p.OTag("p")
		p.Content(RawCont("<!--and and flexibility-->"))
		p.CTag()
		p.Content(RawCont("<![CDATA[...]]>"))
		p.Linebreak()
		p.Content(ScrambleCont("make sure you pair OTag/CTag calls\nand avoid writing <things> that do not comply with XML syntax"))
		p.StopInline() // make sure the following block level closing tag is indented and aligned nicely
	}),
)

fmt.Println(buf.String())
Output:

<?xml version='1.0' encoding='UTF-8'?>
<root key='value' bool='true' int='42'>
  <div/>
  <div>
    <subdiv>
      <subsubdiv/>
    </subdiv>
  </div>
  <em/><em><em><em/></em></em>
  <h1>String Content</h1>
  Plain text content with automatic
  character reference scrambling
  that also supports aligned wrapping
  <p>String content with <strong>inline formatting</strong> is handled as expected</p>
  <p>String content with
    <div>block subtags</div>
    is handled differently</p>
  <div key='val' k1='v1' k2='42' k3='3.14'>automatically sorts between attributes and content</div>
  <div s1='v1' s2='v2' s3='v3'>maps that are not `map[string]any` must be wrapped with Attrs(mymap)</div>
  <div k='v' k2='subfunc'>functional and subfunctional attribute writing</div>
  <p>functional content writing</p>
  <p>can be nested</p>
  <div>
    direct raw writing with higher performance
    <p><!--and and flexibility--></p>
    <![CDATA[...]]>
    make sure you pair OTag/CTag calls
    and avoid writing &lt;things&gt; that do not comply with XML syntax
  </div>
</root>

func NewWriter

func NewWriter(p Printer) Writer

NewWriter wraps Printer p providing TagWriter API. Notice, that for a valid XML document, you will need to write exactly one tag into it that becomes the root.

Jump to

Keyboard shortcuts

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