Refactor Netscape bookmark exporting
Relates to https://github.com/shaarli/netscape-bookmark-parser/issues/5 Fixes: - respect the Netscape bookmark format "specification" Modifications: - [application] introduce the NetscapeBookmarkUtils class - [template] export - improve formatting, rename export selection parameter - [template] export.bookmarks - template for Netscape exports - [tests] bookmark filtering, additional field generation Signed-off-by: VirtualTam <virtualtam@flibidi.net>
This commit is contained in:
parent
745304c842
commit
cd5327bee8
5 changed files with 202 additions and 42 deletions
47
application/NetscapeBookmarkUtils.php
Normal file
47
application/NetscapeBookmarkUtils.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Utilities to import and export bookmarks using the Netscape format
|
||||
*/
|
||||
class NetscapeBookmarkUtils
|
||||
{
|
||||
|
||||
/**
|
||||
* Filters links and adds Netscape-formatted fields
|
||||
*
|
||||
* Added fields:
|
||||
* - timestamp link addition date, using the Unix epoch format
|
||||
* - taglist comma-separated tag list
|
||||
*
|
||||
* @param LinkDB $linkDb The link datastore
|
||||
* @param string $selection Which links to export: (all|private|public)
|
||||
*
|
||||
* @throws Exception Invalid export selection
|
||||
*
|
||||
* @return array The links to be exported, with additional fields
|
||||
*/
|
||||
public static function filterAndFormat($linkDb, $selection)
|
||||
{
|
||||
// see tpl/export.html for possible values
|
||||
if (! in_array($selection, array('all','public','private'))) {
|
||||
throw new Exception('Invalid export selection: "'.$selection.'"');
|
||||
}
|
||||
|
||||
$bookmarkLinks = array();
|
||||
|
||||
foreach ($linkDb as $link) {
|
||||
if ($link['private'] != 0 && $selection == 'public') {
|
||||
continue;
|
||||
}
|
||||
if ($link['private'] == 0 && $selection == 'private') {
|
||||
continue;
|
||||
}
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$link['timestamp'] = $date->getTimestamp();
|
||||
$link['taglist'] = str_replace(' ', ',', $link['tags']);
|
||||
$bookmarkLinks[] = $link;
|
||||
}
|
||||
|
||||
return $bookmarkLinks;
|
||||
}
|
||||
}
|
55
index.php
55
index.php
|
@ -161,6 +161,7 @@ require_once 'application/HttpUtils.php';
|
|||
require_once 'application/LinkDB.php';
|
||||
require_once 'application/LinkFilter.php';
|
||||
require_once 'application/LinkUtils.php';
|
||||
require_once 'application/NetscapeBookmarkUtils.php';
|
||||
require_once 'application/TimeZone.php';
|
||||
require_once 'application/Url.php';
|
||||
require_once 'application/Utils.php';
|
||||
|
@ -1584,43 +1585,35 @@ function renderPage()
|
|||
}
|
||||
|
||||
// -------- Export as Netscape Bookmarks HTML file.
|
||||
if ($targetPage == Router::$PAGE_EXPORT)
|
||||
{
|
||||
if (empty($_GET['what']))
|
||||
{
|
||||
if ($targetPage == Router::$PAGE_EXPORT) {
|
||||
if (empty($_GET['selection'])) {
|
||||
$PAGE->assign('linkcount',count($LINKSDB));
|
||||
$PAGE->renderPage('export');
|
||||
exit;
|
||||
}
|
||||
$exportWhat=$_GET['what'];
|
||||
if (!array_intersect(array('all','public','private'),array($exportWhat))) die('What are you trying to export???');
|
||||
|
||||
// export as bookmarks_(all|private|public)_YYYYmmdd_HHMMSS.html
|
||||
$selection = $_GET['selection'];
|
||||
try {
|
||||
$PAGE->assign(
|
||||
'links',
|
||||
NetscapeBookmarkUtils::filterAndFormat($LINKSDB, $selection)
|
||||
);
|
||||
} catch (Exception $exc) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $exc->getMessage();
|
||||
exit;
|
||||
}
|
||||
$now = new DateTime();
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html');
|
||||
$currentdate=date('Y/m/d H:i:s');
|
||||
echo <<<HTML
|
||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||
<!-- This is an automatically generated file.
|
||||
It will be read and overwritten.
|
||||
DO NOT EDIT! -->
|
||||
<!-- Shaarli {$exportWhat} bookmarks export on {$currentdate} -->
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||
<TITLE>Bookmarks</TITLE>
|
||||
<H1>Bookmarks</H1>
|
||||
HTML;
|
||||
foreach($LINKSDB as $link)
|
||||
{
|
||||
if ($exportWhat=='all' ||
|
||||
($exportWhat=='private' && $link['private']!=0) ||
|
||||
($exportWhat=='public' && $link['private']==0))
|
||||
{
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
echo '<DT><A HREF="'.$link['url'].'" ADD_DATE="'.$date->getTimestamp().'" PRIVATE="'.$link['private'].'"';
|
||||
if ($link['tags']!='') echo ' TAGS="'.str_replace(' ',',',$link['tags']).'"';
|
||||
echo '>'.$link['title']."</A>\n";
|
||||
if ($link['description']!='') echo '<DD>'.$link['description']."\n";
|
||||
}
|
||||
}
|
||||
header(
|
||||
'Content-disposition: attachment; filename=bookmarks_'
|
||||
.$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html'
|
||||
);
|
||||
$PAGE->assign('date', $now->format(DateTime::RFC822));
|
||||
$PAGE->assign('eol', PHP_EOL);
|
||||
$PAGE->assign('selection', $selection);
|
||||
$PAGE->renderPage('export.bookmarks');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
|
104
tests/NetscapeBookmarkUtilsTest.php
Normal file
104
tests/NetscapeBookmarkUtilsTest.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
require_once 'application/NetscapeBookmarkUtils.php';
|
||||
|
||||
/**
|
||||
* Netscape bookmark import and export
|
||||
*/
|
||||
class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var string datastore to test write operations
|
||||
*/
|
||||
protected static $testDatastore = 'sandbox/datastore.php';
|
||||
|
||||
/**
|
||||
* @var ReferenceLinkDB instance.
|
||||
*/
|
||||
protected static $refDb = null;
|
||||
|
||||
/**
|
||||
* @var LinkDB private LinkDB instance.
|
||||
*/
|
||||
protected static $linkDb = null;
|
||||
|
||||
/**
|
||||
* Instantiate reference data
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$refDb = new ReferenceLinkDB();
|
||||
self::$refDb->write(self::$testDatastore);
|
||||
self::$linkDb = new LinkDB(self::$testDatastore, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to export an invalid link selection
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionMessageRegExp /Invalid export selection/
|
||||
*/
|
||||
public function testFilterAndFormatInvalid()
|
||||
{
|
||||
NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'derp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare all links for export
|
||||
*/
|
||||
public function testFilterAndFormatAll()
|
||||
{
|
||||
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all');
|
||||
$this->assertEquals(self::$refDb->countLinks(), sizeof($links));
|
||||
foreach ($links as $link) {
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$this->assertEquals(
|
||||
$date->getTimestamp(),
|
||||
$link['timestamp']
|
||||
);
|
||||
$this->assertEquals(
|
||||
str_replace(' ', ',', $link['tags']),
|
||||
$link['taglist']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare private links for export
|
||||
*/
|
||||
public function testFilterAndFormatPrivate()
|
||||
{
|
||||
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private');
|
||||
$this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
|
||||
foreach ($links as $link) {
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$this->assertEquals(
|
||||
$date->getTimestamp(),
|
||||
$link['timestamp']
|
||||
);
|
||||
$this->assertEquals(
|
||||
str_replace(' ', ',', $link['tags']),
|
||||
$link['taglist']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare public links for export
|
||||
*/
|
||||
public function testFilterAndFormatPublic()
|
||||
{
|
||||
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public');
|
||||
$this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
|
||||
foreach ($links as $link) {
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$this->assertEquals(
|
||||
$date->getTimestamp(),
|
||||
$link['timestamp']
|
||||
);
|
||||
$this->assertEquals(
|
||||
str_replace(' ', ',', $link['tags']),
|
||||
$link['taglist']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
10
tpl/export.bookmarks.html
Normal file
10
tpl/export.bookmarks.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||
<!-- This is an automatically generated file.
|
||||
It will be read and overwritten.
|
||||
Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore}
|
||||
<TITLE>{$pagetitle}</TITLE>
|
||||
<H1>Shaarli export of {$selection} bookmarks on {$date}</H1>
|
||||
<DL><p>{loop="links"}
|
||||
<DT><A HREF="{$value.url}" ADD_DATE="{$value.timestamp}" PRIVATE="{$value.private}" TAGS="{$value.taglist}">{$value.title}</A>{if="$value.description"}{$eol}<DD>{$value.description}{/if}{/loop}
|
||||
</DL><p>
|
|
@ -2,15 +2,21 @@
|
|||
<html>
|
||||
<head>{include="includes"}</head>
|
||||
<body>
|
||||
<div id="pageheader">
|
||||
<div id="pageheader">
|
||||
{include="page.header"}
|
||||
<div id="toolsdiv">
|
||||
<a href="?do=export&what=all"><b>Export all</b> <span>: Export all links</span></a><br><br>
|
||||
<a href="?do=export&what=public"><b>Export public</b> <span>: Export public links only</span></a><br><br>
|
||||
<a href="?do=export&what=private"><b>Export private</b> <span>: Export private links only</span></a>
|
||||
<a href="?do=export&selection=all">
|
||||
<b>Export all</b><span>: Export all links</span>
|
||||
</a><br>
|
||||
<a href="?do=export&selection=public">
|
||||
<b>Export public</b><span>: Only export public links</span>
|
||||
</a><br>
|
||||
<a href="?do=export&selection=private">
|
||||
<b>Export private</b><span>: Only export private links</span>
|
||||
</a>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
{include="page.footer"}
|
||||
</div>
|
||||
{include="page.footer"}
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue