20 Commits

Author SHA1 Message Date
9ab48787d4 Make comments more useful 2025-01-24 23:06:32 +01:00
be79a13d48 Generalize updating entry and feed with update method 2025-01-24 23:05:46 +01:00
089c573aed Simplify some functions 2025-01-24 22:59:20 +01:00
d8d0526a05 Make NewCommonAttributes public 2024-10-20 15:59:46 +02:00
8fc6a10b0d Just implement update logic in Methods where it is needed, since if there is no pointer I cannot hand it to any function or method 2024-10-20 15:54:49 +02:00
0b70a8ee10 Turn updateDateTime into *Date.Update 2024-10-20 15:47:10 +02:00
da5d955b2d Implement more sophisticated updateDateTime function 2024-10-20 15:37:03 +02:00
ee72e91593 Drop unnecessary SetLabel method 2024-10-20 15:17:53 +02:00
ddf5b26a1e Quick fix 2024-10-20 15:15:14 +02:00
cf131f0bcf Touched up README 2024-10-20 15:10:58 +02:00
ae24db0c08 Clarified Add method's comments 2024-10-20 14:31:54 +02:00
8ce7d54d00 Return the index of the added element 2024-10-20 12:57:24 +02:00
764b143ff8 Changed comments a bit 2024-10-20 12:41:09 +02:00
bcf2532372 Rename id in Delete methods to index 2024-10-20 12:35:26 +02:00
b6b8970810 Add comments to generic functions 2024-10-20 12:30:23 +02:00
c0f5306715 Use pointers to make generic functions work 2024-10-20 12:20:25 +02:00
8a00759c4b Delete useless lines from README 2024-10-20 12:06:09 +02:00
a49e853efb Use generics for Add and Delete Methods 2024-10-20 12:03:26 +02:00
e0384904b4 Added Delete methods for slice elements to common attributes, entry, feed, person and source 2024-10-20 10:49:29 +02:00
e2986e70b1 Created all necessary Add methods for source 2024-10-20 10:42:12 +02:00
24 changed files with 631 additions and 310 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 and extension of elements.
easy creation, extension and deletion of elements.
## Installation
@ -14,48 +14,47 @@ go get git.streifling.com/jason/atom@latest
## Usage
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.
### 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.
```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")
feed.Title = atom.NewText("text", "Example Feed")
feed.ID = atom.NewID(atom.NewURN())
if err := feed.Check(); err != nil {
log.Fatalln(err)
}
feed := atom.NewFeed("Example Feed")
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.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)
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)
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)
}
```
@ -66,70 +65,177 @@ provide.
package main
import (
"fmt"
"log"
"time"
"fmt"
"log"
"time"
"git.streifling.com/jason/atom"
"git.streifling.com/jason/atom"
"github.com/google/uuid"
)
func main() {
now := time.Now()
now := time.Now()
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.",
},
},
},
}
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.",
},
},
},
}
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>
<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>
</feed>
```

57
atom.go
View File

@ -1,6 +1,7 @@
package atom
import (
"encoding/xml"
"fmt"
"html"
"mime"
@ -11,6 +12,33 @@ import (
"golang.org/x/text/language"
)
type Countable interface {
*xml.Attr | *Person | *Category | *Link | *ExtensionElement | *Entry
}
// addToSlice adds a Countable countable to to a *[]Countable slice. It returns
// the index as 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 at index from the *[]Countable slice.
// It returns 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 {
@ -18,7 +46,7 @@ func isValidIRI(iri string) bool {
return regexp.MustCompile(pattern).MatchString(iri)
}
// isCorrectlyEscaped checks whether a string is correctly escaped as per
// isCorrectlyEscaped checks whether the text is correctly escaped as per
// RFC4287. It returns a bool.
func isCorrectlyEscaped(text string) bool {
relevantEntities := []string{"&amp;", "&lt;", "&gt;", "&quot;", "&apos;"}
@ -32,8 +60,8 @@ func isCorrectlyEscaped(text string) bool {
return true
}
// isCompositeMediaType checks whether a string is a composite media type. It
// returns a bool.
// isCompositeMediaType checks whether the string m is a composite media type.
// It returns a bool.
func isCompositeMediaType(m string) bool {
mediaType, _, err := mime.ParseMediaType(m)
if err != nil {
@ -43,7 +71,7 @@ func isCompositeMediaType(m string) bool {
return strings.HasPrefix(mediaType, "multipart/") || strings.HasPrefix(mediaType, "message/")
}
// isXMLMediaType checks whether a string is an xml media type. It returns a
// isXMLMediaType checks whether the string m is an xml media type. It returns a
// bool.
func isXMLMediaType(m string) bool {
mediaType, _, err := mime.ParseMediaType(m)
@ -54,8 +82,8 @@ func isXMLMediaType(m string) bool {
return strings.HasSuffix(mediaType, "/xml") || strings.HasSuffix(mediaType, "+xml")
}
// isValidMediaType checks whether a string is a valid media type. It returns a
// bool.
// isValidMediaType checks whether the string m is a valid media type. It
// returns a bool.
func isValidMediaType(m string) bool {
mediaType, _, err := mime.ParseMediaType(m)
if err != nil {
@ -63,23 +91,20 @@ func isValidMediaType(m string) bool {
}
typeParts := strings.Split(mediaType, "/")
if len(typeParts) != 2 || typeParts[0] == "" || typeParts[1] == "" {
return false
}
return true
return len(typeParts) == 2 && typeParts[0] != "" && typeParts[1] != ""
}
// isValidLanguageTag checks whether a LanguageTag is valid. It returns a bool.
// isValidLanguageTag checks whether the string languageTag is valid. It returns
// a bool.
func isValidLanguageTag(languageTag string) bool {
_, err := language.Parse(languageTag)
return err == nil
}
// isValidAttribute checks whether an Attribute is valid. It returns a bool.
// isValidAttribute checks whether the string attribute is valid. It returns a
// bool.
func isValidAttribute(attribute string) bool {
regex := regexp.MustCompile(`^[a-zA-Z0-9_]+="[^"]*"$`)
return regex.MatchString(attribute)
return regexp.MustCompile(`^[a-zA-Z0-9_]+="[^"]*"$`).MatchString(attribute)
}
// NewURN generates an new valid IRI based on a UUID. It returns an IRI.
@ -87,7 +112,7 @@ func NewURN() string {
return fmt.Sprint("urn:uuid:", uuid.New())
}
// Unescape unescapes a string. It returns an IRI.
// Unescape unescapes the string s. It returns an IRI.
func Unescape(s string) string {
return html.UnescapeString(s)
}

View File

@ -10,22 +10,18 @@ type Category struct {
*CommonAttributes
Term string `xml:"term,attr"`
Scheme string `xml:"scheme,attr,omitempty"` // IRI
Label string `xml:"label,attr,omitempty"`
Label string `xml:"label,attr,omitempty"` // Must be unescaped
}
// NewCategory creates a new Category. It returns a *Category.
// NewCategory creates a new Category. It takes in a string term and 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,18 +13,23 @@ 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.
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})
// AddAttribute adds an attribute to the CommonAttributes. It takes in the
// strings name and value and returns the 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
// returns 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)
}
return nil
}
// Check checks the CommonAttributes for incompatibilities with RFC4287. It

View File

@ -14,7 +14,10 @@ type Content interface {
Check() error
}
// NewContent creates a new Content. It returns a Content and an error.
// NewContent creates a new Content. It takes in an int contentType, a string
// mediaType and an any content and returns a Content.
//
// If contentType is invalid, it returns nil.
func NewContent(contentType int, mediaType string, content any) Content {
switch contentType {
case 0:

View File

@ -10,16 +10,16 @@ type Date struct {
DateTime string `xml:",chardata"`
}
// DateTime formats a time.Time to string formated as defined by RFC3339. It
// DateTime formats the time.Time t to a string as defined by RFC3339. It
// returns a string.
func DateTime(t time.Time) string {
return t.Format(time.RFC3339)
}
// NewDate creates a new Date. It returns a *Date.
// NewDate creates a new Date. It takes in a time.Time t and returns a *Date.
func NewDate(t time.Time) *Date {
return &Date{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
DateTime: DateTime(t),
}
}

182
entry.go
View File

@ -30,25 +30,26 @@ type Entry struct {
}
// checkAuthors checks the entry's authors for incompatibilities with RFC4287.
// It returns an errors.
// It takes in a bool authorIsInFeed and returns an errors.
//
// atom:entry elements MUST contain one or more atom:author elements, unless
// the atom:entry contains an atom:source element that contains an atom:author
// element or, in an Atom Feed Document, the atom:feed element contains an
// atom:author element itself.
func (e *Entry) checkAuthors(authorInFeed bool) error {
func (e *Entry) checkAuthors(authorIsInFeed bool) error {
if e.Authors == nil {
if !authorInFeed {
if !authorIsInFeed {
if e.Source == nil {
return fmt.Errorf("no authors set in entry %v", e)
return fmt.Errorf("no authors set in entry %v", e.ID.URI)
}
if e.Source.Authors == nil {
return fmt.Errorf("no authors set in entry %v", e)
return fmt.Errorf("no authors set in entry %v", e.ID.URI)
}
}
} 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, err)
return fmt.Errorf("author element %v of entry %v: %v", i, e.ID.URI, err)
}
}
}
@ -56,74 +57,110 @@ func (e *Entry) checkAuthors(authorInFeed bool) error {
return nil
}
// NewEntry creates a new Entry. It returns a *Entry.
// update sets the Updated time to time.Now.
func (e *Entry) update() {
if e.Updated == nil {
e.Updated = NewDate(time.Now())
} else {
e.Updated.DateTime = DateTime(time.Now())
}
}
// NewEntry creates a new Entry. It takes in a string title and 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.
func (e *Entry) AddAuthor(p *Person) {
if e.Authors == nil {
e.Authors = make([]*Person, 1)
e.Authors[0] = p
} else {
e.Authors = append(e.Authors, p)
}
e.Updated.DateTime = DateTime(time.Now())
// AddAuthor adds the Person a as an author to the Entry. It returns the index
// as an int.
func (e *Entry) AddAuthor(a *Person) int {
e.update()
return addToSlice(&e.Authors, a)
}
// 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.Categories = append(e.Categories, c)
// DeleteAuthor deletes the Person at index from the Entry. It returns 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)
}
e.Updated.DateTime = DateTime(time.Now())
e.update()
return nil
}
// 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.Contributors = append(e.Contributors, c)
}
e.Updated.DateTime = DateTime(time.Now())
// AddCategory adds the Category c to the Entry. It returns the index as an int.
func (e *Entry) AddCategory(c *Category) int {
e.update()
return addToSlice(&e.Categories, c)
}
// 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.Links = append(e.Links, l)
// DeleteCategory deletes the Category at index from the Entry. It returns 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)
}
e.Updated.DateTime = DateTime(time.Now())
e.update()
return nil
}
// 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.Extensions = append(e.Extensions, x)
// AddContributor adds the Person c as a contributor to the Entry. It returns
// the index as an int.
func (e *Entry) AddContributor(c *Person) int {
e.update()
return addToSlice(&e.Contributors, c)
}
// DeleteContributor deletes the Person at index from the Entry. It returns 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)
}
e.Updated.DateTime = DateTime(time.Now())
e.update()
return nil
}
// AddLink adds the Link l to the Entry. It returns the index as an int.
func (e *Entry) AddLink(l *Link) int {
e.update()
return addToSlice(&e.Links, l)
}
// DeleteLink deletes the Link at index from the Entry. It returns 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)
}
e.update()
return nil
}
// AddExtension adds the ExtensionElement x to the Entry. It returns the index
// as an int.
func (e *Entry) AddExtension(x *ExtensionElement) int {
e.update()
return addToSlice(&e.Extensions, x)
}
// DeleteExtension deletes the Extension at index from the Entry. It returns 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)
}
e.update()
return nil
}
// Check checks the Entry for incompatibilities with RFC4287. It returns an
@ -138,64 +175,64 @@ func (e *Entry) Check() error {
}
if err := e.checkAuthors(true); err != nil {
return fmt.Errorf("entry %v: %v", e, err)
return fmt.Errorf("entry %v: %v", e.ID.URI, 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, err)
return fmt.Errorf("category element %v of entry %v: %v", i, e.ID.URI, err)
}
}
if e.Content != nil {
if err := e.Content.Check(); err != nil {
return fmt.Errorf("content element of entry %v: %v", e, err)
return fmt.Errorf("content element of entry %v: %v", e.ID.URI, 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)
return fmt.Errorf("no content element of entry %v and no link element with rel \"alternate\"", e.ID.URI)
}
}
for i, c := range e.Contributors {
if err := c.Check(); err != nil {
return fmt.Errorf("contributor element %v of entry %v: %v", i, e, err)
return fmt.Errorf("contributor element %v of entry %v: %v", i, e.ID.URI, 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, err)
return fmt.Errorf("link element %v of entry %v: %v", i, e.ID.URI, 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)
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)
}
if e.Published != nil {
if err := e.Published.Check(); err != nil {
return fmt.Errorf("published element of entry %v: %v", e, err)
return fmt.Errorf("published element of entry %v: %v", e.ID.URI, err)
}
}
if e.Rights != nil {
if err := e.Rights.Check(); err != nil {
return fmt.Errorf("rights element of entry %v: %v", e, err)
return fmt.Errorf("rights element of entry %v: %v", e.ID.URI, err)
}
}
if e.Source != nil {
if err := e.Source.Check(); err != nil {
return fmt.Errorf("source element of entry %v: %v", e, err)
return fmt.Errorf("source element of entry %v: %v", e.ID.URI, err)
}
}
if e.Summary != nil {
if err := e.Summary.Check(); err != nil {
return fmt.Errorf("summary element of entry %v: %v", e, err)
return fmt.Errorf("summary element of entry %v: %v", e.ID.URI, err)
}
} else {
// atom:entry elements MUST contain an atom:summary element in either
@ -203,7 +240,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)
return fmt.Errorf("no summary element of entry %v but content of type out of line content", e.ID.URI)
}
// 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
@ -211,40 +248,41 @@ 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)
return fmt.Errorf("no summary element of entry %v but media type not xml", e.ID.URI)
}
}
if e.Title == nil {
return fmt.Errorf("no title element of entry %v", e)
return fmt.Errorf("no title element of entry %v", e.ID.URI)
} else {
if err := e.Title.Check(); err != nil {
return fmt.Errorf("title element of entry %v: %v", e, err)
return fmt.Errorf("title element of entry %v: %v", e.ID.URI, err)
}
}
if e.Updated == nil {
return fmt.Errorf("no updated element of entry %v", e)
return fmt.Errorf("no updated element of entry %v", e.ID.URI)
} else {
if err := e.Updated.Check(); err != nil {
return fmt.Errorf("updated element of entry %v: %v", e, err)
return fmt.Errorf("updated element of entry %v: %v", e.ID.URI, 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, err)
return fmt.Errorf("extension element %v of entry %v: %v", i, e.ID.URI, err)
}
}
return nil
}
// ToXML converts the Feed to XML. It returns a string and an error.
// ToXML converts the Feed to XML. It takes in a string encoding and returns a
// string and an 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", err)
return "", fmt.Errorf("error xml encoding entry %v: %v", e.ID.URI, err)
}
return fmt.Sprintln(`<?xml version="1.0" encoding="`+encoding+`"?>`) + string(xml), nil

View File

@ -10,8 +10,8 @@ type ExtensionElement struct {
XMLName xml.Name
}
// NewExtensionElement creates a new ExtensionElement. It returns a
// *ExtensionElement.
// NewExtensionElement creates a new ExtensionElement. It takes in a string name
// and any value and returns a *ExtensionElement.
func NewExtensionElement(name string, value any) *ExtensionElement {
return &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value}
}

213
feed.go
View File

@ -25,86 +25,151 @@ type Feed struct {
Entries []*Entry `xml:",omitempty"`
}
// NewFeed creates a new Feed. It returns a *Feed.
// update sets the Updated time to time.Now.
func (f *Feed) update() {
if f.Updated == nil {
f.Updated = NewDate(time.Now())
} else {
f.Updated.DateTime = DateTime(time.Now())
}
}
// NewFeed creates a new Feed. It takes in a string title and 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.
func (f *Feed) AddAuthor(p *Person) {
if f.Authors == nil {
f.Authors = make([]*Person, 1)
f.Authors[0] = p
} else {
f.Authors = append(f.Authors, p)
}
f.Updated.DateTime = DateTime(time.Now())
// AddAuthor adds the Person a as an author to the Feed. It returns the index as
// an int.
func (f *Feed) AddAuthor(a *Person) int {
f.update()
return addToSlice(&f.Authors, a)
}
// 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.Categories = append(f.Categories, c)
// DeleteAuthor deletes the Person at index from the Feed. It returns 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)
}
f.Updated.DateTime = DateTime(time.Now())
f.update()
return nil
}
// 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.Contributors = append(f.Contributors, c)
}
f.Updated.DateTime = DateTime(time.Now())
// AddCategory adds the Category c to the Feed. It returns the index as an int.
func (f *Feed) AddCategory(c *Category) int {
f.update()
return addToSlice(&f.Categories, c)
}
// AddLink adds the Link to the Feed. There should be one Link with Rel "self".
func (f *Feed) AddLink(l *Link) {
if f.Links == nil {
f.Links = make([]*Link, 1)
f.Links[0] = l
} else {
f.Links = append(f.Links, l)
// DeleteCategory deletes the Category at index from the Feed. It returns 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)
}
f.Updated.DateTime = DateTime(time.Now())
f.update()
return nil
}
// 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.Extensions = append(f.Extensions, e)
}
f.Updated.DateTime = DateTime(time.Now())
// AddContributor adds the Person c as a contributor to the Feed. It returns the
// index as an int.
func (f *Feed) AddContributor(c *Person) int {
f.update()
return addToSlice(&f.Contributors, c)
}
// 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.Entries = append(f.Entries, e)
// DeleteContributor deletes the Person at index from the Feed. It returns 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)
}
f.Updated.DateTime = DateTime(time.Now())
f.update()
return nil
}
// AddLink adds the Link l to the Feed. It returns the index as an int.
//
// There should be one Link with Rel "self".
func (f *Feed) AddLink(l *Link) int {
f.update()
return addToSlice(&f.Links, l)
}
// DeleteLink deletes the Link at index from the Feed. It returns 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)
}
f.update()
return nil
}
// AddExtension adds the Extension e to the Feed. It returns the index as an
// int.
func (f *Feed) AddExtension(e *ExtensionElement) int {
f.update()
return addToSlice(&f.Extensions, e)
}
// DeleteExtension deletes the Extension at index from the Feed. It returns 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)
}
f.update()
return nil
}
// AddEntry adds the Entry e to the Feed. It returns the index as an int.
func (f *Feed) AddEntry(e *Entry) int {
f.update()
return addToSlice(&f.Entries, e)
}
// DeleteEntry deletes the Entry at index from the Feed. It returns 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)
}
f.update()
return nil
}
// DeleteEntryByURI deletes the Entry from the Feed. It takes in a string uri
// and returns 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:]...)
f.update()
return nil
}
// Check checks the Feed for incompatibilities with RFC4287. It returns an
@ -124,93 +189,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, err)
return fmt.Errorf("no authors set in feed %v: %v", f.ID.URI, 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, err)
return fmt.Errorf("author element %v of feed %v: %v", i, f.ID.URI, 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, err)
return fmt.Errorf("category element %v of feed %v: %v", i, f.ID.URI, 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, err)
return fmt.Errorf("contributor element %v of feed %v: %v", i, f.ID.URI, err)
}
}
if f.Generator != nil {
if err := f.Generator.Check(); err != nil {
return fmt.Errorf("generator element of feed %v: %v", f, err)
return fmt.Errorf("generator element of feed %v: %v", f.ID.URI, err)
}
}
if f.Icon != nil {
if err := f.Icon.Check(); err != nil {
return fmt.Errorf("icon element of feed %v: %v", f, err)
return fmt.Errorf("icon element of feed %v: %v", f.ID.URI, 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, err)
return fmt.Errorf("link element %v of feed %v: %v", i, f.ID.URI, 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)
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)
}
if f.Logo != nil {
if err := f.Logo.Check(); err != nil {
return fmt.Errorf("logo element of feed %v: %v", f, err)
return fmt.Errorf("logo element of feed %v: %v", f.ID.URI, err)
}
}
if f.Rights != nil {
if err := f.Rights.Check(); err != nil {
return fmt.Errorf("rights element of feed %v: %v", f, err)
return fmt.Errorf("rights element of feed %v: %v", f.ID.URI, err)
}
}
if f.Subtitle != nil {
if err := f.Subtitle.Check(); err != nil {
return fmt.Errorf("subtitle element of feed %v: %v", f, err)
return fmt.Errorf("subtitle element of feed %v: %v", f.ID.URI, err)
}
}
if f.Title == nil {
return fmt.Errorf("no title element of feed %v", f)
return fmt.Errorf("no title element of feed %v", f.ID.URI)
} else {
if err := f.Title.Check(); err != nil {
return fmt.Errorf("title element of feed %v: %v", f, err)
return fmt.Errorf("title element of feed %v: %v", f.ID.URI, err)
}
}
if f.Updated == nil {
return fmt.Errorf("no updated element of feed %v", f)
return fmt.Errorf("no updated element of feed %v", f.ID.URI)
} else {
if err := f.Updated.Check(); err != nil {
return fmt.Errorf("updated element of feed %v: %v", f, err)
return fmt.Errorf("updated element of feed %v: %v", f.ID.URI, 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, err)
return fmt.Errorf("extension element %v of feed %v: %v", i, f.ID.URI, 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, err)
return fmt.Errorf("entry element %v of feed %v: %v", i, f.ID.URI, err)
}
}
@ -221,7 +286,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", err)
return "", fmt.Errorf("error xml encoding feed %v: %v", f.ID.URI, err)
}
return fmt.Sprintln(`<?xml version="1.0" encoding="`+encoding+`"?>`) + string(xml), nil

View File

@ -14,10 +14,11 @@ type Generator struct {
Text string `xml:",chardata"`
}
// NewGenerator creates a new Generator. It returns a *Generator.
// NewGenerator creates a new Generator. It takes in a string text and returns a
// *Generator.
func NewGenerator(text string) *Generator {
return &Generator{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Text: html.UnescapeString(text),
}
}

View File

@ -12,10 +12,10 @@ type Icon struct {
URI string `xml:",chardata"` // IRI
}
// NewIcon creates a new Icon. It returns a *Icon.
// NewIcon creates a new Icon. It takes in a string uri and returns a *Icon.
func NewIcon(uri string) *Icon {
return &Icon{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
URI: uri,
}
}

4
id.go
View File

@ -12,10 +12,10 @@ type ID struct {
URI string `xml:",chardata"` // IRI
}
// NewID creates a new ID. It returns a *ID.
// NewID creates a new ID. It takes in a string uri and returns a *ID.
func NewID(uri string) *ID {
return &ID{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
URI: uri,
}
}

View File

@ -13,13 +13,13 @@ type InlineOtherContent struct {
Type string `xml:"type,attr,omitempty"` // MediaType
}
// newInlineOtherContent creates a new InlineOtherContent. It returns a
// *InlineOtherContent and an error.
// newInlineOtherContent creates a new InlineOtherContent. It takes in the string
// mediaType and any content and returns a *InlineOtherContent and an error.
func newInlineOtherContent(mediaType string, content any) *InlineOtherContent {
mediaType, _, _ = mime.ParseMediaType(mediaType)
return &InlineOtherContent{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Type: mediaType,
AnyElement: content,
}

View File

@ -12,11 +12,11 @@ type InlineTextContent struct {
Text string `xml:",chardata"`
}
// newInlineTextContent creates a new InlineTextContent. It returns a
// *InlineTextContent.
// newInlineTextContent creates a new InlineTextContent. It takes in the strings
// mediaType and text and returns a *InlineTextContent.
func newInlineTextContent(mediaType, text string) *InlineTextContent {
return &InlineTextContent{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Type: mediaType,
Text: text,
}

View File

@ -12,11 +12,11 @@ type InlineXHTMLContent struct {
Type string `xml:"type,attr"`
}
// newInlineXHTMLContent creates a new InlineXHTMLContent. It returns a
// *InlineXHTMLContent.
// newInlineXHTMLContent creates a new InlineXHTMLContent. It takes in the
// string mediaType and the XHTMLDiv div and returns a *InlineXHTMLContent.
func newInlineXHTMLContent(mediaType string, div *XHTMLDiv) *InlineXHTMLContent {
return &InlineXHTMLContent{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Type: mediaType,
XHTMLDiv: div,
}

View File

@ -17,10 +17,10 @@ type Link struct {
Length uint `xml:"length,attr,omitempty"`
}
// NewLink creates a new Link. It returns a *Link.
// NewLink creates a new Link. It takes in the string href and returns a *Link.
func NewLink(href string) *Link {
return &Link{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Href: href,
}
}

View File

@ -11,10 +11,10 @@ type Logo struct {
URI string `xml:",chardata"` // IRI
}
// NewLogo creates a new Logo. It returns a *Logo.
// NewLogo creates a new Logo. It takes in a string uri and returns a *Logo.
func NewLogo(uri string) *Logo {
return &Logo{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
URI: uri,
}
}

View File

@ -13,13 +13,13 @@ type OutOfLineContent struct {
SRC string `xml:"src,attr"` // IRI
}
// newOutOfLineContent creates a new OutOfLineContent. It returns a
// *OutOfLineContent.
// newOutOfLineContent creates a new OutOfLineContent. It takes in the strings
// mediaType and src and returns a *OutOfLineContent.
func newOutOfLineContent(mediaType, src string) *OutOfLineContent {
mediaType, _, _ = mime.ParseMediaType(mediaType)
return &OutOfLineContent{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Type: mediaType,
SRC: src,
}

View File

@ -13,22 +13,28 @@ type Person struct {
Extensions []*ExtensionElement `xml:",any,omitempty"`
}
// NewPerson creates a new Person. It returns a *Person.
// NewPerson creates a new Person. It takes in a string name and returns a
// *Person.
func NewPerson(name string) *Person {
return &Person{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Name: name,
}
}
// 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)
// AddExtension adds the Extension e to the Person. It returns the 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 returns 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)
}
return nil
}
// Check checks the Person for incompatibilities with RFC4287. It returns an
@ -50,11 +56,9 @@ func (p *Person) Check() error {
}
}
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)
}
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

@ -13,10 +13,11 @@ type PlainText struct {
// isText checks whether the PlainText is a Text. It returns a bool.
func (p *PlainText) isText() bool { return true }
// newPlainText creates a new PlainText. It returns a *PlainText.
// newPlainText creates a new PlainText. It takes in the strings textType and
// content and returns a *PlainText.
func newPlainText(textType, content string) *PlainText {
return &PlainText{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Type: textType,
Text: content,
}

View File

@ -25,7 +25,79 @@ type Source struct {
// NewSource creates a new Source. It returns a *Source.
func NewSource() *Source {
return &Source{CommonAttributes: newCommonAttributes()}
return &Source{CommonAttributes: NewCommonAttributes()}
}
// AddAuthor adds the Person a as an author to the Source. It returns the index
// as an int.
func (s *Source) AddAuthor(a *Person) int {
return addToSlice(&s.Authors, a)
}
// DeleteAuthor deletes the Person at index from the Source. It returns 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 c to the Source. It returns the 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 returns 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 c as a contributor to the Source. It returns
// the 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 returns 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 l to the Source. It returns the 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 returns 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 e to the Source. It returns the 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 returns 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
}
// Check checks the Source for incompatibilities with RFC4287. It returns an

View File

@ -7,7 +7,10 @@ type Text interface {
Check() error
}
// NewText creates a new Text. It returns a Text.
// NewText creates a new Text. It takes in the strings textType and content and
// returns a Text.
//
// If textType is invalid it returns nil.
func NewText(textType, content string) Text {
switch textType {
case "text", "":

View File

@ -11,7 +11,8 @@ type XHTMLDiv struct {
Content string `xml:",innerxml"`
}
// NewXHTMLDiv creates a new XHTMLDiv. It returns a *XHTMLDiv.
// NewXHTMLDiv creates a new XHTMLDiv. It takes in a string content and returns
// a *XHTMLDiv.
func NewXHTMLDiv(content string) *XHTMLDiv {
return &XHTMLDiv{
XMLNS: "http://www.w3.org/1999/xhtml",

View File

@ -13,10 +13,11 @@ type XHTMLText struct {
// isText checks whether the XHTMLText is a Text. It returns a bool.
func (x *XHTMLText) isText() bool { return true }
// newPlainText creates a new PlainText. It returns a *PlainText.
// newPlainText creates a new PlainText. It takes in the strings textType and
// content and returns a *PlainText.
func newXHTMLText(textType, content string) *XHTMLText {
return &XHTMLText{
CommonAttributes: newCommonAttributes(),
CommonAttributes: NewCommonAttributes(),
Type: textType,
XHTMLDiv: NewXHTMLDiv(content),
}