Compare commits

..

No commits in common. "main" and "v0.4.0" have entirely different histories.
main ... v0.4.0

20 changed files with 242 additions and 642 deletions

272
README.md
View File

@ -2,7 +2,7 @@
An extensible Atom feed generator library that aims to be very close to RFC4287.
It diligently checks for compliance with the standard and provides functions for
easy creation, extension and deletion of elements.
easy creation and extension of elements.
## Installation
@ -14,47 +14,48 @@ go get git.streifling.com/jason/atom@latest
## Usage
### Basic Feed
This library provides convenient functions to safely create, extend and delete
elements and attributes of Atom feeds. It also provides checks for all
constructs' adherence to RFC4287.
This library provides convenient functions to safely create and extend elements
and attributes of Atom feeds. It also provides checks for all constructs'
adherence to RFC4287.
```go
package main
import (
"fmt"
"log"
"fmt"
"log"
"git.streifling.com/jason/atom"
"git.streifling.com/jason/atom"
)
func main() {
feed := atom.NewFeed("Example Feed")
if err := feed.Check(); err != nil {
log.Fatalln(err)
}
feed := atom.NewFeed("Example Feed")
feed.Title = atom.NewText("text", "Example Feed")
feed.ID = atom.NewID(atom.NewURN())
if err := feed.Check(); err != nil {
log.Fatalln(err)
}
author := atom.NewPerson("John Doe")
author.Email = "john.doe@example.com"
if err := author.Check(); err != nil {
log.Fatalln(err)
}
feed.AddAuthor(author)
author := atom.NewPerson("John Doe")
author.Email = "john.doe@example.com"
if err := author.Check(); err != nil {
log.Fatalln(err)
}
feed.AddAuthor(author)
entry := atom.NewEntry("First Entry")
entry.Content = atom.NewContent(atom.InlineText, "text", "This is the content of the first entry.")
if err := entry.Check(); err != nil {
log.Fatalln(err)
}
feed.AddEntry(entry)
entry := atom.NewEntry("First Entry")
entry.ID = atom.NewID(atom.NewURN())
entry.Content = atom.NewContent(atom.InlineText, "text", "This is the content of the first entry.")
if err := entry.Check(); err != nil {
log.Fatalln(err)
}
feed.AddEntry(entry)
feedString, err := feed.ToXML("UTF-8")
if err != nil {
log.Fatalln(err)
}
fmt.Println(feedString)
feedString, err := feed.ToXML("utf-8")
if err != nil {
log.Fatalln(err)
}
fmt.Println(feedString)
}
```
@ -65,177 +66,70 @@ provide.
package main
import (
"fmt"
"log"
"time"
"fmt"
"log"
"time"
"git.streifling.com/jason/atom"
"github.com/google/uuid"
"git.streifling.com/jason/atom"
)
func main() {
now := time.Now()
now := time.Now()
feed := &atom.Feed{
Title: &atom.PlainText{
Type: "text",
Text: "Example Feed",
},
ID: &atom.ID{URI: fmt.Sprint("urn:uuid:", uuid.New())},
Updated: &atom.Date{DateTime: atom.DateTime(now)},
Authors: []*atom.Person{
{
Name: "John Doe",
Email: "john.doe@example.com",
},
},
Entries: []*atom.Entry{
{
Title: &atom.PlainText{
Type: "text",
Text: "First Entry",
},
ID: &atom.ID{URI: fmt.Sprint("urn:uuid:", uuid.New())},
Updated: &atom.Date{DateTime: atom.DateTime(now)},
Content: &atom.InlineTextContent{
Type: "text",
Text: "This is the content of the first entry.",
},
},
},
}
feed := &atom.Feed{
Title: &atom.PlainText{
Type: "text",
Text: "Example Feed",
},
ID: &atom.ID{URI: "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6"},
Updated: &atom.Date{DateTime: atom.DateTime(now)},
Authors: []*atom.Person{
{
Name: "John Doe",
Email: "john.doe@example.com",
},
},
Entries: []*atom.Entry{
{
Title: &atom.PlainText{
Type: "text",
Text: "First Entry",
},
ID: &atom.ID{URI: "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a"},
Updated: &atom.Date{DateTime: atom.DateTime(now)},
Content: &atom.InlineTextContent{
Type: "text",
Text: "This is the content of the first entry.",
},
},
},
}
feedString, err := feed.ToXML("UTF-8")
if err != nil {
log.Fatalln(err)
}
fmt.Println(feedString)
feedString, err := feed.ToXML("utf-8")
if err != nil {
log.Fatalln(err)
}
fmt.Println(feedString)
}
```
The output of both ways of using it is an RFC4287 compliant Atom feed.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<author>
<name>John Doe</name>
<email>john.doe@example.com</email>
</author>
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
<title type="text">Example Feed</title>
<updated>2024-10-18T05:49:08+02:00</updated>
<entry>
<content type="text">This is the content of the first entry.</content>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<title type="text">First Entry</title>
<updated>2006-01-02T15:04:05+07:00</updated>
</entry>
</feed>
```
### Compliance and Error Checking
All Atom constructs have their own ```construct.Check()``` method. It checks for
compliance with RFC4287 and errors. This allows one to be certain that only
compliant constructs are added to their respective parent construct. It also
means that not the entire feed has to be checked every time a new construct is
added. Instead one only checks the current construct and all of its
sub-constructs with a single ```construct.Check()``` call.
```go
package main
import (
"fmt"
"log"
"git.streifling.com/jason/atom"
)
func main() {
feed := atom.NewFeed("Example Feed")
if err := feed.Check(); err != nil {
log.Fatalln(err)
}
entry := atom.NewEntry("One")
entry.Content = atom.NewContent(atom.InlineText, "text", "Entry One")
entry.AddAuthor(atom.NewPerson("John Doe"))
if err := entry.Check(); err != nil {
log.Fatalln(err)
}
feed.AddEntry(entry)
feedString, err := feed.ToXML("UTF-8")
if err != nil {
log.Fatalln(err)
}
fmt.Println(feedString)
}
```
### Adding and Deleting Items
To add elements to any slice one calls the appropriate
```someone.AddSomething(toBeAdded)``` method. It returns the item's index
number. To delete the item one calls the Delete method.
```go
package main
import (
"fmt"
"log"
"git.streifling.com/jason/atom"
)
func main() {
feed := atom.NewFeed("Feed")
if err := feed.Check(); err != nil {
log.Fatalln(err)
}
e1 := atom.NewEntry("One")
e1.Content = atom.NewContent(atom.InlineText, "text", "Entry One")
if err := e1.Check(); err != nil {
log.Fatalln(err)
}
i1 := feed.AddEntry(e1)
e2 := atom.NewEntry("Two")
e2.Content = atom.NewContent(atom.InlineText, "text", "Entry Two")
if err := e2.Check(); err != nil {
log.Fatalln(err)
}
feed.AddEntry(e2)
if err := feed.DeleteEntry(i1); err != nil {
log.Fatalln(err)
}
feedString, err := feed.ToXML("UTF-8")
if err != nil {
log.Fatalln(err)
}
fmt.Println(feedString)
}
```
The output of this example looks like this. It only shows the second entry.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>urn:uuid:ae89d343-b535-447d-ac14-4b80d3e02a2f</id>
<title type="text">Example Feed</title>
<updated>2024-10-20T14:57:02+02:00</updated>
<entry>
<content type="text">Entry Two</content>
<id>urn:uuid:620c6f73-ee1d-4c1e-be98-b0b1ad7a053f</id>
<title type="text">Two</title>
<updated>2024-10-20T14:57:02+02:00</updated>
</entry>
<author>
<name>John Doe</name>
<email>john.doe@example.com</email>
</author>
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
<title type="text">Example Feed</title>
<updated>2024-10-18T05:49:08+02:00</updated>
<entry>
<content type="text">This is the content of the first entry.</content>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<title type="text">First Entry</title>
<updated>2006-01-02T15:04:05+07:00</updated>
</entry>
</feed>
```

27
atom.go
View File

@ -1,7 +1,6 @@
package atom
import (
"encoding/xml"
"fmt"
"html"
"mime"
@ -12,32 +11,6 @@ import (
"golang.org/x/text/language"
)
type Countable interface {
*xml.Attr | *Person | *Category | *Link | *ExtensionElement | *Entry
}
// addToSlice adds a Countable to to a *[]Countable. It returns an int.
func addToSlice[C Countable](slice *[]C, countable C) int {
if *slice == nil {
*slice = make([]C, 0)
}
*slice = append(*slice, countable)
return len(*slice) - 1
}
// deleteFromSlice deletes the Countable with the index from the *[]Countable.
// It return an error.
func deleteFromSlice[C Countable](slice *[]C, index int) error {
length := len(*slice)
if index > length {
return fmt.Errorf("id %v out of range %v", index, length)
}
*slice = append((*slice)[:index], (*slice)[index+1:]...)
return nil
}
// isValidIRI checks whether an IRI is valid or not. It returns a bool.
// https://www.w3.org/2011/04/XMLSchema/TypeLibrary-IRI-RFC3987.xsd
func isValidIRI(iri string) bool {

View File

@ -10,17 +10,22 @@ type Category struct {
*CommonAttributes
Term string `xml:"term,attr"`
Scheme string `xml:"scheme,attr,omitempty"` // IRI
Label string `xml:"label,attr,omitempty"` // Must be unescaped
Label string `xml:"label,attr,omitempty"`
}
// NewCategory creates a new Category. It returns a *Category.
func NewCategory(term string) *Category {
return &Category{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Term: term,
}
}
// SetLabel sets the Label attribute of the Category.
func (c *Category) SetLabel(label string) {
c.Label = Unescape(label)
}
// Check checks the Category for incompatibilities with RFC4287. It returns an
// error.
func (c *Category) Check() error {

View File

@ -13,23 +13,18 @@ type CommonAttributes struct {
// NewCommonAttributes creates a new set of CommonAttributes. It returns a
// *CommonAttributes.
func NewCommonAttributes() *CommonAttributes {
func newCommonAttributes() *CommonAttributes {
return new(CommonAttributes)
}
// AddAttribute adds the attribute to the CommonAttributes. It returns its index
// as an int.
func (c *CommonAttributes) AddAttribute(name, value string) int {
return addToSlice(&c.UndefinedAttributes, &xml.Attr{Name: xml.Name{Local: name}, Value: value})
}
// DeleteAttribute deletes the attribute at index from the CommonAttributes. It
// return an error.
func (c *CommonAttributes) DeleteAttribute(index int) error {
if err := deleteFromSlice(&c.UndefinedAttributes, index); err != nil {
return fmt.Errorf("error deleting undefined attribute %v from common attributes %v: %v", index, c, err)
// AddAttribute adds the Attribute to the CommonAttributes.
func (c *CommonAttributes) AddAttribute(name, value string) {
if c.UndefinedAttributes == nil {
c.UndefinedAttributes = make([]*xml.Attr, 1)
c.UndefinedAttributes[0] = &xml.Attr{Name: xml.Name{Local: name}, Value: value}
} else {
c.UndefinedAttributes = append(c.UndefinedAttributes, &xml.Attr{Name: xml.Name{Local: name}, Value: value})
}
return nil
}
// Check checks the CommonAttributes for incompatibilities with RFC4287. It

View File

@ -19,7 +19,7 @@ func DateTime(t time.Time) string {
// NewDate creates a new Date. It returns a *Date.
func NewDate(t time.Time) *Date {
return &Date{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
DateTime: DateTime(t),
}
}

192
entry.go
View File

@ -39,16 +39,16 @@ func (e *Entry) checkAuthors(authorInFeed bool) error {
if e.Authors == nil {
if !authorInFeed {
if e.Source == nil {
return fmt.Errorf("no authors set in entry %v", e.ID.URI)
return fmt.Errorf("no authors set in entry %v", e)
}
if e.Source.Authors == nil {
return fmt.Errorf("no authors set in entry %v", e.ID.URI)
return fmt.Errorf("no authors set in entry %v", e)
}
}
} else {
for i, a := range e.Authors {
if err := a.Check(); err != nil {
return fmt.Errorf("author element %v of entry %v: %v", i, e.ID.URI, err)
return fmt.Errorf("author element %v of entry %v: %v", i, e, err)
}
}
}
@ -59,147 +59,71 @@ func (e *Entry) checkAuthors(authorInFeed bool) error {
// NewEntry creates a new Entry. It returns a *Entry.
func NewEntry(title string) *Entry {
return &Entry{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
ID: NewID(NewURN()),
Title: NewText("text", title),
Updated: NewDate(time.Now()),
}
}
// AddAuthor adds the Person as an author to the Entry. It returns its index as
// an int.
func (e *Entry) AddAuthor(p *Person) int {
if e.Updated == nil {
e.Updated = NewDate(time.Now())
// AddAuthor adds the Person as an author to the Entry.
func (e *Entry) AddAuthor(p *Person) {
if e.Authors == nil {
e.Authors = make([]*Person, 1)
e.Authors[0] = p
} else {
e.Updated.DateTime = DateTime(time.Now())
e.Authors = append(e.Authors, p)
}
return addToSlice(&e.Authors, p)
e.Updated.DateTime = DateTime(time.Now())
}
// DeleteAuthor deletes the Person at index from the Entry. It return an error.
func (e *Entry) DeleteAuthor(index int) error {
if err := deleteFromSlice(&e.Authors, index); err != nil {
return fmt.Errorf("error deleting author %v from entry %v: %v", index, e.ID.URI, err)
}
if e.Updated == nil {
e.Updated = NewDate(time.Now())
// AddCategory adds the Category to the Entry.
func (e *Entry) AddCategory(c *Category) {
if e.Categories == nil {
e.Categories = make([]*Category, 1)
e.Categories[0] = c
} else {
e.Updated.DateTime = DateTime(time.Now())
e.Categories = append(e.Categories, c)
}
return nil
e.Updated.DateTime = DateTime(time.Now())
}
// AddCategory adds the Category to the Entry. It returns ts index as an int.
func (e *Entry) AddCategory(c *Category) int {
if e.Updated == nil {
e.Updated = NewDate(time.Now())
// AddContributor adds the Person as a contributor to the Entry.
func (e *Entry) AddContributor(c *Person) {
if e.Contributors == nil {
e.Contributors = make([]*Person, 1)
e.Contributors[0] = c
} else {
e.Updated.DateTime = DateTime(time.Now())
e.Contributors = append(e.Contributors, c)
}
return addToSlice(&e.Categories, c)
e.Updated.DateTime = DateTime(time.Now())
}
// DeleteCategory deletes the Category at index from the Entry. It return an
// error.
func (e *Entry) DeleteCategory(index int) error {
if err := deleteFromSlice(&e.Categories, index); err != nil {
return fmt.Errorf("error deleting category %v from entry %v: %v", index, e.ID.URI, err)
}
if e.Updated == nil {
e.Updated = NewDate(time.Now())
// AddLink adds the Link to the Entry.
func (e *Entry) AddLink(l *Link) {
if e.Links == nil {
e.Links = make([]*Link, 1)
e.Links[0] = l
} else {
e.Updated.DateTime = DateTime(time.Now())
e.Links = append(e.Links, l)
}
return nil
e.Updated.DateTime = DateTime(time.Now())
}
// AddContributor adds the Person as a contributor to the Entry. It returns its
// index as an int.
func (e *Entry) AddContributor(c *Person) int {
if e.Updated == nil {
e.Updated = NewDate(time.Now())
// AddExtension adds the ExtensionElement to the Entry.
func (e *Entry) AddExtension(x *ExtensionElement) {
if e.Extensions == nil {
e.Extensions = make([]*ExtensionElement, 1)
e.Extensions[0] = x
} else {
e.Updated.DateTime = DateTime(time.Now())
e.Extensions = append(e.Extensions, x)
}
return addToSlice(&e.Contributors, c)
}
// DeleteContributor deletes the Person at index from the Entry. It return an
// error.
func (e *Entry) DeleteContributor(index int) error {
if err := deleteFromSlice(&e.Contributors, index); err != nil {
return fmt.Errorf("error deleting contributor %v from entry %v: %v", index, e.ID.URI, err)
}
if e.Updated == nil {
e.Updated = NewDate(time.Now())
} else {
e.Updated.DateTime = DateTime(time.Now())
}
return nil
}
// AddLink adds the Link to the Entry. It returns its index as an int.
func (e *Entry) AddLink(l *Link) int {
if e.Updated == nil {
e.Updated = NewDate(time.Now())
} else {
e.Updated.DateTime = DateTime(time.Now())
}
return addToSlice(&e.Links, l)
}
// DeleteLink deletes the Link at index from the Entry. It return an error.
func (e *Entry) DeleteLink(index int) error {
if err := deleteFromSlice(&e.Links, index); err != nil {
return fmt.Errorf("error deleting link %v from entry %v: %v", index, e.ID.URI, err)
}
if e.Updated == nil {
e.Updated = NewDate(time.Now())
} else {
e.Updated.DateTime = DateTime(time.Now())
}
return nil
}
// AddExtension adds the ExtensionElement to the Entry. It returns its index as
// an int.
func (e *Entry) AddExtension(x *ExtensionElement) int {
if e.Updated == nil {
e.Updated = NewDate(time.Now())
} else {
e.Updated.DateTime = DateTime(time.Now())
}
return addToSlice(&e.Extensions, x)
}
// DeleteExtension deletes the Extension at index from the Entry. It return an
// error.
func (e *Entry) DeleteExtension(index int) error {
if err := deleteFromSlice(&e.Extensions, index); err != nil {
return fmt.Errorf("error deleting extension %v from entry %v: %v", index, e.ID.URI, err)
}
if e.Updated == nil {
e.Updated = NewDate(time.Now())
} else {
e.Updated.DateTime = DateTime(time.Now())
}
return nil
e.Updated.DateTime = DateTime(time.Now())
}
// Check checks the Entry for incompatibilities with RFC4287. It returns an
@ -214,64 +138,64 @@ func (e *Entry) Check() error {
}
if err := e.checkAuthors(true); err != nil {
return fmt.Errorf("entry %v: %v", e.ID.URI, err)
return fmt.Errorf("entry %v: %v", e, err)
}
for i, c := range e.Categories {
if err := c.Check(); err != nil {
return fmt.Errorf("category element %v of entry %v: %v", i, e.ID.URI, err)
return fmt.Errorf("category element %v of entry %v: %v", i, e, err)
}
}
if e.Content != nil {
if err := e.Content.Check(); err != nil {
return fmt.Errorf("content element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("content element of entry %v: %v", e, err)
}
} else {
// atom:entry elements that contain no child atom:content element MUST
// contain at least one atom:link element with a rel attribute value of
// "alternate".
if !alternateRelExists(e.Links) {
return fmt.Errorf("no content element of entry %v and no link element with rel \"alternate\"", e.ID.URI)
return fmt.Errorf("no content element of entry %v and no link element with rel \"alternate\"", e)
}
}
for i, c := range e.Contributors {
if err := c.Check(); err != nil {
return fmt.Errorf("contributor element %v of entry %v: %v", i, e.ID.URI, err)
return fmt.Errorf("contributor element %v of entry %v: %v", i, e, err)
}
}
for i, l := range e.Links {
if err := l.Check(); err != nil {
return fmt.Errorf("link element %v of entry %v: %v", i, e.ID.URI, err)
return fmt.Errorf("link element %v of entry %v: %v", i, e, err)
}
}
if hasAlternateDuplicateLinks(e.Links) {
return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in entry %v", e.ID.URI)
return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in entry %v", e)
}
if e.Published != nil {
if err := e.Published.Check(); err != nil {
return fmt.Errorf("published element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("published element of entry %v: %v", e, err)
}
}
if e.Rights != nil {
if err := e.Rights.Check(); err != nil {
return fmt.Errorf("rights element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("rights element of entry %v: %v", e, err)
}
}
if e.Source != nil {
if err := e.Source.Check(); err != nil {
return fmt.Errorf("source element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("source element of entry %v: %v", e, err)
}
}
if e.Summary != nil {
if err := e.Summary.Check(); err != nil {
return fmt.Errorf("summary element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("summary element of entry %v: %v", e, err)
}
} else {
// atom:entry elements MUST contain an atom:summary element in either
@ -279,7 +203,7 @@ func (e *Entry) Check() error {
// the atom:entry contains an atom:content that has a "src" attribute
// (and is thus empty).
if e.Content.hasSRC() {
return fmt.Errorf("no summary element of entry %v but content of type out of line content", e.ID.URI)
return fmt.Errorf("no summary element of entry %v but content of type out of line content", e)
}
// the atom:entry contains content that is encoded in Base64; i.e., the
// "type" attribute of atom:content is a MIME media type [MIMEREG], but
@ -287,29 +211,29 @@ func (e *Entry) Check() error {
// does not end with "/xml" or "+xml".
mediaType := e.Content.getType()
if isValidMediaType(mediaType) && !isXMLMediaType(mediaType) && !strings.HasPrefix(mediaType, "text/") {
return fmt.Errorf("no summary element of entry %v but media type not xml", e.ID.URI)
return fmt.Errorf("no summary element of entry %v but media type not xml", e)
}
}
if e.Title == nil {
return fmt.Errorf("no title element of entry %v", e.ID.URI)
return fmt.Errorf("no title element of entry %v", e)
} else {
if err := e.Title.Check(); err != nil {
return fmt.Errorf("title element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("title element of entry %v: %v", e, err)
}
}
if e.Updated == nil {
return fmt.Errorf("no updated element of entry %v", e.ID.URI)
return fmt.Errorf("no updated element of entry %v", e)
} else {
if err := e.Updated.Check(); err != nil {
return fmt.Errorf("updated element of entry %v: %v", e.ID.URI, err)
return fmt.Errorf("updated element of entry %v: %v", e, err)
}
}
for i, x := range e.Extensions {
if err := x.Check(); err != nil {
return fmt.Errorf("extension element %v of entry %v: %v", i, e.ID.URI, err)
return fmt.Errorf("extension element %v of entry %v: %v", i, e, err)
}
}
@ -320,7 +244,7 @@ func (e *Entry) Check() error {
func (e *Entry) ToXML(encoding string) (string, error) {
xml, err := xml.MarshalIndent(e, "", " ")
if err != nil {
return "", fmt.Errorf("error xml encoding entry %v: %v", e.ID.URI, err)
return "", fmt.Errorf("error xml encoding entry: %v", err)
}
return fmt.Sprintln(`<?xml version="1.0" encoding="`+encoding+`"?>`) + string(xml), nil

237
feed.go
View File

@ -28,200 +28,83 @@ type Feed struct {
// NewFeed creates a new Feed. It returns a *Feed.
func NewFeed(title string) *Feed {
return &Feed{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
ID: NewID(NewURN()),
Title: NewText("text", title),
Updated: NewDate(time.Now()),
}
}
// AddAuthor adds the Person as an author to the Feed. It returns its index as
// an int.
func (f *Feed) AddAuthor(p *Person) int {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
// AddAuthor adds the Person as an author to the Feed.
func (f *Feed) AddAuthor(p *Person) {
if f.Authors == nil {
f.Authors = make([]*Person, 1)
f.Authors[0] = p
} else {
f.Updated.DateTime = DateTime(time.Now())
f.Authors = append(f.Authors, p)
}
return addToSlice(&f.Authors, p)
f.Updated.DateTime = DateTime(time.Now())
}
// DeleteAuthor deletes the Person at index from the Feed. It return an error.
func (f *Feed) DeleteAuthor(index int) error {
if err := deleteFromSlice(&f.Authors, index); err != nil {
return fmt.Errorf("error deleting author %v from entry %v: %v", index, f.ID.URI, err)
}
if f.Updated == nil {
f.Updated = NewDate(time.Now())
// AddCategory adds the Category to the Feed.
func (f *Feed) AddCategory(c *Category) {
if f.Categories == nil {
f.Categories = make([]*Category, 1)
f.Categories[0] = c
} else {
f.Updated.DateTime = DateTime(time.Now())
f.Categories = append(f.Categories, c)
}
return nil
f.Updated.DateTime = DateTime(time.Now())
}
// AddCategory adds the Category to the Feed. It returns its index as an int.
func (f *Feed) AddCategory(c *Category) int {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
// AddContributor adds the Person as a contributor to the Feed.
func (f *Feed) AddContributor(c *Person) {
if f.Contributors == nil {
f.Contributors = make([]*Person, 1)
f.Contributors[0] = c
} else {
f.Updated.DateTime = DateTime(time.Now())
f.Contributors = append(f.Contributors, c)
}
return addToSlice(&f.Categories, c)
}
// DeleteCategory deletes the Category at index from the Feed. It return an
// error.
func (f *Feed) DeleteCategory(index int) error {
if err := deleteFromSlice(&f.Categories, index); err != nil {
return fmt.Errorf("error deleting category %v from entry %v: %v", index, f.ID.URI, err)
}
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return nil
}
// AddContributor adds the Person as a contributor to the Feed. It returns its
// index as an int.
func (f *Feed) AddContributor(c *Person) int {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return addToSlice(&f.Contributors, c)
}
// DeleteContributor deletes the Person at index from the Feed. It return an
// error.
func (f *Feed) DeleteContributor(index int) error {
if err := deleteFromSlice(&f.Contributors, index); err != nil {
return fmt.Errorf("error deleting contributor %v from entry %v: %v", index, f.ID.URI, err)
}
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return nil
f.Updated.DateTime = DateTime(time.Now())
}
// AddLink adds the Link to the Feed. There should be one Link with Rel "self".
// It returns its index as an int.
func (f *Feed) AddLink(l *Link) int {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
func (f *Feed) AddLink(l *Link) {
if f.Links == nil {
f.Links = make([]*Link, 1)
f.Links[0] = l
} else {
f.Updated.DateTime = DateTime(time.Now())
f.Links = append(f.Links, l)
}
return addToSlice(&f.Links, l)
f.Updated.DateTime = DateTime(time.Now())
}
// DeleteLink deletes the Link at index from the Feed. It return an error.
func (f *Feed) DeleteLink(index int) error {
if err := deleteFromSlice(&f.Links, index); err != nil {
return fmt.Errorf("error deleting link %v from entry %v: %v", index, f.ID.URI, err)
}
if f.Updated == nil {
f.Updated = NewDate(time.Now())
// AddExtension adds the Extension to the Feed.
func (f *Feed) AddExtension(e *ExtensionElement) {
if f.Extensions == nil {
f.Extensions = make([]*ExtensionElement, 1)
f.Extensions[0] = e
} else {
f.Updated.DateTime = DateTime(time.Now())
f.Extensions = append(f.Extensions, e)
}
return nil
f.Updated.DateTime = DateTime(time.Now())
}
// AddExtension adds the Extension to the Feed. It returns its index as an int.
func (f *Feed) AddExtension(e *ExtensionElement) int {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
// AddEntry adds the Entry to the Feed.
func (f *Feed) AddEntry(e *Entry) {
if f.Entries == nil {
f.Entries = make([]*Entry, 1)
f.Entries[0] = e
} else {
f.Updated.DateTime = DateTime(time.Now())
f.Entries = append(f.Entries, e)
}
return addToSlice(&f.Extensions, e)
}
// DeleteExtension deletes the Extension at index from the Feed. It return an
// error.
func (f *Feed) DeleteExtension(index int) error {
if err := deleteFromSlice(&f.Extensions, index); err != nil {
return fmt.Errorf("error deleting extension %v from entry %v: %v", index, f.ID.URI, err)
}
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return nil
}
// AddEntry adds the Entry to the Feed. It returns its index as an int.
func (f *Feed) AddEntry(e *Entry) int {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return addToSlice(&f.Entries, e)
}
// DeleteEntry deletes the Entry at index from the Feed. It return an error.
func (f *Feed) DeleteEntry(index int) error {
if err := deleteFromSlice(&f.Entries, index); err != nil {
return fmt.Errorf("error deleting entry %v from entry %v: %v", index, f.ID.URI, err)
}
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return nil
}
// DeleteEntryByURI deletes the Entry from the Feed. It return an error.
func (f *Feed) DeleteEntryByURI(uri string) error {
if !isValidIRI(uri) {
return fmt.Errorf("error deleting entry from feed %v: uri %v invalid", f.ID.URI, uri)
}
index := -1
for i, e := range f.Entries {
if e.ID.URI == uri {
index = i
break
}
}
if index < 0 {
return fmt.Errorf("error deleting entry from feed %v: id %v not found", f.ID.URI, uri)
}
f.Entries = append(f.Entries[:index], f.Entries[index+1:]...)
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
return nil
f.Updated.DateTime = DateTime(time.Now())
}
// Check checks the Feed for incompatibilities with RFC4287. It returns an
@ -241,93 +124,93 @@ func (f *Feed) Check() error {
if f.Authors == nil {
for _, e := range f.Entries {
if err := e.checkAuthors(false); err != nil {
return fmt.Errorf("no authors set in feed %v: %v", f.ID.URI, err)
return fmt.Errorf("no authors set in feed %v: %v", f, err)
}
}
} else {
for i, a := range f.Authors {
if err := a.Check(); err != nil {
return fmt.Errorf("author element %v of feed %v: %v", i, f.ID.URI, err)
return fmt.Errorf("author element %v of feed %v: %v", i, f, err)
}
}
}
for i, c := range f.Categories {
if err := c.Check(); err != nil {
return fmt.Errorf("category element %v of feed %v: %v", i, f.ID.URI, err)
return fmt.Errorf("category element %v of feed %v: %v", i, f, err)
}
}
for i, c := range f.Contributors {
if err := c.Check(); err != nil {
return fmt.Errorf("contributor element %v of feed %v: %v", i, f.ID.URI, err)
return fmt.Errorf("contributor element %v of feed %v: %v", i, f, err)
}
}
if f.Generator != nil {
if err := f.Generator.Check(); err != nil {
return fmt.Errorf("generator element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("generator element of feed %v: %v", f, err)
}
}
if f.Icon != nil {
if err := f.Icon.Check(); err != nil {
return fmt.Errorf("icon element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("icon element of feed %v: %v", f, err)
}
}
for i, l := range f.Links {
if err := l.Check(); err != nil {
return fmt.Errorf("link element %v of feed %v: %v", i, f.ID.URI, err)
return fmt.Errorf("link element %v of feed %v: %v", i, f, err)
}
}
if hasAlternateDuplicateLinks(f.Links) {
return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f.ID.URI)
return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f)
}
if f.Logo != nil {
if err := f.Logo.Check(); err != nil {
return fmt.Errorf("logo element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("logo element of feed %v: %v", f, err)
}
}
if f.Rights != nil {
if err := f.Rights.Check(); err != nil {
return fmt.Errorf("rights element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("rights element of feed %v: %v", f, err)
}
}
if f.Subtitle != nil {
if err := f.Subtitle.Check(); err != nil {
return fmt.Errorf("subtitle element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("subtitle element of feed %v: %v", f, err)
}
}
if f.Title == nil {
return fmt.Errorf("no title element of feed %v", f.ID.URI)
return fmt.Errorf("no title element of feed %v", f)
} else {
if err := f.Title.Check(); err != nil {
return fmt.Errorf("title element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("title element of feed %v: %v", f, err)
}
}
if f.Updated == nil {
return fmt.Errorf("no updated element of feed %v", f.ID.URI)
return fmt.Errorf("no updated element of feed %v", f)
} else {
if err := f.Updated.Check(); err != nil {
return fmt.Errorf("updated element of feed %v: %v", f.ID.URI, err)
return fmt.Errorf("updated element of feed %v: %v", f, err)
}
}
for i, x := range f.Extensions {
if err := x.Check(); err != nil {
return fmt.Errorf("extension element %v of feed %v: %v", i, f.ID.URI, err)
return fmt.Errorf("extension element %v of feed %v: %v", i, f, err)
}
}
for i, n := range f.Entries {
if err := n.Check(); err != nil {
return fmt.Errorf("entry element %v of feed %v: %v", i, f.ID.URI, err)
return fmt.Errorf("entry element %v of feed %v: %v", i, f, err)
}
}
@ -338,7 +221,7 @@ func (f *Feed) Check() error {
func (f *Feed) ToXML(encoding string) (string, error) {
xml, err := xml.MarshalIndent(f, "", " ")
if err != nil {
return "", fmt.Errorf("error xml encoding feed %v: %v", f.ID.URI, err)
return "", fmt.Errorf("error xml encoding feed: %v", err)
}
return fmt.Sprintln(`<?xml version="1.0" encoding="`+encoding+`"?>`) + string(xml), nil

View File

@ -17,7 +17,7 @@ type Generator struct {
// NewGenerator creates a new Generator. It returns a *Generator.
func NewGenerator(text string) *Generator {
return &Generator{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Text: html.UnescapeString(text),
}
}

View File

@ -15,7 +15,7 @@ type Icon struct {
// NewIcon creates a new Icon. It returns a *Icon.
func NewIcon(uri string) *Icon {
return &Icon{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
URI: uri,
}
}

2
id.go
View File

@ -15,7 +15,7 @@ type ID struct {
// NewID creates a new ID. It returns a *ID.
func NewID(uri string) *ID {
return &ID{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
URI: uri,
}
}

View File

@ -19,7 +19,7 @@ func newInlineOtherContent(mediaType string, content any) *InlineOtherContent {
mediaType, _, _ = mime.ParseMediaType(mediaType)
return &InlineOtherContent{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Type: mediaType,
AnyElement: content,
}

View File

@ -16,7 +16,7 @@ type InlineTextContent struct {
// *InlineTextContent.
func newInlineTextContent(mediaType, text string) *InlineTextContent {
return &InlineTextContent{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Type: mediaType,
Text: text,
}

View File

@ -16,7 +16,7 @@ type InlineXHTMLContent struct {
// *InlineXHTMLContent.
func newInlineXHTMLContent(mediaType string, div *XHTMLDiv) *InlineXHTMLContent {
return &InlineXHTMLContent{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Type: mediaType,
XHTMLDiv: div,
}

View File

@ -20,7 +20,7 @@ type Link struct {
// NewLink creates a new Link. It returns a *Link.
func NewLink(href string) *Link {
return &Link{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Href: href,
}
}

View File

@ -14,7 +14,7 @@ type Logo struct {
// NewLogo creates a new Logo. It returns a *Logo.
func NewLogo(uri string) *Logo {
return &Logo{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
URI: uri,
}
}

View File

@ -19,7 +19,7 @@ func newOutOfLineContent(mediaType, src string) *OutOfLineContent {
mediaType, _, _ = mime.ParseMediaType(mediaType)
return &OutOfLineContent{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Type: mediaType,
SRC: src,
}

View File

@ -16,24 +16,19 @@ type Person struct {
// NewPerson creates a new Person. It returns a *Person.
func NewPerson(name string) *Person {
return &Person{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Name: name,
}
}
// AddExtension adds the Extension to the Person. It returns its index as an
// int.
func (p *Person) AddExtension(e *ExtensionElement) int {
return addToSlice(&p.Extensions, e)
}
// DeleteExtension deletes the Extension at index from the Person. It return an
// error.
func (p *Person) DeleteExtension(index int) error {
if err := deleteFromSlice(&p.Extensions, index); err != nil {
return fmt.Errorf("error deleting extension %v from person %v: %v", index, p, err)
// AddExtension adds the Extension to the Person.
func (p *Person) AddExtension(e *ExtensionElement) {
if p.Extensions == nil {
p.Extensions = make([]*ExtensionElement, 1)
p.Extensions[0] = e
} else {
p.Extensions = append(p.Extensions, e)
}
return nil
}
// Check checks the Person for incompatibilities with RFC4287. It returns an
@ -55,9 +50,11 @@ func (p *Person) Check() error {
}
}
for i, e := range p.Extensions {
if err := e.Check(); err != nil {
return fmt.Errorf("extension element %v of person %v: %v", i, p, err)
if p.Extensions != nil {
for i, e := range p.Extensions {
if err := e.Check(); err != nil {
return fmt.Errorf("extension element %v of person %v: %v", i, p, err)
}
}
}

View File

@ -16,7 +16,7 @@ func (p *PlainText) isText() bool { return true }
// newPlainText creates a new PlainText. It returns a *PlainText.
func newPlainText(textType, content string) *PlainText {
return &PlainText{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Type: textType,
Text: content,
}

View File

@ -25,78 +25,7 @@ type Source struct {
// NewSource creates a new Source. It returns a *Source.
func NewSource() *Source {
return &Source{CommonAttributes: NewCommonAttributes()}
}
// AddAuthor adds the Person as an author to the Source. It returns its index as
// an int.
func (s *Source) AddAuthor(p *Person) int {
return addToSlice(&s.Authors, p)
}
// DeleteAuthor deletes the Person at index from the Source. It return an error.
func (s *Source) DeleteAuthor(index int) error {
if err := deleteFromSlice(&s.Authors, index); err != nil {
return fmt.Errorf("error deleting author %v from source %v: %v", index, s, err)
}
return nil
}
// AddCategory adds the Category to the Source. It returns its index as an int.
func (s *Source) AddCategory(c *Category) int {
return addToSlice(&s.Categories, c)
}
// DeleteCategory deletes the Category at index from the Source. It return an
// error.
func (s *Source) DeleteCategory(index int) error {
if err := deleteFromSlice(&s.Categories, index); err != nil {
return fmt.Errorf("error deleting category %v from source %v: %v", index, s, err)
}
return nil
}
// AddContributor adds the Person as a contributor to the Source. It returns its
// index as an int.
func (s *Source) AddContributor(c *Person) int {
return addToSlice(&s.Contributors, c)
}
// DeleteContributor deletes the Person at index from the Source. It return an
// error.
func (s *Source) DeleteContributor(index int) error {
if err := deleteFromSlice(&s.Contributors, index); err != nil {
return fmt.Errorf("error deleting contributor %v from source %v: %v", index, s, err)
}
return nil
}
// AddLink adds the Link to the Source. It returns its index as an int.
func (s *Source) AddLink(l *Link) int {
return addToSlice(&s.Links, l)
}
// DeleteLink deletes the Link at index from the Source. It return an error.
func (s *Source) DeleteLink(index int) error {
if err := deleteFromSlice(&s.Links, index); err != nil {
return fmt.Errorf("error deleting link %v from source %v: %v", index, s, err)
}
return nil
}
// AddExtension adds the ExtensionElement to the Source. It returns its index as
// an int.
func (s *Source) AddExtension(e *ExtensionElement) int {
return addToSlice(&s.Extensions, e)
}
// DeleteExtension deletes the Extension at index from the Source. It return an
// error.
func (s *Source) DeleteExtension(index int) error {
if err := deleteFromSlice(&s.Extensions, index); err != nil {
return fmt.Errorf("error deleting extension %v from source %v: %v", index, s, err)
}
return nil
return &Source{CommonAttributes: newCommonAttributes()}
}
// Check checks the Source for incompatibilities with RFC4287. It returns an

View File

@ -16,7 +16,7 @@ func (x *XHTMLText) isText() bool { return true }
// newPlainText creates a new PlainText. It returns a *PlainText.
func newXHTMLText(textType, content string) *XHTMLText {
return &XHTMLText{
CommonAttributes: NewCommonAttributes(),
CommonAttributes: newCommonAttributes(),
Type: textType,
XHTMLDiv: NewXHTMLDiv(content),
}