diff options
Diffstat (limited to 'src/FeedNim')
| -rw-r--r-- | src/FeedNim/atom.nim | 268 | ||||
| -rw-r--r-- | src/FeedNim/jsonfeed.nim | 133 | ||||
| -rw-r--r-- | src/FeedNim/rss.nim | 225 |
3 files changed, 626 insertions, 0 deletions
diff --git a/src/FeedNim/atom.nim b/src/FeedNim/atom.nim new file mode 100644 index 0000000..ee60672 --- /dev/null +++ b/src/FeedNim/atom.nim @@ -0,0 +1,268 @@ +# 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 + AtomCommon = ref object of RootObj # These properties aren't'gathered + xmlbase*: string + xmllang*: string + + Atom* = ref object of AtomCommon + author*: AtomAuthor # Sugar, not in Atom spec. Refers to the first author. + id*: string # Required Atom field + title*: AtomText # Required Atom field + updated*: string # Required Atom field + authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one + categories*: seq[AtomCategory] + contributors*: seq[AtomAuthor] + generator*: AtomGenerator + icon*: string + link*: AtomLink + logo*: string + rights*: string + subtitle*: AtomText + entries*: seq[AtomEntry] + + AtomText* = ref object of AtomCommon + textType*: string + text*: string + + AtomGenerator* = ref object of AtomText + uri*: string + + AtomAuthor* = ref object of AtomCommon + name*: string # Required Atom field + uri*: string + email*: string + + AtomCategory* = ref object of AtomCommon + term*: string + label*: string + scheme*: string + + AtomContent* = ref object of AtomText + src*: string + + AtomLink* = ref object of AtomCommon + href*: string + rel*: string + linktype*: string + hreflang*: string + title*: string + length*: int + + AtomEntry* = ref object of AtomCommon + id*: string # Required Atom field + title*: AtomText # 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 + categories*: seq[AtomCategory] + content*: AtomContent + contributors*: seq[AtomAuthor] + link*: AtomLink + published*: string + rights*: string + source*: AtomSource + summary*: string + + AtomSource* = ref object of AtomCommon + author*: AtomAuthor # Sugar, not in Atom spec. Returns the first author. + authors*: seq[AtomAuthor] + categories*: seq[AtomCategory] + contributors*: seq[AtomAuthor] + generator*: AtomGenerator + icon*: string + id*: string + link*: AtomLink + logo*: string + rights*: string + subtitle*: string + title*: string + updated*: string + + +# Promotes text node to the top of an AtomText object if caller expects a string +converter toString*(obj: AtomText): string = + return obj.text + + +func parseAuthors ( node: XmlNode, mode="author" ) : seq[AtomAuthor] = + var authors:seq[AtomAuthor] + if node.child(mode) != nil: + for athr_node in node.findAll(mode): + var author: AtomAuthor = AtomAuthor() + author.name = athr_node.child("name").innerText + if athr_node.child("uri") != nil: author.uri = athr_node.child("uri").innerText + if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText + authors.add(author) + if authors.len == 0: return @[] + return authors + +func parseCategories ( node: XmlNode ) : seq[AtomCategory] = + var categories:seq[AtomCategory] + if node.child("category") != nil: + for cat_node in node.findAll("category"): + var category: AtomCategory = AtomCategory() + if cat_node.attr("term") != "": category.term = cat_node.attr("term") + if cat_node.attr("label") != "": category.label = cat_node.attr("label") + if cat_node.attr("scheme") != "": category.scheme = cat_node.attr("scheme") + + categories.add(category) + + if categories.len == 0: return @[] + return categories + +func parseGenerator ( node: XmlNode ): AtomGenerator = + var generator = AtomGenerator() + let generator_node = node.child("generator") + generator.text = generator_node.innerText + if node.attrs != nil: generator.uri = generator_node.attr("uri") + return generator + +func parseLink ( node: XmlNode ): AtomLink = + var link: AtomLink = AtomLink() + if node.attrs != nil: + if node.attr("href") != "": link.href = node.attr("href") + if node.attr("rel") != "": link.rel = node.attr("rel") + if node.attr("type") != "": link.linktype = node.attr("type") + if node.attr("hreflang") != "": link.hreflang = node.attr("hreflang") + if node.attr("title") != "": link.title = node.attr("title") + if node.attr("length") != "": link.length = node.attr("length").parseInt() + return link + +func parseText ( node: XmlNode ): string = + if node.attr("type") == "xhtml" or node.attr("type") == "html": + var content = "" + for item in node.items: + content = content & $item + # Strip CDATA + if content[0 .. 8] == "<![CDATA[": + content = content.substr[9 .. content.len()-4 ] + return content + else: + return node.innerText + +func parseEntry( node: XmlNode ) : AtomEntry = + var entry: AtomEntry = AtomEntry() + + # Fill the required fields + entry.id = node.child("id").innerText + entry.title = AtomText() + if node.attrs != nil: entry.title.textType = node.attr("type") + entry.title.text = node.child("title").parseText() + entry.updated = node.child("updated").innerText + + # Fill the optinal fields + entry.authors = node.parseAuthors() + + if node.child("category") != nil: entry.categories = node.parseCategories() + + if node.child("content") != nil: + let content_node = node.child("content") + entry.content = AtomContent() + entry.content.text = content_node.innerText + + if content_node.attrs != nil: + entry.content.src = content_node.attr("src") + entry.content.texttype = content_node.attr("type") + entry.content.text = content_node.parseText() + + if node.child("contributor") != nil: + entry.contributors = node.parseAuthors(mode="contributor") + + if node.child("link") != nil: entry.link = node.child("link").parseLink() + + 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("source") != nil: + let source = node.child("source") + entry.source = AtomSource() + if source.child("author") != nil: entry.source.authors = source.parseAuthors() + if source.child("category") != nil: entry.source.categories = source.parseCategories() + if source.child("contributor") != nil: entry.source.contributors = source.parseAuthors(mode="contributor") + if source.child("generator") != nil: entry.source.generator = source.parseGenerator() + if source.child("icon") != nil: entry.source.icon = source.child("icon").innerText + if source.child("id") != nil: entry.source.id = source.child("id").innerText + if source.child("link") != nil: entry.source.link = source.child("link").parseLink() + if source.child("logo") != nil: entry.source.logo = source.child("logo").innerText + if source.child("rights") != nil: entry.source.rights = source.child("rights").innerText + if source.child("subtitle") != nil: entry.source.subtitle = source.child("subtitle").parseText() + if source.child("title") != nil: entry.source.title = source.child("title").parseText() + if source.child("updated") != nil: entry.source.updated = source.child("updated").innerText + + entry.source.author = entry.source.authors[0] + + 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 = AtomText() + atom.title.text = node.child("title").parseText() + atom.updated = node.child("updated").innerText + + # Fill in the optional fields + if node.child("author") != nil: atom.authors = node.parseAuthors() + + if node.child("category") != nil: atom.categories = node.parseCategories() + + if node.child("contributor") != nil: atom.contributors = node.parseAuthors(mode="contributor") + + if node.child("generator") != nil: atom.generator = node.parseGenerator() + + if node.child("icon") != nil: atom.icon = node.child("icon").innerText + + if node.child("link") != nil: atom.link = node.child("link").parseLink() + + 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 = AtomText() + atom.subtitle.text = node.child("subtitle").innerText + + if atom.authors.len() > 0: + atom.author = atom.authors[0] + else: + atom.author = AtomAuthor() + + + if node.child("entry") == nil: # If there are no entries: + atom.entries = @[] + return atom + + if node.child("entry") != nil: # Otherwise, add the entries. + atom.entries = map( node.findAll("entry"), parseEntry ) + + # Return the Atom data. + return atom
\ No newline at end of file diff --git a/src/FeedNim/jsonfeed.nim b/src/FeedNim/jsonfeed.nim new file mode 100644 index 0000000..e523778 --- /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* = ref object of RootObj + 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*: int + duration_in_seconds*: int + +proc parseItem( node: JsonNode) : JSONFeedItem = + var item: JSONFeedItem = JSONFeedItem() + + if node.getOrDefault( "author" ) != nil: + let author = node["author"] + item.author.name = getStr( author.getOrDefault "name" ) + item.author.url = getStr( author.getOrDefault "url" ) + item.author.avatar = getStr( author.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.to(string) ) + + 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 = getInt( jattach.getOrDefault "size_in_bytes" ) + attachment.duration_in_seconds = getInt( 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..35d3520 --- /dev/null +++ b/src/FeedNim/rss.nim @@ -0,0 +1,225 @@ +# 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 + categories*: seq[RSSCategory] + generator*: string + docs*: string + cloud*: RSSCloud + ttl*: int + image*: RSSImage + rating*: string + textInput*: RSSTextInput + skipHours*: seq[int] + skipDays*: seq[string] + items*: seq[RSSItem] + + RSSText = ref object of RootObj + text*: string + + RSSCategory = ref object of RSSText + domain*: string + + 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 + + RSSSource* = ref object of RSSText + url*: string + + RSSItem* = object + title*: string + link*: string + description*: string + author*: string + categories*: seq[RSSCategory] + comments*: string + enclosure*: RSSEnclosure + guid*: string + pubDate*: string + source*: RSSSource + +converter rssToString*(obj: RSSText): string = + return obj.text + +func parseCategories( node: XmlNode ): seq[RSSCategory] = + var categories:seq[RSSCategory] + for cat_node in node.findAll("category"): + var category: RSSCategory = RSSCategory() + if cat_node.attr("domain") != "": category.domain = cat_node.attr("domain") + category.text = cat_node.innerText + categories.add(category) + if categories.len == 0: return @[] + return categories + +func parseText ( node: XmlNode ): string = + var content = node.innerText + if content[0 .. 8] == "<![CDATA[": + return content.substr[9 .. content.len()-4 ] + return content + +func parseItem( node: XmlNode) : RSSItem = + var item: RSSItem = RSSItem() + if node.child("title") != nil: item.title = node.child("title").parseText() + + if node.child("link") != nil: item.link = node.child("link").innerText + + if node.child("description") != nil: item.description = node.child("description").parseText() + + for key in @["author", "dc:creator"]: + if node.child(key) != nil: item.author = node.child(key).innerText + + if node.child("category") != nil: item.categories = node.parseCategories() + + 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.source = RSSSource() + item.source.url = node.child("source").attr("url") + item.source.text = 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").parseText() + rss.link = channel.child("link").innerText + rss.description = channel.child("description").parseText() + + # 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.categories = channel.parseCategories() + + 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.parseInt() + + 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").parseText() + 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) -> int => x.innerText.parseInt() ) + 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 |
