aboutsummaryrefslogtreecommitdiff
path: root/src/feednim
diff options
context:
space:
mode:
authorJohn Conway <john.a.conway@gmail.com>2019-05-08 10:37:10 +0100
committerJohn Conway <john.a.conway@gmail.com>2019-05-08 10:37:10 +0100
commitf45b14403744f99ae265cff2e05e98e74836e501 (patch)
treefa2bc6022bb42411bf8b001f857a107f4b6ed71f /src/feednim
parented9adeb45b0f8d25585df8fdc5e769be021fa1b2 (diff)
downloadfeed-nim-f45b14403744f99ae265cff2e05e98e74836e501.tar.gz
feed-nim-f45b14403744f99ae265cff2e05e98e74836e501.zip
Changed tothe Nimble package structure
Diffstat (limited to 'src/feednim')
-rw-r--r--src/feednim/atom.nim187
-rw-r--r--src/feednim/jsonfeed.nim133
-rw-r--r--src/feednim/rss.nim198
-rw-r--r--src/feednim/submodule.nim12
4 files changed, 530 insertions, 0 deletions
diff --git a/src/feednim/atom.nim b/src/feednim/atom.nim
new file mode 100644
index 0000000..cce9c44
--- /dev/null
+++ b/src/feednim/atom.nim
@@ -0,0 +1,187 @@
+# Nim Atom Syndication Format module
+
+# Written by John Conway
+# Released under the MIT open source license.
+
+import httpclient
+import strutils
+import sequtils
+import xmlparser
+import xmltree
+import streams
+import sugar
+
+type
+ Atom* = object
+ author: AtomAuthor # Sugar, not in Atom spec. Returns the first author.
+ id*: string # Required Atom field
+ title*: string # Required Atom field
+ updated*: string # Required Atom field
+ authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one
+ category*: seq[string]
+ generator*: string
+ icon*: string
+ link*: AtomLink
+ logo*: string
+ rights*: string
+ subtitle*: string
+ entrys*: seq[AtomEntry]
+
+ AtomAuthor* = object
+ name*: string # Required Atom field
+ url*: string
+ email*: string
+
+ AtomImage* = object
+ url*: string
+ title*: string
+ link*: string
+ width*: string
+ height*: string
+ description*: string
+
+ AtomLink* = object
+ href*: string
+ rel*: string
+ linktype*: string
+ hreflang*: string
+ title*: string
+ length*: string
+
+ AtomEntry* = object
+ id*: string # Required Atom field
+ title*: string # Required Atom field
+ updated*: string # Required Atom field
+ author: AtomAuthor # Sugar, not in Atom spec. Returns the first author.
+ authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one
+ category*: seq[string]
+ content*: string
+ contentSrc*: string
+ contentType*: string
+ link*: AtomLink
+ published*: string
+ rights*: string
+ source*: string
+ summary*: string
+
+
+proc parseEntry( node: XmlNode) : AtomEntry =
+ var entry: AtomEntry = AtomEntry()
+
+ # Fill the required fields
+ entry.id = node.child("id").innerText
+ entry.title = node.child("title").innerText
+ entry.updated = node.child("updated").innerText
+
+ # Fill the optinal fields
+ if node.child("author") != nil:
+ for athr_node in node.findAll("author"):
+ var author: AtomAuthor = AtomAuthor()
+ author.name = athr_node.child("name").innerText
+ if athr_node.child("url") != nil: author.url = athr_node.child("url").innerText
+ if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText
+ entry.authors.add(author)
+
+ if node.child("category") != nil:
+ entry.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText)
+
+ if node.child("content") != nil:
+ entry.content = node.child("content").innerText
+ if node.attrs != nil:
+ if node.attr("type") != "": entry.contentType = node.attr("type")
+ if node.attr("src") != "": entry.contentSrc = node.attr("src")
+
+ if node.child("contributer") != nil:
+ for con_node in node.findAll("author"):
+ var author: AtomAuthor = AtomAuthor()
+ if con_node.child("name") != nil: author.name = con_node.child("name").innerText
+ if con_node.child("url") != nil: author.url = con_node.child("url").innerText
+ if con_node.child("email") != nil: author.email = con_node.child("email").innerText
+ entry.authors.add(author)
+
+ if node.child("link") != nil:
+ if node.attrs != nil:
+ if node.attr("href") != "": entry.link.href = node.attr("href")
+ if node.attr("rel") != "": entry.link.rel = node.attr("rel")
+ if node.attr("type") != "": entry.link.linktype = node.attr("type")
+ if node.attr("hreflang") != "": entry.link.rel = node.attr("hreflang")
+ if node.attr("title") != "": entry.link.rel = node.attr("title")
+ if node.attr("length") != "": entry.link.rel = node.attr("length")
+
+ if node.child("published") != nil: entry.published = node.child("published").innerText
+
+ if node.child("rights") != nil: entry.rights = node.child("rights").innerText
+
+ if node.child("summary") != nil: entry.summary = node.child("summary").innerText
+
+ # SUGAR an easy way to access an author
+ if entry.authors.len() > 0:
+ entry.author = entry.authors[0]
+ else:
+ entry.author = AtomAuthor()
+
+ return entry
+
+proc parseAtom*(data: string): Atom =
+ ## Parses the Atom from the given string.
+
+ # Parse into XML.
+ let node: XmlNode = parseXML(newStringStream(data))
+
+ # Create the return object.
+ var atom: Atom = Atom()
+
+ # Fill in the required fields
+ atom.id = node.child("id").innerText
+ atom.title = node.child("title").innerText
+ atom.updated = node.child("updated").innerText
+
+ # Fill in the optional fields
+ if node.child("author") != nil:
+ for athr_node in node.findAll("author"):
+ var author: AtomAuthor = AtomAuthor()
+ author.name = athr_node.child("name").innerText
+ if athr_node.child("url") != nil: author.url = athr_node.child("url").innerText
+ if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText
+ atom.authors.add(author)
+
+ if node.child("category") != nil:
+ atom.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText)
+
+ if node.child("generator") != nil: atom.generator = node.child("generator").innerText
+
+ if node.child("icon") != nil: atom.icon = node.child("icon").innerText
+
+ if node.child("link") != nil:
+ if node.attrs != nil:
+ if node.attr("href") != "": atom.link.href = node.attr("href")
+ if node.attr("rel") != "": atom.link.rel = node.attr("rel")
+ if node.attr("type") != "": atom.link.linktype = node.attr("type")
+ if node.attr("hreflang") != "": atom.link.rel = node.attr("hreflang")
+ if node.attr("title") != "": atom.link.rel = node.attr("title")
+ if node.attr("length") != "": atom.link.rel = node.attr("length")
+
+ if node.child("logo") != nil: atom.logo = node.child("logo").innerText
+
+ if node.child("rights") != nil: atom.rights = node.child("rights").innerText
+
+ if node.child("subtitle") != nil: atom.subtitle = node.child("subtitle").innerText
+
+ if atom.authors.len() > 0:
+ atom.author = atom.authors[0]
+ else:
+ atom.author = AtomAuthor()
+
+ # If there are no entrys:
+ if node.child("entry") == nil:
+ atom.entrys = @[]
+ return atom
+
+ # Otherwise, add the entrys.
+ if node.child("entry") != nil:
+ atom.entrys = map( node.findAll("entry"), parseEntry )
+
+ # Return the Atom data.
+ return atom
+
+
diff --git a/src/feednim/jsonfeed.nim b/src/feednim/jsonfeed.nim
new file mode 100644
index 0000000..8ff246f
--- /dev/null
+++ b/src/feednim/jsonfeed.nim
@@ -0,0 +1,133 @@
+# Nim JSONFeed Syndication module
+
+# Written by John Conway
+# Released under the MIT open source license.
+
+import strutils
+import sequtils
+import json
+import streams
+import sugar
+
+type
+ JSONFeed* = object
+ author*: JSONFeedAuthor
+ version*: string
+ title*: string
+ home_page_url*: string
+ feed_url*: string
+ description*: string
+ next_url*: string
+ icon*: string
+ favicon*: string
+ expired*: bool
+ hubs*: seq[JSONFeedHub]
+ items*: seq[JSONFeedItem]
+
+ JSONFeedHub* = object
+ hubType: string
+ url: string
+
+ JSONFeedAuthor* = object
+ name*: string
+ url*: string
+ avatar*: string
+
+ JSONFeedItem* = object
+ author*: JSONFeedAuthor
+ id*: string
+ url*: string
+ external_url*: string
+ title*: string
+ content_html*: string
+ content_text*: string
+ summary*: string
+ image*: string
+ banner_image*: string
+ date_published*: string
+ date_modified*: string
+ tags: seq[string]
+ attachments*: seq[JSONFeedAttachment]
+
+ JSONFeedAttachment* = object
+ url*: string
+ mime_type*: string
+ title*: string
+ size_in_bytes*: string
+ duration_in_seconds*: string
+
+proc parseItem( node: JsonNode) : JSONFeedItem =
+ var item: JSONFeedItem = JSONFeedItem()
+
+ if node.getOrDefault( "author" ) != nil:
+ let author = node["author"]
+ item.author.name = getStr( node.getOrDefault "name" )
+ item.author.url = getStr( node.getOrDefault "url" )
+ item.author.avatar = getStr( node.getOrDefault "avatar" )
+
+ item.id = getStr( node.getOrDefault "id" )
+ item.url = getStr( node.getOrDefault "url" )
+ item.external_url = getStr( node.getOrDefault "external_url" )
+ item.title = getStr( node.getOrDefault "title" )
+ item.content_html = getStr( node.getOrDefault "content_html" )
+ item.content_text = getStr( node.getOrDefault "content_text" )
+ item.summary = getStr( node.getOrDefault "summary" )
+ item.image = getStr( node.getOrDefault "image" )
+ item.banner_image = getStr( node.getOrDefault "banner_image" )
+ item.date_published = getStr( node.getOrDefault "date_published" )
+ item.date_modified = getStr( node.getOrDefault "date_modified" )
+
+ if node.getOrDefault( "tags" ) != nil:
+ for tag in node["tags"]:
+ item.tags.add( $tag )
+
+ if node.getOrDefault( "attachments" ) != nil:
+ for jattach in node["attachments"]:
+ var attachment: JSONFeedAttachment = JSONFeedAttachment()
+ attachment.url = getStr( jattach.getOrDefault "url" )
+ attachment.mime_type = getStr( jattach.getOrDefault "mime_type" )
+ attachment.title = getStr( jattach.getOrDefault "title" )
+ attachment.size_in_bytes = getStr( jattach.getOrDefault "size_in_bytes" )
+ attachment.duration_in_seconds = getStr( jattach.getOrDefault "duration_in_seconds" )
+
+ item.attachments.add( attachment )
+
+ return item
+
+proc parseJSONFeed*(data: string): JSONFeed =
+ let node = data.parseJson()
+ var feed: JSONFeed = JSONFeed()
+
+ if node.getOrDefault( "author" ) != nil:
+ let author = node["author"]
+ feed.author.name = getStr( author.getOrDefault "name" )
+ feed.author.url = getStr( author.getOrDefault "url" )
+ feed.author.avatar = getStr( author.getOrDefault "avatar" )
+
+ feed.version = getStr( node.getOrDefault "version" )
+ feed.title = getStr( node.getOrDefault "title" )
+ feed.home_page_url = getStr( node.getOrDefault "home_page_url" )
+ feed.feed_url = getStr( node.getOrDefault "feed_url" )
+ feed.description = getStr( node.getOrDefault "description" ) # What is this?
+ feed.next_url = getStr( node.getOrDefault "next_url" )
+ feed.icon = getStr( node.getOrDefault "icon" )
+ feed.favicon = getStr( node.getOrDefault "favicon" )
+
+ if node.getOrDefault( "expired" ) != nil:
+ feed.expired = node["expired"].getBool()
+
+ if node.getOrDefault( "hubs" ) != nil:
+ for jhub in node["hubs"]:
+ var hub: JSONFeedHub = JSONFeedHub()
+ hub.hubType = getStr( jhub.getOrDefault "url" )
+ hub.url = getStr( jhub.getOrDefault "mime_type" )
+
+ feed.hubs.add( hub )
+
+ feed.items = @[]
+ if node.getOrDefault( "items" ) != nil:
+ for item in node["items"]:
+ feed.items.add item.parseItem()
+
+
+ return feed \ No newline at end of file
diff --git a/src/feednim/rss.nim b/src/feednim/rss.nim
new file mode 100644
index 0000000..abd4883
--- /dev/null
+++ b/src/feednim/rss.nim
@@ -0,0 +1,198 @@
+# Nim RSS (Really Simple Syndication) module
+
+# Orginally written by Adam Chesak.
+# Rewritten by John Conway
+
+# Released under the MIT open source license.
+
+import httpclient
+import strutils
+import sequtils
+import xmlparser
+import xmltree
+import streams
+import sugar
+
+type
+ RSS* = object
+ title*: string
+ link*: string
+ description*: string
+ language*: string
+ copyright*: string
+ managingEditor*: string
+ webMaster*: string
+ pubDate*: string
+ lastBuildDate*: string
+ category*: seq[string]
+ generator*: string
+ docs*: string
+ cloud*: RSSCloud
+ ttl*: string
+ image*: RSSImage
+ rating*: string
+ textInput*: RSSTextInput
+ skipHours*: seq[string]
+ skipDays*: seq[string]
+ items*: seq[RSSItem]
+
+ RSSEnclosure* = object
+ url*: string
+ length*: string
+ enclosureType*: string
+
+ RSSCloud* = object
+ domain*: string
+ port*: string
+ path*: string
+ registerProcedure*: string
+ protocol*: string
+
+ RSSImage* = object
+ url*: string
+ title*: string
+ link*: string
+ width*: string
+ height*: string
+ description*: string
+
+ RSSTextInput* = object
+ title*: string
+ description*: string
+ name*: string
+ link*: string
+
+ RSSItem* = object
+ title*: string
+ link*: string
+ description*: string
+ author*: string
+ category*: seq[string]
+ comments*: string
+ enclosure*: RSSEnclosure
+ guid*: string
+ pubDate*: string
+ sourceUrl*: string
+ sourceText*: string
+
+proc parseItem( node: XmlNode) : RSSItem =
+ var item: RSSItem = RSSItem()
+ if node.child("title") != nil: item.title = node.child("title").innerText
+
+ if node.child("link") != nil: item.link = node.child("link").innerText
+
+ if node.child("description") != nil: item.description = node.child("description").innerText
+
+ for key in @["author", "dc:creator"]:
+ if node.child(key) != nil: item.author = node.child(key).innerText
+
+ if node.child("category") != nil:
+ item.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText)
+
+ if node.child("comments") != nil: item.comments = node.child("comments").innerText
+
+ if node.child("enclosure") != nil:
+ var encl: RSSEnclosure = RSSEnclosure()
+ encl.url = node.child("enclosure").attr("url")
+ encl.length = node.child("enclosure").attr("length")
+ encl.enclosureType = node.child("enclosure").attr("type")
+ item.enclosure = encl
+
+ if node.child("guid") != nil: item.guid = node.child("guid").innerText
+
+ if node.child("pubDate") != nil: item.pubDate = node.child("pubDate").innerText
+
+ if node.child("source") != nil:
+ item.sourceUrl = node.child("source").attr("url")
+ item.sourceText = node.child("source").innerText
+
+ return item
+
+proc parseRSS*(data: string): RSS =
+ ## Parses the RSS from the given string.
+
+ # Parse into XML.
+ let root: XmlNode = parseXML(newStringStream(data))
+ let channel: XmlNode = root.child("channel")
+
+ # Create the return object.
+ var rss: RSS = RSS()
+
+ # Fill the required fields.
+ rss.title = channel.child("title").innerText
+ rss.link = channel.child("link").innerText
+ rss.description = channel.child("description").innerText
+
+ # Fill the optional fields.
+ for key in @["language", "dc:language"]:
+ if channel.child(key) != nil:
+ rss.language = channel.child(key).innerText
+
+ if channel.child("copyright") != nil: rss.copyright = channel.child("copyright").innerText
+
+ if channel.child("managingEditor") != nil: rss.managingEditor = channel.child("managingEditor").innerText
+
+ if channel.child("webMaster") != nil: rss.webMaster = channel.child("webMaster").innerText
+
+ for key in @["pubDate", "dc:date"]:
+ if channel.child(key) != nil:
+ rss.pubDate = channel.child(key).innerText
+
+ if channel.child("lastBuildDate") != nil: rss.lastBuildDate = channel.child("lastBuildDate").innerText
+
+ if channel.child("category") != nil: rss.category = map(channel.findAll("category"), (x: XmlNode) -> string => x.innerText)
+
+ for key in @["generator", "dc:publisher"]:
+ if channel.child(key) != nil: rss.generator = channel.child(key).innerText
+
+ if channel.child("docs") != nil: rss.docs = channel.child("docs").innerText
+
+ if channel.child("cloud") != nil:
+ var cloud: RSSCloud = RSSCloud()
+ cloud.domain = channel.child("cloud").attr("domain")
+ cloud.port = channel.child("cloud").attr("port")
+ cloud.path = channel.child("cloud").attr("path")
+ cloud.registerProcedure = channel.child("cloud").attr("registerProcedure")
+ cloud.protocol = channel.child("cloud").attr("protocol")
+ rss.cloud = cloud
+
+ if channel.child("ttl") != nil: rss.ttl = channel.child("ttl").innerText
+
+ if channel.child("image") != nil:
+ var image: RSSImage = RSSImage()
+ let img = channel.child("image")
+ if img.child("url") != nil: image.url = img.child("url").innerText
+ if img.attr("rdf:resource") != "" and img.attr("rdf:resource") != "": image.url = img.attr("rdf:resource")
+ if img.child("title") != nil: image.title = img.child("title").innerText
+ if img.child("link") != nil: image.link = img.child("link").innerText
+ if img.child("width") != nil: image.width = img.child("width").innerText
+ if img.child("height") != nil: image.height = img.child("height").innerText
+ if img.child("description") != nil: image.description = img.child("description").innerText
+ rss.image = image
+
+ if channel.child("rating") != nil: rss.rating = channel.child("rating").innerText
+
+ if channel.child("textInput") != nil:
+ var textInput: RSSTextInput = RSSTextInput()
+ textInput.title = channel.child("textInput").child("title").innerText
+ textInput.description = channel.child("textInput").child("description").innerText
+ textInput.name = channel.child("textInput").child("name").innerText
+ textInput.link = channel.child("textInput").child("link").innerText
+ rss.textInput = textInput
+
+ if channel.child("skipHours") != nil:
+ rss.skipHours = map(channel.findAll("hour"), (x: XmlNode) -> string => x.innerText)
+ if channel.child("skipDays") != nil:
+ rss.skipDays = map(channel.findAll("day"), (x: XmlNode) -> string => x.innerText)
+
+ # If there are no items:
+ if channel.child("item") == nil and root.child("item") == nil:
+ rss.items = @[]
+ return rss
+
+ # Otherwise, add the items.
+ if channel.child("item") != nil: rss.items = map(channel.findAll("item"), parseItem)
+ else: rss.items = map(root.findAll("item"), parseItem)
+
+ # Return the RSS data.
+ return rss \ No newline at end of file
diff --git a/src/feednim/submodule.nim b/src/feednim/submodule.nim
new file mode 100644
index 0000000..1af4e81
--- /dev/null
+++ b/src/feednim/submodule.nim
@@ -0,0 +1,12 @@
+# This is just an example to get you started. Users of your library will
+# import this file by writing ``import FeedNim/submodule``. Feel free to rename or
+# remove this file altogether. You may create additional modules alongside
+# this file as required.
+
+type
+ Submodule* = object
+ name*: string
+
+proc initSubmodule*(): Submodule =
+ ## Initialises a new ``Submodule`` object.
+ Submodule(name: "Anonymous")