Skip to content

Commit

Permalink
improve crossrefxml writer
Browse files Browse the repository at this point in the history
  • Loading branch information
mfenner committed May 12, 2024
1 parent 72a7786 commit 1755489
Show file tree
Hide file tree
Showing 19 changed files with 768 additions and 64 deletions.
11 changes: 10 additions & 1 deletion cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ commonmeta 10.5555/12345678`,
var err error
var data commonmeta.Data

depositor, _ := cmd.Flags().GetString("depositor")
email, _ := cmd.Flags().GetString("email")
registrant, _ := cmd.Flags().GetString("registrant")

if len(args) == 0 {
fmt.Println("Please provide an input")
return
Expand Down Expand Up @@ -111,7 +115,12 @@ commonmeta 10.5555/12345678`,
} else if to == "schemaorg" {
output, jsErr = schemaorg.Write(data)
} else if to == "crossrefxml" {
output, jsErr = crossrefxml.Write(data)
account := crossrefxml.Account{
Depositor: depositor,
Email: email,
Registrant: registrant,
}
output, jsErr = crossrefxml.Write(data, account)
}

if err != nil {
Expand Down
24 changes: 16 additions & 8 deletions cmd/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,29 @@ package cmd
import (
"fmt"

"github.com/front-matter/commonmeta/doiutils"
"github.com/front-matter/commonmeta/utils"
"github.com/spf13/cobra"
)

// encodeCmd represents the encode command
var encodeCmd = &cobra.Command{
Use: "encode",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Short: "Generate a random DOI string given a prefix",
Long: `Generate a random DOI string given a prefix. For example: commonmeta encode 10.5555`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("encode called")
if len(args) == 0 {
fmt.Println("Please provide an input")
return
}
input := args[0]
prefix, ok := doiutils.ValidatePrefix(input)
if !ok {
fmt.Println("Invalid prefix")
return
}
doi := utils.EncodeDOI(prefix)
fmt.Println(doi)
},
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/front-matter/commonmeta/crossref"
"github.com/front-matter/commonmeta/crossrefxml"
"github.com/front-matter/commonmeta/jsonfeed"

"github.com/front-matter/commonmeta/datacite"

Expand Down Expand Up @@ -72,6 +73,8 @@ var listCmd = &cobra.Command{
data, err = crossrefxml.LoadAll(str)
} else if str != "" && from == "datacite" {
data, err = datacite.LoadAll(str)
} else if str != "" && from == "jsonfeed" {
data, err = jsonfeed.LoadAll(str)
} else if from == "crossref" {
data, err = crossref.FetchAll(number, member, type_, sample, hasORCID, hasROR, hasReferences, hasRelation, hasAbstract, hasAward, hasLicense, hasArchive)
} else if from == "datacite" {
Expand Down
7 changes: 7 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ func init() {
rootCmd.PersistentFlags().BoolP("has-award", "", false, "has award")
rootCmd.PersistentFlags().BoolP("has-license", "", false, "has license")
rootCmd.PersistentFlags().BoolP("has-archive", "", false, "has archive")

// needed for DOI registration
rootCmd.PersistentFlags().StringP("prefix", "", "", "DOI prefix")

rootCmd.PersistentFlags().StringP("depositor", "", "", "Crossref account depositor")
rootCmd.PersistentFlags().StringP("email", "", "", "Crossref account email")
rootCmd.PersistentFlags().StringP("registrant", "", "", "Crossref account registrant")
}
44 changes: 44 additions & 0 deletions cmd/updateGhostAPI.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright © 2024 Front Matter <[email protected]>
*/
package cmd

import (
"fmt"

"github.com/front-matter/commonmeta/ghost"
"github.com/spf13/cobra"
)

// encodeCmd represents the encode command
var updateGhostAPICmd = &cobra.Command{
Use: "update-ghost-post",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Println("Please provide an input")
return
}
id := args[0]
apiKey, _ := cmd.Flags().GetString("api-key")
apiURL, _ := cmd.Flags().GetString("api-url")
output, err := ghost.UpdateGhostPost(id, apiKey, apiURL)
if err != nil {
fmt.Println(err)
}
fmt.Println(output)
},
}

func init() {
rootCmd.AddCommand(updateGhostAPICmd)

rootCmd.PersistentFlags().StringP("api-key", "", "", "Ghost API key")
rootCmd.PersistentFlags().StringP("api-url", "", "", "Ghost API URL")
}
106 changes: 106 additions & 0 deletions crockford/crockford.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package crockford

import (
"fmt"
"math"
"math/rand/v2"
"strconv"
"strings"
)

// Generate, encode and decode random base32 identifiers.
// This encoder/decoder:
// - uses Douglas Crockford Base32 encoding: https://www.crockford.com/base32.html
// - allows for ISO 7064 checksum
// - encodes the checksum using only characters in the base32 set
// - produces string that are URI-friendly (no '=' or '/' for instance)
// This is based on: https://github.com/front-matter/base32-url

// NO i, l, o or u
const ENCODING_CHARS = "0123456789abcdefghjkmnpqrstvwxyz"

// Encode a number to a URI-friendly Douglas Crockford base32 string.
// optionally split with ' -' every n characters, pad with zeros to a minimum length,
// and append a checksum using modulo 97-10 (ISO 7064).
func Encode(number int64, splitEvery int, length int, checksum bool) string {
encoded := ""
originalNumber := number
if number == 0 {
encoded = "0"
} else {
for number > 0 {
remainder := number % 32
number /= 32
encoded = string(ENCODING_CHARS[remainder]) + encoded
}
}

if checksum && length > 2 {
length -= 2
}
if length > 0 && len(encoded) < length {
encoded = strings.Repeat("0", length-len(encoded)) + encoded
}
if length > 0 {
encoded = encoded[:length]
}

if checksum {
computedChecksum := 97 - ((100 * originalNumber) % 97) + 1
encoded += fmt.Sprintf("%02d", computedChecksum)
}

if splitEvery > 0 {
var splits []string
for i := 0; i < len(encoded); i += splitEvery {
nn := i + splitEvery
if nn > len(encoded) {
nn = len(encoded)
}
splits = append(splits, string(encoded[i:nn]))
}
encoded = strings.Join(splits, "-")
}

return encoded
}

// Generate a random Cockroft base32 string.
// optionally split with ' -' every n characters, pad with zeros to a minimum length,
// and append a checksum using modulo 97-10 (ISO 7064).
func Generate(length int, splitEvery int, checksum bool) string {
if checksum && length < 3 {
panic("Invalid 'length'. Must be >= 3 if checksum enabled.")
}
// generate a random number between 0 and 32^length
n := math.Pow(float64(32), float64(length))
number := int64(rand.IntN(int(n)))
str := Encode(number, splitEvery, length, checksum)
return str
}

// Decode a URI-friendly Douglas Crockford base32 string to a number.
func Decode(encoded string, checksum bool) (int64, error) {
var encodedChecksum int64
number := int64(0)
encoded = strings.ReplaceAll(encoded, "-", "")
if checksum {
encodedChecksum, _ = strconv.ParseInt(encoded[len(encoded)-2:], 10, 64)
encoded = encoded[:len(encoded)-2]
}
for _, c := range encoded {
number *= 32
pos := strings.Index(ENCODING_CHARS, string(c))
if pos == -1 {
return 0, fmt.Errorf("invalid character: %s", string(c))
}
number += int64(pos)
}
if checksum {
computedChecksum := 97 - ((100 * number) % 97) + 1
if computedChecksum != encodedChecksum {
return 0, fmt.Errorf("invalid checksum: %d", encodedChecksum)
}
}
return number, nil
}
83 changes: 83 additions & 0 deletions crockford/crockford_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package crockford_test

import (
"testing"

"github.com/front-matter/commonmeta/crockford"
)

func TestEncode(t *testing.T) {
t.Parallel()
type testCase struct {
input int64
splitEvery int
length int
checksum bool
want string
}
testCases := []testCase{
{input: 0, want: "0", splitEvery: 0, length: 0, checksum: false},
{input: 1234, want: "16j", splitEvery: 0, length: 0, checksum: false},
{input: 1234, want: "16-j", splitEvery: 2, length: 0, checksum: false},
{input: 1234, want: "01-6j", splitEvery: 2, length: 4, checksum: false},
{input: 538751765283013, want: "f9zqn-sf065", splitEvery: 5, length: 10, checksum: false},
{input: 712266282077, want: "mqb61-x2x15", splitEvery: 5, length: 10, checksum: true},
}
for _, tc := range testCases {
got := crockford.Encode(tc.input, tc.splitEvery, tc.length, tc.checksum)
if tc.want != got {
t.Errorf("Encode(%v): want %v, got %v",
tc.input, tc.want, got)
}
}
}

func TestGenerate(t *testing.T) {
t.Parallel()
type testCase struct {
length int
splitEvery int
checksum bool
}
testCases := []testCase{
{length: 4, splitEvery: 0, checksum: false},
{length: 10, splitEvery: 5, checksum: false},
{length: 10, splitEvery: 5, checksum: true},
}
for _, tc := range testCases {
got := crockford.Generate(tc.length, tc.splitEvery, tc.checksum)
length := tc.length
if tc.splitEvery > 0 {
length += tc.length/tc.splitEvery - 1
}
if len(got) != length {
t.Errorf("Generate(%v): want length %v, got %v",
tc.length, tc.length, len(got))
}
}
}

func TestDecode(t *testing.T) {
t.Parallel()
type testCase struct {
input string
checksum bool
want int64
}
testCases := []testCase{
{input: "0", want: 0, checksum: false},
{input: "16j", want: 1234, checksum: false},
{input: "16-j", want: 1234, checksum: false},
{input: "01-6j", want: 1234, checksum: false},
{input: "f9zqn-sf065", want: 538751765283013, checksum: false},
{input: "mqb61-x2x15", want: 712266282077, checksum: true},
{input: "axgv5-6aq97", want: 375301249367, checksum: true},
}
for _, tc := range testCases {
got, err := crockford.Decode(tc.input, tc.checksum)
if tc.want != got {
t.Errorf("Decode(%v): want %v, got %v, error %v",
tc.input, tc.want, got, err)
}
}
}
10 changes: 5 additions & 5 deletions crossrefxml/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ type DOI struct {

type DOIRecord struct {
XMLName xml.Name `xml:"doi_record"`
Crossref Crossref `xml:"crossref"`
Crossref Crossref `xml:"body"`
}

type Crossref struct {
XMLName xml.Name `xml:"crossref"`
Xmlns string `xml:"xmlns,attr"`
SchemaLocation string `xml:"schemaLocation,attr"`
Version string `xml:"version,attr"`
XMLName xml.Name `xml:"body"`
Xmlns string `xml:"xmlns,attr,omitempty"`
SchemaLocation string `xml:"schemaLocation,attr,omitempty"`
Version string `xml:"version,attr,omitempty"`
Book *Book `xml:"book,omitempty"`
Conference *Conference `xml:"conference,omitempty"`
Database *Database `xml:"database,omitempty"`
Expand Down
Loading

0 comments on commit 1755489

Please sign in to comment.