Frédéric Guillot 4 лет назад
Родитель
Сommit
0f6f4c8c60
4 измененных файлов с 68 добавлено и 55 удалено
  1. 41 29
      reader/opml/opml.go
  2. 3 3
      reader/opml/parser.go
  3. 23 20
      reader/opml/serializer.go
  4. 1 3
      reader/opml/serializer_test.go

+ 41 - 29
reader/opml/opml.go

@@ -8,21 +8,49 @@ import (
 	"encoding/xml"
 )
 
-type opml struct {
-	XMLName  xml.Name  `xml:"opml"`
-	Version  string    `xml:"version,attr"`
-	Outlines []outline `xml:"body>outline"`
+// Specs: http://opml.org/spec2.opml
+type opmlDocument struct {
+	XMLName  xml.Name      `xml:"opml"`
+	Version  string        `xml:"version,attr"`
+	Header   opmlHeader    `xml:"head"`
+	Outlines []opmlOutline `xml:"body>outline"`
 }
 
-type outline struct {
-	Title    string    `xml:"title,attr,omitempty"`
-	Text     string    `xml:"text,attr"`
-	FeedURL  string    `xml:"xmlUrl,attr,omitempty"`
-	SiteURL  string    `xml:"htmlUrl,attr,omitempty"`
-	Outlines []outline `xml:"outline,omitempty"`
+func NewOPMLDocument() *opmlDocument {
+	return &opmlDocument{}
 }
 
-func (o *outline) GetTitle() string {
+func (o *opmlDocument) GetSubscriptionList() SubcriptionList {
+	var subscriptions SubcriptionList
+	for _, outline := range o.Outlines {
+		if len(outline.Outlines) > 0 {
+			for _, element := range outline.Outlines {
+				// outline.Text is only available in OPML v2.
+				subscriptions = element.Append(subscriptions, outline.Text)
+			}
+		} else {
+			subscriptions = outline.Append(subscriptions, "")
+		}
+	}
+
+	return subscriptions
+}
+
+type opmlHeader struct {
+	Title       string `xml:"title,omitempty"`
+	DateCreated string `xml:"dateCreated,omitempty"`
+	OwnerName   string `xml:"ownerName,omitempty"`
+}
+
+type opmlOutline struct {
+	Title    string        `xml:"title,attr,omitempty"`
+	Text     string        `xml:"text,attr"`
+	FeedURL  string        `xml:"xmlUrl,attr,omitempty"`
+	SiteURL  string        `xml:"htmlUrl,attr,omitempty"`
+	Outlines []opmlOutline `xml:"outline,omitempty"`
+}
+
+func (o *opmlOutline) GetTitle() string {
 	if o.Title != "" {
 		return o.Title
 	}
@@ -42,7 +70,7 @@ func (o *outline) GetTitle() string {
 	return ""
 }
 
-func (o *outline) GetSiteURL() string {
+func (o *opmlOutline) GetSiteURL() string {
 	if o.SiteURL != "" {
 		return o.SiteURL
 	}
@@ -50,7 +78,7 @@ func (o *outline) GetSiteURL() string {
 	return o.FeedURL
 }
 
-func (o *outline) Append(subscriptions SubcriptionList, category string) SubcriptionList {
+func (o *opmlOutline) Append(subscriptions SubcriptionList, category string) SubcriptionList {
 	if o.FeedURL != "" {
 		subscriptions = append(subscriptions, &Subcription{
 			Title:        o.GetTitle(),
@@ -62,19 +90,3 @@ func (o *outline) Append(subscriptions SubcriptionList, category string) Subcrip
 
 	return subscriptions
 }
-
-func (o *opml) Transform() SubcriptionList {
-	var subscriptions SubcriptionList
-	for _, outline := range o.Outlines {
-		if len(outline.Outlines) > 0 {
-			for _, element := range outline.Outlines {
-				// outline.Text is only available in OPML v2.
-				subscriptions = element.Append(subscriptions, outline.Text)
-			}
-		} else {
-			subscriptions = outline.Append(subscriptions, "")
-		}
-	}
-
-	return subscriptions
-}

+ 3 - 3
reader/opml/parser.go

@@ -14,16 +14,16 @@ import (
 
 // Parse reads an OPML file and returns a SubcriptionList.
 func Parse(data io.Reader) (SubcriptionList, *errors.LocalizedError) {
-	feeds := new(opml)
+	opmlDocument := NewOPMLDocument()
 	decoder := xml.NewDecoder(data)
 	decoder.Entity = xml.HTMLEntity
 	decoder.Strict = false
 	decoder.CharsetReader = encoding.CharsetReader
 
-	err := decoder.Decode(feeds)
+	err := decoder.Decode(opmlDocument)
 	if err != nil {
 		return nil, errors.NewLocalizedError("Unable to parse OPML file: %q", err)
 	}
 
-	return feeds.Transform(), nil
+	return opmlDocument.GetSubscriptionList(), nil
 }

+ 23 - 20
reader/opml/serializer.go

@@ -9,6 +9,7 @@ import (
 	"bytes"
 	"encoding/xml"
 	"sort"
+	"time"
 
 	"miniflux.app/logger"
 )
@@ -19,10 +20,10 @@ func Serialize(subscriptions SubcriptionList) string {
 	writer := bufio.NewWriter(&b)
 	writer.WriteString(xml.Header)
 
-	feeds := normalizeFeeds(subscriptions)
+	opmlDocument := convertSubscriptionsToOPML(subscriptions)
 	encoder := xml.NewEncoder(writer)
-	encoder.Indent("    ", "    ")
-	if err := encoder.Encode(feeds); err != nil {
+	encoder.Indent("", "    ")
+	if err := encoder.Encode(opmlDocument); err != nil {
 		logger.Error("[OPML:Serialize] %v", err)
 		return ""
 	}
@@ -30,19 +31,11 @@ func Serialize(subscriptions SubcriptionList) string {
 	return b.String()
 }
 
-func groupSubscriptionsByFeed(subscriptions SubcriptionList) map[string]SubcriptionList {
-	groups := make(map[string]SubcriptionList)
-
-	for _, subscription := range subscriptions {
-		groups[subscription.CategoryName] = append(groups[subscription.CategoryName], subscription)
-	}
-
-	return groups
-}
-
-func normalizeFeeds(subscriptions SubcriptionList) *opml {
-	feeds := new(opml)
-	feeds.Version = "2.0"
+func convertSubscriptionsToOPML(subscriptions SubcriptionList) *opmlDocument {
+	opmlDocument := NewOPMLDocument()
+	opmlDocument.Version = "2.0"
+	opmlDocument.Header.Title = "Miniflux"
+	opmlDocument.Header.DateCreated = time.Now().Format("Mon, 02 Jan 2006 15:04:05 MST")
 
 	groupedSubs := groupSubscriptionsByFeed(subscriptions)
 	var categories []string
@@ -52,9 +45,9 @@ func normalizeFeeds(subscriptions SubcriptionList) *opml {
 	sort.Strings(categories)
 
 	for _, categoryName := range categories {
-		category := outline{Text: categoryName}
+		category := opmlOutline{Text: categoryName}
 		for _, subscription := range groupedSubs[categoryName] {
-			category.Outlines = append(category.Outlines, outline{
+			category.Outlines = append(category.Outlines, opmlOutline{
 				Title:   subscription.Title,
 				Text:    subscription.Title,
 				FeedURL: subscription.FeedURL,
@@ -62,8 +55,18 @@ func normalizeFeeds(subscriptions SubcriptionList) *opml {
 			})
 		}
 
-		feeds.Outlines = append(feeds.Outlines, category)
+		opmlDocument.Outlines = append(opmlDocument.Outlines, category)
 	}
 
-	return feeds
+	return opmlDocument
+}
+
+func groupSubscriptionsByFeed(subscriptions SubcriptionList) map[string]SubcriptionList {
+	groups := make(map[string]SubcriptionList)
+
+	for _, subscription := range subscriptions {
+		groups[subscription.CategoryName] = append(groups[subscription.CategoryName], subscription)
+	}
+
+	return groups
 }

+ 1 - 3
reader/opml/serializer_test.go

@@ -6,7 +6,6 @@ package opml // import "miniflux.app/reader/opml"
 
 import (
 	"bytes"
-	"fmt"
 	"testing"
 )
 
@@ -17,7 +16,6 @@ func TestSerialize(t *testing.T) {
 	subscriptions = append(subscriptions, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: "Category 2"})
 
 	output := Serialize(subscriptions)
-	fmt.Println(output)
 	feeds, err := Parse(bytes.NewBufferString(output))
 	if err != nil {
 		t.Error(err)
@@ -56,7 +54,7 @@ func TestNormalizedCategoriesOrder(t *testing.T) {
 	subscriptions = append(subscriptions, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed/2", SiteURL: "http://example.org/2", CategoryName: orderTests[1].naturalOrderName})
 	subscriptions = append(subscriptions, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: orderTests[2].naturalOrderName})
 
-	feeds := normalizeFeeds(subscriptions)
+	feeds := convertSubscriptionsToOPML(subscriptions)
 
 	for i, o := range orderTests {
 		if feeds.Outlines[i].Text != o.correctOrderName {