protobufquery

package module
v0.0.0-...-4b01db2 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2023 License: MIT Imports: 6 Imported by: 1

README

⚠ This repository is moved to https://github.com/srebhan/protobufquery for better maintenance! ⚠

protobufquery

Overview

Protobufquery is an XPath query package for ProtocolBuffer documents. It lets you extract data from parsed ProtocolBuffer message through an XPath expression. Built-in XPath expression cache avoid re-compilation of XPath expression for each query.

Getting Started

Install Package
go get github.com/doclambda/protobufquery
Load ProtocolBuffer message.
msg := addressbookSample.ProtoReflect()
doc, err := Parse(msg)

ProtocolBuffer messages can also be instantiated dynamically using the dynamicpb package. Check out the referenced documentation for examples on loading bytes into those instances.

Example data

Using the ProtocolBuffer definition in testcases/addressbook we can define an example addressbook as

addressbook.AddressBook{
	People: []*addressbook.Person {
		{
			Name:  "John Doe",
			Id:    101,
			Email: "john@example.com",
			Age:   42,
		},
		{
			Name: "Jane Doe",
			Id:   102,
			Age:  40,
		},
		{
			Name:  "Jack Doe",
			Id:    201,
			Email: "jack@example.com",
			Age:   12,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-555-5555", Type: addressbook.Person_WORK},
			},
		},
		{
			Name:  "Jack Buck",
			Id:    301,
			Email: "buck@example.com",
			Age:   19,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-555-0000", Type: addressbook.Person_HOME},
				{Number: "555-555-0001", Type: addressbook.Person_MOBILE},
				{Number: "555-555-0002", Type: addressbook.Person_WORK},
			},
		},
		{
			Name:  "Janet Doe",
			Id:    1001,
			Email: "janet@example.com",
			Age:   16,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-777-0000"},
				{Number: "555-777-0001", Type: addressbook.Person_HOME},
			},
		},
	},
	Tags: []string {"home", "private", "friends"},
}

Using this definition we can perform the example queries below.

Get the XML equivalent of the addressbook.
xml := doc.OutputXML()
Find all names in the addressbook.
list := protobufquery.Find(doc, "/descendant::*[name() = 'people']/name")
// or equal to
list := protobufquery.Find(doc, "//name")
// or by QueryAll()
nodes, err := protobufquery.QueryAll(doc, "//name")
Find the third entry in the addressbook.
list := protobufquery.Find(doc, "/people[3]")
Find the first phone number.
book := protobufquery.Find(doc, "//phones[1]/number")
Find the last phone number.
book := protobufquery.Find(doc, "//phones[last()]/number")
Find all people without email address.
list := protobufquery.Find(doc, "/people[not(email)]")
Find all persons older than 18.
list := protobufquery.Find(doc, "/people[age > 18]")

Examples

func main() {
	addressbookSample := &addressbook.AddressBook{
		People: []*addressbook.Person {
			{
				Name:  "John Doe",
				Id:    101,
				Email: "john@example.com",
				Age:   42,
			},
			{
				Name: "Jane Doe",
				Id:   102,
				Age:  40,
			},
			{
				Name:  "Jack Doe",
				Id:    201,
				Email: "jack@example.com",
				Age:   12,
				Phones: []*addressbook.Person_PhoneNumber{
					{Number: "555-555-5555", Type: addressbook.Person_WORK},
				},
			},
			{
				Name:  "Jack Buck",
				Id:    301,
				Email: "buck@example.com",
				Age:   19,
				Phones: []*addressbook.Person_PhoneNumber{
					{Number: "555-555-0000", Type: addressbook.Person_HOME},
					{Number: "555-555-0001", Type: addressbook.Person_MOBILE},
					{Number: "555-555-0002", Type: addressbook.Person_WORK},
				},
			},
			{
				Name:  "Janet Doe",
				Id:    1001,
				Email: "janet@example.com",
				Age:   16,
				Phones: []*addressbook.Person_PhoneNumber{
					{Number: "555-777-0000"},
					{Number: "555-777-0001", Type: addressbook.Person_HOME},
				},
			},
		},
		Tags: []string {"home", "private", "friends"},
	}
	doc, err := protobufquery.Parse(addressbookSample.ProtoReflect())
	if err != nil {
		panic(err)
	}

	nodes, err := protobufquery.QueryAll(doc, "//people")
	if err != nil {
		panic(err)
	}

	for _, person := range nodes {
		name := protobufquery.FindOne(person, "name").InnerText()
		numbers := make([]string, 0)
		for _, node := range protobufquery.Find(person, "phones/number") {
			numbers = append(numbers, node.InnerText())
		}
		fmt.Printf("%s: %s", name, strings.Join(numbers, ","))
	}
}

Implement Principle

If you are familiar with XPath and XML, you can easily figure out how to write your XPath expression.

addressbook.AddressBook{
	People: []*addressbook.Person {
		{
			Name:  "John Doe",
			Id:    101,
			Email: "john@example.com",
			Age:   42,
		},
		{
			Name: "Jane Doe",
			Id:   102,
			Age:  40,
		},
		{
			Name:  "Jack Doe",
			Id:    201,
			Email: "jack@example.com",
			Age:   12,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-555-5555", Type: addressbook.Person_WORK},
			},
		},
		{
			Name:  "Jack Buck",
			Id:    301,
			Email: "buck@example.com",
			Age:   19,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-555-0000", Type: addressbook.Person_HOME},
				{Number: "555-555-0001", Type: addressbook.Person_MOBILE},
				{Number: "555-555-0002", Type: addressbook.Person_WORK},
			},
		},
		{
			Name:  "Janet Doe",
			Id:    1001,
			Email: "janet@example.com",
			Age:   16,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-777-0000"},
				{Number: "555-777-0001", Type: addressbook.Person_HOME},
			},
		},
	},
	Tags: []string {"home", "private", "friends"},
}

The above ProtocolBuffer representation above will be convert by protobufquery to a structure similar to the XML document below:

<?xml version="1.0" encoding="UTF-8"?>
<people>
  <name>John Doe</name>
  <id>101</id>
  <email>john@example.com</email>
  <age>42</age>
</people>
<people>
  <name>Jane Doe</name>
  <id>102</id>
  <age>40</age>
</people>
<people>
  <name>Jack Doe</name>
  <id>201</id>
  <email>jack@example.com</email>
  <age>12</age>
  <phones>
    <number>555-555-5555</number>
    <type>2</type>
  </phones>
</people>
<people>
  <name>Jack Buck</name>
  <id>301</id>
  <email>buck@example.com</email>
  <age>19</age>
  <phones>
    <number>555-555-0000</number>
    <type>1</type>
  </phones>
  <phones>
    <number>555-555-0001</number>
  </phones>
  <phones>
    <number>555-555-0002</number>
    <type>2</type>
  </phones>
</people>
<people>
  <name>Janet Doe</name>
  <id>1001</id>
  <email>janet@example.com</email>
  <age>16</age>
  <phones>
    <number>555-777-0000</number>
  </phones>
  <phones>
    <number>555-777-0001</number>
    <type>1</type>
  </phones>
</people>
<tags>
  <element>home</element>
  <element>private</element>
  <element>friends</element>
</tags>

Note: element is an anonymous element without name.

List of XPath query packages

Name Description
htmlquery XPath query package for the HTML document
xmlquery XPath query package for the XML document
jsonquery XPath query package for the JSON document
protobufquery XPath query package for ProtocolBuffer messages

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DisableSelectorCache = false

DisableSelectorCache will disable caching for the query selector if value is true.

View Source
var SelectorCacheMaxEntries = 50

SelectorCacheMaxEntries allows how many selector object can be caching. Default is 50. Will disable caching if SelectorCacheMaxEntries <= 0.

Functions

This section is empty.

Types

type Node

type Node struct {
	Parent, PrevSibling, NextSibling, FirstChild, LastChild *Node

	Type NodeType
	Name string
	Data *protoreflect.Value
	// contains filtered or unexported fields
}

A Node consists of a NodeType and some Data (tag name for element nodes, content for text) and are part of a tree of Nodes.

func Find

func Find(top *Node, expr string) []*Node

Find is like QueryAll but will panics if `expr` cannot be parsed.

func FindOne

func FindOne(top *Node, expr string) *Node

FindOne is like Query but will panics if `expr` cannot be parsed.

func Parse

func Parse(msg protoreflect.Message) (*Node, error)

Parse ProtocolBuffer message.

func Query

func Query(top *Node, expr string) (*Node, error)

Query searches the Node that matches by the specified XPath expr, and returns first element of matched.

func QueryAll

func QueryAll(top *Node, expr string) ([]*Node, error)

QueryAll searches the Node that matches by the specified XPath expr. Return an error if the expression `expr` cannot be parsed.

func QuerySelector

func QuerySelector(top *Node, selector *xpath.Expr) *Node

QuerySelector returns the first matched XML Node by the specified XPath selector.

func QuerySelectorAll

func QuerySelectorAll(top *Node, selector *xpath.Expr) []*Node

QuerySelectorAll searches all of the Node that matches the specified XPath selectors.

func (*Node) ChildNodes

func (n *Node) ChildNodes() []*Node

ChildNodes gets all child nodes of the node.

func (*Node) InnerText

func (n *Node) InnerText() string

InnerText gets the value of the node and all its child nodes.

func (*Node) OutputXML

func (n *Node) OutputXML() string

OutputXML prints the XML string.

func (*Node) SelectElement

func (n *Node) SelectElement(name string) *Node

SelectElement finds the first of child elements with the specified name.

func (*Node) Value

func (n *Node) Value() interface{}

Value return the value of the node itself or its 'TextElement' children. If `nil`, the value is either really `nil` or there is no matching child.

type NodeNavigator

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

NodeNavigator is for navigating JSON document.

func CreateXPathNavigator

func CreateXPathNavigator(top *Node) *NodeNavigator

CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node.

func (*NodeNavigator) Copy

func (a *NodeNavigator) Copy() xpath.NodeNavigator

func (*NodeNavigator) Current

func (a *NodeNavigator) Current() *Node

func (*NodeNavigator) GetValue

func (a *NodeNavigator) GetValue() interface{}

func (*NodeNavigator) LocalName

func (a *NodeNavigator) LocalName() string

func (*NodeNavigator) MoveTo

func (a *NodeNavigator) MoveTo(other xpath.NodeNavigator) bool

func (*NodeNavigator) MoveToChild

func (a *NodeNavigator) MoveToChild() bool

func (*NodeNavigator) MoveToFirst

func (a *NodeNavigator) MoveToFirst() bool

func (*NodeNavigator) MoveToNext

func (a *NodeNavigator) MoveToNext() bool

func (*NodeNavigator) MoveToNextAttribute

func (x *NodeNavigator) MoveToNextAttribute() bool

func (*NodeNavigator) MoveToParent

func (a *NodeNavigator) MoveToParent() bool

func (*NodeNavigator) MoveToPrevious

func (a *NodeNavigator) MoveToPrevious() bool

func (*NodeNavigator) MoveToRoot

func (a *NodeNavigator) MoveToRoot()

func (*NodeNavigator) NodeType

func (a *NodeNavigator) NodeType() xpath.NodeType

func (*NodeNavigator) Prefix

func (a *NodeNavigator) Prefix() string

func (*NodeNavigator) String

func (a *NodeNavigator) String() string

func (*NodeNavigator) Value

func (a *NodeNavigator) Value() string

type NodeType

type NodeType uint

A NodeType is the type of a Node.

const (
	// DocumentNode is a document object that, as the root of the document tree,
	// provides access to the entire XML document.
	DocumentNode NodeType = iota
	// ElementNode is an element.
	ElementNode
	// TextNode is the text content of a node.
	TextNode
)

Directories

Path Synopsis
testcases

Jump to

Keyboard shortcuts

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