Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ for _, keyword := range doc.Keywords().Values() {
}
```

`Strings` projects a term into display text, unwrapping `@list`, `@set`, and `@value` and reading `@id` or `name` from objects. It returns scalar spellings in source order and skips empty strings and nulls:

```go
fmt.Println(doc.Strings("keywords"))
fmt.Println(doc.Strings("license"))
```

Values expose `Kind`, `Text`, `Items`, `Fields`, and `Position`. `Items` returns only array elements; `Values` returns array elements or a single scalar or object. A missing field has kind `Missing` and no values, while explicit JSON `Null` remains a value. Numbers retain their spelling and precision. Dates retain their text.

`Author`, `Contributor`, `Maintainer`, `CopyrightHolder`, and `Funder` return ordered agent views, accepting either a single value or an array. `Agent.Kind` distinguishes people, organisations, strings, ID references, roles, and conflicting fields. `Value` preserves each original value; `Get` accesses its fields. `RoleName` and `Agents` expose a role and its nested agents without discarding the wrapper.
Expand Down
8 changes: 5 additions & 3 deletions model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package codemeta

func (d *Document) Name() string { return d.Get("name").Text() }
const nameTerm = "name"

func (d *Document) Name() string { return d.Get(nameTerm).Text() }
func (d *Document) Description() string { return d.Get("description").Text() }
func (d *Document) CodeRepository() Value { return d.Get("codeRepository") }
func (d *Document) SoftwareVersion() Value { return d.Get("version") }
Expand Down Expand Up @@ -39,7 +41,7 @@ type Agent struct {

func (a Agent) Value() Value { return a.value }
func (a Agent) Get(term string) Value { return resolvedGet(a.value, term, a.terms) }
func (a Agent) Name() string { return a.Get("name").Text() }
func (a Agent) Name() string { return a.Get(nameTerm).Text() }
func (a Agent) GivenName() string { return a.Get("givenName").Text() }
func (a Agent) FamilyName() string { return a.Get("familyName").Text() }
func (a Agent) RoleName() Value { return a.Get("roleName") }
Expand Down Expand Up @@ -72,7 +74,7 @@ func (a Agent) Kind() AgentKind {
if personType {
return AgentPerson
}
if orgType || a.Get("name").kind != Missing {
if orgType || a.Get(nameTerm).kind != Missing {
return AgentOrganization
}
return AgentUnknown
Expand Down
46 changes: 46 additions & 0 deletions strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package codemeta

// Strings projects a term's scalar spelling, object identifiers, or object names
// in source order. It unwraps JSON-LD lists, sets, and value objects and resolves
// keys using each object's context. Empty strings and nulls are skipped. The
// original values remain available through Get.
func (d *Document) Strings(term string) []string {
if d == nil {
return nil
}
var terms map[string]termDefinition
if d.context.usable() {
terms = contextTerms[d.context.version]
}
return stringValues(d.Get(term), terms)
}

func stringValues(value Value, terms map[string]termDefinition) []string {
var values []string
for _, item := range value.Values() {
if item.kind == String || item.kind == Number || item.kind == Boolean {
if item.text != "" {
values = append(values, item.text)
}
continue
}
if item.kind != Object {
continue
}
localTerms := scopedTerms(item, terms)
// An empty identifier means absent, so the search falls through to a name;
// an empty list, set, or value is genuinely empty and stops the search.
for _, key := range []string{keywordList, keywordSet, keywordValue, keywordID, nameTerm} {
field := resolvedGet(item, key, localTerms)
if field.kind == Missing {
continue
}
projected := stringValues(field, localTerms)
values = append(values, projected...)
if key != keywordID || len(projected) != 0 {
break
}
}
}
return values
}
46 changes: 46 additions & 0 deletions strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package codemeta_test

import (
"reflect"
"testing"

"github.com/git-pkgs/codemeta"
)

func TestDocumentStrings(t *testing.T) {
for _, tc := range []struct {
name, input, term string
want []string
}{
{"scalar", `{"keywords":"science"}`, "keywords", []string{"science"}},
{"array", `{"keywords":["science","metadata"]}`, "keywords", []string{"science", "metadata"}},
{"set", `{"keywords":{"@set":["science","metadata"]}}`, "keywords", []string{"science", "metadata"}},
{"list", `{"keywords":{"@list":["science",{"@value":"metadata"}]}}`, "keywords", []string{"science", "metadata"}},
{"id alias", `{"@context":"https://w3id.org/codemeta/3.0","license":{"id":"https://spdx.org/licenses/MIT"}}`, "license", []string{"https://spdx.org/licenses/MIT"}},
{"prefixed names", `{"@context":"https://w3id.org/codemeta/3.0","schema:programmingLanguage":[{"schema:name":"Go"},{"http://schema.org/name":"Ruby"}]}`, "programmingLanguage", []string{"Go", "Ruby"}},
{"identifier first", `{"license":{"@id":"MIT","name":"MIT License"}}`, "license", []string{"MIT"}},
{"empty identifier", `{"license":{"@id":"","name":"MIT License"}}`, "license", []string{"MIT License"}},
{"exact key first", `{"@context":"https://w3id.org/codemeta/3.0","programmingLanguage":{"name":"Go","schema:name":"Ruby"}}`, "programmingLanguage", []string{"Go"}},
{"local context", `{"programmingLanguage":{"@context":"https://w3id.org/codemeta/3.0","schema:name":"Go"}}`, "programmingLanguage", []string{"Go"}},
{"reset context", `{"@context":"https://w3id.org/codemeta/3.0","programmingLanguage":{"@context":null,"schema:name":"Go"}}`, "programmingLanguage", nil},
{"unsupported context", `{"@context":"https://example.org/context","programmingLanguage":{"schema:name":"Go"}}`, "programmingLanguage", nil},
{"scalars", `{"keywords":[null,4,true,"",{},"science"]}`, "keywords", []string{"4", "true", "science"}},
{"number spelling", `{"copyrightYear":2020}`, "copyrightYear", []string{"2020"}},
{"number value object", `{"copyrightYear":{"@value":2020}}`, "copyrightYear", []string{"2020"}},
{"missing", `{}`, "keywords", nil},
} {
t.Run(tc.name, func(t *testing.T) {
doc, err := codemeta.Parse([]byte(tc.input))
if err != nil {
t.Fatal(err)
}
before := doc.Get(tc.term)
if got := doc.Strings(tc.term); !reflect.DeepEqual(got, tc.want) {
t.Fatalf("Strings(%q) = %v, want %v", tc.term, got, tc.want)
}
if !reflect.DeepEqual(doc.Get(tc.term), before) {
t.Fatal("projection changed the source value")
}
})
}
}
Loading