1
0
Fork 0

Adding upstream version 0.0~git20221030.f2a1913.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-18 22:22:16 +02:00
parent 958064e3c4
commit 2ef6a43d9f
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
19 changed files with 4554 additions and 0 deletions

18
.build.yml Normal file
View file

@ -0,0 +1,18 @@
image: archlinux
packages:
- go
- postgresql
sources:
- https://github.com/go-ap/jsonld
environment:
GO111MODULE: 'on'
tasks:
- setup: |
cd jsonld && go mod download
- tests: |
cd jsonld && make test
- coverage: |
set -a +x
cd jsonld && make coverage
GIT_SHA=$(git rev-parse --verify HEAD)
GIT_BRANCH=$(git name-rev --name-only HEAD)

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Gogland
.idea/
# Binaries for programs and plugins
*.so
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tools
*.out
*.coverprofile
*pkg

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Marius Orcsik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
Makefile Normal file
View file

@ -0,0 +1,18 @@
TEST := go test
TEST_FLAGS ?= -v
TEST_TARGET ?= ./...
GO111MODULE=on
PROJECT_NAME := $(shell basename $(PWD))
.PHONY: test coverage clean
test:
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
coverage: TEST_TARGET := .
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
coverage: test
clean:
$(RM) -v *.coverprofile

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# JSON-ld for Go
[![MIT Licensed](https://img.shields.io/github/license/go-ap/jsonld.svg)](https://raw.githubusercontent.com/go-ap/jsonld/master/LICENSE)
[![Build Status](https://builds.sr.ht/~mariusor/jsonld.svg)](https://builds.sr.ht/~mariusor/jsonld)
[![Test Coverage](https://img.shields.io/codecov/c/github/go-ap/jsonld.svg)](https://codecov.io/gh/go-ap/jsonld)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-ap/jsonld)](https://goreportcard.com/report/github.com/go-ap/jsonld)
<!--[![Codacy Badge](https://api.codacy.com/project/badge/Grade/29664f7ae6c643bca76700143e912cd3)](https://www.codacy.com/app/go-ap/jsonld/dashboard)-->
Basic lib for using [activity pub](https://www.w3.org/TR/activitypub/#Overview) API in Go.
## Usage
```go
import "github.com/go-ap/jsonld"
```

219
context.go Normal file
View file

@ -0,0 +1,219 @@
package jsonld
import (
"encoding/json"
"strings"
)
// From the JSON-LD spec 3.3
// https://www.w3.org/TR/json-ld/#dfn-keyword
const (
// @context
// Used to define the short-hand names that are used throughout a JSON-LD document.
// These short-hand names are called terms and help developers to express specific identifiers in a compact manner.
// The @context keyword is described in detail in section 5.1 The Context.
ContextKw Term = "@context"
// @id
//Used to uniquely identify things that are being described in the document with IRIs or blank node identifiers.
// This keyword is described in section 5.3 Node Identifiers.
IdKw Term = "@id"
// @value
// Used to specify the data that is associated with a particular property in the graph.
// This keyword is described in section 6.9 String Internationalization and section 6.4 Typed Values.
ValueKw Term = "@value"
// @language
// Used to specify the language for a particular string value or the default language of a JSON-LD document.
// This keyword is described in section 6.9 String Internationalization.
LanguageKw Term = "@language"
//@type
//Used to set the data type of a node or typed value. This keyword is described in section 6.4 Typed Values.
TypeKw Term = "@type"
// @container
// Used to set the default container type for a term. This keyword is described in section 6.11 Sets and Lists.
ContainerKw Term = "@container"
//@list
//Used to express an ordered set of data. This keyword is described in section 6.11 Sets and Lists.
ListKw Term = "@list"
// @set
// Used to express an unordered set of data and to ensure that values are always represented as arrays.
// This keyword is described in section 6.11 Sets and Lists.
SetKw Term = "@set"
// @reverse
// Used to express reverse properties. This keyword is described in section 6.12 Reverse Properties.
ReverseKw Term = "@reverse"
// @index
// Used to specify that a container is used to index information and that processing should continue deeper
// into a JSON data structure. This keyword is described in section 6.16 Data Indexing.
IndexKw Term = "@index"
// @base
// Used to set the base IRI against which relative IRIs are resolved. T
// his keyword is described in section 6.1 Base IRI.
BaseKw Term = "@base"
// @vocab
// Used to expand properties and values in @type with a common prefix IRI.
// This keyword is described in section 6.2 Default Vocabulary.
VocabKw Term = "@vocab"
// @graph
// Used to express a graph. This keyword is described in section 6.13 Named Graphs.
GraphKw Term = "@graph"
)
// ContentType is the content type of JsonLD documents
const ContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
type (
// Ref basic type
LangRef string
// Term represents the JSON-LD term for @context maps
Term string
// IRI is a International Resource Identificator
IRI string
// Terms is an array of Term values
Terms []Term
)
// Nillable
type Nillable interface {
IsNil() bool
}
type IRILike interface {
IsCompact() bool
IsAbsolute() bool
IsRelative() bool
}
func (i IRI) IsCompact() bool {
return !i.IsAbsolute() && strings.Contains(string(i), ":")
}
func (i IRI) IsAbsolute() bool {
return strings.Contains(string(i), "https://")
}
func (i IRI) IsRelative() bool {
return !i.IsAbsolute()
}
var keywords = Terms{
BaseKw,
ContextKw,
ContainerKw,
GraphKw,
IdKw,
IndexKw,
LanguageKw,
ListKw,
ReverseKw,
SetKw,
TypeKw,
ValueKw,
VocabKw,
}
const NilTerm Term = "-"
const NilLangRef LangRef = "-"
type ContextObject struct {
ID interface{} `jsonld:"@id,omitempty,collapsible"`
Type interface{} `jsonld:"@type,omitempty,collapsible"`
}
// Context is of of the basic JSON-LD elements.
// It represents an array of ContextElements
type Context []ContextElement
// ContextElement is used to map terms to IRIs or JSON objects.
// Terms are case sensitive and any valid string that is not a reserved JSON-LD
// keyword can be used as a term.
type ContextElement struct {
Term Term
IRI IRI
}
func GetContext() Context {
return Context{}
}
//type Context Collapsible
// Collapsible is an interface used by the JSON-LD marshaller to collapse a struct to one single value
type Collapsible interface {
Collapse() interface{}
}
// Collapse returns the plain text collapsed value of the current Context object
func (c Context) Collapse() interface{} {
if len(c) == 1 && len(c[0].IRI) > 0 {
return c[0].IRI
}
for _, el := range c {
if el.Term == NilTerm {
}
}
return c
}
// Collapse returns the plain text collapsed value of the current IRI string
func (i IRI) Collapse() interface{} {
return i
}
// MarshalText basic stringify function
func (i IRI) MarshalText() ([]byte, error) {
return []byte(i), nil
}
// MarshalJSON returns the JSON document represented by the current Context
// This should return :
// If only one element in the context and the element has no Term -> json marshaled string
// If multiple elements in the context without Term -> json marshaled array of strings
// If multiple elements where at least one doesn't have a Term and one has a Term -> json marshaled array
// If multiple elements where all have Terms -> json marshaled object
func (c Context) MarshalJSON() ([]byte, error) {
mapIRI := make(map[Term]IRI, 0)
arr := make([]interface{}, 0)
i := 0
if len(c) == 1 && len(c[0].IRI) > 0 {
return json.Marshal(c[0].IRI)
}
for _, el := range c {
t := el.Term
iri := el.IRI
if t.IsNil() {
arr = append(arr, iri)
i += 1
} else {
if len(iri) > 0 {
mapIRI[t] = iri
}
}
}
if len(mapIRI) > 0 {
if len(arr) == 0 {
return json.Marshal(mapIRI)
}
arr = append(arr, mapIRI)
}
return json.Marshal(arr)
}
// UnmarshalJSON tries to load the Context from the incoming json value
func (c *Context) UnmarshalJSON(data []byte) error {
return nil
}
// IsNil returns if current LangRef is equal to empty string or to its nil value
func (l LangRef) IsNil() bool {
return len(l) == 0 || l == NilLangRef
}
// IsNil returns if current IRI is equal to empty string
func (i IRI) IsNil() bool {
return len(i) == 0
}
// IsNil returns if current Term is equal to empty string or to its nil value
func (i Term) IsNil() bool {
return len(i) == 0 || i == NilTerm
}

101
context_test.go Normal file
View file

@ -0,0 +1,101 @@
package jsonld
import (
"bytes"
"encoding/json"
"strings"
"testing"
)
func TestRef_MarshalText(t *testing.T) {
test := "test"
a := IRI(test)
out, err := a.MarshalText()
if err != nil {
t.Errorf("Error %s", err)
}
if bytes.Compare(out, []byte(test)) != 0 {
t.Errorf("Invalid result '%s', expected '%s'", out, test)
}
}
func TestContext_MarshalJSON(t *testing.T) {
{
url := "test"
c := Context{{NilTerm, IRI(url)}}
out, err := c.MarshalJSON()
if err != nil {
t.Errorf("%s", err)
}
if !strings.Contains(string(out), url) {
t.Errorf("Json doesn't contain %s, %s", url, string(out))
}
jUrl, _ := json.Marshal(url)
if !bytes.Equal(jUrl, out) {
t.Errorf("Strings should be equal %s, %s", jUrl, out)
}
}
{
url := "example.com"
asTerm := "testingTerm##"
asUrl := "https://activitipubrocks.com"
c2 := Context{{NilTerm, IRI(url)}, {Term(asTerm), IRI(asUrl)}}
out, err := c2.MarshalJSON()
if err != nil {
t.Errorf("%s", err)
}
if !strings.Contains(string(out), url) {
t.Errorf("Json doesn't contain URL %s, %s", url, string(out))
}
if !strings.Contains(string(out), asUrl) {
t.Errorf("Json doesn't contain URL %s, %s", asUrl, string(out))
}
if !strings.Contains(string(out), asTerm) {
t.Errorf("Json doesn't contain Term %s, %s", asTerm, string(out))
}
}
{
url := "test"
testTerm := "test_term"
asTerm := "testingTerm##"
asUrl := "https://activitipubrocks.com"
c3 := Context{{Term(testTerm), IRI(url)}, {Term(asTerm), IRI(asUrl)}}
out, err := c3.MarshalJSON()
if err != nil {
t.Errorf("%s", err)
}
if !strings.Contains(string(out), url) {
t.Errorf("Json doesn't contain URL %s, %s", url, string(out))
}
if !strings.Contains(string(out), asUrl) {
t.Errorf("Json doesn't contain URL %s, %s", asUrl, string(out))
}
if !strings.Contains(string(out), asTerm) {
t.Errorf("Json doesn't contain Term %s, %s", asTerm, string(out))
}
if !strings.Contains(string(out), testTerm) {
t.Errorf("Json doesn't contain Term %s, %s", testTerm, string(out))
}
}
{
url1 := "test"
url2 := "http://example.com"
c := Context{
{IRI: IRI(url1)},
{IRI: IRI(url2)},
}
out, err := c.MarshalJSON()
if err != nil {
t.Errorf("%s", err)
}
if !strings.Contains(string(out), url1) {
t.Errorf("Json doesn't contain %s, %s", url1, string(out))
}
if !strings.Contains(string(out), url2) {
t.Errorf("Json doesn't contain %s, %s", url1, string(out))
}
}
}

1253
decode.go Normal file

File diff suppressed because it is too large Load diff

149
decode_test.go Normal file
View file

@ -0,0 +1,149 @@
package jsonld
import (
"strconv"
"testing"
)
func TestUnmarshalWithEmptyJsonObject(t *testing.T) {
obj := mockTypeA{}
err := Unmarshal([]byte("{}"), &obj)
if err != nil {
t.Error(err)
}
if obj.Id != "" {
t.Errorf("Id should have been an empty string, found %s", obj.Id)
}
if obj.Name != "" {
t.Errorf("Name should have been an empty string, found %s", obj.Name)
}
if obj.Type != "" {
t.Errorf("Type should have been an empty string, found %s", obj.Type)
}
if obj.PropA != "" {
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
}
if obj.PropB != 0 {
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
}
}
type mockWithContext struct {
mockTypeA
Context Context `jsonld:"@context"`
}
func TestUnmarshalWithEmptyJsonObjectWithStringContext(t *testing.T) {
obj := mockWithContext{}
url := "http://www.habarnam.ro"
data := []byte(`{"@context": "` + url + `" }`)
err := Unmarshal(data, &obj)
if err != nil {
t.Error(err)
}
if obj.Id != "" {
t.Errorf("Id should have been an empty string, found %s", obj.Id)
}
if obj.Name != "" {
t.Errorf("Name should have been an empty string, found %s", obj.Name)
}
if obj.Type != "" {
t.Errorf("Type should have been an empty string, found %s", obj.Type)
}
if obj.PropA != "" {
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
}
if obj.PropB != 0 {
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
}
}
func TestUnmarshalWithEmptyJsonObjectWithObjectContext(t *testing.T) {
obj := mockWithContext{}
url := "http://www.habarnam.ro"
data := []byte(`{"@context": { "@url": "` + url + `"} }`)
err := Unmarshal(data, &obj)
if err != nil {
t.Error(err)
}
if obj.Id != "" {
t.Errorf("Id should have been an empty string, found %s", obj.Id)
}
if obj.Name != "" {
t.Errorf("Name should have been an empty string, found %s", obj.Name)
}
if obj.Type != "" {
t.Errorf("Type should have been an empty string, found %s", obj.Type)
}
if obj.PropA != "" {
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
}
if obj.PropB != 0 {
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
}
}
func TestUnmarshalWithEmptyJsonObjectWithOneLanguageContext(t *testing.T) {
obj := mockWithContext{}
url := "http://www.habarnam.ro"
langEn := "en-US"
data := []byte(`{"@context": { "@url": "` + url + `", "@language": "` + langEn + `"} }`)
err := Unmarshal(data, &obj)
if err != nil {
t.Error(err)
}
if obj.Id != "" {
t.Errorf("Id should have been an empty string, found %s", obj.Id)
}
if obj.Name != "" {
t.Errorf("Name should have been an empty string, found %s", obj.Name)
}
if obj.Type != "" {
t.Errorf("Type should have been an empty string, found %s", obj.Type)
}
if obj.PropA != "" {
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
}
if obj.PropB != 0 {
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
}
}
func TestUnmarshalWithEmptyJsonObjectWithFullObject(t *testing.T) {
obj := mockWithContext{}
url := "http://www.habarnam.ro"
langEn := "en-US"
propA := "ana"
var propB float32 = 6.66
typ := "test"
name := "test object #1"
id := "777sdad"
data := []byte(`{
"@context": { "@url": "` + url + `", "@language": "` + langEn + `"},
"PropA": "` + propA + `",
"PropB": ` + strconv.FormatFloat(float64(propB), 'f', 2, 32) + `,
"Id" : "` + id + `",
"Name" : "` + name + `",
"Type" : "` + typ + `"
}`)
err := Unmarshal(data, &obj)
if err != nil {
t.Error(err)
}
if obj.Id != id {
t.Errorf("Id should have been %q, found %q", id, obj.Id)
}
if obj.Name != name {
t.Errorf("Name should have been %q, found %q", name, obj.Name)
}
if obj.Type != typ {
t.Errorf("Type should have been %q, found %q", typ, obj.Type)
}
if obj.PropA != propA {
t.Errorf("PropA should have been %q, found %q", propA, obj.PropA)
}
if obj.PropB != propB {
t.Errorf("PropB should have been %f, found %f", propB, obj.PropB)
}
}

1377
encode.go Normal file

File diff suppressed because it is too large Load diff

156
encode_test.go Normal file
View file

@ -0,0 +1,156 @@
package jsonld
import (
"bytes"
"fmt"
"reflect"
"strings"
"testing"
)
type mockBase struct {
Id string
Name string
Type string
}
type mockTypeA struct {
mockBase
PropA string
PropB float32
}
func TestMarshal(t *testing.T) {
a := mockTypeA{mockBase{"base_id", "MockObjA", "mock_obj"}, "prop_a", 0.001}
b := mockTypeA{}
url := "http://www.habarnam.ro"
p := WithContext(IRI(url))
var err error
var out []byte
out, err = p.Marshal(a)
if err != nil {
t.Errorf("%s", err)
}
if !strings.Contains(string(out), string(ContextKw)) {
t.Errorf("Context name not found %q in %s", ContextKw, out)
}
if !strings.Contains(string(out), url) {
t.Errorf("Context url not found %q in %s", url, out)
}
err = Unmarshal(out, &b)
if err != nil {
t.Errorf("%s", err)
}
if a.Id != b.Id {
t.Errorf("Id isn't equal %q expected %q in %s", a.Id, b.Id, out)
}
if a.Name != b.Name {
t.Errorf("Name isn't equal %q expected %q", a.Name, b.Name)
}
if a.Type != b.Type {
t.Errorf("Type isn't equal %q expected %q", a.Type, b.Type)
}
if a.PropA != b.PropA {
t.Errorf("PropA isn't equal %q expected %q", a.PropA, b.PropA)
}
if a.PropB != b.PropB {
t.Errorf("PropB isn't equal %f expected %f", a.PropB, b.PropB)
}
}
func TestMarshalNullContext(t *testing.T) {
var a = struct {
PropA string
PropB float64
}{"test", 0.0004}
outL, errL := Marshal(a)
if errL != nil {
t.Errorf("%s", errL)
}
outJ, errJ := Marshal(a)
if errJ != nil {
t.Errorf("%s", errJ)
}
if !bytes.Equal(outL, outJ) {
t.Errorf("Json output should be equal %q, received %q", outL, outJ)
}
}
func TestMarshalNullValue(t *testing.T) {
var a = interface{}(nil)
url := "http://www.habarnam.ro"
p := WithContext(IRI(url))
outL, errL := p.Marshal(a)
if errL != nil {
t.Errorf("%s", errL)
}
outJ := []byte(fmt.Sprintf(`{"@context":"%s"}`, url))
if !bytes.Equal(outL, outJ) {
t.Errorf("JsonLD output is wrong: %s, expected %s", outL, outJ)
}
}
func TestIsEmpty(t *testing.T) {
var a int
if !isEmptyValue(reflect.ValueOf(a)) {
t.Errorf("Invalid empty value %v", a)
}
if !isEmptyValue(reflect.ValueOf(uint(a))) {
t.Errorf("Invalid empty value %v", uint(a))
}
var b float64
if !isEmptyValue(reflect.ValueOf(b)) {
t.Errorf("Invalid empty value %v", b)
}
var c string
if !isEmptyValue(reflect.ValueOf(c)) {
t.Errorf("Invalid empty value %s", c)
}
var d []byte
if !isEmptyValue(reflect.ValueOf(d)) {
t.Errorf("Invalid empty value %v", d)
}
var e *interface{}
if !isEmptyValue(reflect.ValueOf(e)) {
t.Errorf("Invalid empty value %v", e)
}
g := false
if !isEmptyValue(reflect.ValueOf(g)) {
t.Errorf("Invalid empty value %v", g)
}
h := true
if isEmptyValue(reflect.ValueOf(h)) {
t.Errorf("Invalid empty value %v", h)
}
}
func TestWithContext_MarshalJSON(t *testing.T) {
tv := "value_test"
v := struct{ Test string }{Test: tv}
data, err := WithContext(IRI("http://example.com")).Marshal(v)
if err != nil {
t.Error(err)
}
if !bytes.Contains(data, []byte(ContextKw)) {
t.Errorf("%q not found in %s", ContextKw, data)
}
m := reflect.TypeOf(v)
mv := reflect.ValueOf(v)
for i := 0; i < m.NumField(); i++ {
f := m.Field(i)
v := mv.Field(i)
if !bytes.Contains(data, []byte(f.Name)) {
t.Errorf("%q not found in %s", f.Name, data)
}
if !bytes.Contains(data, []byte(v.String())) {
t.Errorf("%q not found in %s", v.String(), data)
}
}
}

143
fold.go Normal file
View file

@ -0,0 +1,143 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"bytes"
"unicode/utf8"
)
const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a'
smallLongEss = '\u017f'
)
// foldFunc returns one of four different case folding equivalence
// functions, from most general (and slow) to fastest:
//
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
// 3) asciiEqualFold, no special, but includes non-letters (including _)
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// * S maps to s and to U+017F 'ſ' Latin small letter long s
// * k maps to K and to U+212A '' Kelvin sign
// See https://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and
// should only be given s. It's not curried for performance reasons.
func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false
special := false // special letter
for _, b := range s {
if b >= utf8.RuneSelf {
return bytes.EqualFold
}
upper := b & caseMask
if upper < 'A' || upper > 'Z' {
nonLetter = true
} else if upper == 'K' || upper == 'S' {
// See above for why these letters are special.
special = true
}
}
if special {
return equalFoldRight
}
if nonLetter {
return asciiEqualFold
}
return simpleLetterEqualFold
}
// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
for _, sb := range s {
if len(t) == 0 {
return false
}
tb := t[0]
if tb < utf8.RuneSelf {
if sb != tb {
sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask {
return false
}
} else {
return false
}
}
t = t[1:]
continue
}
// sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t)
switch sb {
case 's', 'S':
if tr != smallLongEss {
return false
}
case 'k', 'K':
if tr != kelvin {
return false
}
default:
return false
}
t = t[size:]
}
if len(t) > 0 {
return false
}
return true
}
// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, sb := range s {
tb := t[i]
if sb == tb {
continue
}
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask {
return false
}
} else {
return false
}
}
return true
}
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, b := range s {
if b&caseMask != t[i]&caseMask {
return false
}
}
return true
}

116
fold_test.go Normal file
View file

@ -0,0 +1,116 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"bytes"
"strings"
"testing"
"unicode/utf8"
)
var foldTests = []struct {
fn func(s, t []byte) bool
s, t string
want bool
}{
{equalFoldRight, "", "", true},
{equalFoldRight, "a", "a", true},
{equalFoldRight, "", "a", false},
{equalFoldRight, "a", "", false},
{equalFoldRight, "a", "A", true},
{equalFoldRight, "AB", "ab", true},
{equalFoldRight, "AB", "ac", false},
{equalFoldRight, "sbkKc", "ſbKc", true},
{equalFoldRight, "SbKkc", "ſbKc", true},
{equalFoldRight, "SbKkc", "ſbKK", false},
{equalFoldRight, "e", "é", false},
{equalFoldRight, "s", "S", true},
{simpleLetterEqualFold, "", "", true},
{simpleLetterEqualFold, "abc", "abc", true},
{simpleLetterEqualFold, "abc", "ABC", true},
{simpleLetterEqualFold, "abc", "ABCD", false},
{simpleLetterEqualFold, "abc", "xxx", false},
{asciiEqualFold, "a_B", "A_b", true},
{asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent
}
func TestFold(t *testing.T) {
for i, tt := range foldTests {
if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want {
t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want)
}
truth := strings.EqualFold(tt.s, tt.t)
if truth != tt.want {
t.Errorf("strings.EqualFold doesn't agree with case %d", i)
}
}
}
func TestFoldAgainstUnicode(t *testing.T) {
const bufSize = 5
buf1 := make([]byte, 0, bufSize)
buf2 := make([]byte, 0, bufSize)
var runes []rune
for i := 0x20; i <= 0x7f; i++ {
runes = append(runes, rune(i))
}
runes = append(runes, kelvin, smallLongEss)
funcs := []struct {
name string
fold func(s, t []byte) bool
letter bool // must be ASCII letter
simple bool // must be simple ASCII letter (not 'S' or 'K')
}{
{
name: "equalFoldRight",
fold: equalFoldRight,
},
{
name: "asciiEqualFold",
fold: asciiEqualFold,
simple: true,
},
{
name: "simpleLetterEqualFold",
fold: simpleLetterEqualFold,
simple: true,
letter: true,
},
}
for _, ff := range funcs {
for _, r := range runes {
if r >= utf8.RuneSelf {
continue
}
if ff.letter && !isASCIILetter(byte(r)) {
continue
}
if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') {
continue
}
for _, r2 := range runes {
buf1 := append(buf1[:0], 'x')
buf2 := append(buf2[:0], 'x')
buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)]
buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)]
buf1 = append(buf1, 'x')
buf2 = append(buf2, 'x')
want := bytes.EqualFold(buf1, buf2)
if got := ff.fold(buf1, buf2); got != want {
t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want)
}
}
}
}
}
func isASCIILetter(b byte) bool {
return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z')
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/go-ap/jsonld
go 1.13

632
scanner.go Normal file
View file

@ -0,0 +1,632 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
// JSON value parser state machine.
// Just about at the limit of what is reasonable to write by hand.
// Some parts are a bit tedious, but overall it nicely factors out the
// otherwise common code from the multiple scanning functions
// in this package (Compact, Indent, checkValid, nextValue, etc).
//
// This file starts with two simple examples using the scanner
// before diving into the scanner itself.
import (
"strconv"
)
// Valid reports whether data is a valid JSON encoding.
func Valid(data []byte) bool {
return checkValid(data, &scanner{}) == nil
}
// checkValid verifies that data is valid JSON-encoded data.
// scan is passed in for use by checkValid to avoid an allocation.
func checkValid(data []byte, scan *scanner) error {
scan.reset()
for _, c := range data {
scan.bytes++
if scan.step(scan, c) == scanError {
return scan.err
}
}
if scan.eof() == scanError {
return scan.err
}
return nil
}
// nextValue splits data after the next whole JSON value,
// returning that value and the bytes that follow it as separate slices.
// scan is passed in for use by nextValue to avoid an allocation.
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
scan.reset()
for i, c := range data {
v := scan.step(scan, c)
if v >= scanEndObject {
switch v {
// probe the scanner with a space to determine whether we will
// get scanEnd on the next character. Otherwise, if the next character
// is not a space, scanEndTop allocates a needless error.
case scanEndObject, scanEndArray:
if scan.step(scan, ' ') == scanEnd {
return data[:i+1], data[i+1:], nil
}
case scanError:
return nil, nil, scan.err
case scanEnd:
return data[:i], data[i:], nil
}
}
}
if scan.eof() == scanError {
return nil, nil, scan.err
}
return data, nil, nil
}
// A SyntaxError is a description of a JSON syntax error.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
// A scanner is a JSON scanning state machine.
// Callers call scan.reset() and then pass bytes in one at a time
// by calling scan.step(&scan, c) for each byte.
// The return value, referred to as an opcode, tells the
// caller about significant parsing events like beginning
// and ending literals, objects, and arrays, so that the
// caller can follow along if it wishes.
// The return value scanEnd indicates that a single top-level
// JSON value has been completed, *before* the byte that
// just got passed in. (The indication must be delayed in order
// to recognize the end of numbers: is 123 a whole value or
// the beginning of 12345e+6?).
type scanner struct {
// The step is a func to be called to execute the next transition.
// Also tried using an integer constant and a single func
// with a switch, but using the func directly was 10% faster
// on a 64-bit Mac Mini, and it's nicer to read.
step func(*scanner, byte) int
// Reached end of top-level value.
endTop bool
// Stack of what we're in the middle of - array values, object keys, object values.
parseState []int
// Error that happened, if any.
err error
// 1-byte redo (see undo method)
redo bool
redoCode int
redoState func(*scanner, byte) int
// total bytes consumed, updated by decoder.Decode
bytes int64
}
// These values are returned by the state transition functions
// assigned to scanner.state and the method scanner.eof.
// They give details about the current state of the scan that
// callers might be interested to know about.
// It is okay to Ignore the return value of any particular
// call to scanner.state: if one call returns scanError,
// every subsequent call will return scanError too.
const (
// Continue.
scanContinue = iota // uninteresting byte
scanBeginLiteral // end implied by next result != scanContinue
scanBeginObject // begin object
scanObjectKey // just finished object key (string)
scanObjectValue // just finished non-last object value
scanEndObject // end object (implies scanObjectValue if possible)
scanBeginArray // begin array
scanArrayValue // just finished array value
scanEndArray // end array (implies scanArrayValue if possible)
scanSkipSpace // space byte; can skip; known to be last "continue" result
// Stop.
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
scanError // hit an error, scanner.err.
scanFindType // need to find the "jsonld:type" element of the object.
)
// These values are stored in the parseState stack.
// They give the current state of a composite value
// being scanned. If the parser is inside a nested value
// the parseState describes the nested state, outermost at entry 0.
const (
parseObjectKey = iota // parsing object key (before colon)
parseObjectValue // parsing object value (after colon)
parseArrayValue // parsing array value
)
// reset prepares the scanner for use.
// It must be called before calling s.step.
func (s *scanner) reset() {
s.step = stateBeginValue
s.parseState = s.parseState[0:0]
s.err = nil
s.redo = false
s.endTop = false
}
// eof tells the scanner that the end of input has been reached.
// It returns a scan status just as s.step does.
func (s *scanner) eof() int {
if s.err != nil {
return scanError
}
if s.endTop {
return scanEnd
}
s.step(s, ' ')
if s.endTop {
return scanEnd
}
if s.err == nil {
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
}
return scanError
}
// pushParseState pushes a new parse state p onto the parse stack.
func (s *scanner) pushParseState(p int) {
s.parseState = append(s.parseState, p)
}
// popParseState pops a parse state (already obtained) off the stack
// and updates s.step accordingly.
func (s *scanner) popParseState() {
n := len(s.parseState) - 1
s.parseState = s.parseState[0:n]
s.redo = false
if n == 0 {
s.step = stateEndTop
s.endTop = true
} else {
s.step = stateEndValue
}
}
func isSpace(c byte) bool {
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
}
// stateBeginValueOrEmpty is the state after reading `[`.
func stateBeginValueOrEmpty(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
if c == ']' {
return stateEndValue(s, c)
}
return stateBeginValue(s, c)
}
// stateBeginValue is the state at the beginning of the input.
func stateBeginValue(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
switch c {
case '{':
s.step = stateBeginStringOrEmpty
s.pushParseState(parseObjectKey)
return scanBeginObject
case '[':
s.step = stateBeginValueOrEmpty
s.pushParseState(parseArrayValue)
return scanBeginArray
case '"':
s.step = stateInString
return scanBeginLiteral
case '-':
s.step = stateNeg
return scanBeginLiteral
case '0': // beginning of 0.123
s.step = state0
return scanBeginLiteral
case 't': // beginning of true
s.step = stateT
return scanBeginLiteral
case 'f': // beginning of false
s.step = stateF
return scanBeginLiteral
case 'n': // beginning of null
s.step = stateN
return scanBeginLiteral
}
if '1' <= c && c <= '9' { // beginning of 1234.5
s.step = state1
return scanBeginLiteral
}
return s.error(c, "looking for beginning of value")
}
// stateBeginStringOrEmpty is the state after reading `{`.
func stateBeginStringOrEmpty(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
if c == '}' {
n := len(s.parseState)
s.parseState[n-1] = parseObjectValue
return stateEndValue(s, c)
}
return stateBeginString(s, c)
}
// stateBeginString is the state after reading `{"key": value,`.
func stateBeginString(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
if c == '"' {
s.step = stateInString
return scanBeginLiteral
}
return s.error(c, "looking for beginning of object key string")
}
// stateEndValue is the state after completing a value,
// such as after reading `{}` or `true` or `["x"`.
func stateEndValue(s *scanner, c byte) int {
n := len(s.parseState)
if n == 0 {
// Completed top-level before the current byte.
s.step = stateEndTop
s.endTop = true
return stateEndTop(s, c)
}
if c <= ' ' && isSpace(c) {
s.step = stateEndValue
return scanSkipSpace
}
ps := s.parseState[n-1]
switch ps {
case parseObjectKey:
if c == ':' {
s.parseState[n-1] = parseObjectValue
s.step = stateBeginValue
return scanObjectKey
}
return s.error(c, "after object key")
case parseObjectValue:
if c == ',' {
s.parseState[n-1] = parseObjectKey
s.step = stateBeginString
return scanObjectValue
}
if c == '}' {
s.popParseState()
return scanEndObject
}
return s.error(c, "after object key:value pair")
case parseArrayValue:
if c == ',' {
s.step = stateBeginValue
return scanArrayValue
}
if c == ']' {
s.popParseState()
return scanEndArray
}
return s.error(c, "after array element")
}
return s.error(c, "")
}
// stateEndTop is the state after finishing the top-level value,
// such as after reading `{}` or `[1,2,3]`.
// Only space characters should be seen now.
func stateEndTop(s *scanner, c byte) int {
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
// Complain about non-space byte on next call.
s.error(c, "after top-level value")
}
return scanEnd
}
// stateInString is the state after reading `"`.
func stateInString(s *scanner, c byte) int {
if c == '"' {
s.step = stateEndValue
return scanContinue
}
if c == '\\' {
s.step = stateInStringEsc
return scanContinue
}
if c < 0x20 {
return s.error(c, "in string literal")
}
return scanContinue
}
// stateInStringEsc is the state after reading `"\` during a quoted string.
func stateInStringEsc(s *scanner, c byte) int {
switch c {
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
s.step = stateInString
return scanContinue
case 'u':
s.step = stateInStringEscU
return scanContinue
}
return s.error(c, "in string escape code")
}
// stateInStringEscU is the state after reading `"\u` during a quoted string.
func stateInStringEscU(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU1
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
func stateInStringEscU1(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU12
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
func stateInStringEscU12(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU123
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
func stateInStringEscU123(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInString
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateNeg is the state after reading `-` during a number.
func stateNeg(s *scanner, c byte) int {
if c == '0' {
s.step = state0
return scanContinue
}
if '1' <= c && c <= '9' {
s.step = state1
return scanContinue
}
return s.error(c, "in numeric literal")
}
// state1 is the state after reading a non-zero integer during a number,
// such as after reading `1` or `100` but not `0`.
func state1(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = state1
return scanContinue
}
return state0(s, c)
}
// state0 is the state after reading `0` during a number.
func state0(s *scanner, c byte) int {
if c == '.' {
s.step = stateDot
return scanContinue
}
if c == 'e' || c == 'E' {
s.step = stateE
return scanContinue
}
return stateEndValue(s, c)
}
// stateDot is the state after reading the integer and decimal point in a number,
// such as after reading `1.`.
func stateDot(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = stateDot0
return scanContinue
}
return s.error(c, "after decimal point in numeric literal")
}
// stateDot0 is the state after reading the integer, decimal point, and subsequent
// digits of a number, such as after reading `3.14`.
func stateDot0(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
return scanContinue
}
if c == 'e' || c == 'E' {
s.step = stateE
return scanContinue
}
return stateEndValue(s, c)
}
// stateE is the state after reading the mantissa and e in a number,
// such as after reading `314e` or `0.314e`.
func stateE(s *scanner, c byte) int {
if c == '+' || c == '-' {
s.step = stateESign
return scanContinue
}
return stateESign(s, c)
}
// stateESign is the state after reading the mantissa, e, and sign in a number,
// such as after reading `314e-` or `0.314e+`.
func stateESign(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = stateE0
return scanContinue
}
return s.error(c, "in exponent of numeric literal")
}
// stateE0 is the state after reading the mantissa, e, optional sign,
// and at least one digit of the exponent in a number,
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
func stateE0(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
return scanContinue
}
return stateEndValue(s, c)
}
// stateT is the state after reading `t`.
func stateT(s *scanner, c byte) int {
if c == 'r' {
s.step = stateTr
return scanContinue
}
return s.error(c, "in literal true (expecting 'r')")
}
// stateTr is the state after reading `tr`.
func stateTr(s *scanner, c byte) int {
if c == 'u' {
s.step = stateTru
return scanContinue
}
return s.error(c, "in literal true (expecting 'u')")
}
// stateTru is the state after reading `tru`.
func stateTru(s *scanner, c byte) int {
if c == 'e' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal true (expecting 'e')")
}
// stateF is the state after reading `f`.
func stateF(s *scanner, c byte) int {
if c == 'a' {
s.step = stateFa
return scanContinue
}
return s.error(c, "in literal false (expecting 'a')")
}
// stateFa is the state after reading `fa`.
func stateFa(s *scanner, c byte) int {
if c == 'l' {
s.step = stateFal
return scanContinue
}
return s.error(c, "in literal false (expecting 'l')")
}
// stateFal is the state after reading `fal`.
func stateFal(s *scanner, c byte) int {
if c == 's' {
s.step = stateFals
return scanContinue
}
return s.error(c, "in literal false (expecting 's')")
}
// stateFals is the state after reading `fals`.
func stateFals(s *scanner, c byte) int {
if c == 'e' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal false (expecting 'e')")
}
// stateN is the state after reading `n`.
func stateN(s *scanner, c byte) int {
if c == 'u' {
s.step = stateNu
return scanContinue
}
return s.error(c, "in literal null (expecting 'u')")
}
// stateNu is the state after reading `nu`.
func stateNu(s *scanner, c byte) int {
if c == 'l' {
s.step = stateNul
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}
// stateNul is the state after reading `nul`.
func stateNul(s *scanner, c byte) int {
if c == 'l' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}
// stateError is the state after reaching a syntax error,
// such as after reading `[1}` or `5.1.2`.
func stateError(s *scanner, c byte) int {
return scanError
}
// error records an error and switches to the error state.
func (s *scanner) error(c byte, context string) int {
s.step = stateError
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
return scanError
}
// quoteChar formats c as a quoted character literal
func quoteChar(c byte) string {
// special cases - different from quoted strings
if c == '\'' {
return `'\''`
}
if c == '"' {
return `'"'`
}
// use quoted string with different quotation marks
s := strconv.Quote(string(c))
return "'" + s[1:len(s)-1] + "'"
}
// undo causes the scanner to return scanCode from the next state transition.
// This gives callers a simple 1-byte undo mechanism.
func (s *scanner) undo(scanCode int) {
if s.redo {
panic("json: invalid use of scanner")
}
s.redoCode = scanCode
s.redoState = s.step
s.step = stateRedo
s.redo = true
}
// stateRedo helps implement the scanner's 1-byte undo.
func stateRedo(s *scanner, c byte) int {
s.redo = false
s.step = s.redoState
return s.redoCode
}

29
scanner_test.go Normal file
View file

@ -0,0 +1,29 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"testing"
)
var validTests = []struct {
data string
ok bool
}{
{`foo`, false},
{`}{`, false},
{`{]`, false},
{`{}`, true},
{`{"foo":"bar"}`, true},
{`{"foo":"bar","bar":{"baz":["qux"]}}`, true},
}
func TestValid(t *testing.T) {
for _, tt := range validTests {
if ok := Valid([]byte(tt.data)); ok != tt.ok {
t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok)
}
}
}

218
tables.go Normal file
View file

@ -0,0 +1,218 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import "unicode/utf8"
// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
var safeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': true,
'=': true,
'>': true,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}
// htmlSafeSet holds the value true if the ASCII character with the given
// array position can be safely represented inside a JSON string, embedded
// inside of HTML <script> tags, without any additional escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), the backslash character ("\"), HTML opening and closing
// tags ("<" and ">"), and the ampersand ("&").
var htmlSafeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': false,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': false,
'=': true,
'>': false,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}

44
tags.go Normal file
View file

@ -0,0 +1,44 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"strings"
)
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

28
tags_test.go Normal file
View file

@ -0,0 +1,28 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"testing"
)
func TestTagParsing(t *testing.T) {
name, opts := parseTag("field,foobar,foo")
if name != "field" {
t.Fatalf("name = %q, want field", name)
}
for _, tt := range []struct {
opt string
want bool
}{
{"foobar", true},
{"foo", true},
{"bar", false},
} {
if opts.Contains(tt.opt) != tt.want {
t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
}
}
}