Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

[![Build Status](https://travis-ci.org/mmcdole/gofeed.svg?branch=master)](https://travis-ci.org/mmcdole/gofeed) [![Coverage Status](https://coveralls.io/repos/github/mmcdole/gofeed/badge.svg?branch=master)](https://coveralls.io/github/mmcdole/gofeed?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/mmcdole/gofeed)](https://goreportcard.com/report/github.com/mmcdole/gofeed) [![](https://godoc.org/github.com/mmcdole/gofeed?status.svg)](http://godoc.org/github.com/mmcdole/gofeed) [![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org)

# Gofeed: A Robust Feed Parser for Golang
# Gofeed: A Robust Feed Library for Golang

<img src="https://github.com/mmcdole/gofeed/assets/3767096/ab4e7b0e-1472-4249-880c-c6784000ed31" width="150" height="150">
<br /><br />

`gofeed` is a powerful and flexible library designed for parsing **RSS**, **Atom**, and **JSON** feeds across various formats and versions. It effectively manages non-standard elements and known extensions, and demonstrates resilience against common feed issues.

`gofeed` allows you to render a feed to RSS, Atom, or JSON, allowing for conversion between feed types, creation of new feeds, or postprocessing of an existing feed.

## Table of Contents
- [Features](#features)
- [Overview](#overview)
Expand All @@ -26,7 +28,9 @@
- JSON (1.0, 1.1)

### Handling Invalid Feeds

`gofeed` takes a best-effort approach to deal with broken or invalid XML feeds, capable of handling issues like:

- Unescaped markup
- Undeclared namespace prefixes
- Missing or illegal tags
Expand All @@ -42,12 +46,15 @@ For added convenience, gofeed includes native support for parsing certain well-k

- Dublin Core: Accessible via `Feed.DublinCoreExt` and `Item.DublinCoreExt`
- Apple iTunes: Accessible via `Feed.ITunesExt` and `Item.ITunesExt`

### Feed Rendering

Whether a feed is created by parsing an existing feed or creating a feed from scratch, `gofeed` can render the feed to JSON Feed 1.1, RSS 2.0, or Atom 1.0.

## Overview

In `gofeed`, you have two primary choices for feed parsing: a universal parser for handling multiple feed types seamlessly, and specialized parsers for more granular control over individual feed types.


### Universal Feed Parser

The universal `gofeed.Parser` is designed to make it easy to work with various types of feeds—RSS, Atom, JSON—by converting them into a unified `gofeed.Feed` model. This is especially useful when you're dealing with multiple feed formats and you want to treat them the same way.
Expand Down Expand Up @@ -150,6 +157,47 @@ jsonFeed, _ := fp.Parse(strings.NewReader(feedData))
fmt.Println(jsonFeed.HomePageURL)
```

### Feed Creation

#### From Scratch

The following example demonstrates building a feed and outputting it as Atom to standard output:

```go
feed := &Feed{
Title: "Test Feed",
ID: "http://example.com/feed",
Updated: "2024-01-01T12:00:00Z",
Entries: []*Entry{
Title: "Simple Entry",
ID: "http://example.com/entry2",
Updated: "2024-01-01T11:30:00Z",
Summary: "A simple Atom entry",
Content: &Content{
Type: "text",
Value: "Plain text content",
},
},
}
_ = feed.RenderAtom(os.Stdout, nil)
```

#### From an Existing Feed

The following example will convert a feed from RSS to Atom, printing the result to standard output:

```go
feedData := `<rss version="2.0">
<channel>
<webMaster>example@site.com (Example Name)</webMaster>
</channel>
</rss>`
fp := gofeed.NewParser()
feed, _ := fp.ParseString(feedData)

_ = feed.RenderAtom(os.Stdout, nil)
```

## Advanced Usage

#### With Basic Authentication
Expand Down Expand Up @@ -240,3 +288,4 @@ This project is licensed under the [MIT License](https://raw.githubusercontent.c
* [Matt Jibson](https://mattjibson.com/) for his date parsing function in the [goread](https://github.com/mjibson/goread) project.
* [Jim Teeuwen](https://github.com/jteeuwen) for his method of representing arbitrary feed extensions in the [go-pkg-rss](https://github.com/jteeuwen/go-pkg-rss) library.
* [Sudhanshu Raheja](https://revolt.ist) for supporting JSON Feed parser
* [Chris Dzombak](https://github.com/cdzombak) for adding feed rendering
130 changes: 66 additions & 64 deletions atom/feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@ package atom

import (
"encoding/json"
"encoding/xml"
"time"

"github.com/mmcdole/gofeed/extensions"
)

// Feed is an Atom Feed
type Feed struct {
Title string `json:"title,omitempty"`
ID string `json:"id,omitempty"`
Updated string `json:"updated,omitempty"`
UpdatedParsed *time.Time `json:"updatedParsed,omitempty"`
Subtitle string `json:"subtitle,omitempty"`
Links []*Link `json:"links,omitempty"`
Language string `json:"language,omitempty"`
Generator *Generator `json:"generator,omitempty"`
Icon string `json:"icon,omitempty"`
Logo string `json:"logo,omitempty"`
Rights string `json:"rights,omitempty"`
Contributors []*Person `json:"contributors,omitempty"`
Authors []*Person `json:"authors,omitempty"`
Categories []*Category `json:"categories,omitempty"`
Entries []*Entry `json:"entries"`
Extensions ext.Extensions `json:"extensions,omitempty"`
Version string `json:"version"`
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed" json:"-"`
Title string `json:"title,omitempty" xml:"title"`
ID string `json:"id,omitempty" xml:"id"`
Updated string `json:"updated,omitempty" xml:"updated"`
UpdatedParsed *time.Time `json:"updatedParsed,omitempty" xml:"-"`
Subtitle string `json:"subtitle,omitempty" xml:"subtitle,omitempty"`
Links []*Link `json:"links,omitempty" xml:"link"`
Language string `json:"language,omitempty" xml:"lang,attr,omitempty"`
Generator *Generator `json:"generator,omitempty" xml:"generator"`
Icon string `json:"icon,omitempty" xml:"icon,omitempty"`
Logo string `json:"logo,omitempty" xml:"logo,omitempty"`
Rights string `json:"rights,omitempty" xml:"rights,omitempty"`
Contributors []*Person `json:"contributors,omitempty" xml:"contributor"`
Authors []*Person `json:"authors,omitempty" xml:"author"`
Categories []*Category `json:"categories,omitempty" xml:"category"`
Entries []*Entry `json:"entries" xml:"entry"`
Extensions ext.Extensions `json:"extensions,omitempty" xml:"-"`
Version string `json:"version" xml:"-"`
}

func (f Feed) String() string {
Expand All @@ -35,80 +37,80 @@ func (f Feed) String() string {

// Entry is an Atom Entry
type Entry struct {
Title string `json:"title,omitempty"`
ID string `json:"id,omitempty"`
Updated string `json:"updated,omitempty"`
UpdatedParsed *time.Time `json:"updatedParsed,omitempty"`
Summary string `json:"summary,omitempty"`
Authors []*Person `json:"authors,omitempty"`
Contributors []*Person `json:"contributors,omitempty"`
Categories []*Category `json:"categories,omitempty"`
Links []*Link `json:"links,omitempty"`
Rights string `json:"rights,omitempty"`
Published string `json:"published,omitempty"`
PublishedParsed *time.Time `json:"publishedParsed,omitempty"`
Source *Source `json:"source,omitempty"`
Content *Content `json:"content,omitempty"`
Extensions ext.Extensions `json:"extensions,omitempty"`
Title string `json:"title,omitempty" xml:"title"`
ID string `json:"id,omitempty" xml:"id"`
Updated string `json:"updated,omitempty" xml:"updated"`
UpdatedParsed *time.Time `json:"updatedParsed,omitempty" xml:"-"`
Summary string `json:"summary,omitempty" xml:"summary"`
Authors []*Person `json:"authors,omitempty" xml:"author"`
Contributors []*Person `json:"contributors,omitempty" xml:"contributor"`
Categories []*Category `json:"categories,omitempty" xml:"category"`
Links []*Link `json:"links,omitempty" xml:"link"`
Rights string `json:"rights,omitempty" xml:"rights,omitempty"`
Published string `json:"published,omitempty" xml:"published"`
PublishedParsed *time.Time `json:"publishedParsed,omitempty" xml:"-"`
Source *Source `json:"source,omitempty" xml:"source"`
Content *Content `json:"content,omitempty" xml:"content"`
Extensions ext.Extensions `json:"extensions,omitempty" xml:"-"`
}

// Category is category metadata for Feeds and Entries
type Category struct {
Term string `json:"term,omitempty"`
Scheme string `json:"scheme,omitempty"`
Label string `json:"label,omitempty"`
Term string `json:"term,omitempty" xml:"term,attr"`
Scheme string `json:"scheme,omitempty" xml:"scheme,attr"`
Label string `json:"label,omitempty" xml:"label,attr"`
}

// Person represents a person in an Atom feed
// for things like Authors, Contributors, etc
type Person struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
URI string `json:"uri,omitempty"`
Name string `json:"name,omitempty" xml:"name"`
Email string `json:"email,omitempty" xml:"email"`
URI string `json:"uri,omitempty" xml:"uri"`
}

// Link is an Atom link that defines a reference
// from an entry or feed to a Web resource
type Link struct {
Href string `json:"href,omitempty"`
Hreflang string `json:"hreflang,omitempty"`
Rel string `json:"rel,omitempty"`
Type string `json:"type,omitempty"`
Title string `json:"title,omitempty"`
Length string `json:"length,omitempty"`
Href string `json:"href,omitempty" xml:"href,attr"`
Hreflang string `json:"hreflang,omitempty" xml:"hreflang,attr,omitempty"`
Rel string `json:"rel,omitempty" xml:"rel,attr"`
Type string `json:"type,omitempty" xml:"type,attr"`
Title string `json:"title,omitempty" xml:"title,attr,omitempty"`
Length string `json:"length,omitempty" xml:"length,attr,omitempty"`
}

// Content either contains or links to the content of
// the entry
type Content struct {
Src string `json:"src,omitempty"`
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
Src string `json:"src,omitempty" xml:"src,attr"`
Type string `json:"type,omitempty" xml:"type,attr"`
Value string `json:"value,omitempty" xml:",chardata"`
}

// Generator identifies the agent used to generate a
// feed, for debugging and other purposes.
type Generator struct {
Value string `json:"value,omitempty"`
URI string `json:"uri,omitempty"`
Version string `json:"version,omitempty"`
Value string `json:"value,omitempty" xml:",chardata"`
URI string `json:"uri,omitempty" xml:"uri,attr"`
Version string `json:"version,omitempty" xml:"version,attr"`
}

// Source contains the feed information for another
// feed if a given entry came from that feed.
type Source struct {
Title string `json:"title,omitempty"`
ID string `json:"id,omitempty"`
Updated string `json:"updated,omitempty"`
UpdatedParsed *time.Time `json:"updatedParsed,omitempty"`
Subtitle string `json:"subtitle,omitempty"`
Links []*Link `json:"links,omitempty"`
Generator *Generator `json:"generator,omitempty"`
Icon string `json:"icon,omitempty"`
Logo string `json:"logo,omitempty"`
Rights string `json:"rights,omitempty"`
Contributors []*Person `json:"contributors,omitempty"`
Authors []*Person `json:"authors,omitempty"`
Categories []*Category `json:"categories,omitempty"`
Extensions ext.Extensions `json:"extensions,omitempty"`
Title string `json:"title,omitempty" xml:"title"`
ID string `json:"id,omitempty" xml:"id"`
Updated string `json:"updated,omitempty" xml:"updated"`
UpdatedParsed *time.Time `json:"updatedParsed,omitempty" xml:"-"`
Subtitle string `json:"subtitle,omitempty" xml:"subtitle,omitempty"`
Links []*Link `json:"links,omitempty" xml:"link"`
Generator *Generator `json:"generator,omitempty" xml:"generator"`
Icon string `json:"icon,omitempty" xml:"icon,omitempty"`
Logo string `json:"logo,omitempty" xml:"logo,omitempty"`
Rights string `json:"rights,omitempty" xml:"rights,omitempty"`
Contributors []*Person `json:"contributors,omitempty" xml:"contributor"`
Authors []*Person `json:"authors,omitempty" xml:"author"`
Categories []*Category `json:"categories,omitempty" xml:"category"`
Extensions ext.Extensions `json:"extensions,omitempty" xml:"-"`
}
23 changes: 23 additions & 0 deletions atom/render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package atom

import (
"encoding/xml"
"io"
)

// Render writes the Atom feed as Atom 1.0 XML to the provided io.Writer
func (f *Feed) Render(w io.Writer) error {
if f == nil {
return nil
}

// Write XML declaration
if _, err := w.Write([]byte(xml.Header)); err != nil {
return err
}

// Create XML encoder and encode the feed
encoder := xml.NewEncoder(w)
encoder.Indent("", " ")
return encoder.Encode(f)
}
Loading