Categorygithub.com/doclambda/protobufquery
modulepackage
0.0.0-20230803121924-4b01db20a3f3
Repository: https://github.com/doclambda/protobufquery.git
Documentation: pkg.go.dev

# README

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

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: "[email protected]",
			Age:   42,
		},
		{
			Name: "Jane Doe",
			Id:   102,
			Age:  40,
		},
		{
			Name:  "Jack Doe",
			Id:    201,
			Email: "[email protected]",
			Age:   12,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-555-5555", Type: addressbook.Person_WORK},
			},
		},
		{
			Name:  "Jack Buck",
			Id:    301,
			Email: "[email protected]",
			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: "[email protected]",
			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: "[email protected]",
				Age:   42,
			},
			{
				Name: "Jane Doe",
				Id:   102,
				Age:  40,
			},
			{
				Name:  "Jack Doe",
				Id:    201,
				Email: "[email protected]",
				Age:   12,
				Phones: []*addressbook.Person_PhoneNumber{
					{Number: "555-555-5555", Type: addressbook.Person_WORK},
				},
			},
			{
				Name:  "Jack Buck",
				Id:    301,
				Email: "[email protected]",
				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: "[email protected]",
				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: "[email protected]",
			Age:   42,
		},
		{
			Name: "Jane Doe",
			Id:   102,
			Age:  40,
		},
		{
			Name:  "Jack Doe",
			Id:    201,
			Email: "[email protected]",
			Age:   12,
			Phones: []*addressbook.Person_PhoneNumber{
				{Number: "555-555-5555", Type: addressbook.Person_WORK},
			},
		},
		{
			Name:  "Jack Buck",
			Id:    301,
			Email: "[email protected]",
			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: "[email protected]",
			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>[email protected]</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>[email protected]</email>
  <age>12</age>
  <phones>
    <number>555-555-5555</number>
    <type>2</type>
  </phones>
</people>
<people>
  <name>Jack Buck</name>
  <id>301</id>
  <email>[email protected]</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>[email protected]</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

NameDescription
htmlqueryXPath query package for the HTML document
xmlqueryXPath query package for the XML document
jsonqueryXPath query package for the JSON document
protobufqueryXPath query package for ProtocolBuffer messages

# Packages

No description provided by the author

# Functions

CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node.
Find is like QueryAll but will panics if `expr` cannot be parsed.
FindOne is like Query but will panics if `expr` cannot be parsed.
Parse ProtocolBuffer message.
Query searches the Node that matches by the specified XPath expr, and returns first element of matched.
QueryAll searches the Node that matches by the specified XPath expr.
QuerySelector returns the first matched XML Node by the specified XPath selector.
QuerySelectorAll searches all of the Node that matches the specified XPath selectors.

# Constants

DocumentNode is a document object that, as the root of the document tree, provides access to the entire XML document.
ElementNode is an element.
TextNode is the text content of a node.

# Variables

DisableSelectorCache will disable caching for the query selector if value is true.
SelectorCacheMaxEntries allows how many selector object can be caching.

# Structs

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.
NodeNavigator is for navigating JSON document.

# Type aliases

A NodeType is the type of a Node.