| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 | ;;; ox-rss.el --- RSS 2.0 Back-End for Org Export Engine;; Copyright (C) 2013, 2014  Bastien Guerry;; Author: Bastien Guerry <bzg@gnu.org>;; Keywords: org, wp, blog, feed, rss;; This file is not yet part of GNU Emacs.;; This program is free software: you can redistribute it and/or modify;; it under the terms of the GNU General Public License as published by;; the Free Software Foundation, either version 3 of the License, or;; (at your option) any later version.;; This program is distributed in the hope that it will be useful,;; but WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the;; GNU General Public License for more details.;; You should have received a copy of the GNU General Public License;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.;;; Commentary:;; This library implements a RSS 2.0 back-end for Org exporter, based on;; the `html' back-end.;;;; It requires Emacs 24.1 at least.;;;; It provides two commands for export, depending on the desired output:;; `org-rss-export-as-rss' (temporary buffer) and `org-rss-export-to-rss';; (as a ".xml" file).;;;; This backend understands two new option keywords:;;;; #+RSS_EXTENSION: xml;; #+RSS_IMAGE_URL: http://myblog.org/mypicture.jpg;;;; It uses #+HTML_LINK_HOME: to set the base url of the feed.;;;; Exporting an Org file to RSS modifies each top-level entry by adding a;; PUBDATE property.  If `org-rss-use-entry-url-as-guid', it will also add;; an ID property, later used as the guid for the feed's item.;;;; You typically want to use it within a publishing project like this:;;;; (add-to-list;;  'org-publish-project-alist;;  '("homepage_rss";;    :base-directory "~/myhomepage/";;    :base-extension "org";;    :rss-image-url "http://lumiere.ens.fr/~guerry/images/faces/15.png";;    :html-link-home "http://lumiere.ens.fr/~guerry/";;    :html-link-use-abs-url t;;    :rss-extension "xml";;    :publishing-directory "/home/guerry/public_html/";;    :publishing-function (org-rss-publish-to-rss);;    :section-numbers nil;;    :exclude ".*"            ;; To exclude all files...;;    :include ("index.org")   ;; ... except index.org.;;    :table-of-contents nil));;;; ... then rsync /home/guerry/public_html/ with your server.;;;; By default, the permalink for a blog entry points to the headline.;; You can specify a different one by using the :RSS_PERMALINK:;; property within an entry.;;; Code:(require 'ox-html)(declare-function url-encode-url "url-util" (url));;; Variables and options(defgroup org-export-rss nil  "Options specific to RSS export back-end."  :tag "Org RSS"  :group 'org-export  :version "24.4"  :package-version '(Org . "8.0"))(defcustom org-rss-image-url "http://orgmode.org/img/org-mode-unicorn-logo.png"  "The URL of the an image for the RSS feed."  :group 'org-export-rss  :type 'string)(defcustom org-rss-extension "xml"  "File extension for the RSS 2.0 feed."  :group 'org-export-rss  :type 'string)(defcustom org-rss-categories 'from-tags  "Where to extract items category information from.The default is to extract categories from the tags of theheadlines.  When set to another value, extract the categoryfrom the :CATEGORY: property of the entry."  :group 'org-export-rss  :type '(choice	  (const :tag "From tags" from-tags)	  (const :tag "From the category property" from-category)))(defcustom org-rss-use-entry-url-as-guid t  "Use the URL for the <guid> metatag?When nil, Org will create ids using `org-icalendar-create-uid'."  :group 'org-export-rss  :type 'boolean);;; Define backend(org-export-define-derived-backend 'rss 'html  :menu-entry  '(?r "Export to RSS"       ((?R "As RSS buffer"	    (lambda (a s v b) (org-rss-export-as-rss a s v)))	(?r "As RSS file" (lambda (a s v b) (org-rss-export-to-rss a s v)))	(?o "As RSS file and open"	    (lambda (a s v b)	      (if a (org-rss-export-to-rss t s v)		(org-open-file (org-rss-export-to-rss nil s v)))))))  :options-alist  '((:with-toc nil nil nil) ;; Never include HTML's toc    (:rss-extension "RSS_EXTENSION" nil org-rss-extension)    (:rss-image-url "RSS_IMAGE_URL" nil org-rss-image-url)    (:rss-categories nil nil org-rss-categories))  :filters-alist '((:filter-final-output . org-rss-final-function))  :translate-alist '((headline . org-rss-headline)		     (comment . (lambda (&rest args) ""))		     (comment-block . (lambda (&rest args) ""))		     (timestamp . (lambda (&rest args) ""))		     (plain-text . org-rss-plain-text)		     (section . org-rss-section)		     (template . org-rss-template)));;; Export functions;;;###autoload(defun org-rss-export-as-rss (&optional async subtreep visible-only)  "Export current buffer to a RSS buffer.If narrowing is active in the current buffer, only export itsnarrowed part.If a region is active, export that region.A non-nil optional argument ASYNC means the process should happenasynchronously.  The resulting buffer should be accessiblethrough the `org-export-stack' interface.When optional argument SUBTREEP is non-nil, export the sub-treeat point, extracting information from the headline propertiesfirst.When optional argument VISIBLE-ONLY is non-nil, don't exportcontents of hidden elements.Export is done in a buffer named \"*Org RSS Export*\", which willbe displayed when `org-export-show-temporary-export-buffer' isnon-nil."  (interactive)  (let ((file (buffer-file-name (buffer-base-buffer))))    (org-icalendar-create-uid file 'warn-user)    (org-rss-add-pubdate-property))  (org-export-to-buffer 'rss "*Org RSS Export*"    async subtreep visible-only nil nil (lambda () (text-mode))));;;###autoload(defun org-rss-export-to-rss (&optional async subtreep visible-only)  "Export current buffer to a RSS file.If narrowing is active in the current buffer, only export itsnarrowed part.If a region is active, export that region.A non-nil optional argument ASYNC means the process should happenasynchronously.  The resulting file should be accessible throughthe `org-export-stack' interface.When optional argument SUBTREEP is non-nil, export the sub-treeat point, extracting information from the headline propertiesfirst.When optional argument VISIBLE-ONLY is non-nil, don't exportcontents of hidden elements.Return output file's name."  (interactive)  (let ((file (buffer-file-name (buffer-base-buffer))))    (org-icalendar-create-uid file 'warn-user)    (org-rss-add-pubdate-property))  (let ((outfile (org-export-output-file-name		  (concat "." org-rss-extension) subtreep)))    (org-export-to-file 'rss outfile async subtreep visible-only)));;;###autoload(defun org-rss-publish-to-rss (plist filename pub-dir)  "Publish an org file to RSS.FILENAME is the filename of the Org file to be published.  PLISTis the property list for the given project.  PUB-DIR is thepublishing directory.Return output file name."  (let ((bf (get-file-buffer filename)))    (if bf	(progn	  (org-icalendar-create-uid filename 'warn-user)	  (with-current-buffer bf	    (org-rss-add-pubdate-property)	    (write-file filename)))      (find-file filename)      (org-icalendar-create-uid filename 'warn-user)      (org-rss-add-pubdate-property)      (write-file filename) (kill-buffer)))  (org-publish-org-to   'rss filename (concat "." org-rss-extension) plist pub-dir));;; Main transcoding functions(defun org-rss-headline (headline contents info)  "Transcode HEADLINE element into RSS format.CONTENTS is the headline contents.  INFO is a plist used as acommunication channel."  (unless (or (org-element-property :footnote-section-p headline)	      ;; Only consider first-level headlines	      (> (org-export-get-relative-level headline info) 1))    (let* ((author (and (plist-get info :with-author)			(let ((auth (plist-get info :author)))			  (and auth (org-export-data auth info)))))	   (htmlext (plist-get info :html-extension))	   (hl-number (org-export-get-headline-number headline info))	   (hl-home (file-name-as-directory (plist-get info :html-link-home)))	   (hl-pdir (plist-get info :publishing-directory))	   (hl-perm (org-element-property :RSS_PERMALINK headline))	   (anchor	    (org-export-solidify-link-text	     (or (org-element-property :CUSTOM_ID headline)		 (concat "sec-" (mapconcat 'number-to-string hl-number "-")))))	   (category (org-rss-plain-text		      (or (org-element-property :CATEGORY headline) "") info))	   (pubdate	    (let ((system-time-locale "C"))	      (format-time-string	       "%a, %d %b %Y %H:%M:%S %z"	       (org-time-string-to-time		(or (org-element-property :PUBDATE headline)		    (error "Missing PUBDATE property"))))))	   (title (org-element-property :raw-value headline))	   (publink	    (or (and hl-perm (concat (or hl-home hl-pdir) hl-perm))		(concat		 (or hl-home hl-pdir)		 (file-name-nondirectory		  (file-name-sans-extension		   (plist-get info :input-file))) "." htmlext "#" anchor)))	   (guid (if org-rss-use-entry-url-as-guid		     publink		   (org-rss-plain-text		    (or (org-element-property :ID headline)			(org-element-property :CUSTOM_ID headline)			publink)		    info))))      (format       (concat	"<item>\n"	"<title>%s</title>\n"	"<link>%s</link>\n"	"<author>%s</author>\n"	"<guid isPermaLink=\"false\">%s</guid>\n"	"<pubDate>%s</pubDate>\n"	(org-rss-build-categories headline info) "\n"	"<description><![CDATA[%s]]></description>\n"	"</item>\n")       title publink author guid pubdate contents))))(defun org-rss-build-categories (headline info)  "Build categories for the RSS item."  (if (eq (plist-get info :rss-categories) 'from-tags)      (mapconcat       (lambda (c) (format "<category><![CDATA[%s]]></category>" c))       (org-element-property :tags headline)       "\n")    (let ((c (org-element-property :CATEGORY headline)))      (format "<category><![CDATA[%s]]></category>" c))))(defun org-rss-template (contents info)  "Return complete document string after RSS conversion.CONTENTS is the transcoded contents string.  INFO is a plist usedas a communication channel."  (concat   (format "<?xml version=\"1.0\" encoding=\"%s\"?>"	   (symbol-name org-html-coding-system))   "\n<rss version=\"2.0\"	xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"	xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"	xmlns:dc=\"http://purl.org/dc/elements/1.1/\"	xmlns:atom=\"http://www.w3.org/2005/Atom\"	xmlns:sy=\"http://purl.org/rss/1.0/modules/syndication/\"	xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"	xmlns:georss=\"http://www.georss.org/georss\"        xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"        xmlns:media=\"http://search.yahoo.com/mrss/\">"   "<channel>"   (org-rss-build-channel-info info) "\n"   contents   "</channel>\n"   "</rss>"))(defun org-rss-build-channel-info (info)  "Build the RSS channel information."  (let* ((system-time-locale "C")	 (title (plist-get info :title))	 (email (org-export-data (plist-get info :email) info))	 (author (and (plist-get info :with-author)		      (let ((auth (plist-get info :author)))			(and auth (org-export-data auth info)))))	 (date (format-time-string "%a, %d %b %Y %H:%M:%S %z")) ;; RFC 882	 (description (org-export-data (plist-get info :description) info))	 (lang (plist-get info :language))	 (keywords (plist-get info :keywords))	 (rssext (plist-get info :rss-extension))	 (blogurl (or (plist-get info :html-link-home)		      (plist-get info :publishing-directory)))	 (image (url-encode-url (plist-get info :rss-image-url)))	 (ifile (plist-get info :input-file))	 (publink	  (concat (file-name-as-directory blogurl)		   (file-name-nondirectory		    (file-name-sans-extension ifile))		  "." rssext)))    (format     "\n<title>%s</title><atom:link href=\"%s\" rel=\"self\" type=\"application/rss+xml\" /><link>%s</link><description><![CDATA[%s]]></description><language>%s</language><pubDate>%s</pubDate><lastBuildDate>%s</lastBuildDate><generator>%s</generator><webMaster>%s (%s)</webMaster><image><url>%s</url><title>%s</title><link>%s</link></image>"     title publink blogurl description lang date date     (concat (format "Emacs %d.%d"		     emacs-major-version		     emacs-minor-version)	     " Org-mode " (org-version))     email author image title blogurl)))(defun org-rss-section (section contents info)  "Transcode SECTION element into RSS format.CONTENTS is the section contents.  INFO is a plist used asa communication channel."  contents)(defun org-rss-timestamp (timestamp contents info)  "Transcode a TIMESTAMP object from Org to RSS.CONTENTS is nil.  INFO is a plist holding contextualinformation."  (org-html-encode-plain-text   (org-timestamp-translate timestamp)))(defun org-rss-plain-text (contents info)  "Convert plain text into RSS encoded text."  (let (output)    (setq output (org-html-encode-plain-text contents)	  output (org-export-activate-smart-quotes		  output :html info))));;; Filters(defun org-rss-final-function (contents backend info)  "Prettify the RSS output."  (with-temp-buffer    (xml-mode)    (insert contents)    (indent-region (point-min) (point-max))    (buffer-substring-no-properties (point-min) (point-max))));;; Miscellaneous(defun org-rss-add-pubdate-property ()  "Set the PUBDATE property for top-level headlines."  (let (msg)    (org-map-entries     (lambda ()       (let* ((entry (org-element-at-point))	      (level (org-element-property :level entry)))	 (when (= level 1)	   (unless (org-entry-get (point) "PUBDATE")	     (setq msg t)	     (org-set-property	      "PUBDATE" (format-time-string			 (cdr org-time-stamp-formats)))))))     nil nil 'comment 'archive)    (when msg      (message "Property PUBDATE added to top-level entries in %s"	       (buffer-file-name))      (sit-for 2))))(provide 'ox-rss);;; ox-rss.el ends here
 |