atomfeed

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 22, 2017 License: MIT Imports: 8 Imported by: 0

README


logo
atomfeed – create atom syndication feeds the easy way

Create Atom 1.0 compliant feeds for blogs and more.

GoDoc Go Report Card

Main Features

This package:

  • allows easy creation of valid Atom 1.0 feeds.
  • provides convenience functions to create feeds suitable for most blogs.
  • enables creation of complex atom feeds by usage of low–level structs.
  • checks created feeds for most common issues (missing IDs, titles, time stamps…).
  • has no external dependencies

Installation

Import the library with

import "github.com/denisbrodbeck/atomfeed"

Usage

package main

import (
	"bytes"
	"fmt"
	"log"
	"time"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	// create constant and unique feed ID
	feedID := atomfeed.NewID("tag:example.com,2005-12-21:blog")
	// set basic feed properties
	title := "example.com blog"
	subtitle := "Get the very latest news from the net."
	author := atomfeed.NewPerson("Go Pher", "", "https://blog.golang.org/gopher")
	coauthor := atomfeed.NewPerson("Octo Cat", "[email protected]", "https://octodex.github.com/")
	updated := time.Date(2015, time.March, 21, 8, 30, 15, 0, time.UTC)
	baseURL := "https://example.com"
	feedURL := baseURL + "/feed.atom"

	entry1Date := time.Date(2012, time.October, 21, 8, 30, 15, 0, time.UTC)
	entry1 := atomfeed.NewEntry(
		atomfeed.NewEntryID(feedID, entry1Date), // constant and unique id
		"Article 1",                              // title
		baseURL+"/post/1",                        // permalink
		author,                                   // author of the entry/post
		entry1Date,                               // updated date – mandatory
		entry1Date,                               // published date – optional
		[]string{"tech", "go"},                   // categories
		[]byte("<em>go go go</em>"),              // summary – optional
		[]byte("<h1>Header 1</h1>"),              // content
	)
	entry2Date := time.Date(2012, time.December, 21, 8, 30, 15, 0, time.UTC)
	entry2 := atomfeed.NewEntry(
		atomfeed.NewEntryID(feedID, entry2Date), // constant and unique id
		"Article 2",                              // title
		baseURL+"/post/2",                        // permalink
		coauthor,                                 // author of the entry/post
		entry2Date,                               // updated date – mandatory
		entry2Date,                               // published date – optional
		[]string{"cat", "dog"},                   // categories – optional
		[]byte("I'm a cat!"),                     // summary – optional
		[]byte("<h1>Header 2</h1>"),              // content
	)
	entries := []atomfeed.Entry{
		entry1,
		entry2,
	}

	feed := atomfeed.NewFeed(feedID, author, title, subtitle, baseURL, feedURL, updated, entries)
	// most atom elements support language attributes (optional)
	feed.CommonAttributes = &atomfeed.CommonAttributes{Lang: "en"}
	// perform sanity checks on created feed
	if err := feed.Verify(); err != nil {
		log.Fatal(err)
	}

	out := &bytes.Buffer{}
	// serialize XML data into stream
	if err := feed.Encode(out); err != nil {
		log.Fatal(err)
	}
	fmt.Print(out.String())
}

Make your atom feed discoverable by adding a link to your html's head:

<head>
	…
	<link rel="alternate" type="application/atom+xml" title="Atom Feed" href="https://example.com/feed.atom" />
	…
</head>

Link to your feed from your content:

<a href="https://example.com/feed.atom" type="application/atom+xml" title="Atom Feed">Subscribe with Atom</a>

ID

So what IDs should I use for my atom feed / entries?

RFC 4287 writes the following:

When an Atom Document is relocated, migrated, syndicated, republished, exported, or imported, the content of its atom:id element MUST NOT change.

There are three requirements for an Atom ID:

  1. The ID must be a valid URI (see RFC 4287).
  2. The ID must be globally unique, across all Atom feeds, everywhere, for all time.
  3. The ID must never change.

I can use permalinks, they are unique and don't change, aren't they?

Well, you could use permalinks as Atom IDs, but depending on how your permalinks are constructed, they could change. Imagine permalinks, which are automatically constructed from your base URL and your post's title. What happens, if you update the title of your post? The permalink to your post changes and thus the Atom ID of your entry changes.

So what do I use instead? UUID? URN?

Again, you could use a UUID and store it along side your post, but it's not easily readable by humans. URNs require additional registration.

There is an easier way to human readable, unique and constant IDs, though: Tag URIs

tag URI

Tag URIs are defined in RFC 4151.

Example-Feed: tag:example.com,2005:blog

Example-Post: tag:example.com,2005:blog.post-20171224083015

  • start with tag:
  • append an authority name (the domain you own or an email address): example.com
  • append a comma ,
  • append a date, that signifies, when you had control/ownership over this authority name, like 2005 or 2005-02 or 2005-02-24
  • append a colon :
  • append a specifier (anything you like): blog
  • you've got a valid ID for an atom:feed: tag:example.com,2005:blog
    • append a dot .
    • append the posts creation time without special characters, turn 2017-12-24 08:30:15 into 20171224083015
    • you've got a valid ID for an atom:entry: tag:example.com,2005:blog.post-20171224083015

For further info check out Mark Pilgrims article on how to make a good ID in Atom.

Verification

The Atom 1.0 standard defines several must–have properties of valid atom feeds and this package allows the feed author to verify the validity of created feeds and entries.

Most common issues are (verified by this package):

  • Missing or invalid ID on atom:feed
  • Missing or invalid ID on atom:entry
  • Missing titles on atom:feed / atom:entry
  • Invalid time stamps (missing or not RFC3339 compliant)
  • Missing author
  • Invalid URIs in elements which require a valid IRI (atom:icon)
  • Invalid content
package main

import (
	"log"
	"time"
	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	feed := atomfeed.Feed{
		ID:      atomfeed.NewID("tag:example.com,2005-07-18:blog"),
		Title:   &atomfeed.TextConstruct{Value: "Deep Dive Into Go"},
		Updated: atomfeed.NewDate(time.Now()),
		Author:  &atomfeed.Person{Name: "Go Pher"},
	}
	// perform sanity checks on created feed
	if err := feed.Verify(); err != nil {
		log.Fatal(err)
	}
}

Further checks can be made with the atom feed validator from W3C. Please do run this validator, if you are constructing a complex feed.

What is not included?

RFC 4287 defines several very advanced features, which were deliberately not implemented:

Credits

The Go gopher was created by Denis Brodbeck with gopherize.me, based on original artwork from Renee French.

License

The MIT License (MIT) — Denis Brodbeck. Please have a look at the LICENSE for more details.

Documentation

Overview

Package atomfeed creates atom syndication feeds (Atom 1.0 RFC4287).

https://github.com/denisbrodbeck/atomfeed
https://tools.ietf.org/html/rfc4287

This package allows easy creation of valid Atom 1.0 feeds. It provides functions to create feeds suitable for most blogs. Direct usage of low–level structs allows the creation of more complex atom feeds.

The Atom 1.0 standard defines several must–have properties of valid atom feeds and this package allows the feed author to verify the validity of created feeds and entries and to check for most common issues (missing IDs, titles, time stamps…).

Further validation with the final output of this package can be done with the online validator https://validator.w3.org/feed/ from W3C.

Example

Create a basic atom syndication feed suitable for blogs:

package main

import (
	"bytes"
	"fmt"
	"log"
	"time"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	// create constant and unique feed ID
	feedID := atomfeed.NewID("tag:example.com,2005-12-21:blog")
	// set basic feed properties
	title := "example.com blog"
	subtitle := "Get the very latest news from the net."
	author := atomfeed.NewPerson("Go Pher", "", "https://blog.golang.org/gopher")
	coauthor := atomfeed.NewPerson("Octo Cat", "[email protected]", "https://octodex.github.com/")
	updated := time.Date(2015, time.March, 21, 8, 30, 15, 0, time.UTC)
	baseURL := "https://example.com"
	feedURL := baseURL + "/feed.atom"

	entry1Date := time.Date(2012, time.October, 21, 8, 30, 15, 0, time.UTC)
	entry1 := atomfeed.NewEntry(
		atomfeed.NewEntryID(feedID, entry1Date), // constant and unique id
		"Article 1",                             // title
		baseURL+"/post/1",                       // permalink
		author,                                  // author of the entry/post
		entry1Date,                              // updated date – mandatory
		entry1Date,                              // published date – optional
		[]string{"tech", "go"},                  // categories
		[]byte("<em>go go go</em>"),             // summary – optional
		[]byte("<h1>Header 1</h1>"),             // content
	)
	entry2Date := time.Date(2012, time.December, 21, 8, 30, 15, 0, time.UTC)
	entry2 := atomfeed.NewEntry(
		atomfeed.NewEntryID(feedID, entry2Date), // constant and unique id
		"Article 2",                             // title
		baseURL+"/post/2",                       // permalink
		coauthor,                                // author of the entry/post
		entry2Date,                              // updated date – mandatory
		entry2Date,                              // published date – optional
		[]string{"cat", "dog"},                  // categories – optional
		[]byte("I'm a cat!"),                    // summary – optional
		[]byte("<h1>Header 2</h1>"),             // content
	)
	entries := []atomfeed.Entry{
		entry1,
		entry2,
	}

	feed := atomfeed.NewFeed(feedID, author, title, subtitle, baseURL, feedURL, updated, entries)
	// most atom elements support language attributes (optional)
	feed.CommonAttributes = &atomfeed.CommonAttributes{Lang: "en"}
	// perform sanity checks on created feed
	if err := feed.Verify(); err != nil {
		log.Fatal(err)
	}

	out := &bytes.Buffer{}
	// serialize XML data into stream
	if err := feed.Encode(out); err != nil {
		log.Fatal(err)
	}
	fmt.Print(out.String())
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Category

type Category struct {
	// Term is a mandatory string that identifies the category
	// to which the entry or feed belongs.
	// https://tools.ietf.org/html/rfc4287#section-4.2.2.1
	Term string `xml:"term,attr"`
	// Scheme is an optional IRI that identifies a categorization scheme.
	// https://tools.ietf.org/html/rfc4287#section-4.2.2.2
	Scheme string `xml:"scheme,attr,omitempty"`
	// Label provides an optional human-readable label for display in end-user applications.
	// https://tools.ietf.org/html/rfc4287#section-4.2.2.3
	Label string `xml:"label,attr,omitempty"`
	*CommonAttributes
}

Category is an atom:category element.

https://tools.ietf.org/html/rfc4287#section-4.2.2

func NewCategory

func NewCategory(category string) *Category

NewCategory returns an atom:category element.

Example

Create new category / tag:

package main

import (
	"fmt"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	category := atomfeed.NewCategory("golang")
	fmt.Println(category.Term)
}
Output:

golang

type CommonAttributes

type CommonAttributes struct {
	Base string `xml:"xml:base,attr,omitempty"`
	Lang string `xml:"xml:lang,attr,omitempty"`
}

CommonAttributes is an atom:commonattributes element.

https://tools.ietf.org/html/rfc4287#section-2
Example

Add attributes like "lang" to feed or entry elements:

package main

import (
	"time"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	feed := atomfeed.Feed{
		ID:      atomfeed.ID{Value: "tag:example.com,2005-07-18:blog"},
		Title:   &atomfeed.TextConstruct{Value: "Deep Dive Into Go"},
		Updated: atomfeed.NewDate(time.Now()),
		Author:  &atomfeed.Person{Name: "Go Pher"},
	}
	// add language attribute
	feed.CommonAttributes = &atomfeed.CommonAttributes{Lang: "en"}
}
Output:

type Content

type Content struct {
	// Type MAY be one of "text", "html", or "xhtml".
	// https://tools.ietf.org/html/rfc4287#section-4.1.3.1
	Type string `xml:"type,attr,omitempty"`
	// Source is an optional attribute, whose value MUST be an IRI reference [RFC3987].
	// If the "src" attribute is present, atom:content MUST be empty.
	// https://tools.ietf.org/html/rfc4287#section-4.1.3.2
	Source   string `xml:"src,attr,omitempty"`
	Value    string `xml:",chardata"`
	ValueXML string `xml:",innerxml"`
	*CommonAttributes
	// contains filtered or unexported fields
}

Content is an atom:content element which either contains or links to the content of the entry.

https://tools.ietf.org/html/rfc4287#section-4.1.3

func NewContent

func NewContent(contentType, source string, value []byte) *Content

NewContent creates the correct atom:content element depending on type attribute.

https://tools.ietf.org/html/rfc4287#section-4.1.3.3

type Date

type Date struct {
	// Value must be a date conforming to RFC3339.
	// Try: `time.Now().Format(time.RFC3339)`
	Value string `xml:",chardata"`
	*CommonAttributes
}

Date is an atom:date element whose content MUST conform to the "date-time" format defined in [RFC3339].

https://tools.ietf.org/html/rfc4287#section-3.3

func NewDate

func NewDate(t time.Time) *Date

NewDate returns an atom:date element with valid RFC3339 time data.

Example

Create RFC 3339 compliant date:

package main

import (
	"fmt"
	"time"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	now := time.Date(2017, time.December, 22, 8, 30, 15, 0, time.UTC)
	date := atomfeed.NewDate(now)
	fmt.Println(date.Value)
}
Output:

2017-12-22T08:30:15Z

type Entry

type Entry struct {
	ID          ID             `xml:"id"`
	Title       *TextConstruct `xml:"title"`
	Links       []Link         `xml:"link"`
	Published   *Date          `xml:"published"`
	Updated     *Date          `xml:"updated"`
	Author      *Person        `xml:"author"`
	Categories  []Category     `xml:"category"`
	Copyright   *TextConstruct `xml:"rights"`
	Contributor []Person       `xml:"contributor"`
	Source      *Source        `xml:"source"`
	Summary     *Content       `xml:"summary"`
	Content     *Content       `xml:"content"`
	*CommonAttributes
}

Entry is an atom:entry element and represents an individual entry, acting as a container for metadata and data associated with the entry.

https://tools.ietf.org/html/rfc4287#section-4.1.2

func NewEntry

func NewEntry(id ID, title, permalink string, author *Person, updated, published time.Time, categories []string, summary, content []byte) Entry

NewEntry creates a basic atom:entry suitable for e.g. a blog.

func (*Entry) String added in v0.8.0

func (e *Entry) String() string

func (*Entry) Verify

func (e *Entry) Verify() *VerificationError

Verify checks an atom:entry element for most common errors.

Common checks are the existence of atom:id, atom:author, atom:title, atom:updated, atom:content.

type Feed

type Feed struct {
	XMLName     xml.Name       `xml:"feed"`
	Namespace   string         `xml:"xmlns,attr"` // xmlns="http://www.w3.org/2005/Atom"
	ID          ID             `xml:"id"`
	Generator   *Generator     `xml:"generator"`
	Links       []Link         `xml:"link"`
	Updated     *Date          `xml:"updated"`
	Title       *TextConstruct `xml:"title"`
	Subtitle    *TextConstruct `xml:"subtitle"`
	Icon        *Icon          `xml:"icon"`
	Categories  []Category     `xml:"category"`
	Author      *Person        `xml:"author"`
	Contributor []Person       `xml:"contributor"`
	Copyright   *TextConstruct `xml:"rights"` // https://tools.ietf.org/html/rfc4287#section-4.2.10
	Entries     []Entry        `xml:"entry"`
	*CommonAttributes
}

Feed is an atom:feed element and is the document (i.e., top-level) element of an Atom Feed Document, acting as a container for metadata and data associated with the feed.

https://tools.ietf.org/html/rfc4287#section-4.1.1

func NewFeed

func NewFeed(id ID, author *Person, title, subtitle, baseURL, feedURL string, updated time.Time, entries []Entry) Feed

NewFeed creates a basic atom:feed element suitable for e.g. a blog.

func (*Feed) Encode

func (f *Feed) Encode(w io.Writer) error

Encode writes the XML encoding of Feed to the stream.

func (*Feed) Verify

func (f *Feed) Verify() *VerificationError

Verify checks an atom:feed element for most common errors.

Common checks are the existence of atom:id, atom:author, atom:title, atom:updated. Any entries will be checked, too.

type Generator

type Generator struct {
	URI     string `xml:"uri,attr,omitempty"`
	Version string `xml:"version,attr,omitempty"`
	Value   string `xml:",chardata"`
	*CommonAttributes
}

Generator is an atom:generator element. The "atom:generator" element's content identifies the agent used to generate a feed.

https://tools.ietf.org/html/rfc4287#section-4.2.4

type ID

type ID struct {
	Value string `xml:",chardata"`
	*CommonAttributes
}

ID is an atom:id element and conveys a permanent, universally unique identifier for an entry or feed.

The value of ID must be a valid IRI. A permalink SHOULDN'T be used as an ID.

https://github.com/denisbrodbeck/atomfeed/blob/master/README.md#id
https://tools.ietf.org/html/rfc4287#section-4.2.6

func NewEntryID

func NewEntryID(feedID ID, entryCreationTime time.Time) ID

NewEntryID creates a stable ID for an atom:entry element. The resulting ID follows the 'tag' URI scheme as defined in RFC 4151. More specifically the function creates valid atom IDs by article creation time.

Further info:

https://github.com/denisbrodbeck/atomfeed/blob/master/README.md#id
http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id
https://tools.ietf.org/html/rfc4151
Example

Create new constant and unique entry id:

package main

import (
	"fmt"
	"time"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	feedID := atomfeed.ID{Value: "tag:example.com,2005-07-18:blog"}
	entryCreationTime := time.Date(2017, time.December, 21, 8, 30, 15, 0, time.UTC)
	id := atomfeed.NewEntryID(feedID, entryCreationTime)
	fmt.Println(id.Value)
}
Output:

tag:example.com,2005-07-18:blog.post-20171221083015

func NewFeedID

func NewFeedID(authorityName string, creationTime time.Time, specific string) ID

NewFeedID creates a stable ID for an atom:feed element. The resulting ID follows the 'tag' URI scheme as defined in RFC 4151. More specifically the function creates valid atom IDs by feed creation time and a custom specifier.

Further info:

https://github.com/denisbrodbeck/atomfeed/blob/master/README.md#id
http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id
https://tools.ietf.org/html/rfc4151
Example

Create new constant and unique feed id:

package main

import (
	"fmt"
	"time"

	"github.com/denisbrodbeck/atomfeed"
)

func main() {
	authorityName := "example.com"
	ownedAt := time.Date(2005, time.July, 18, 0, 0, 0, 0, time.UTC)
	specifier := "blog"
	id := atomfeed.NewFeedID(authorityName, ownedAt, specifier)
	fmt.Println(id.Value)
}
Output:

tag:example.com,2005-07-18:blog

func NewID added in v0.7.0

func NewID(id string) ID

NewID creates an atom:id element. The given id parameter is taken as a raw value for ID.

type Icon

type Icon struct {
	Value string `xml:",chardata"`
	*CommonAttributes
}

Icon is an optional atom:icon element, which is an IRI reference [RFC3987] that identifies an image that provides iconic visual identification for a feed.

The image SHOULD have an aspect ratio of 1 (horizontal) to 1 (vertical) and SHOULD be suitable for presentation at a small size.

https://tools.ietf.org/html/rfc4287#section-4.2.5
type Link struct {
	// Href contains the link's mandatory IRI.
	// https://tools.ietf.org/html/rfc4287#section-4.2.7.1
	Href string `xml:"href,attr"`
	// Rel is an optional attribute that indicates the link relation type.
	// https://tools.ietf.org/html/rfc4287#section-4.2.7.2
	Rel string `xml:"rel,attr,omitempty"`
	// Type is an optional advisory media type.
	// It is a hint about the type of the representation that is
	// expected to be returned when the value of the href attribute is
	// dereferenced.  Note that the type attribute does not override the
	// actual media type returned with the representation.
	// https://tools.ietf.org/html/rfc4287#section-4.2.7.3
	Type string `xml:"type,attr,omitempty"`
	// HrefLang describes the optional language of the resource pointed to by the href attribute.
	// When used together with the rel="alternate", it implies a translated version of the entry.
	// https://tools.ietf.org/html/rfc4287#section-4.2.7.4
	HrefLang string `xml:"hreflang,attr,omitempty"`
	// Title conveys optional human-readable information about the link.
	// https://tools.ietf.org/html/rfc4287#section-4.2.7.5
	Title string `xml:"title,attr,omitempty"`
	// Length indicates an optional advisory length of the linked content in octets.
	// https://tools.ietf.org/html/rfc4287#section-4.2.7.6
	Length string `xml:"length,attr,omitempty"`
	*CommonAttributes
}

Link is an atom:link element that defines a reference from an entry or feed to a Web resource.

https://tools.ietf.org/html/rfc4287#section-4.2.7
type Logo struct {
	Value string `xml:",chardata"`
	*CommonAttributes
}

Logo is an atom:logo element, which is n IRI reference [RFC3987] that identifies an image that provides visual identification for a feed.

The image SHOULD have an aspect ratio of 2 (horizontal) to 1 (vertical).

https://tools.ietf.org/html/rfc4287#section-4.2.8

type Person

type Person struct {
	Name  string `xml:"name"`
	Email string `xml:"email,omitempty"`
	URI   string `xml:"uri,omitempty"`
	*CommonAttributes
}

Person is an atom:person element that describes a person, corporation, or similar entity. Use the person struct to create author, coauthor or contributor entities.

https://tools.ietf.org/html/rfc4287#section-3.2

func NewPerson

func NewPerson(name, email, uri string) *Person

NewPerson returns an atom:person element.

type Source

type Source struct {
	ID          *ID            `xml:"id"`
	Generator   *Generator     `xml:"generator"`
	Links       []Link         `xml:"link"`
	Updated     *Date          `xml:"updated"`
	Title       *TextConstruct `xml:"title"`
	Subtitle    *TextConstruct `xml:"subtitle"`
	Icon        *Icon          `xml:"icon"`
	Categories  []Category     `xml:"category"`
	Author      *Person        `xml:"author"`
	Contributor []Person       `xml:"contributor"`
	Copyright   *TextConstruct `xml:"rights"`
	*CommonAttributes
}

Source is an atom:source element.

https://tools.ietf.org/html/rfc4287#section-4.2.11

type TextConstruct

type TextConstruct struct {
	// Type MAY be one of "text", "html", or "xhtml".
	// https://tools.ietf.org/html/rfc4287#section-3.1.1
	Type  string `xml:"type,attr,omitempty"`
	Value string `xml:",chardata"`
	*CommonAttributes
}

TextConstruct contains human-readable text, usually in small quantities.

https://tools.ietf.org/html/rfc4287#section-3.1

type VerificationError added in v0.8.0

type VerificationError struct {
	Errors []error
}

VerificationError describes problems encountered during feed verification.

func (*VerificationError) Error added in v0.8.0

func (e *VerificationError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL