github.com/readium/readium-lcp-server@v0.0.0-20240509124024-799e77a0bbd6/rwpm/metadata.go (about)

     1  // Copyright 2020 Readium Foundation. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license
     3  // that can be found in the LICENSE file exposed on Github (readium) in the project repository.
     4  
     5  package rwpm
     6  
     7  import (
     8  	"encoding/json"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  // Metadata for the default context in WebPub
    14  type Metadata struct {
    15  	Type               string        `json:"@type,omitempty"`
    16  	ConformsTo         string        `json:"conformsTo,omitempty"`
    17  	Identifier         string        `json:"identifier,omitempty"`
    18  	Title              MultiLanguage `json:"title"`
    19  	Subtitle           MultiLanguage `json:"subtitle,omitempty"`
    20  	SortAs             string        `json:"sortAs,omitempty"`
    21  	Description        string        `json:"description,omitempty"`
    22  	Language           MultiString   `json:"language,omitempty"`
    23  	ReadingProgression string        `json:"readingProgression,omitempty"`
    24  	//
    25  	Modified  *time.Time `json:"modified,omitempty"`
    26  	Published *Date      `json:"published,omitempty"`
    27  	// contributors
    28  	Publisher   Contributors `json:"publisher,omitempty"`
    29  	Artist      Contributors `json:"artist,omitempty"`
    30  	Author      Contributors `json:"author,omitempty"`
    31  	Colorist    Contributors `json:"colorist,omitempty"`
    32  	Contributor Contributors `json:"contributor,omitempty"`
    33  	Editor      Contributors `json:"editor,omitempty"`
    34  	Illustrator Contributors `json:"illustrator,omitempty"`
    35  	Imprint     Contributors `json:"imprint,omitempty"`
    36  	Inker       Contributors `json:"inker,omitempty"`
    37  	Letterer    Contributors `json:"letterer,omitempty"`
    38  	Narrator    Contributors `json:"narrator,omitempty"`
    39  	Penciler    Contributors `json:"penciler,omitempty"`
    40  	Translator  Contributors `json:"translator,omitempty"`
    41  	// other descriptive metadata
    42  	Subject       Subjects `json:"subject,omitempty"`
    43  	Duration      float32  `json:"duration,omitempty"`
    44  	NumberOfPages int      `json:"numberOfPages,omitempty"`
    45  	Abridged      bool     `json:"abridged,omitempty"`
    46  	// collections & series
    47  	BelongsTo *BelongsTo `json:"belongsTo,omitempty"`
    48  
    49  	OtherMetadata []Meta `json:"-"` //Extension point for other metadata
    50  }
    51  
    52  // DateOrDatetime struct
    53  type DateOrDatetime time.Time
    54  
    55  // UnmarshalJSON unmarshalls DateOrDatetime
    56  func (d *DateOrDatetime) UnmarshalJSON(b []byte) error {
    57  
    58  	s := strings.Trim(string(b), "\"")
    59  	// process a date
    60  	if len(s) == 11 && strings.Index(s, "Z") == 10 { // a date may end with a 'Z'
    61  		s = strings.TrimRight(s, "Z")
    62  	}
    63  	if len(s) == 10 {
    64  		s = s + "T00:00:00Z"
    65  	}
    66  
    67  	// process a date-time, RFC 3999 compliant
    68  	date, err := time.Parse(time.RFC3339, s)
    69  	*d = DateOrDatetime(date)
    70  	return err
    71  }
    72  
    73  // MarshalJSON marshalls DateOrDatetime
    74  func (d DateOrDatetime) MarshalJSON() ([]byte, error) {
    75  	return json.Marshal(time.Time(d))
    76  }
    77  
    78  // Date struct
    79  type Date time.Time
    80  
    81  // UnmarshalJSON unmarshalls Date
    82  func (d *Date) UnmarshalJSON(b []byte) error {
    83  
    84  	// trim the quotes around the value
    85  	s := string(b[1 : len(b)-1])
    86  	// process a date
    87  	if len(s) == 11 && strings.Index(s, "Z") == 10 { // a date may end with a 'Z'
    88  		s = strings.TrimRight(s, "Z")
    89  	}
    90  	if len(s) == 10 {
    91  		s = s + "T00:00:00Z"
    92  	}
    93  
    94  	// process a date-time, RFC 3999 compliant
    95  	date, err := time.Parse(time.RFC3339, s)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	*d = Date(date)
   100  	return nil
   101  }
   102  
   103  // MarshalJSON marshalls Date
   104  func (d Date) MarshalJSON() ([]byte, error) {
   105  
   106  	date := time.Time(d)
   107  	return []byte(date.Format("\"2006-01-02\"")), nil
   108  }
   109  
   110  // Meta is a generic structure for other metadata
   111  type Meta struct {
   112  	Property string
   113  	Value    interface{}
   114  	Children []Meta
   115  }
   116  
   117  // Properties object used to link properties
   118  // Used also in Rendition for fxl
   119  type Properties struct {
   120  	Contains     []string   `json:"contains,omitempty"`
   121  	Layout       string     `json:"layout,omitempty"`
   122  	MediaOverlay string     `json:"mediaOverlay,omitempty"`
   123  	Orientation  string     `json:"orientation,omitempty"`
   124  	Overflow     string     `json:"overflow,omitempty"`
   125  	Page         string     `json:"page,omitempty"`
   126  	Spread       string     `json:"spread,omitempty"`
   127  	Encrypted    *Encrypted `json:"encrypted,omitempty"`
   128  }
   129  
   130  // Encrypted contains metadata from encryption xml
   131  type Encrypted struct {
   132  	Scheme         string `json:"scheme,omitempty"`
   133  	Profile        string `json:"profile,omitempty"`
   134  	Algorithm      string `json:"algorithm,omitempty"`
   135  	Compression    string `json:"compression,omitempty"`
   136  	OriginalLength int    `json:"originalLength,omitempty"`
   137  }
   138  
   139  // Subjects is an array of subjects
   140  type Subjects []Subject
   141  
   142  // Subject of a publication
   143  type Subject struct {
   144  	Name   string `json:"name"`
   145  	SortAs string `json:"sortAs,omitempty"`
   146  	Scheme string `json:"scheme,omitempty"`
   147  	Code   string `json:"code,omitempty"`
   148  }
   149  
   150  // UnmarshalJSON unmarshals Subjects
   151  func (s *Subjects) UnmarshalJSON(b []byte) error {
   152  
   153  	var sbjs []Subject
   154  	sbjs = make([]Subject, 1)
   155  	var sbj Subject
   156  
   157  	// literal value
   158  	var literal string
   159  	var err error
   160  	if err = json.Unmarshal(b, &literal); err == nil {
   161  		sbjs[0].Name = literal
   162  
   163  		// object value
   164  	} else if err = json.Unmarshal(b, &sbj); err == nil {
   165  		sbjs[0] = sbj
   166  
   167  		// array value
   168  	} else {
   169  		err = json.Unmarshal(b, &sbjs)
   170  	}
   171  	if err == nil {
   172  		*s = sbjs
   173  		return nil
   174  	}
   175  	return err
   176  }
   177  
   178  // MarshalJSON marshals Subjects
   179  func (s Subjects) MarshalJSON() ([]byte, error) {
   180  
   181  	// literal value
   182  	if len(s) == 1 && s[0].Name != "" &&
   183  		s[0].SortAs == "" && s[0].Scheme == "" && s[0].Code == "" {
   184  		return json.Marshal(s[0].Name)
   185  	}
   186  
   187  	// object value
   188  	if len(s) == 1 {
   189  		sbj := s[0]
   190  		return json.Marshal(sbj)
   191  	}
   192  
   193  	// array value
   194  	var sbjs []Subject
   195  	sbjs = s
   196  	return json.Marshal(sbjs)
   197  }
   198  
   199  // Add adds a value to a subject array
   200  func (s *Subjects) Add(item Subject) {
   201  
   202  	*s = append(*s, item)
   203  }
   204  
   205  // UnmarshalJSON unmarshals Subject
   206  func (s *Subject) UnmarshalJSON(b []byte) error {
   207  
   208  	var literal string
   209  	var err error
   210  	if err = json.Unmarshal(b, &literal); err == nil {
   211  		s.Name = literal
   212  		s.SortAs = ""
   213  		s.Scheme = ""
   214  		s.Code = ""
   215  		return nil
   216  	}
   217  	type Alias Subject
   218  	var sbjAlias Alias
   219  	err = json.Unmarshal(b, &sbjAlias)
   220  	if err != nil {
   221  		return err
   222  	}
   223  	*s = Subject(sbjAlias)
   224  	return nil
   225  }
   226  
   227  // MarshalJSON marshals Subject
   228  func (s Subject) MarshalJSON() ([]byte, error) {
   229  
   230  	// literal value
   231  	if s.Name != "" && s.SortAs == "" && s.Scheme == "" && s.Code == "" {
   232  		return json.Marshal(s.Name)
   233  	}
   234  	type Alias Subject
   235  	sbjAlias := Alias{s.Name, s.SortAs, s.Scheme, s.Code}
   236  	return json.Marshal(sbjAlias)
   237  }
   238  
   239  // BelongsTo is a list of collections/series that a publication belongs to
   240  type BelongsTo struct {
   241  	Series     []Collection `json:"series,omitempty"`
   242  	Collection []Collection `json:"collection,omitempty"`
   243  }
   244  
   245  // Collection construct used for collection/serie metadata
   246  type Collection struct {
   247  	Name       string  `json:"name"`
   248  	SortAs     string  `json:"sort_as,omitempty"`
   249  	Identifier string  `json:"identifier,omitempty"`
   250  	Position   float32 `json:"position,omitempty"`
   251  }
   252  
   253  // Contributors is an array of contributors
   254  type Contributors []Contributor
   255  
   256  // UnmarshalJSON unmarshals contributors
   257  func (c *Contributors) UnmarshalJSON(b []byte) error {
   258  
   259  	var ctors []Contributor
   260  	ctors = make([]Contributor, 1)
   261  	var ctor Contributor
   262  
   263  	// literal value
   264  	var literal string
   265  	var err error
   266  	if err = json.Unmarshal(b, &literal); err == nil {
   267  		ctors[0].Name.SetDefault(literal)
   268  
   269  		// object value
   270  	} else if err = json.Unmarshal(b, &ctor); err == nil {
   271  		ctors[0] = ctor
   272  
   273  		// array value
   274  	} else {
   275  		err = json.Unmarshal(b, &ctors)
   276  	}
   277  	if err == nil {
   278  		*c = ctors
   279  		return nil
   280  	}
   281  	return err
   282  }
   283  
   284  // MarshalJSON marshals Contributors
   285  func (c Contributors) MarshalJSON() ([]byte, error) {
   286  
   287  	// literal value
   288  	if len(c) == 1 && c[0].Name.Text() != "" &&
   289  		c[0].Identifier == "" && c[0].SortAs == "" && c[0].Role == "" {
   290  		return json.Marshal(c[0].Name.Text())
   291  	}
   292  
   293  	// object value
   294  	if len(c) == 1 {
   295  		ctor := c[0]
   296  		return json.Marshal(ctor)
   297  	}
   298  
   299  	// array value
   300  	var ctors []Contributor
   301  	ctors = c
   302  	return json.Marshal(ctors)
   303  }
   304  
   305  // AddName adds a Contributor to Contributors
   306  func (c *Contributors) AddName(name string) {
   307  
   308  	var ctor Contributor
   309  	ctor.Name.SetDefault(name)
   310  	c.Add(ctor)
   311  }
   312  
   313  // Add adds a Contributor to Contributors
   314  func (c *Contributors) Add(ctor Contributor) {
   315  
   316  	*c = append(*c, ctor)
   317  }
   318  
   319  // Name gets the name of a contributor
   320  func (c Contributors) Name() string {
   321  
   322  	if len(c) == 1 && c[0].Name.Text() != "" {
   323  		return c[0].Name.Text()
   324  	}
   325  	return ""
   326  }
   327  
   328  // Contributor construct used internally for all contributors
   329  type Contributor struct {
   330  	Name       MultiLanguage `json:"name,omitempty"`
   331  	SortAs     string        `json:"sortAs,omitempty"`
   332  	Identifier string        `json:"identifier,omitempty"`
   333  	Role       string        `json:"role,omitempty"`
   334  }
   335  
   336  // UnmarshalJSON unmarshals Contributor
   337  func (c *Contributor) UnmarshalJSON(b []byte) error {
   338  
   339  	var literal string
   340  	var err error
   341  	if err = json.Unmarshal(b, &literal); err == nil {
   342  		c.Name = make(map[string]string)
   343  		c.Name["und"] = literal
   344  		c.SortAs = ""
   345  		c.Identifier = ""
   346  		c.Role = ""
   347  		return nil
   348  	}
   349  	type Alias Contributor
   350  	var ctorAlias Alias
   351  	err = json.Unmarshal(b, &ctorAlias)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	*c = Contributor(ctorAlias)
   356  	return nil
   357  }
   358  
   359  // MarshalJSON marshals Contributor
   360  func (c Contributor) MarshalJSON() ([]byte, error) {
   361  
   362  	// literal value
   363  	if c.Name["und"] != "" && c.Identifier == "" && c.Role == "" && c.SortAs == "" {
   364  		return json.Marshal(c.Name["und"])
   365  	}
   366  	type Alias Contributor
   367  	ctorAlias := Alias{c.Name, c.SortAs, c.Identifier, c.Role}
   368  	return json.Marshal(ctorAlias)
   369  }
   370  
   371  // MultiLanguage stores one or more values indexed by language.
   372  type MultiLanguage map[string]string
   373  
   374  // UnmarshalJSON unmarshalls Multilanguage
   375  // The "und" (undefined)language corresponds to a literal value
   376  func (m *MultiLanguage) UnmarshalJSON(b []byte) error {
   377  
   378  	var mmap map[string]string
   379  	mmap = make(map[string]string)
   380  
   381  	var literal string
   382  	var err error
   383  	if err = json.Unmarshal(b, &literal); err == nil {
   384  		mmap["und"] = literal
   385  	} else {
   386  		err = json.Unmarshal(b, &mmap)
   387  	}
   388  	if err != nil {
   389  		return err
   390  	}
   391  	*m = mmap
   392  	return nil
   393  }
   394  
   395  // MarshalJSON marshalls MultiLanguage
   396  func (m MultiLanguage) MarshalJSON() ([]byte, error) {
   397  
   398  	if len(m) > 1 || m["und"] == "" {
   399  		var mmap map[string]string
   400  		mmap = make(map[string]string)
   401  
   402  		for key, value := range m {
   403  			mmap[key] = value
   404  		}
   405  		return json.Marshal(mmap)
   406  	}
   407  	return json.Marshal(m["und"])
   408  }
   409  
   410  // Text returns the "und" language value or the single value found in the map
   411  func (m MultiLanguage) Text() string {
   412  
   413  	if m["und"] != "" {
   414  		return m["und"]
   415  	} else if len(m) == 1 {
   416  		for _, v := range m {
   417  			return v
   418  		}
   419  	}
   420  	return ""
   421  }
   422  
   423  // SetDefault inits the "und" localized value
   424  func (m *MultiLanguage) SetDefault(literal string) {
   425  
   426  	if *m == nil {
   427  		*m = make(map[string]string)
   428  	}
   429  	(*m)["und"] = literal
   430  }
   431  
   432  // Set inits a localized value
   433  func (m *MultiLanguage) Set(language string, value string) {
   434  
   435  	if *m == nil {
   436  		*m = make(map[string]string)
   437  	}
   438  	(*m)[language] = value
   439  }
   440  
   441  // MultiString stores one or more strings
   442  // Used for properties which take a string || an array of strings
   443  type MultiString []string
   444  
   445  // UnmarshalJSON unmarshals MultiString
   446  func (m *MultiString) UnmarshalJSON(b []byte) error {
   447  
   448  	var mstring []string
   449  	var literal string
   450  	var err error
   451  
   452  	// literal value
   453  	if err = json.Unmarshal(b, &literal); err == nil {
   454  		mstring = append(mstring, literal)
   455  
   456  		// string array
   457  	} else {
   458  		err = json.Unmarshal(b, &mstring)
   459  	}
   460  	if err != nil {
   461  		return err
   462  	}
   463  	*m = mstring
   464  	return nil
   465  }
   466  
   467  // MarshalJSON marshalls MultiString
   468  func (m MultiString) MarshalJSON() ([]byte, error) {
   469  
   470  	if len(m) == 1 {
   471  		literal := m[0]
   472  		return json.Marshal(literal)
   473  	}
   474  	var mstring []string
   475  	for _, v := range m {
   476  		mstring = append(mstring, v)
   477  	}
   478  	return json.Marshal(mstring)
   479  }
   480  
   481  // Add adds a value to a multistring array
   482  func (m *MultiString) Add(value string) {
   483  
   484  	*m = append(*m, value)
   485  }
   486  
   487  // Text returns the concatenation of all string values
   488  func (m MultiString) Text() string {
   489  
   490  	return strings.Join([]string(m), ", ")
   491  }