gojax

package module
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: May 10, 2023 License: ISC Imports: 11 Imported by: 0

Documentation

Overview

Gojax provides a json-ish and xml-ish tool to convert json-ish to xml-ish so you can use an xpath-ish expression to manipulate the resulting xml. If you can set aside the ishs, you can use this package to work with JSON and XML.

The Consequences of ish

For JSON, the following are meant by the expression of 'ish' above:

  • A single object is the only allowed root element
  • For numbers, only sequences of digits possibly prefixed with a negative sign are supported, including zero prefixed numbers
  • For strings, anything can be escaped, but unicode hexadecimal expressions are not supported
  • Finally, for whitespace, anything that returns true from unicode.IsSpace is accepted

For XML, only the least is supported - hence the 'ish' above.

  • Nodes can have names from any JSON string
  • Attribute keys and values are any Golang string
  • JSON arrays have each inner-element nested inside a node with the following format: `<n index="0">...</n>`, where the 0 is replaced with the element's index in the array
  • The JSON values of true, false, and null all convert to XML content as those runes
  • Also, XML attributes are sorted in the output

For XPath, also, only the least is supported.

  • You can separate path parts with either a / or // as in `/firstPathPart/secondPathPart`
  • A single / requires the successive path part to match the node name of a child of the current XMLNode
  • The double // allows the successive path part to match any descendant node name and can occur as a prefix to any path part such as `/firstPathPart//secondPathPart“
  • Filters are placed after node names in any path part following a syntax such as `/nodeName[k=v]`, where k is any attribute key (or any descendant node name) and v is any attribute value (or that same descendant node's AnyContent result) with both being required to match the entire key or value respectively
  • As an example of a descendant node content filter, given structure like `{"a":[{"b":{"c":1}}, {"b":{"c":1}}, {"b":{"c":2}}]}` and a path like `//b[c=2]`, you could find the node `<b><c>2</c></b>`
  • Filters also support two special keys: meta and position(). The meta special key only supports one value 'no_attrs', which will only match nodes that have no attributes. The position() special key supports digits as values and checks that the position of that node in it's parents slice of children matches the given value.
  • In addition to checking an exact match for the childs position with an =, the position() special key supports > and < operators that filter nodes as expected.
  • The wildcard * is supported and matches any node name

As noted below, the above results in some restrictions:

  • Node names cannot contain [ or / (unless you use [ in the givenName parameter when calling NewXMLNode)
  • Node attribute keys and values cannot contain [ or ] or = or > or < or /

Sample Code

All that given, here is a simple sample usage:

package main

import (
	"fmt"
	"git.ondollo.com/gojax"
)

func main() {
		r := gojax.NewXMLNode("root#123[a=1]")
		b := gojax.NewXMLNode("b")
		b.Add(gojax.NewXMLContentNode("2"))
		r.Add(b)
		c := gojax.NewXMLNode("c")
		d := gojax.NewXMLNode("d")
		d.Add(gojax.NewXMLContentNode("3"))
		c.Add(d)
		r.Add(c)
		fmt.Println(r) // <root a="1" id="123"><b>2</b><c><d>3</d></c></root>

		x, err := gojax.FromJSON("root", `{"a":{"b":1}}`)
		if err != nil {
			fmt.Println("Failed to build an XMLNode from the given JSON:", err)
		}
		fmt.Println(x.Path("//b")) // [<b>1</b>] <nil>

		x, err = gojax.FromJSON("root", `{"a":[{"b":"c"},{"b":"d"}]}`)
		if err != nil {
			fmt.Println("Failed to build an XMLNode from the given JSON:", err)
		}
		fmt.Println(x.Path("//n[index=0]")) // [<n index="0"><b>c</b></n>] <nil>
	}
}

Index

Constants

This section is empty.

Variables

View Source
var DefaultTestOptions = Options{false, false, true}

DefaultTestOptions provide a reference to the default test Options

Functions

This section is empty.

Types

type Options

type Options struct {
	Debug             bool
	OutputHeader      bool
	WrapArrayElements bool
}

Options allows setting any of three options to make minor adjustments to the output of this library. For functions ending in `WithOptions`, the final parameter will have the Options type.

When Debug is true additional data is available. Avoid using this. The default is false.

When OutputHeader is true, the string `<?xml version="1.0" encoding="utf-8"?>` will be output on any call to String for an XMLNode without a parent. The default is true.

When WrapArrayElements is true, each element in an array will be wrapped with an element that follows the format: `<n index="0">...</n>`, where the 0 is replaced with the element's index in the array. The default is true.

type XMLNode

type XMLNode struct {
	Content string
	Attrs   map[string]string
	// contains filtered or unexported fields
}

XMLNode provides just enough structure for the subset of XML that gojax supports.

func First

func First(xns []*XMLNode, err error) (*XMLNode, error)

First returns the first XMLNode from a slice. The parameters are meant to match the Path return values. The error either came from Path or was a result of calling First on an empty slice of XMLNodes.

func FirstOr

func FirstOr(givenNode *XMLNode, givenPath string) *XMLNode

FirstOr will call Path on the givenNode passing in the givenPath, and either return the first match, or panic

func FromJSON

func FromJSON(root string, givenJson string) (xn *XMLNode, returnedErr error)

FromJSON processes the givenJson and creates as much of an XML-ish structure as it can with root as the first XMLNode's name.

If there is any failure from invalid input, it will return as much of the resulting XMLNode structure as it can and have an error in the location where the invalid input would have been placed (wrapped in an element with a name 'error').

The following invalid input:

fmt.Println(gojax.FromJSON("j", `[1]`))

Would result in an output that contained:

<j><error>Invalid input: root must be an object</error></j>

func FromJSONDebug

func FromJSONDebug(root string, givenJson string) (xn *XMLNode, returnedErr error)

FromJSONDebug sets the Debug Option and passes all given args to FromJSONWithOptions and returns all results from FromJSONWithOptions

func FromJSONWithOptions

func FromJSONWithOptions(root string, givenJson string, givenOptions Options) (xn *XMLNode, returnedErr error)

FromJSONWithOptions is the same as FromJSON, but allows passing custom Options in. You will likely want to just use FromJSON.

func FromXML

func FromXML(givenXML string) (*XMLNode, error)

FromXML will take xml-ish (see above) and produce an XMLNode structure to mimic. One goal with this method is to get JSON back out of an XMLNode structure with the JSON method.

func FromXMLWithOptions

func FromXMLWithOptions(givenXML string, givenOptions Options) (*XMLNode, error)

FromXMLWithOptions is the same as FromXML, but allows passing custom Options in. You will likely want to just use FromXML.

func Last

func Last(xns []*XMLNode, err error) (*XMLNode, error)

Last returns the last XMLNode from a slice. The parameters are meant to match the Path return values. The error either came from Path or was a result of calling Last on an empty slice of XMLNodes.

func LastOr

func LastOr(givenNodes []*XMLNode, err error) *XMLNode

LastOr will return the last in a slice of XMLNode, or panic. The parameters are meant to match the Path return values. The error either came from Path or was a result of calling Last on an empty slice of XMLNodes.

func NewXMLContentNode

func NewXMLContentNode(givenContent string) *XMLNode

NewXMLContentNode returns an XMLNode with the givenContent as it's Content

func NewXMLNode

func NewXMLNode(givenName string) *XMLNode

NewXMLNode returns an XMLNode with the givenName as it's name. There are some supported variations to express attributes, including an id, inside the givenName:

  • You can set the id attribute with nodeName#idValue
  • You can set attributes with nodeName[k=v]
  • If any, the id needs to come before attrs and will override any attribute key 'id' set through [] notation

So, combining the above, you can set attrs and id with:

nodeName#idValue[k=v]

Additionally, the special attribute keys available in paths won't hold specific meaning here and are ignored.

Finally, no attempt is made to ensure the uniqueness of any id attribute within any XMLNode

func Reprocess

func Reprocess(givenNodes []*XMLNode, givenPath string) []*XMLNode

Reprocess runs the givenPath on each of the XMLNodes in givenNodes, collecting and then returning a unique list of matches

func UnwrappedOr

func UnwrappedOr(givenJson string) []*XMLNode

UnwrappedOr will call FromJSON passing in an internally known root and then return the contents inside that root, or panic

func UnwrappedOrWithOptions added in v0.8.0

func UnwrappedOrWithOptions(givenJson string, givenOptions Options) []*XMLNode

UnwrappedOrWithOptions is the same as UnwrappedOr, but allows passing custom Options in. You will likely want to just use UnwrappedOr.

func (*XMLNode) Add

func (x *XMLNode) Add(xn *XMLNode)

Add appends the XMLNode xn as a child in the children XMLNode slice of the XMLNode x

func (*XMLNode) AddAll

func (x *XMLNode) AddAll(xns []*XMLNode)

AddAll appends all of the XMLNodes xns as children to the XMLNode x

func (*XMLNode) AddBefore

func (x *XMLNode) AddBefore(xn *XMLNode) error

AddBefore will add xn as a sibling of x just before x's position in the list of the parents children and possibly return an error if x is the root. The XMLNode xn will be Cut before being added to x.

func (*XMLNode) AddCloneOf

func (x *XMLNode) AddCloneOf(nonChild *XMLNode)

AddCloneOf calls Clone on the given nonChild and then calls Add on x passing in the new clone

func (*XMLNode) AnyContent

func (x *XMLNode) AnyContent() string

AnyContent will return either the content inside x or, if x has only one child that is a content node, the content of that one

func (*XMLNode) Clone

func (x *XMLNode) Clone() *XMLNode

Clone recursively clones X and returns the new XMLNode detatched from the x's parent

func (*XMLNode) CloneInto

func (x *XMLNode) CloneInto(parent *XMLNode) *XMLNode

CloneInto calls Clone on x and then calls Add on the given parent passing in the new clone

func (*XMLNode) Cut

func (x *XMLNode) Cut() error

Cut will remove the XMLNode x from it's parent and possibly return an error if x is the root

func (*XMLNode) Get

func (x *XMLNode) Get(k string) string

Get will the value v that is assigned to the attribute key k

func (*XMLNode) GetName

func (x *XMLNode) GetName() string

GetName will return the calculated name of the node.

func (XMLNode) JSON

func (x XMLNode) JSON() string

JSON produces a JSON output from an XMLNode structure. Children are output in the same order they exist in in the XMLNode structure with matching names simply repeated.

func (*XMLNode) Path

func (x *XMLNode) Path(givenPath string) ([]*XMLNode, error)

Path will take the givenPath and attempt to traverse the XMLNode structure. If it is successful it will return all XMLNodes that satisfy the constraints of the givenPath, or an empty XMLNode slice with an error.

At each node we can have some remainder of the givenPath (with double prioritized to disambiguate):

/		- return the node we're at
//a		- find all descendants with a name of 'a' and return them
/a		- find a child with a name of 'a' and return it
//a/<rest>	- recurse into all descendants with a name of 'a' and run Path(`/<rest>`) on every descendant - keep any full match
/a/<rest>	- recurse into a child with a name of 'a' and run Path(`/<rest>`) on that child - keep any full match

Attribute filters look like:

nodeName[k=v]	- only match the node with a name of 'nodeName' if it has an attribute 'k' with a value of 'v'

Consequences of this approach:

  • Node names cannot contain [ or / (unless you use [ in the givenName parameter when calling NewXMLNode)
  • Node attribute keys and values cannot contain [ or ] or = or > or < or /

func (*XMLNode) PathOr

func (x *XMLNode) PathOr(givenPath string) []*XMLNode

PathOr will call Path on the givenNode passing in the givenPath, and either return all matches, or panic

func (*XMLNode) Prepend

func (x *XMLNode) Prepend(xn *XMLNode) error

Prepend will add the XMLNode xn to the beginning of the children of x possibly returning an error

func (*XMLNode) Remove

func (x *XMLNode) Remove(xn *XMLNode) error

Remove will remove the XMLNode xn from x and possibly return an error if xn is not a child of x

func (*XMLNode) Set

func (x *XMLNode) Set(k string, v string)

Set will assign the value v to the attribute key k

func (*XMLNode) SetName

func (x *XMLNode) SetName(givenName string)

SetName will turn any valid givenName expression into the resulting name and attributes, including id.

func (XMLNode) String

func (x XMLNode) String() string

String will output XML from an XMLNode structure. If you'd like to output JSON, use the JSON method.

func (XMLNode) StringWithOptions

func (x XMLNode) StringWithOptions(givenOptions Options) string

StringWithOptions is the same as String, but allows passing custom Options in. You will likely want to just use String.

Jump to

Keyboard shortcuts

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