reStructuredText output
authorChristian Weiske <cweiske@cweiske.de>
Tue, 17 Mar 2015 21:03:39 +0000 (22:03 +0100)
committerChristian Weiske <cweiske@cweiske.de>
Tue, 17 Mar 2015 21:03:39 +0000 (22:03 +0100)
appinfo/routes.php
controller/guicontroller.php
controller/notescontroller.php
lib/converter/base.php [new file with mode: 0644]
lib/converter/html.php
lib/converter/restructuredtext.php [new file with mode: 0644]
lib/response/textresponse.php [new file with mode: 0644]
templates/gui-note.php

index c96ec30..29ce8ad 100644 (file)
@@ -74,6 +74,11 @@ $application->registerRoutes(
                 'verb' => 'GET',
             ),
             array(
+                'url'  => '/note/{guid}.txt',
+                'name' => 'notes#text',
+                'verb' => 'GET',
+            ),
+            array(
                 'url'  => '/note/{guid}.xml',
                 'name' => 'notes#xml',
                 'verb' => 'GET',
index 15380c7..29dd03f 100644 (file)
@@ -130,6 +130,9 @@ class GuiController extends Controller
                             'guid' => $guid, 'username' => $this->user->getUid()
                         )
                     ),
+                    'text' => $this->urlGen->linkToRoute(
+                        'grauphel.notes.text', array('guid' => $guid)
+                    ),
                     'xml' => $this->urlGen->linkToRoute(
                         'grauphel.notes.xml', array('guid' => $guid)
                     ),
index 893a100..c599e75 100644 (file)
@@ -124,7 +124,7 @@ class NotesController extends Controller
         } catch (\OCA\Grauphel\Converter\Exception $e) {
             $res = new ErrorResponse(
                 'Error converting note to HTML.'
-                . ' Please repport a bug to the grauphel developers.'
+                . ' Please report a bug to the grauphel developers.'
             );
             $res->setStatus(\OCP\AppFramework\Http::STATUS_NOT_FOUND);
             return $res;
@@ -142,6 +142,44 @@ class NotesController extends Controller
     }
 
     /**
+     * Output a note as a standalone text file
+     *
+     * @NoAdminRequired
+     * @NoCSRFRequired
+     */
+    public function text($guid)
+    {
+        $note = $this->getNotes()->load($guid, false);
+        if ($note === null) {
+            $res = new ErrorResponse('Note does not exist');
+            $res->setStatus(\OCP\AppFramework\Http::STATUS_NOT_FOUND);
+            return $res;
+        }
+
+        $converter = new \OCA\Grauphel\Converter\ReStructuredText();
+        $converter->internalLinkHandler = array($this, 'textNoteLinkHandler');
+        try {
+            $text = $note->title . "\n"
+                . str_repeat('*', strlen($note->title)) . "\n"
+                . "\n";
+            $text .= $converter->convert($note->{'note-content'});
+            return new \OCA\Grauphel\Response\TextResponse($text);
+        } catch (\OCA\Grauphel\Converter\Exception $e) {
+            $res = new ErrorResponse(
+                'Error converting note to reStructuredText.'
+                . ' Please report a bug to the grauphel developers.'
+            );
+            $res->setStatus(\OCP\AppFramework\Http::STATUS_NOT_FOUND);
+            return $res;
+        }
+    }
+
+    public function textNoteLinkHandler($noteTitle)
+    {
+        return $noteTitle;
+    }
+
+    /**
      * Output a note in tomboy XML format
      *
      * @link https://wiki.gnome.org/Apps/Tomboy/NoteXmlFormat
diff --git a/lib/converter/base.php b/lib/converter/base.php
new file mode 100644 (file)
index 0000000..2694374
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Converter;
+
+/**
+ * Base class to convert tomboy XML to some other format.
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class Base
+{
+    /**
+     * Re-arranges the XML of formatted links to that clean link tags can
+     * be generated.
+     *
+     * Tomboy 1.15.2 allows link formatting, and the resulting XML is a
+     * mess of multiple(!) link tags that are within or around other formatting
+     * tags.
+     *
+     * This method tries to re-arrange the links so that only a single link tag
+     * appears with all the formatting inside.
+     * 
+     * @param string $xmlContent Tomboy note content
+     *
+     * @return string XML content, with re-arranged link tags.
+     */
+    protected function fixNastyLinks($xmlContent)
+    {
+        preg_match_all(
+            '#(?:<.*>)?<link:internal>.+</link:internal><link:internal>.+</link:internal>#U',
+            $xmlContent,
+            $matches
+        );
+
+        foreach ($matches[0] as $nastyLink) {
+            $cleaner = str_replace('</link:internal><link:internal>', '', $nastyLink);
+            $cleaner = preg_replace('#<([a-z]+)><(link:internal)>#U', '<\2><\1>', $cleaner);
+            $cleaner = preg_replace('#</(link:internal)></([a-z]+)>#U', '</\2></\1>', $cleaner);
+            $cleaner = str_replace('</link:internal><link:internal>', '', $cleaner);
+            $xmlContent = str_replace($nastyLink, $cleaner, $xmlContent);
+        }
+
+        return $xmlContent;
+    }
+}
+?>
index eeb2b66..a6a92bc 100644 (file)
@@ -30,7 +30,7 @@ use \XMLReader;
  * @version   Release: @package_version@
  * @link      http://cweiske.de/grauphel.htm
  */
-class Html
+class Html extends Base
 {
     protected static $tagMap = array(
         'list'      => 'ul',
@@ -159,39 +159,5 @@ class Html
     {
         return $linkUrl . '.htm';
     }
-
-    /**
-     * Re-arranges the XML of formatted links to that clean link tags can
-     * be generated.
-     *
-     * Tomboy 1.15.2 allows link formatting, and the resulting XML is a
-     * mess of multiple(!) link tags that are within or around other formatting
-     * tags.
-     *
-     * This method tries to re-arrange the links so that only a single link tag
-     * appears with all the formatting inside.
-     * 
-     * @param string $xmlContent Tomboy note content
-     *
-     * @return string XML content, with re-arranged link tags.
-     */
-    protected function fixNastyLinks($xmlContent)
-    {
-        preg_match_all(
-            '#(?:<.*>)?<link:internal>.+</link:internal><link:internal>.+</link:internal>#U',
-            $xmlContent,
-            $matches
-        );
-
-        foreach ($matches[0] as $nastyLink) {
-            $cleaner = str_replace('</link:internal><link:internal>', '', $nastyLink);
-            $cleaner = preg_replace('#<([a-z]+)><(link:internal)>#U', '<\2><\1>', $cleaner);
-            $cleaner = preg_replace('#</(link:internal)></([a-z]+)>#U', '</\2></\1>', $cleaner);
-            $cleaner = str_replace('</link:internal><link:internal>', '', $cleaner);
-            $xmlContent = str_replace($nastyLink, $cleaner, $xmlContent);
-        }
-
-        return $xmlContent;
-    }
 }
 ?>
diff --git a/lib/converter/restructuredtext.php b/lib/converter/restructuredtext.php
new file mode 100644 (file)
index 0000000..8419af6
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Converter;
+use \XMLReader;
+
+/**
+ * Convert Tomboy note XML to reStructuredText.
+ * Mainly used to paste the content of a note into an e-mail
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class ReStructuredText extends Base
+{
+    protected static $simpleMap = array(
+        'bold'      => '**',
+        'italic'    => '*',
+        'monospace' => '``',
+        'strikethrough' => '-',
+        'highlight'  => '**',
+    );
+
+    public $internalLinkHandler;
+
+
+
+    public function __construct()
+    {
+        $this->internalLinkHandler = array($this, 'internalLinkHandler');
+    }
+
+    /**
+     * Converts the tomboy note XML into reStructuredText
+     *
+     * @param string $xmlContent Tomboy note content
+     *
+     * @return string Plain text
+     */
+    public function convert($xmlContent)
+    {
+        if (strpos($xmlContent, '</link:internal><link:internal>') !== false) {
+            $xmlContent = $this->fixNastyLinks($xmlContent);
+        }
+
+        $rst = '';
+        $reader = new XMLReader();
+        $reader->xml(
+            '<?xml version="1.0" encoding="utf-8"?>' . "\n"
+            . '<content xmlns:size="size" xmlns:link="link">'
+            . $xmlContent
+            . '</content>'
+        );
+
+        $withinLink = false;
+        $store = &$rst;
+        $listLevel  = -1;
+        $listPrefix = '';
+        $listItemCount = 0;
+        $heading = false;
+        $headingLength = 0;
+        while ($reader->read()) {
+            switch ($reader->nodeType) {
+            case XMLReader::ELEMENT:
+                //echo $reader->name . "\n";
+                if (isset(static::$simpleMap[$reader->name])) {
+                    $store .= static::$simpleMap[$reader->name];
+                } else if ($reader->name == 'list') {
+                    ++$listLevel;
+                    $listItemCount = 0;
+                    $listPrefix = str_repeat('  ', $listLevel);
+                } else if ($reader->name == 'list-item') {
+                    ++$listItemCount;
+                    if ($listItemCount == 1) {
+                        $store .= "\n";
+                    }
+                    $store .= $listPrefix . '- ';
+                } else if ($reader->name == 'size:large'
+                    || $reader->name == 'size:huge'
+                ) {
+                    $store .= "\n";
+                    $heading = true;
+                } else if (substr($reader->name, 0, 5) == 'link:') {
+                    $withinLink = true;
+                    $linkText    = '';
+                    $store       = &$linkText;
+                }
+                break;
+            case XMLReader::END_ELEMENT:
+                if (isset(static::$simpleMap[$reader->name])) {
+                    $store .= static::$simpleMap[$reader->name];
+                } else if ($reader->name == 'list') {
+                    --$listLevel;
+                    $listPrefix = str_repeat('  ', $listLevel);
+                    if ($listLevel == -1) {
+                        $store .= "\n";
+                    }
+                } else if ($reader->name == 'size:large') {
+                    $store .= "\n" . str_repeat('-', $headingLength);
+                    $heading = false;
+                } else if ($reader->name == 'size:huge') {
+                    $store .= "\n" . str_repeat('=', $headingLength);
+                    $heading = false;
+                } else if (substr($reader->name, 0, 5) == 'link:') {
+                    $withinLink = false;
+                    $store      = &$rst;
+                    $linkUrl = htmlspecialchars_decode(strip_tags($linkText));
+                    if ($reader->name == 'link:internal') {
+                        $linkUrl = call_user_func($this->internalLinkHandler, $linkUrl);
+                    } else {
+                        $linkUrl = $this->fixLinkUrl($linkUrl);
+                    }
+                    $store .= $linkUrl;
+                }
+                break;
+            case XMLReader::TEXT:
+            case XMLReader::SIGNIFICANT_WHITESPACE:
+                if ($heading) {
+                    $headingLength = strlen(trim($reader->value));
+                    $store .= trim($reader->value);
+                } else {
+                    $text = wordwrap($reader->value, 72 - 2 * $listLevel, "\n", true);
+                    $parts = explode("\n", $text);
+                    foreach ($parts as $k => $v) {
+                        if ($k == 0) {
+                            continue;
+                        }
+                        if ($v != '') {
+                            $parts[$k] = str_repeat(' ', $listLevel * 2 + 2) . $v;
+                        }
+                    }
+                    $store .= implode("\n", $parts);
+                }
+                break;
+            default:
+                throw new Exception(
+                    'Unsupported XML node type: ' . $reader->nodeType
+                );
+            }
+        }
+
+        return $rst;
+    }
+
+    /**
+     * Fixes external URLs without a protocol
+     *
+     * @param string $linkUrl URL to fix
+     *
+     * @return string Fixed URL
+     */
+    protected function fixLinkUrl($linkUrl)
+    {
+        if ($linkUrl{0} == '/') {
+            //Unix file path
+            $linkUrl = 'file://' . $linkUrl;
+        }
+        return $linkUrl;
+    }
+
+    /**
+     * Dummy internal link handler that simply adds ".htm" to the note title
+     *
+     * @param string $linkUrl Title of page that is linked
+     *
+     * @return string URL to link to
+     */
+    public function internalLinkHandler($linkUrl)
+    {
+        return $linkUrl . '.htm';
+    }
+}
+?>
diff --git a/lib/response/textresponse.php b/lib/response/textresponse.php
new file mode 100644 (file)
index 0000000..ba6bb70
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Response;
+
+/**
+ * Returns plain text
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class TextResponse extends \OCP\AppFramework\Http\Response
+{
+    protected $text;
+
+    public function __construct($text)
+    {
+        $this->setStatus(\OCP\AppFramework\Http::STATUS_OK);
+        $this->addHeader('Content-Type', 'text/plain; charset=utf-8');
+        $this->text = $text;
+    }
+
+    public function render()
+    {
+        return $this->text;
+    }
+}
+?>
index 86336bc..ae004d6 100644 (file)
@@ -8,6 +8,7 @@
 <div id="app-content" class="content">
  <div class="actions">
   <a class="button" href="<?php echo p($_['links']['html']); ?>">HTML</a>
+  <a class="button" href="<?php echo p($_['links']['text']); ?>">Text</a>
   <a class="button" href="<?php echo p($_['links']['json']); ?>">JSON</a>
   <a class="button" href="<?php echo p($_['links']['xml']); ?>">XML</a>
  </div>