summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2022-09-13 17:12:51 -0300
committerYaco <franco@reevo.org>2022-09-13 17:12:51 -0300
commitdf13df9e176f548aa8dbad85459a0ade9265200c (patch)
treed45dafbd06327e474ba031f20d868926ad64d428
parentfba06520eeb19b35f14f2e909c28d4585a8ef7f6 (diff)
adds refnotes plugin, with the hack for almost good markdown footnotes
-rw-r--r--platform/www/lib/plugins/refnotes/action.php634
-rw-r--r--platform/www/lib/plugins/refnotes/admin.css100
-rw-r--r--platform/www/lib/plugins/refnotes/admin.js1035
-rw-r--r--platform/www/lib/plugins/refnotes/admin.php751
-rw-r--r--platform/www/lib/plugins/refnotes/admin.svg1
-rw-r--r--platform/www/lib/plugins/refnotes/bibtex.php669
-rw-r--r--platform/www/lib/plugins/refnotes/conf/namespaces.dat1
-rw-r--r--platform/www/lib/plugins/refnotes/conf/notes.dat1
-rw-r--r--platform/www/lib/plugins/refnotes/config.php77
-rw-r--r--platform/www/lib/plugins/refnotes/core.php483
-rw-r--r--platform/www/lib/plugins/refnotes/database.php563
-rw-r--r--platform/www/lib/plugins/refnotes/instructions.php318
-rw-r--r--platform/www/lib/plugins/refnotes/lang/en/__template.txt82
-rw-r--r--platform/www/lib/plugins/refnotes/lang/en/intro.txt15
-rw-r--r--platform/www/lib/plugins/refnotes/lang/en/lang.php146
-rw-r--r--platform/www/lib/plugins/refnotes/locale.php92
-rw-r--r--platform/www/lib/plugins/refnotes/namespace.php463
-rw-r--r--platform/www/lib/plugins/refnotes/note.php336
-rw-r--r--platform/www/lib/plugins/refnotes/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/refnotes/reference.php192
-rw-r--r--platform/www/lib/plugins/refnotes/refnote.php51
-rw-r--r--platform/www/lib/plugins/refnotes/rendering.php1272
-rw-r--r--platform/www/lib/plugins/refnotes/scope.php190
-rw-r--r--platform/www/lib/plugins/refnotes/script.js114
-rw-r--r--platform/www/lib/plugins/refnotes/style.css48
-rw-r--r--platform/www/lib/plugins/refnotes/syntax/notes.php221
-rw-r--r--platform/www/lib/plugins/refnotes/syntax/references.php348
27 files changed, 8210 insertions, 0 deletions
diff --git a/platform/www/lib/plugins/refnotes/action.php b/platform/www/lib/plugins/refnotes/action.php
new file mode 100644
index 0000000..6f6af89
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/action.php
@@ -0,0 +1,634 @@
+<?php
+
+/**
+ * Plugin RefNotes: Event handler
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+require_once(DOKU_PLUGIN . 'refnotes/core.php');
+require_once(DOKU_PLUGIN . 'refnotes/instructions.php');
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class action_plugin_refnotes extends DokuWiki_Action_Plugin {
+ use refnotes_localization_plugin;
+
+ private $afterParserHandlerDone;
+ private $beforeAjaxCallUnknown;
+ private $beforeParserCacheUse;
+ private $beforeParserWikitextPreprocess;
+ private $beforeTplMetaheaderOutput;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ refnotes_localization::initialize($this);
+
+ $this->afterParserHandlerDone = new refnotes_after_parser_handler_done();
+ $this->beforeAjaxCallUnknown = new refnotes_before_ajax_call_unknown();
+ $this->beforeParserCacheUse = new refnotes_before_parser_cache_use();
+ $this->beforeParserWikitextPreprocess = new refnotes_before_parser_wikitext_preprocess();
+ $this->beforeTplMetaheaderOutput = new refnotes_before_tpl_metaheader_output();
+ }
+
+ /**
+ * Register callbacks
+ */
+ public function register(Doku_Event_Handler $controller) {
+ $this->afterParserHandlerDone->register($controller);
+ $this->beforeAjaxCallUnknown->register($controller);
+ $this->beforeParserCacheUse->register($controller);
+ $this->beforeParserWikitextPreprocess->register($controller);
+ $this->beforeTplMetaheaderOutput->register($controller);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_after_parser_handler_done {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('PARSER_HANDLER_DONE', 'AFTER', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ refnotes_parser_core::getInstance()->exitParsingContext($event->data);
+
+ /* We need a new instance of mangler for each event because we can trigger it recursively
+ * by loading reference database or by parsing structured notes.
+ */
+ $mangler = new refnotes_instruction_mangler($event);
+
+ $mangler->process();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_instruction_mangler {
+
+ private $core;
+ private $calls;
+ private $paragraphReferences;
+ private $referenceGroup;
+ private $hidden;
+ private $inReference;
+
+ /**
+ * Constructor
+ */
+ public function __construct($event) {
+ $this->core = new refnotes_action_core();
+ $this->calls = new refnotes_instruction_list($event);
+ $this->paragraphReferences = array();
+ $this->referenceGroup = array();
+ $this->hidden = true;
+ $this->inReference = false;
+ }
+
+ /**
+ *
+ */
+ public function process() {
+ $this->scanInstructions();
+
+ if ($this->core->getNamespaceCount() > 0) {
+ $this->insertNotesInstructions($this->core->getStyles(), 'refnotes_notes_style_instruction');
+ $this->insertNotesInstructions($this->core->getMappings(), 'refnotes_notes_map_instruction');
+ $this->renderLeftovers();
+
+ $this->calls->applyChanges();
+
+ $this->renderStructuredNotes();
+
+ $this->calls->applyChanges();
+ }
+ }
+
+ /**
+ *
+ */
+ private function scanInstructions() {
+ foreach ($this->calls as $call) {
+ $this->markHiddenReferences($call);
+ $this->markReferenceGroups($call);
+ $this->markScopeLimits($call);
+ $this->extractStyles($call);
+ $this->extractMappings($call);
+ }
+ }
+
+ /**
+ *
+ */
+ private function markHiddenReferences($call) {
+ switch ($call->getName()) {
+ case 'p_open':
+ $this->paragraphReferences = array();
+ $this->hidden = true;
+ break;
+
+ case 'p_close':
+ if ($this->hidden) {
+ foreach ($this->paragraphReferences as $call) {
+ $call->setRefnotesAttribute('hidden', true);
+ }
+ }
+ break;
+
+ case 'cdata':
+ if (!$this->inReference && !empty(trim($call->getData(0)))) {
+ $this->hidden = false;
+ }
+ break;
+
+ case 'plugin_refnotes_references':
+ switch ($call->getPluginData(0)) {
+ case 'start':
+ $this->inReference = true;
+ break;
+
+ case 'render':
+ $this->inReference = false;
+ $this->paragraphReferences[] = $call;
+ break;
+ }
+ break;
+
+ default:
+ if (!$this->inReference) {
+ $this->hidden = false;
+ }
+ break;
+ }
+ }
+
+ /**
+ *
+ */
+ private function markReferenceGroups($call) {
+ if (($call->getName() == 'plugin_refnotes_references') && ($call->getPluginData(0) == 'render')) {
+ if (!empty($this->referenceGroup)) {
+ $groupNamespace = $this->referenceGroup[0]->getRefnotesAttribute('ns');
+
+ if ($call->getRefnotesAttribute('ns') != $groupNamespace) {
+ $this->closeReferenceGroup();
+ }
+ }
+
+ $this->referenceGroup[] = $call;
+ }
+ elseif (!$this->inReference && !empty($this->referenceGroup)) {
+ // Allow whitespace "cdata" istructions between references in a group
+ if ($call->getName() == 'cdata' && empty(trim($call->getData(0)))) {
+ return;
+ }
+
+ $this->closeReferenceGroup();
+ }
+ }
+
+ /**
+ *
+ */
+ private function closeReferenceGroup() {
+ $count = count($this->referenceGroup);
+
+ if ($count > 1) {
+ $this->referenceGroup[0]->setRefnotesAttribute('group', 'open');
+
+ for ($i = 1; $i < $count - 1; $i++) {
+ $this->referenceGroup[$i]->setRefnotesAttribute('group', 'hold');
+ }
+
+ $this->referenceGroup[$count - 1]->setRefnotesAttribute('group', 'close');
+ }
+
+ $this->referenceGroup = array();
+ }
+
+ /**
+ *
+ */
+ private function markScopeLimits($call) {
+ switch ($call->getName()) {
+ case 'plugin_refnotes_references':
+ if ($call->getPluginData(0) == 'render') {
+ $this->core->markScopeStart($call->getRefnotesAttribute('ns'), $call->getIndex());
+ }
+ break;
+
+ case 'plugin_refnotes_notes':
+ $this->core->markScopeEnd($call->getRefnotesAttribute('ns'), $call->getIndex());
+ break;
+ }
+ }
+
+ /**
+ * Extract style data and replace "split" instructions with "render"
+ */
+ private function extractStyles($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'split')) {
+ $this->core->addStyle($call->getRefnotesAttribute('ns'), $call->getPluginData(2));
+
+ $call->setPluginData(0, 'render');
+ $call->unsetPluginData(2);
+ }
+ }
+
+ /**
+ * Extract namespace mapping info
+ */
+ private function extractMappings($call) {
+ if ($call->getName() == 'plugin_refnotes_notes') {
+ $map = $call->getRefnotesAttribute('map');
+
+ if (!empty($map)) {
+ $this->core->addMapping($call->getRefnotesAttribute('ns'), $map);
+ $call->unsetRefnotesAttribute('map');
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function insertNotesInstructions($stash, $instruction) {
+ if ($stash->getCount() == 0) {
+ return;
+ }
+
+ $stash->sort();
+
+ foreach ($stash->getIndex() as $index) {
+ foreach ($stash->getAt($index) as $data) {
+ $this->calls->insert($index, new $instruction($data->getNamespace(), $data->getData()));
+ }
+ }
+ }
+
+ /**
+ * Insert render call at the very bottom of the page
+ */
+ private function renderLeftovers() {
+ $this->calls->append(new refnotes_notes_render_instruction('*'));
+ }
+
+ /**
+ *
+ */
+ private function renderStructuredNotes() {
+ $this->core->reset();
+
+ foreach ($this->calls as $call) {
+ $this->styleNamespaces($call);
+ $this->setNamespaceMappings($call);
+ $this->addReferences($call);
+ $this->rewriteReferences($call);
+ }
+ }
+
+ /**
+ *
+ */
+ private function styleNamespaces($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'style')) {
+ $this->core->styleNamespace($call->getRefnotesAttribute('ns'), $call->getPluginData(2));
+ }
+ }
+
+ /**
+ *
+ */
+ private function setNamespaceMappings($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'map')) {
+ $this->core->setNamespaceMapping($call->getRefnotesAttribute('ns'), $call->getPluginData(2));
+ }
+ }
+
+ /**
+ *
+ */
+ private function addReferences($call) {
+ if (($call->getName() == 'plugin_refnotes_references') && ($call->getPluginData(0) == 'render')) {
+ $attributes = $call->getPluginData(1);
+ $data = (count($call->getData(1)) > 2) ? $call->getPluginData(2) : array();
+ $reference = $this->core->addReference($attributes, $data, $call);
+
+ if ($call->getPrevious()->getName() != 'plugin_refnotes_references') {
+ $reference->getNote()->setText('defined');
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function rewriteReferences($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'render')) {
+ $this->core->rewriteReferences($call->getRefnotesAttribute('ns'), $call->getRefnotesAttribute('limit'));
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_ajax_call_unknown {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ global $conf;
+
+ if ($event->data == 'refnotes-admin') {
+ $event->preventDefault();
+ $event->stopPropagation();
+
+ /* Check admin rights */
+ if (auth_quickaclcheck($conf['start']) < AUTH_ADMIN) {
+ die('access denied');
+ }
+
+ switch ($_POST['action']) {
+ case 'load-settings':
+ $this->sendConfig();
+ break;
+
+ case 'save-settings':
+ $this->saveConfig($_POST['settings']);
+ break;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function sendResponse($contentType, $data) {
+ static $cookie = '{B27067E9-3DDA-4E31-9768-E66F23D18F4A}';
+
+ header('Content-Type: ' . $contentType);
+ print($cookie . $data . $cookie);
+ }
+
+ /**
+ *
+ */
+ private function sendConfig() {
+ $namespace = refnotes_configuration::load('namespaces');
+ $namespace = $this->translateStyles($namespace, 'dw', 'js');
+
+ $config['general'] = refnotes_configuration::load('general');
+ $config['namespaces'] = $namespace;
+ $config['notes'] = refnotes_configuration::load('notes');
+
+ $this->sendResponse('application/x-suggestions+json', json_encode($config));
+ }
+
+ /**
+ *
+ */
+ private function saveConfig($config) {
+ global $config_cascade;
+
+ $config = json_decode($config, true);
+
+ $namespace = $config['namespaces'];
+ $namespace = $this->translateStyles($namespace, 'js', 'dw');
+
+ $saved = refnotes_configuration::save('general', $config['general']);
+ $saved = $saved && refnotes_configuration::save('namespaces', $namespace);
+ $saved = $saved && refnotes_configuration::save('notes', $config['notes']);
+
+ if ($config['general']['reference-db-enable']) {
+ $saved = $saved && $this->setupReferenceDatabase($config['general']['reference-db-namespace']);
+ }
+
+ /* Touch local config file to expire the cache */
+ $saved = $saved && touch(reset($config_cascade['main']['local']));
+
+ $this->sendResponse('text/plain', $saved ? 'saved' : 'failed');
+ }
+
+ /**
+ *
+ */
+ private function translateStyles($namespace, $from, $to) {
+ foreach ($namespace as &$ns) {
+ foreach ($ns as $styleName => &$style) {
+ $style = $this->translateStyle($styleName, $style, $from, $to);
+ }
+ }
+
+ return $namespace;
+ }
+
+ /**
+ *
+ */
+ private function translateStyle($styleName, $style, $from, $to) {
+ static $dictionary = array(
+ 'refnote-id' => array(
+ 'dw' => array('1' , 'a' , 'A' , 'i' , 'I' , '*' , 'name' ),
+ 'js' => array('numeric', 'latin-lower', 'latin-upper', 'roman-lower', 'roman-upper', 'stars', 'note-name')
+ ),
+ 'reference-base' => array(
+ 'dw' => array('sup' , 'text' ),
+ 'js' => array('super', 'normal-text')
+ ),
+ 'reference-format' => array(
+ 'dw' => array(')' , '()' , ']' , '[]' ),
+ 'js' => array('right-parent', 'parents', 'right-bracket', 'brackets')
+ ),
+ 'reference-group' => array(
+ 'dw' => array('none' , ',' , 's' ),
+ 'js' => array('group-none', 'group-comma', 'group-semicolon')
+ ),
+ 'multi-ref-id' => array(
+ 'dw' => array('ref' , 'note' ),
+ 'js' => array('ref-counter', 'note-counter')
+ ),
+ 'note-id-base' => array(
+ 'dw' => array('sup' , 'text' ),
+ 'js' => array('super', 'normal-text')
+ ),
+ 'note-id-format' => array(
+ 'dw' => array(')' , '()' , ']' , '[]' , '.' ),
+ 'js' => array('right-parent', 'parents', 'right-bracket', 'brackets', 'dot')
+ ),
+ 'back-ref-base' => array(
+ 'dw' => array('sup' , 'text' ),
+ 'js' => array('super', 'normal-text')
+ ),
+ 'back-ref-format' => array(
+ 'dw' => array('1' , 'a' , 'note' ),
+ 'js' => array('numeric', 'latin', 'note-id')
+ ),
+ 'back-ref-separator' => array(
+ 'dw' => array(',' ),
+ 'js' => array('comma')
+ ),
+ 'struct-refs' => array(
+ 'dw' => array('off' , 'on' ),
+ 'js' => array('disable', 'enable')
+ )
+ );
+
+ if (array_key_exists($styleName, $dictionary)) {
+ $key = array_search($style, $dictionary[$styleName][$from]);
+
+ if ($key !== false) {
+ $style = $dictionary[$styleName][$to][$key];
+ }
+ }
+
+ return $style;
+ }
+
+ /**
+ *
+ */
+ private function setupReferenceDatabase($namespace) {
+ $success = true;
+ $source = refnotes_localization::getInstance()->getFileName('__template');
+ $destination = wikiFN(cleanID($namespace . ':template'));
+ $destination = preg_replace('/template.txt$/', '__template.txt', $destination);
+
+ if (@filemtime($destination) < @filemtime($source)) {
+ if (!file_exists(dirname($destination))) {
+ @mkdir(dirname($destination), 0755, true);
+ }
+
+ $success = copy($source, $destination);
+
+ touch($destination, filemtime($source));
+ }
+
+ return $success;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_parser_cache_use {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ global $ID;
+
+ $cache = $event->data;
+
+ if (isset($cache->page) && ($cache->page == $ID)) {
+ if (isset($cache->mode) && (($cache->mode == 'xhtml') || ($cache->mode == 'i'))) {
+ $meta = p_get_metadata($ID, 'plugin refnotes');
+
+ if (!empty($meta) && isset($meta['dbref'])) {
+ $this->addDependencies($cache, array_keys($meta['dbref']));
+ }
+ }
+ }
+ }
+
+ /**
+ * Add extra dependencies to the cache
+ */
+ private function addDependencies($cache, $depends) {
+ foreach ($depends as $file) {
+ if (!in_array($file, $cache->depends['files']) && file_exists($file)) {
+ $cache->depends['files'][] = $file;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_parser_wikitext_preprocess {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ refnotes_parser_core::getInstance()->enterParsingContext();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_tpl_metaheader_output {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ if (!empty($_REQUEST['do']) && $_REQUEST['do'] == 'admin' &&
+ !empty($_REQUEST['page']) && $_REQUEST['page'] == 'refnotes') {
+ $this->addAdminIncludes($event);
+ }
+ }
+
+ /**
+ *
+ */
+ private function addAdminIncludes($event) {
+ $this->addTemplateHeaderInclude($event, 'admin.js');
+ $this->addTemplateHeaderInclude($event, 'admin.css');
+ }
+
+ /**
+ *
+ */
+ private function addTemplateHeaderInclude($event, $fileName) {
+ $type = '';
+ $fileName = DOKU_BASE . 'lib/plugins/refnotes/' . $fileName;
+
+ switch (pathinfo($fileName, PATHINFO_EXTENSION)) {
+ case 'js':
+ $type = 'script';
+ $data = array('type' => 'text/javascript', 'charset' => 'utf-8', 'src' => $fileName, '_data' => '', 'defer' => 'defer');
+ break;
+
+ case 'css':
+ $type = 'link';
+ $data = array('type' => 'text/css', 'rel' => 'stylesheet', 'href' => $fileName);
+ break;
+ }
+
+ if ($type != '') {
+ $event->data[$type][] = $data;
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/admin.css b/platform/www/lib/plugins/refnotes/admin.css
new file mode 100644
index 0000000..2c97a50
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/admin.css
@@ -0,0 +1,100 @@
+div#refnotes-config div#config__manager div.cleared {
+ background-color: #eee;
+ color: #bbb;
+ border: 1px solid #ccc;
+ font-size: 90%;
+ margin: 0;
+ padding: 0.5em;
+ text-align: center;
+}
+
+div#refnotes-config div#config__manager div.info {
+ border: 1px solid #aaf;
+}
+
+div#refnotes-config div#config__manager div.success {
+ border: 1px solid #9e9;
+}
+
+div#refnotes-config div#config__manager div.error {
+ border: 1px solid #faa;
+}
+
+div#refnotes-config div#config__manager td.list {
+ position: relative;
+ background-clip: padding-box;
+ width: 15em;
+}
+
+div#refnotes-config div#config__manager select.list {
+ position: absolute;
+ top: 0.5em;
+ bottom: 0.5em;
+ width: 15em;
+}
+
+div#refnotes-config div#config__manager td.lean-label {
+ padding: 0.6em 0 0.6em 1em;
+ vertical-align: top;
+}
+
+div#refnotes-config div#config__manager td.value {
+ padding: 0.5em 0.5em;
+ width: 20em;
+}
+
+div#refnotes-config div#config__manager td.value div.input {
+ width: auto;
+}
+
+div#refnotes-config div#config__manager td.value select {
+ width: 99%;
+}
+
+div#refnotes-config div#config__manager td.value input.edit {
+ width: auto;
+}
+
+div#refnotes-config div#config__manager td.value input.button {
+ background-color: #fff;
+ color: #000;
+}
+
+div#refnotes-config div#config__manager input.button[disabled],
+div#refnotes-config div#config__manager td.value input.button[disabled] {
+ color: #666;
+}
+
+div#refnotes-config div#config__manager td.value textarea {
+ width: 100%;
+ height: auto;
+ resize: vertical;
+ margin: 0;
+}
+
+div#refnotes-config div#config__manager div.list-controls {
+ text-align: left;
+ margin-bottom: 1em;
+}
+
+div#refnotes-config div#config__manager div.list-controls input.edit {
+ width: 14em;
+}
+
+div#refnotes-config div#config__manager td .input {
+ background-color: inherit;
+}
+
+div#refnotes-config div#config__manager td.default input,
+div#refnotes-config div#config__manager td.default textarea,
+div#refnotes-config div#config__manager td.default select {
+ background-color: #ccddff;
+ color: #000;
+}
+
+div#refnotes-config div#config__manager td.inherited input,
+div#refnotes-config div#config__manager td.inherited textarea,
+div#refnotes-config div#config__manager td.inherited select {
+ background-color: #ddeedd;
+ color: #000;
+}
diff --git a/platform/www/lib/plugins/refnotes/admin.js b/platform/www/lib/plugins/refnotes/admin.js
new file mode 100644
index 0000000..d3a46dd
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/admin.js
@@ -0,0 +1,1035 @@
+let refnotes_admin = (function () {
+ let modified = false;
+
+
+ class NameMap extends Map {
+ constructor(sentinel) {
+ super();
+
+ this.sentinel = sentinel;
+ }
+
+ get(key) {
+ return key == '' ? this.sentinel : super.get(key);
+ }
+
+ has(key) {
+ return key == '' ? true : super.has(key);
+ }
+ }
+
+
+ class NamedObjectMap extends Map {
+ set(value) {
+ super.set(value.getName(), value);
+ }
+ }
+
+
+ function List(id) {
+ let list = jQuery(id);
+
+ function appendOption(value) {
+ jQuery('<option>')
+ .html(value)
+ .val(value)
+ .prop('sorting', value.replace(/:/g, '-').replace(/(-\w+)$/, '-$1'))
+ .appendTo(list);
+ }
+
+ function sortOptions() {
+ list.append(list.children().get().sort(function (a, b) {
+ return a.sorting > b.sorting ? 1 : -1;
+ }));
+ }
+
+ this.getSelectedValue = function () {
+ return list.val();
+ }
+
+ this.insertValue = function (value) {
+ appendOption(value);
+ sortOptions();
+
+ return list.children('[value="' + value + '"]').attr('selected', 'selected').val();
+ }
+
+ this.reload = function (values) {
+ list.empty();
+
+ for (let value of values.keys()) {
+ if (value != '') {
+ appendOption(value);
+ }
+ }
+
+ sortOptions();
+
+ return list.children(':first').attr('selected', 'selected').val();
+ }
+
+ this.removeValue = function (value) {
+ let option = list.children('[value="' + value + '"]');
+
+ if (option.length == 1) {
+ list.prop('selectedIndex', option.index() + (option.is(':last-child') ? -1 : 1));
+ option.remove();
+ }
+
+ return list.val();
+ }
+
+ this.renameValue = function (oldValue, newValue) {
+ if (list.children('[value="' + oldValue + '"]').remove().length == 1) {
+ this.insertValue(newValue);
+ }
+
+ return list.val();
+ }
+ }
+
+
+ let locale = (function () {
+ let lang = new Map();
+
+ function initialize() {
+ jQuery.each(jQuery('#refnotes-lang').html().split(/:eos:/), function (key, value) {
+ let match = value.match(/^\s*(\w+) : (.+)/);
+ if (match != null) {
+ lang.set(match[1], match[2]);
+ }
+ });
+ }
+
+ function getString(key) {
+ let string = lang.has(key) ? lang.get(key) : '';
+
+ if ((string.length > 0) && (arguments.length > 1)) {
+ for (let i = 1; i < arguments.length; i++) {
+ string = string.replace(new RegExp('\\{' + i + '\\}'), arguments[i]);
+ }
+ }
+
+ return string;
+ }
+
+ return {
+ initialize,
+ getString
+ }
+ })();
+
+
+ let server = (function () {
+ let timer = null;
+ let transaction = null;
+
+ function sendRequest(request, data, success) {
+ if (transaction == null) {
+ transaction = request;
+
+ jQuery.ajax({
+ cache : false,
+ data : data,
+ global : false,
+ success : success,
+ type : 'POST',
+ timeout : 10000,
+ url : DOKU_BASE + 'lib/exe/ajax.php',
+ beforeSend() {
+ setStatus('info', transaction);
+ },
+ error(xhr, status, message) {
+ setErrorStatus((status == 'parseerror') ? 'invalid_data' : transaction + '_failed', message);
+ },
+ dataFilter(data) {
+ let cookie = '{B27067E9-3DDA-4E31-9768-E66F23D18F4A}';
+ let match = data.match(new RegExp(cookie + '(.+?)' + cookie));
+
+ if ((match == null) || (match.length != 2)) {
+ throw 'Malformed response';
+ }
+
+ return match[1];
+ },
+ complete() {
+ transaction = null;
+ }
+ });
+ }
+ else {
+ setErrorStatus(request + '_failed', 'Server is busy');
+ }
+ }
+
+ function loadSettings() {
+ sendRequest('loading', {
+ call : 'refnotes-admin',
+ action : 'load-settings'
+ }, function (data) {
+ setSuccessStatus('loaded', 3000);
+ reloadSettings(data);
+ });
+ }
+
+ function saveSettings(settings) {
+ sendRequest('saving', {
+ call : 'refnotes-admin',
+ action : 'save-settings',
+ settings : JSON.stringify(settings)
+ }, function (data) {
+ if (data == 'saved') {
+ modified = false;
+
+ setSuccessStatus('saved', 10000);
+ }
+ else {
+ setErrorStatus('saving_failed', 'Server FS access error');
+ }
+ });
+ }
+
+ function setStatus(status, message) {
+ window.clearTimeout(timer);
+
+ if (message.match(/^\w+$/) != null) {
+ message = locale.getString(message);
+ }
+
+ jQuery('#server-status')
+ .removeClass()
+ .addClass(status)
+ .text(message);
+ }
+
+ function setErrorStatus(messageId, details) {
+ setStatus('error', locale.getString(messageId, details));
+ }
+
+ function setSuccessStatus(messageId, timeout) {
+ setStatus('success', messageId);
+
+ timer = window.setTimeout(function () {
+ setStatus('cleared', 'status');
+ }, timeout);
+ }
+
+ return {
+ loadSettings,
+ saveSettings
+ }
+ })();
+
+
+ let general = (function () {
+ let fields = new NamedObjectMap();
+ let defaults = new Map([
+ ['replace-footnotes' , false],
+ ['reference-db-enable' , false],
+ ['reference-db-namespace', ':refnotes:']
+ ]);
+
+ function Field(settingName) {
+ this.element = jQuery('#field-' + settingName);
+
+ this.element.change(this, function (event) {
+ event.data.updateDefault();
+ modified = true;
+ });
+
+ this.getName = function () {
+ return settingName;
+ }
+
+ this.updateDefault = function () {
+ this.element.parents('td').toggleClass('default', this.getValue() == defaults.get(settingName));
+ }
+
+ this.enable = function (enable) {
+ this.element.prop('disabled', !enable);
+ }
+ }
+
+ function CheckField(settingName) {
+ this.baseClass = Field;
+ this.baseClass(settingName);
+
+ this.setValue = function (value) {
+ this.element.attr('checked', value);
+ this.updateDefault();
+ }
+
+ this.getValue = function () {
+ return this.element.is(':checked');
+ }
+
+ this.setValue(defaults.get(settingName));
+ this.enable(false);
+ }
+
+ function TextField(settingName) {
+ this.baseClass = Field;
+ this.baseClass(settingName);
+
+ this.setValue = function (value) {
+ this.element.val(value);
+ this.updateDefault();
+ }
+
+ this.getValue = function () {
+ return this.element.val();
+ }
+
+ this.setValue(defaults.get(settingName));
+ this.enable(false);
+ }
+
+ function initialize() {
+ fields.set(new CheckField('replace-footnotes'));
+ fields.set(new CheckField('reference-db-enable'));
+ fields.set(new TextField('reference-db-namespace'));
+
+ jQuery('#field-reference-db-namespace').css('width', '19em');
+ }
+
+ function reload(settings) {
+ for (let name in settings) {
+ if (fields.has(name)) {
+ fields.get(name).setValue(settings[name]);
+ }
+ }
+
+ for (let field of fields.values()) {
+ field.enable(true);
+ }
+ }
+
+ function getSettings() {
+ let settings = {};
+
+ for (let [name, field] of fields) {
+ settings[name] = field.getValue();
+ }
+
+ return settings;
+ }
+
+ return {
+ initialize,
+ reload,
+ getSettings
+ }
+ })();
+
+
+ let namespaces = (function () {
+ let list = null;
+ let fields = new NamedObjectMap();
+ let namespaces = new NameMap(new DefaultNamespace());
+ let current = namespaces.get('');
+ let defaults = new Map([
+ ['refnote-id' , 'numeric'],
+ ['reference-base' , 'super'],
+ ['reference-font-weight', 'normal'],
+ ['reference-font-style' , 'normal'],
+ ['reference-format' , 'right-parent'],
+ ['reference-group' , 'group-none'],
+ ['reference-render' , 'basic'],
+ ['multi-ref-id' , 'ref-counter'],
+ ['note-preview' , 'popup'],
+ ['notes-separator' , '100%'],
+ ['note-text-align' , 'justify'],
+ ['note-font-size' , 'normal'],
+ ['note-render' , 'basic'],
+ ['note-id-base' , 'super'],
+ ['note-id-font-weight' , 'normal'],
+ ['note-id-font-style' , 'normal'],
+ ['note-id-format' , 'right-parent'],
+ ['back-ref-caret' , 'none'],
+ ['back-ref-base' , 'super'],
+ ['back-ref-font-weight' , 'bold'],
+ ['back-ref-font-style' , 'normal'],
+ ['back-ref-format' , 'note-id'],
+ ['back-ref-separator' , 'comma'],
+ ['scoping' , 'reset']
+ ]);
+
+ function DefaultNamespace() {
+ this.isReadOnly = function () {
+ return true;
+ }
+
+ this.setName = function (newName) {
+ }
+
+ this.getName = function () {
+ return '';
+ }
+
+ this.setStyle = function (name, value) {
+ }
+
+ this.getStyle = function (name) {
+ return defaults.get(name);
+ }
+
+ this.getStyleInheritance = function (name) {
+ return 'default';
+ }
+
+ this.getSettings = function () {
+ return {};
+ }
+ }
+
+ function Namespace(name, data) {
+ let styles = data ? new Map(Object.entries(data)) : new Map();
+
+ function getParent() {
+ let parent = name.replace(/\w*:$/, '');
+
+ while (!namespaces.has(parent)) {
+ parent = parent.replace(/\w*:$/, '');
+ }
+
+ return namespaces.get(parent);
+ }
+
+ this.isReadOnly = function () {
+ return false;
+ }
+
+ this.setName = function (newName) {
+ name = newName;
+ }
+
+ this.getName = function () {
+ return name;
+ }
+
+ this.setStyle = function (name, value) {
+ if (value == 'inherit') {
+ styles.delete(name);
+ }
+ else {
+ styles.set(name, value);
+ }
+ }
+
+ this.getStyle = function (name) {
+ let result;
+
+ if (styles.has(name)) {
+ result = styles.get(name);
+ }
+ else {
+ result = getParent().getStyle(name);
+ }
+
+ return result;
+ }
+
+ this.getStyleInheritance = function (name) {
+ let result = '';
+
+ if (!styles.has(name)) {
+ result = getParent().getStyleInheritance(name) || 'inherited';
+ }
+
+ return result;
+ }
+
+ this.getSettings = function () {
+ let settings = {};
+
+ for (let [name, style] of styles) {
+ settings[name] = style;
+ }
+
+ return settings;
+ }
+ }
+
+ function Field(styleName) {
+ this.element = jQuery('#field-' + styleName);
+
+ this.getName = function () {
+ return styleName;
+ }
+
+ this.updateInheretance = function () {
+ this.element.parents('td')
+ .removeClass('default inherited')
+ .addClass(current.getStyleInheritance(styleName));
+ }
+ }
+
+ function SelectField(styleName) {
+ this.baseClass = Field;
+ this.baseClass(styleName);
+
+ let combo = this.element;
+
+ combo.change(this, function (event) {
+ event.data.onChange();
+ });
+
+ function setSelection(value) {
+ combo.val(value);
+ }
+
+ this.onChange = function () {
+ let value = combo.val();
+
+ current.setStyle(styleName, value);
+
+ this.updateInheretance();
+
+ if ((value == 'inherit') || current.isReadOnly()) {
+ setSelection(current.getStyle(styleName));
+ }
+
+ modified = true;
+ }
+
+ this.update = function () {
+ this.updateInheretance();
+ setSelection(current.getStyle(styleName));
+ combo.prop('disabled', current.isReadOnly());
+ }
+ }
+
+ function TextField(styleName, validate) {
+ this.baseClass = Field;
+ this.baseClass(styleName);
+
+ let edit = this.element;
+ let button = jQuery('#field-' + styleName + '-inherit');
+
+ edit.change(this, function (event) {
+ event.data.setValue(validate(edit.val()));
+ });
+
+ button.click(this, function (event) {
+ event.data.setValue('inherit');
+ });
+
+ this.setValue = function (value) {
+ current.setStyle(styleName, value);
+
+ this.updateInheretance();
+
+ if ((edit.val() != value) || (value == 'inherit') || current.isReadOnly()) {
+ edit.val(current.getStyle(styleName));
+ }
+
+ modified = true;
+ }
+
+ this.update = function () {
+ this.updateInheretance();
+
+ edit.val(current.getStyle(styleName));
+ edit.prop('disabled', current.isReadOnly());
+ button.prop('disabled', current.isReadOnly());
+ }
+ }
+
+ function initialize() {
+ list = new List('#select-namespaces');
+
+ fields.set(new SelectField('refnote-id'));
+ fields.set(new SelectField('reference-base'));
+ fields.set(new SelectField('reference-font-weight'));
+ fields.set(new SelectField('reference-font-style'));
+ fields.set(new SelectField('reference-format'));
+ fields.set(new SelectField('reference-group'));
+ fields.set(new SelectField('reference-render'));
+ fields.set(new SelectField('multi-ref-id'));
+ fields.set(new SelectField('note-preview'));
+ fields.set(new TextField('notes-separator', function (value) {
+ return (value.match(/(?:\d+\.?|\d*\.\d+)(?:%|em|px)|none/) != null) ? value : 'none';
+ }));
+ fields.set(new SelectField('note-text-align'));
+ fields.set(new SelectField('note-font-size'));
+ fields.set(new SelectField('note-render'));
+ fields.set(new SelectField('note-id-base'));
+ fields.set(new SelectField('note-id-font-weight'));
+ fields.set(new SelectField('note-id-font-style'));
+ fields.set(new SelectField('note-id-format'));
+ fields.set(new SelectField('back-ref-caret'));
+ fields.set(new SelectField('back-ref-base'));
+ fields.set(new SelectField('back-ref-font-weight'));
+ fields.set(new SelectField('back-ref-font-style'));
+ fields.set(new SelectField('back-ref-format'));
+ fields.set(new SelectField('back-ref-separator'));
+ fields.set(new SelectField('scoping'));
+
+ jQuery('#select-namespaces').change(onNamespaceChange);
+ jQuery('#name-namespaces').prop('disabled', true);
+ jQuery('#add-namespaces').click(onAddNamespace).prop('disabled', true);
+ jQuery('#rename-namespaces').click(onRenameNamespace).prop('disabled', true);
+ jQuery('#delete-namespaces').click(onDeleteNamespace).prop('disabled', true);
+
+ updateFields();
+ }
+
+ function onNamespaceChange(event) {
+ setCurrent(list.getSelectedValue());
+ }
+
+ function onAddNamespace(event) {
+ try {
+ let name = validateName(jQuery('#name-namespaces').val(), 'ns', namespaces);
+
+ namespaces.set(name, new Namespace(name));
+
+ setCurrent(list.insertValue(name));
+
+ modified = true;
+ }
+ catch (error) {
+ alert(error);
+ }
+ }
+
+ function onRenameNamespace(event) {
+ try {
+ let newName = validateName(jQuery('#name-namespaces').val(), 'ns', namespaces);
+ let oldName = current.getName();
+
+ current.setName(newName);
+
+ namespaces.delete(oldName);
+ namespaces.set(newName, current);
+
+ setCurrent(list.renameValue(oldName, newName));
+
+ modified = true;
+ }
+ catch (error) {
+ alert(error);
+ }
+ }
+
+ function onDeleteNamespace(event) {
+ if (confirm(locale.getString('delete_ns', current.getName()))) {
+ namespaces.delete(current.getName());
+
+ setCurrent(list.removeValue(current.getName()));
+
+ modified = true;
+ }
+ }
+
+ function reload(settings) {
+ namespaces.clear();
+
+ for (let name in settings) {
+ if (name.match(/^:$|^:.+?:$/) != null) {
+ namespaces.set(name, new Namespace(name, settings[name]));
+ }
+ }
+
+ jQuery('#name-namespaces').prop('disabled', false);
+ jQuery('#add-namespaces').prop('disabled', false);
+
+ setCurrent(list.reload(namespaces));
+ }
+
+ function setCurrent(name) {
+ current = namespaces.get(name);
+
+ updateFields();
+ }
+
+ function updateFields() {
+ jQuery('#name-namespaces').val(current.getName());
+ jQuery('#rename-namespaces').prop('disabled', current.isReadOnly());
+ jQuery('#delete-namespaces').prop('disabled', current.isReadOnly());
+
+ for (let field of fields.values()) {
+ field.update();
+ }
+ }
+
+ function getSettings() {
+ let settings = {};
+
+ for (let [name, namespace] of namespaces) {
+ settings[name] = namespace.getSettings();
+ }
+
+ return settings;
+ }
+
+ return {
+ initialize,
+ reload,
+ getSettings
+ }
+ })();
+
+
+ let notes = (function () {
+ let list = null;
+ let fields = new NamedObjectMap();
+ let notes = new NameMap(new EmptyNote());
+ let current = notes.get('');
+ let defaults = new Map([
+ ['inline' , false],
+ ['use-reference-base' , true],
+ ['use-reference-font-weight', true],
+ ['use-reference-font-style' , true],
+ ['use-reference-format' , true]
+ ]);
+ let inlineAttributes = [
+ 'use-reference-base',
+ 'use-reference-font-weight',
+ 'use-reference-font-style',
+ 'use-reference-format'
+ ];
+
+ function isInlineAttribute(name) {
+ return inlineAttributes.indexOf(name) != -1;
+ }
+
+ function EmptyNote() {
+ this.isReadOnly = function () {
+ return true;
+ }
+
+ this.setName = function (newName) {
+ }
+
+ this.getName = function () {
+ return '';
+ }
+
+ this.setText = function (text) {
+ }
+
+ this.getText = function () {
+ return '';
+ }
+
+ this.setAttribute = function (name, value) {
+ }
+
+ this.getAttribute = function (name) {
+ return defaults.get(name);
+ }
+
+ this.getSettings = function () {
+ return {};
+ }
+ }
+
+ function Note(name, data) {
+ let attributes = data ? new Map(Object.entries(data)) : new Map();
+
+ this.isReadOnly = function () {
+ return false;
+ }
+
+ this.setName = function (newName) {
+ name = newName;
+ }
+
+ this.getName = function () {
+ return name;
+ }
+
+ this.setText = function (text) {
+ attributes.set('text', text);
+ }
+
+ this.getText = function () {
+ return attributes.get('text');
+ }
+
+ this.setAttribute = function (name, value) {
+ attributes.set(name, value);
+ }
+
+ this.getAttribute = function (name) {
+ if (!attributes.has(name) || (isInlineAttribute(name) && !this.getAttribute('inline'))) {
+ return defaults.get(name);
+ }
+ else {
+ return attributes.get(name);
+ }
+ }
+
+ this.getSettings = function () {
+ let settings = {};
+
+ if (!this.getAttribute('inline')) {
+ for (let i in inlineAttributes) {
+ if (attributes.has(inlineAttributes[i])) {
+ attributes.delete(inlineAttributes[i]);
+ }
+ }
+ }
+
+ for (let [name, attribute] of attributes) {
+ settings[name] = attribute;
+ }
+
+ return settings;
+ }
+ }
+
+ function Field(attributeName) {
+ this.element = jQuery('#field-' + attributeName);
+
+ this.element.change(this, function (event) {
+ current.setAttribute(attributeName, event.data.getValue());
+ modified = true;
+ });
+
+ this.getName = function () {
+ return attributeName;
+ }
+
+ this.enable = function (enable) {
+ this.element.prop('disabled', !enable);
+ }
+ }
+
+ function CheckField(attributeName) {
+ this.baseClass = Field;
+ this.baseClass(attributeName);
+
+ this.setValue = function (value) {
+ this.element.attr('checked', value);
+ }
+
+ this.getValue = function () {
+ return this.element.is(':checked');
+ }
+
+ this.update = function () {
+ this.setValue(current.getAttribute(attributeName));
+ this.enable(!current.isReadOnly() && (!isInlineAttribute(attributeName) || current.getAttribute('inline')));
+ }
+ }
+
+ function InlineField() {
+ this.baseClass = CheckField;
+ this.baseClass('inline');
+
+ this.element.change(this, function (event) {
+ for (let i in inlineAttributes) {
+ fields.get(inlineAttributes[i]).update();
+ }
+ });
+ }
+
+ function initialize() {
+ list = new List('#select-notes');
+
+ fields.set(new InlineField());
+ fields.set(new CheckField('use-reference-base'));
+ fields.set(new CheckField('use-reference-font-weight'));
+ fields.set(new CheckField('use-reference-font-style'));
+ fields.set(new CheckField('use-reference-format'));
+
+ jQuery('#select-notes').change(onNoteChange);
+ jQuery('#name-notes').prop('disabled', true);
+ jQuery('#add-notes').click(onAddNote).prop('disabled', true);
+ jQuery('#rename-notes').click(onRenameNote).prop('disabled', true);
+ jQuery('#delete-notes').click(onDeleteNote).prop('disabled', true);
+ jQuery('#field-note-text').change(onTextChange);
+
+ updateFields();
+ }
+
+ function onNoteChange(event) {
+ setCurrent(list.getSelectedValue());
+ }
+
+ function onAddNote(event) {
+ try {
+ let name = validateName(jQuery('#name-notes').val(), 'note', notes);
+
+ notes.set(name, new Note(name));
+
+ setCurrent(list.insertValue(name));
+
+ modified = true;
+ }
+ catch (error) {
+ alert(error);
+ }
+ }
+
+ function onRenameNote(event) {
+ try {
+ let newName = validateName(jQuery('#name-notes').val(), 'note', notes);
+ let oldName = current.getName();
+
+ current.setName(newName);
+
+ notes.delete(oldName);
+ notes.set(newName, current);
+
+ setCurrent(list.renameValue(oldName, newName));
+
+ modified = true;
+ }
+ catch (error) {
+ alert(error);
+ }
+ }
+
+ function onDeleteNote(event) {
+ if (confirm(locale.getString('delete_note', current.getName()))) {
+ notes.delete(current.getName());
+
+ setCurrent(list.removeValue(current.getName()));
+
+ modified = true;
+ }
+ }
+
+ function onTextChange(event) {
+ current.setText(event.target.value);
+
+ modified = true;
+ }
+
+ function reload(settings) {
+ notes.clear();
+
+ for (let name in settings) {
+ if (name.match(/^:.+?\w$/) != null) {
+ notes.set(name, new Note(name, settings[name]));
+ }
+ }
+
+ jQuery('#name-notes').prop('disabled', false);
+ jQuery('#add-notes').prop('disabled', false);
+
+ setCurrent(list.reload(notes));
+ }
+
+ function setCurrent(name) {
+ current = notes.get(name);
+
+ updateFields();
+ }
+
+ function updateFields() {
+ jQuery('#name-notes').val(current.getName());
+ jQuery('#rename-notes').prop('disabled', current.isReadOnly());
+ jQuery('#delete-notes').prop('disabled', current.isReadOnly());
+ jQuery('#field-note-text').val(current.getText()).prop('disabled', current.isReadOnly());
+
+ for (let field of fields.values()) {
+ field.update();
+ }
+ }
+
+ function getSettings() {
+ let settings = {};
+
+ for (let [name, note] of notes) {
+ settings[name] = note.getSettings();
+ }
+
+ return settings;
+ }
+
+ return {
+ initialize,
+ reload,
+ getSettings
+ }
+ })();
+
+
+ function initialize() {
+ locale.initialize();
+ general.initialize();
+ namespaces.initialize();
+ notes.initialize();
+
+ jQuery('#save-config').click(function () {
+ saveSettings();
+ });
+
+ window.onbeforeunload = onBeforeUnload;
+
+ jQuery('#server-status').show();
+
+ server.loadSettings();
+ }
+
+ function reloadSettings(settings) {
+ general.reload(settings.general);
+ namespaces.reload(settings.namespaces);
+ notes.reload(settings.notes);
+ }
+
+ function saveSettings() {
+ let settings = {};
+
+ settings.general = general.getSettings();
+ settings.namespaces = namespaces.getSettings();
+ settings.notes = notes.getSettings();
+
+ server.saveSettings(settings);
+
+ scroll(0, 0);
+ }
+
+ function onBeforeUnload(event) {
+ if (modified) {
+ let message = locale.getString('unsaved');
+
+ (event || window.event).returnValue = message;
+
+ return message;
+ }
+ }
+
+ function validateName(name, type, existing) {
+ let names = name.split(':');
+
+ name = (type == 'ns') ? ':' : '';
+
+ for (let i = 0; i < names.length; i++) {
+ if (names[i] != '') {
+ /* ECMA regexp doesn't support POSIX character classes, so [a-zA-Z] is used instead of [[:alpha:]] */
+ if (names[i].match(/^[a-zA-Z]\w*$/) == null) {
+ name = '';
+ break;
+ }
+
+ name += (type == 'ns') ? names[i] + ':' : ':' + names[i];
+ }
+ }
+
+ if (name == '') {
+ throw locale.getString('invalid_' + type + '_name');
+ }
+
+ if (existing.has(name)) {
+ throw locale.getString(type + '_name_exists', name);
+ }
+
+ return name;
+ }
+
+ return {
+ initialize
+ }
+})();
+
+
+jQuery(function () {
+ if (jQuery('#refnotes-config').length != 0) {
+ refnotes_admin.initialize();
+ }
+});
diff --git a/platform/www/lib/plugins/refnotes/admin.php b/platform/www/lib/plugins/refnotes/admin.php
new file mode 100644
index 0000000..499aa8c
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/admin.php
@@ -0,0 +1,751 @@
+<?php
+
+/**
+ * Plugin RefNotes: Configuration interface
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class admin_plugin_refnotes extends DokuWiki_Admin_Plugin {
+ use refnotes_localization_plugin;
+
+ private $html;
+ private $locale;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ refnotes_localization::initialize($this);
+
+ $this->html = new refnotes_html_sink();
+ $this->locale = refnotes_localization::getInstance();
+ }
+
+ /**
+ * Handle user request
+ */
+ public function handle() {
+ /* All handling is done using AJAX */
+ }
+
+ /**
+ * Output appropriate html
+ */
+ public function html() {
+ print($this->locale_xhtml('intro'));
+
+ $this->html->ptln('<!-- refnotes -->');
+
+ $this->printLanguageStrings();
+
+ $this->html->ptln('<div id="refnotes-config"><div id="config__manager">');
+ $this->html->ptln('<noscript><div class="error">' . $this->locale->getLang('noscript') . '</div></noscript>');
+ $this->html->ptln('<div id="server-status" class="info" style="display: none;">&nbsp;</div>');
+ $this->html->ptln('<form action="" method="post">');
+ $this->html->indent();
+
+ $this->printGeneral();
+ $this->printNamespaces();
+ $this->printNotes();
+
+ $this->html->ptln($this->getButton('save'));
+
+ $this->html->unindent();
+ $this->html->ptln('</form></div></div>');
+ $this->html->ptln('<!-- /refnotes -->');
+ }
+
+ /**
+ * Built-in JS localization stores all language strings in the common script (produced by js.php).
+ * The strings used by administration plugin seem to be unnecessary in that script. Instead we print
+ * them as part of the page and then load them into the LANG array on the client side.
+ */
+ private function printLanguageStrings() {
+ $lang = $this->locale->getByPrefix('js');
+
+ $this->html->ptln('<div id="refnotes-lang" style="display: none;">');
+
+ foreach ($lang as $key => $value) {
+ ptln($key . ' : ' . $value . ':eos:');
+ }
+
+ $this->html->ptln('</div>');
+ }
+
+ /**
+ *
+ */
+ private function printGeneral() {
+ $section = new refnotes_config_general();
+ $section->printHtml($this->html);
+ }
+
+ /**
+ *
+ */
+ private function printNamespaces() {
+ $section = new refnotes_config_namespaces();
+ $section->printHtml($this->html);
+ }
+
+ /**
+ *
+ */
+ private function printNotes() {
+ $section = new refnotes_config_notes();
+ $section->printHtml($this->html);
+ }
+
+ /**
+ *
+ */
+ private function getButton($action) {
+ $html = '<input type="button" class="button"';
+ $id = $action . '-config';
+ $html .= ' id="' . $id . '"';
+ $html .= ' name="' . $id . '"';
+ $html .= ' value="' . $this->locale->getLang('btn_' . $action) . '"';
+ $html .= ' />';
+
+ return $html;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_section {
+
+ protected $html;
+ protected $id;
+ protected $title;
+
+ /**
+ * Constructor
+ */
+ public function __construct($id) {
+ $this->html = NULL;
+ $this->id = $id;
+ $this->title = 'sec_' . $id;
+ }
+
+ /**
+ *
+ */
+ public function printHtml($html) {
+ $this->html = $html;
+ $this->open();
+ $this->printFields();
+ $this->close();
+ }
+
+ /**
+ *
+ */
+ protected function open() {
+ $title = refnotes_localization::getInstance()->getLang($this->title);
+
+ $this->html->ptln('<fieldset id="' . $this->id . '">');
+ $this->html->ptln('<legend>' . $title . '</legend>');
+ $this->html->ptln('<table class="inline" cols="3">');
+ $this->html->indent();
+ }
+
+ /**
+ *
+ */
+ protected function close() {
+ $this->html->unindent();
+ $this->html->ptln('</table>');
+ $this->html->ptln('</fieldset>');
+ }
+
+ /**
+ *
+ */
+ protected function printFields() {
+ $field = $this->getFields();
+ foreach ($field as $f) {
+ $this->printFieldRow($f);
+ }
+ }
+
+ /**
+ *
+ */
+ protected function getFields() {
+ $fieldData = $this->getFieldDefinitions();
+ $field = array();
+
+ foreach ($fieldData as $id => $fd) {
+ $class = 'refnotes_config_' . $fd['class'];
+ $field[] = new $class($id, $fd);
+ }
+
+ return $field;
+ }
+
+ /**
+ *
+ */
+ protected function printFieldRow($field, $startRow = true) {
+ if ($startRow) {
+ $this->html->ptln('<tr>');
+ $this->html->indent();
+ }
+
+ if (get_class($field) != 'refnotes_config_textarea') {
+ $settingName = $field->getSettingName();
+ if ($settingName != '') {
+ $this->html->ptln('<td class="label">');
+ $this->html->ptln($settingName);
+ }
+ else {
+ $this->html->ptln('<td class="lean-label">');
+ }
+
+ $this->html->ptln($field->getLabel());
+ $this->html->ptln('</td><td class="value">');
+ }
+ else {
+ $this->html->ptln('<td class="value" colspan="2">');
+ }
+
+ $this->html->ptln($field->getControl());
+ $this->html->ptln('</td>');
+
+ $this->html->unindent();
+ $this->html->ptln('</tr>');
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_list_section extends refnotes_config_section {
+
+ private $listRows;
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $listRows) {
+ parent::__construct($id);
+
+ $this->listRows = $listRows;
+ }
+
+ /**
+ *
+ */
+ protected function close() {
+ $this->html->unindent();
+ $this->html->ptln('</table>');
+ $this->printListControls();
+ $this->html->ptln('</fieldset>');
+ }
+
+ /**
+ *
+ */
+ private function printListControls() {
+ $this->html->ptln('<div class="list-controls">');
+ $this->html->indent();
+
+ $this->html->ptln($this->getEdit());
+ $this->html->ptln($this->getButton('add'));
+ $this->html->ptln($this->getButton('rename'));
+ $this->html->ptln($this->getButton('delete'));
+
+ $this->html->unindent();
+ $this->html->ptln('</div>');
+ }
+
+ /**
+ *
+ */
+ private function getEdit() {
+ $html = '<input type="text" class="edit"';
+ $id = 'name-' . $this->id;
+ $html .= ' id="' . $id . '"';
+ $html .= ' name="' . $id . '"';
+ $html .= ' value=""';
+ $html .= ' />';
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ private function getButton($action) {
+ $label = refnotes_localization::getInstance()->getLang('btn_' . $action);
+
+ $id = $action . '-' . $this->id;
+ $html = '<input type="button" class="button"';
+ $html .= ' id="' . $id . '"';
+ $html .= ' name="' . $id . '"';
+ $html .= ' value="' . $label . '"';
+ $html .= ' />';
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function printFields() {
+ $field = $this->getFields();
+ $fields = count($field);
+
+ $this->html->ptln('<tr>');
+ $this->html->indent();
+ $this->html->ptln('<td class="list" rowspan="' . $fields . '">');
+ $this->html->ptln('<select class="list" id="select-' . $this->id . '" size="' . $this->listRows . '"></select>');
+ $this->html->ptln('</td>');
+
+ $this->printFieldRow($field[0], false);
+
+ for ($f = 1; $f < $fields; $f++) {
+ $this->printFieldRow($field[$f]);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_general extends refnotes_config_section {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct('general');
+ }
+
+ /**
+ *
+ */
+ protected function getFieldDefinitions() {
+ static $field = array(
+ 'replace-footnotes' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ ),
+ 'reference-db-enable' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ ),
+ 'reference-db-namespace' => array(
+ 'class' => 'edit',
+ 'lean' => true
+ )
+ );
+
+ return $field;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_namespaces extends refnotes_config_list_section {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct('namespaces', 48);
+ }
+
+ /**
+ *
+ */
+ protected function getFieldDefinitions() {
+ static $field = array(
+ 'refnote-id' => array(
+ 'class' => 'select',
+ 'option' => array('numeric', 'latin-lower', 'latin-upper', 'roman-lower', 'roman-upper', 'stars', 'note-name', 'inherit')
+ ),
+ 'reference-base' => array(
+ 'class' => 'select',
+ 'option' => array('super', 'normal-text', 'inherit')
+ ),
+ 'reference-font-weight' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'bold', 'inherit')
+ ),
+ 'reference-font-style' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'italic', 'inherit')
+ ),
+ 'reference-format' => array(
+ 'class' => 'select',
+ 'option' => array('right-parent', 'parents', 'right-bracket', 'brackets', 'none', 'inherit')
+ ),
+ 'reference-group' => array(
+ 'class' => 'select',
+ 'option' => array('group-none', 'group-comma', 'group-semicolon', 'inherit')
+ ),
+ 'reference-render' => array(
+ 'class' => 'select',
+ 'option' => array('basic', 'harvard', 'inherit')
+ ),
+ 'multi-ref-id' => array(
+ 'class' => 'select',
+ 'option' => array('ref-counter', 'note-counter', 'inherit')
+ ),
+ 'note-preview' => array(
+ 'class' => 'select',
+ 'option' => array('popup', 'tooltip', 'none', 'inherit')
+ ),
+ 'notes-separator' => array(
+ 'class' => 'edit_inherit'
+ ),
+ 'note-text-align' => array(
+ 'class' => 'select',
+ 'option' => array('justify', 'left', 'inherit')
+ ),
+ 'note-font-size' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'small', 'inherit')
+ ),
+ 'note-render' => array(
+ 'class' => 'select',
+ 'option' => array('basic', 'harvard', 'inherit')
+ ),
+ 'note-id-base' => array(
+ 'class' => 'select',
+ 'option' => array('super', 'normal-text', 'inherit')
+ ),
+ 'note-id-font-weight' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'bold', 'inherit')
+ ),
+ 'note-id-font-style' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'italic', 'inherit')
+ ),
+ 'note-id-format' => array(
+ 'class' => 'select',
+ 'option' => array('right-parent', 'parents', 'right-bracket', 'brackets', 'dot', 'none', 'inherit')
+ ),
+ 'back-ref-caret' => array(
+ 'class' => 'select',
+ 'option' => array('prefix', 'merge', 'none', 'inherit')
+ ),
+ 'back-ref-base' => array(
+ 'class' => 'select',
+ 'option' => array('super', 'normal-text', 'inherit')
+ ),
+ 'back-ref-font-weight' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'bold', 'inherit')
+ ),
+ 'back-ref-font-style' => array(
+ 'class' => 'select',
+ 'option' => array('normal', 'italic', 'inherit')
+ ),
+ 'back-ref-format' => array(
+ 'class' => 'select',
+ 'option' => array('note-id', 'latin', 'numeric', 'caret', 'arrow', 'none', 'inherit')
+ ),
+ 'back-ref-separator' => array(
+ 'class' => 'select',
+ 'option' => array('comma', 'none', 'inherit')
+ ),
+ 'scoping' => array(
+ 'class' => 'select',
+ 'option' => array('reset', 'single')
+ )
+ );
+
+ return $field;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_notes extends refnotes_config_list_section {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct('notes', 14);
+ }
+
+ /**
+ *
+ */
+ protected function getFieldDefinitions() {
+ static $field = array(
+ 'note-text' => array(
+ 'class' => 'textarea',
+ 'rows' => '4',
+ 'lean' => true
+ ),
+ 'inline' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ ),
+ 'use-reference-base' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ ),
+ 'use-reference-font-weight' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ ),
+ 'use-reference-font-style' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ ),
+ 'use-reference-format' => array(
+ 'class' => 'checkbox',
+ 'lean' => true
+ )
+ );
+
+ return $field;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_field {
+
+ protected $id;
+ protected $settingName;
+ protected $label;
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $data) {
+ $this->id = 'field-' . $id;
+ $this->label = 'lbl_' . $id;
+
+ if (array_key_exists('lean', $data) && $data['lean']) {
+ $this->settingName = '';
+ }
+ else {
+ $this->settingName = $id;
+ }
+ }
+
+ /**
+ *
+ */
+ public function getSettingName() {
+ $html = '';
+
+ if ($this->settingName != '') {
+ $html = '<span class="outkey">' . $this->settingName . '</span>';
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ public function getLabel() {
+ $label = refnotes_localization::getInstance()->getLang($this->label);
+
+ return '<label for="' . $this->id . '">' . $label . '</label>';
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_checkbox extends refnotes_config_field {
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $data) {
+ parent::__construct($id, $data);
+ }
+
+ /**
+ *
+ */
+ public function getControl() {
+ $html = '<div class="input">';
+ $html .= '<input type="checkbox" class="checkbox"';
+ $html .= ' id="' . $this->id . '"';
+ $html .= ' name="' . $this->id . '" value="1"';
+ $html .= '/></div>';
+
+ return $html;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_select extends refnotes_config_field {
+
+ private $option;
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $data) {
+ parent::__construct($id, $data);
+
+ $this->option = $data['option'];
+ }
+
+ /**
+ *
+ */
+ public function getControl() {
+ $locale = refnotes_localization::getInstance();
+
+ $html = '<div class="input">';
+
+ $html .= '<select class="edit"';
+ $html .= ' id="' . $this->id . '"';
+ $html .= ' name="' . $this->id . '">' . DOKU_LF;
+
+ foreach ($this->option as $option) {
+ $html .= '<option value="' . $option . '">' . $locale->getLang('opt_' . $option) . '</option>' . DOKU_LF;
+ }
+
+ $html .= '</select>';
+ $html .= '</div>';
+
+ return $html;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_edit extends refnotes_config_field {
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $data) {
+ parent::__construct($id, $data);
+ }
+
+ /**
+ *
+ */
+ public function getControl() {
+ $html = '<div class="input">';
+
+ $html .= '<input type="text" class="edit"';
+ $html .= ' id="' . $this->id . '"';
+ $html .= ' name="' . $this->id . '" />' . DOKU_LF;
+
+ $html .= '</div>';
+
+ return $html;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_edit_inherit extends refnotes_config_field {
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $data) {
+ parent::__construct($id, $data);
+ }
+
+ /**
+ *
+ */
+ public function getControl() {
+ $buttonLabel = refnotes_localization::getInstance()->getLang('opt_inherit');
+
+ $html = '<div class="input">';
+
+ $html .= '<input type="text" class="edit"';
+ $html .= ' id="' . $this->id . '"';
+ $html .= ' name="' . $this->id . '" />' . DOKU_LF;
+
+ $html .= '<input type="button" class="button"';
+ $html .= ' id="' . $this->id . '-inherit"';
+ $html .= ' name="' . $this->id . '-inherit"';
+ $html .= ' value="' . $buttonLabel . '"';
+ $html .= ' />';
+
+ $html .= '</div>';
+
+ return $html;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_config_textarea extends refnotes_config_field {
+
+ private $rows;
+
+ /**
+ * Constructor
+ */
+ public function __construct($id, $data) {
+ parent::__construct($id, $data);
+
+ $this->rows = $data['rows'];
+ }
+
+ /**
+ *
+ */
+ public function getControl() {
+ $html = '<div class="input">';
+ $html .= '<textarea class="edit"';
+ $html .= ' id="' . $this->id . '"';
+ $html .= ' name="' . $this->id . '"';
+ $html .= ' cols="40" rows="' . $this->rows . '">';
+ $html .= '</textarea></div>';
+
+ return $html;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_html_sink {
+
+ private $indentIncrement;
+ private $indent;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->indentIncrement = 2;
+ $this->indent = 0;
+ }
+
+ /**
+ *
+ */
+ public function indent() {
+ $this->indent += $this->indentIncrement;
+ }
+
+ /**
+ *
+ */
+ public function unindent() {
+ if ($this->indent >= $this->indentIncrement) {
+ $this->indent -= $this->indentIncrement;
+ }
+ }
+
+ /**
+ *
+ */
+ public function ptln($string, $indentDelta = 0) {
+ if ($indentDelta < 0) {
+ $this->indent += $this->indentIncrement * $indentDelta;
+ }
+
+ $text = explode(DOKU_LF, $string);
+ foreach ($text as $string) {
+ ptln($string, $this->indent);
+ }
+
+ if ($indentDelta > 0) {
+ $this->indent += $this->indentIncrement * $indentDelta;
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/admin.svg b/platform/www/lib/plugins/refnotes/admin.svg
new file mode 100644
index 0000000..02553fa
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/admin.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="24" height="24"><path d="M7.51 21.488H2.526V2.465H7.51v1.432H4.204v16.157H7.51zM12.221 21.589q-4.66-4.371-4.66-9.663 0-1.238.242-2.467.253-1.238.799-2.476.556-1.239 1.455-2.477.91-1.239 2.204-2.457l1.021 1.055q-3.922 3.92-3.922 8.69 0 2.376.99 4.566.991 2.19 2.933 4.135zM14.685 7.108l1.092-1.135 6.795 5.742-6.795 5.742-1.092-1.126 5.51-4.596z"/></svg>
diff --git a/platform/www/lib/plugins/refnotes/bibtex.php b/platform/www/lib/plugins/refnotes/bibtex.php
new file mode 100644
index 0000000..04b335f
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/bibtex.php
@@ -0,0 +1,669 @@
+<?php
+
+/**
+ * Plugin RefNotes: BibTeX parser
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_parser extends \dokuwiki\Parsing\Parser {
+
+ private static $instance = NULL;
+
+ /**
+ *
+ */
+ public static function getInstance() {
+ if (self::$instance == NULL) {
+ self::$instance = new refnotes_bibtex_parser();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->handler = new refnotes_bibtex_handler();
+ $this->lexer = new refnotes_bibtex_lexer($this->handler, 'base', true);
+
+ $this->addBibtexMode(new refnotes_bibtex_outside_mode());
+ $this->addBibtexMode(new refnotes_bibtex_entry_mode('parented'));
+ $this->addBibtexMode(new refnotes_bibtex_entry_mode('braced'));
+ $this->addBibtexMode(new refnotes_bibtex_field_mode());
+ $this->addBibtexMode(new refnotes_bibtex_integer_value_mode());
+ $this->addBibtexMode(new refnotes_bibtex_string_value_mode('quoted'));
+ $this->addBibtexMode(new refnotes_bibtex_string_value_mode('braced'));
+ $this->addBibtexMode(new refnotes_bibtex_nested_braces_mode('quoted'));
+ $this->addBibtexMode(new refnotes_bibtex_nested_braces_mode('braced'));
+ $this->addBibtexMode(new refnotes_bibtex_concatenation_mode());
+ }
+
+ /**
+ *
+ */
+ private function addBibtexMode($mode) {
+ $this->addMode($mode->getName(), $mode);
+ }
+
+ /**
+ *
+ */
+ public function connectModes() {
+ if (!$this->connected) {
+ $this->modes['outside']->connectTo('base');
+ $this->modes['entry_parented']->connectTo('base');
+ $this->modes['entry_braced']->connectTo('base');
+
+ parent::connectModes();
+ }
+ }
+
+ /**
+ *
+ */
+ public function parse($text) {
+ $this->connectModes();
+
+ $this->handler->reset();
+ $this->lexer->parse(str_replace("\r\n", "\n", $text));
+
+ return $this->handler->finalize();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_lexer extends \dokuwiki\Parsing\Lexer\Lexer {
+
+ /**
+ *
+ */
+ public function parse($text) {
+ $lastMode = '';
+
+ while (is_array($parsed = $this->reduce($text))) {
+ list($unmatched, $matched, $mode) = $parsed;
+
+ if (!$this->dispatchTokens($unmatched, $matched, $mode, 0, 0)) {
+ return false;
+ }
+
+ if (empty($unmatched) && empty($matched) && ($lastMode == $this->modeStack->getCurrent())) {
+ return false;
+ }
+
+ $lastMode = $this->modeStack->getCurrent();
+ }
+
+ if (!$parsed) {
+ return false;
+ }
+
+ return $this->invokeHandler($text, DOKU_LEXER_UNMATCHED, 0);
+ }
+
+ /**
+ *
+ */
+ protected function invokeHandler($text, $state, $pos) {
+ if ($text == "" && $state == DOKU_LEXER_UNMATCHED) {
+ return true;
+ }
+
+ $mode = $this->modeStack->getCurrent();
+ $handler = isset($this->mode_handlers[$mode]) ? $this->mode_handlers[$mode] : $mode;
+
+ return $this->handler->$handler($text, $state, $pos);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_mode extends \dokuwiki\Parsing\ParserMode\AbstractMode {
+
+ protected $name;
+ protected $handler;
+ protected $specialPattern;
+ protected $entryPattern;
+ protected $exitPattern;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->name = preg_replace('/refnotes_bibtex_(\w+)_mode/', '$1', get_class($this));
+ $this->handler = '';
+
+ $this->specialPattern = array();
+ $this->entryPattern = array();
+ $this->exitPattern = array();
+ }
+
+ /**
+ *
+ */
+ public function getSort() {
+ return 0;
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ *
+ */
+ public function connectTo($mode) {
+ foreach ($this->specialPattern as $pattern) {
+ $this->Lexer->addSpecialPattern($pattern, $mode, $this->name);
+ }
+
+ foreach ($this->entryPattern as $pattern) {
+ $this->Lexer->addEntryPattern($pattern, $mode, $this->name);
+ }
+
+ if ($this->handler != '') {
+ $this->Lexer->mapHandler($this->name, $this->handler);
+ }
+ }
+
+ /**
+ *
+ */
+ public function postConnect() {
+ foreach ($this->exitPattern as $pattern) {
+ $this->Lexer->addExitPattern($pattern, $this->name);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_outside_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->specialPattern[] = '[^@]+(?=@)';
+ }
+
+ /**
+ *
+ */
+ public function connectTo($mode) {
+ parent::connectTo($mode);
+
+ $this->Lexer->mapHandler('base', $this->name);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_entry_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct($type) {
+ parent::__construct();
+
+ $this->handler = $this->name;
+ $this->name .= '_' . $type;
+
+ list($open, $close) = ($type == 'parented') ? array('\(', '\)') : array('{', '}');
+
+ $this->entryPattern[] = '^@\w+\s*' . $open . '(?=.*' . $close . ')';
+ $this->exitPattern[] = '\s*(?:' . $close . '|(?=@))';
+
+ $this->allowedModes = array('field');
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_field_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->entryPattern[] = '^\s*\w[\w-]+\s*=\s*';
+ $this->exitPattern[] = '\s*(?:,|(?=[\)}@]))';
+
+ $this->allowedModes = array('integer_value', 'string_value_quoted', 'string_value_braced', 'concatenation');
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_integer_value_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->specialPattern[] = '^\d+';
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_string_value_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct($type) {
+ parent::__construct();
+
+ $this->handler = $this->name;
+ $this->name .= '_' . $type;
+
+ list($open, $close, $exit) = ($type == 'quoted') ? array('"', '"', '"') : array('{', '}', '(?:}|(?=@))');
+
+ $this->entryPattern[] = '^' . $open . '(?=.*' . $close . ')';
+ $this->exitPattern[] = $exit;
+
+ $this->allowedModes = array('nested_braces_' . $type);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_nested_braces_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct($type) {
+ parent::__construct();
+
+ $this->handler = $this->name;
+ $this->name .= '_' . $type;
+
+ $this->entryPattern[] = '{(?=.*})';
+ $this->exitPattern[] = ($type == 'quoted') ? '}' : '(?:}|(?=@))';
+
+ $this->allowedModes = array($this->name);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_concatenation_mode extends refnotes_bibtex_mode {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->specialPattern[] = '\s*#\s*';
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_handler {
+
+ private $entries;
+ private $entry;
+ private $field;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->reset();
+ }
+
+ /**
+ *
+ */
+ public function reset() {
+ $this->entries = new refnotes_bibtex_entry_stash();
+ $this->entry = NULL;
+ $this->field = NULL;
+ }
+
+ /**
+ *
+ */
+ public function finalize() {
+ $entries = $this->entries->getEntries();
+
+ foreach ($entries as &$entry) {
+ if (array_key_exists('author', $entry)) {
+ $authors = explode(' and ', $entry['author']);
+
+ foreach ($authors as &$author) {
+ $author = implode(' ', array_reverse(explode(', ', $author)));
+ }
+
+ $entry['author'] = implode(', ', $authors);
+ }
+ }
+
+ return $entries;
+ }
+
+ /**
+ *
+ */
+ public function outside($match, $state) {
+ /* Ignore everything outside the entries */
+ return true;
+ }
+
+ /**
+ *
+ */
+ public function entry($match, $state) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $this->entry = new refnotes_bibtex_entry(preg_replace('/@(\w+)\W+/', '$1', $match));
+ break;
+
+ case DOKU_LEXER_UNMATCHED:
+ $this->entry->handleUnmatched($match);
+ break;
+
+ case DOKU_LEXER_EXIT:
+ $this->entries->add($this->entry);
+ $this->entry = NULL;
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ */
+ public function field($match, $state) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $this->field = new refnotes_bibtex_field(preg_replace('/\W*(\w[\w-]+)\W*/', '$1', $match));
+ break;
+
+ case DOKU_LEXER_UNMATCHED:
+ $this->field->addToken('unmatched', $match);
+ break;
+
+ case DOKU_LEXER_EXIT:
+ $this->entry->addField($this->field);
+ $this->field = NULL;
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ */
+ public function integer_value($match, $state) {
+ $this->field->addToken('integer', $match);
+
+ return true;
+ }
+
+ /**
+ *
+ */
+ public function string_value($match, $state) {
+ if ($state == DOKU_LEXER_UNMATCHED) {
+ $this->field->addToken('string', $match);
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ */
+ public function nested_braces($match, $state) {
+ if ($state == DOKU_LEXER_UNMATCHED) {
+ $this->field->addToken('braces', $match);
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ */
+ public function concatenation($match, $state) {
+ /* Nothing special to do, concatenation will happen anyway */
+ return true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_entry_stash {
+
+ private $entry;
+ private $strings;
+ private $namespace;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->entry = array();
+ $this->strings = new refnotes_bibtex_strings();
+ $this->namespace = ':';
+ }
+
+ /**
+ *
+ */
+ public function getEntries() {
+ return $this->entry;
+ }
+
+ /**
+ *
+ */
+ public function add($entry) {
+ static $entryType = array(
+ 'article', 'book', 'booklet', 'conference', 'inbook', 'incollection', 'inproceedings', 'manual',
+ 'mastersthesis', 'misc', 'phdthesis', 'proceedings', 'techreport', 'unpublished');
+
+ $type = $entry->getType();
+ $name = $entry->getName();
+
+ if (in_array($type, $entryType)) {
+ if ($this->isValidRefnotesName($name)) {
+ if ($name[0] != ':') {
+ $name = $this->namespace . $name;
+ }
+
+ $this->entry[] = array_merge(array('note-name' => $name), $entry->getData($this->strings));
+ }
+ }
+ elseif ($type == 'string') {
+ $data = $entry->getData($this->strings);
+ $name = reset(array_keys($data));
+
+ if ($this->isValidStringName($name)) {
+ $this->strings->add($name, $data[$name]);
+ }
+ }
+ elseif (($type == 'comment') && (strtolower($name) == 'refnotes')) {
+ $data = $entry->getData($this->strings);
+
+ if (isset($data['namespace']) && $this->isValidRefnotesName($data['namespace'])) {
+ $this->namespace = refnotes_namespace::canonizeName($data['namespace']);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function isValidRefnotesName($name) {
+ return preg_match('/^' . refnotes_note::getNamePattern('full-extended') . '$/', $name) == 1;
+ }
+
+ /**
+ *
+ */
+ private function isValidStringName($name) {
+ return preg_match('/^[[:alpha:]]\w*$/', $name) == 1;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_entry {
+
+ private $type;
+ private $name;
+ private $field;
+
+ /**
+ * Constructor
+ */
+ public function __construct($type) {
+ $this->type = strtolower($type);
+ $this->name = '';
+ $this->field = array();
+ }
+
+ /**
+ *
+ */
+ public function getType() {
+ return $this->type;
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ *
+ */
+ public function getData($strings) {
+ $data = array();
+
+ foreach ($this->field as $field) {
+ $data[$field->getName()] = $field->getValue($strings);
+ }
+
+ return $data;
+ }
+
+ /**
+ *
+ */
+ public function handleUnmatched($token) {
+ if (($this->name == '') && (preg_match('/\s*([^\s,]+)\s*,/', $token, $match) == 1)) {
+ $this->name = $match[1];
+ }
+ }
+
+ /**
+ *
+ */
+ public function addField($field) {
+ $this->field[] = $field;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_field {
+
+ private $name;
+ private $token;
+
+ /**
+ * Constructor
+ */
+ public function __construct($name) {
+ $this->name = strtolower($name);
+ $this->token = array();
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ *
+ */
+ public function getValue($strings) {
+ $value = '';
+
+ foreach ($this->token as $token) {
+ $text = $token->text;
+
+ if ($token->type == 'unmatched') {
+ $text = $strings->lookup(strtolower(trim($text)));
+ }
+
+ $value .= $text;
+ }
+
+ return preg_replace('/\s+/', ' ', trim($value));
+ }
+
+ /**
+ *
+ */
+ public function addToken($type, $text) {
+ $this->token[] = new refnotes_bibtex_field_token($type, $text);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_field_token {
+
+ public $type;
+ public $text;
+
+ /**
+ * Constructor
+ */
+ public function __construct($type, $text) {
+ $this->type = $type;
+ $this->text = $text;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_bibtex_strings {
+
+ private $string;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->string = array();
+ }
+
+ /**
+ *
+ */
+ public function add($name, $value) {
+ $this->string[$name] = $value;
+ }
+
+ /**
+ *
+ */
+ public function lookup($name) {
+ return array_key_exists($name, $this->string) ? $this->string[$name] : '';
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/conf/namespaces.dat b/platform/www/lib/plugins/refnotes/conf/namespaces.dat
new file mode 100644
index 0000000..3377084
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/conf/namespaces.dat
@@ -0,0 +1 @@
+a:11:{s:1:":";a:0:{}s:8:":biblio:";a:11:{s:10:"refnote-id";s:4:"name";s:14:"reference-base";s:4:"text";s:16:"reference-format";s:2:"[]";s:12:"multi-ref-id";s:4:"note";s:12:"note-preview";s:7:"tooltip";s:12:"note-id-base";s:4:"text";s:19:"note-id-font-weight";s:6:"normal";s:18:"note-id-font-style";s:6:"normal";s:14:"note-id-format";s:4:"none";s:14:"back-ref-caret";s:6:"prefix";s:15:"back-ref-format";s:1:"a";}s:6:":cite:";a:5:{s:16:"reference-format";s:2:"[]";s:12:"multi-ref-id";s:4:"note";s:12:"note-id-base";s:4:"text";s:14:"note-id-format";s:1:".";s:15:"back-ref-format";s:1:"a";}s:4:":dw:";a:24:{s:10:"refnote-id";s:1:"1";s:14:"reference-base";s:3:"sup";s:21:"reference-font-weight";s:6:"normal";s:20:"reference-font-style";s:6:"normal";s:16:"reference-format";s:1:")";s:15:"reference-group";s:4:"none";s:16:"reference-render";s:5:"basic";s:12:"multi-ref-id";s:3:"ref";s:12:"note-preview";s:5:"popup";s:15:"notes-separator";s:4:"100%";s:15:"note-text-align";s:7:"justify";s:14:"note-font-size";s:6:"normal";s:11:"note-render";s:5:"basic";s:12:"note-id-base";s:3:"sup";s:19:"note-id-font-weight";s:6:"normal";s:18:"note-id-font-style";s:6:"normal";s:14:"note-id-format";s:1:")";s:14:"back-ref-caret";s:4:"none";s:13:"back-ref-base";s:3:"sup";s:20:"back-ref-font-weight";s:4:"bold";s:19:"back-ref-font-style";s:6:"normal";s:15:"back-ref-format";s:4:"note";s:18:"back-ref-separator";s:1:",";s:7:"scoping";s:5:"reset";}s:4:":fn:";a:0:{}s:9:":harvard:";a:11:{s:14:"reference-base";s:4:"text";s:16:"reference-format";s:2:"()";s:16:"reference-render";s:7:"harvard";s:12:"multi-ref-id";s:4:"note";s:15:"notes-separator";s:4:"none";s:11:"note-render";s:7:"harvard";s:12:"note-id-base";s:4:"text";s:14:"note-id-format";s:1:".";s:14:"back-ref-caret";s:5:"merge";s:15:"back-ref-format";s:1:"a";s:18:"back-ref-separator";s:4:"none";}s:4:":mw:";a:24:{s:10:"refnote-id";s:1:"1";s:14:"reference-base";s:3:"sup";s:21:"reference-font-weight";s:6:"normal";s:20:"reference-font-style";s:6:"normal";s:16:"reference-format";s:2:"[]";s:15:"reference-group";s:4:"none";s:16:"reference-render";s:5:"basic";s:12:"multi-ref-id";s:4:"note";s:12:"note-preview";s:4:"none";s:15:"notes-separator";s:4:"none";s:15:"note-text-align";s:4:"left";s:14:"note-font-size";s:6:"normal";s:11:"note-render";s:5:"basic";s:12:"note-id-base";s:4:"text";s:19:"note-id-font-weight";s:6:"normal";s:18:"note-id-font-style";s:6:"normal";s:14:"note-id-format";s:1:".";s:14:"back-ref-caret";s:5:"merge";s:13:"back-ref-base";s:3:"sup";s:20:"back-ref-font-weight";s:4:"bold";s:19:"back-ref-font-style";s:6:"italic";s:15:"back-ref-format";s:1:"a";s:18:"back-ref-separator";s:4:"none";s:7:"scoping";s:5:"reset";}s:7:":stars:";a:5:{s:10:"refnote-id";s:1:"*";s:16:"reference-format";s:4:"none";s:12:"multi-ref-id";s:4:"note";s:12:"note-preview";s:7:"tooltip";s:15:"back-ref-format";s:1:"a";}s:7:":table:";a:4:{s:12:"multi-ref-id";s:4:"note";s:12:"note-preview";s:7:"tooltip";s:15:"notes-separator";s:4:"none";s:15:"back-ref-format";s:4:"none";}s:13:":table:alpha:";a:1:{s:10:"refnote-id";s:1:"a";}s:13:":table:stars:";a:2:{s:10:"refnote-id";s:1:"*";s:16:"reference-format";s:4:"none";}} \ No newline at end of file
diff --git a/platform/www/lib/plugins/refnotes/conf/notes.dat b/platform/www/lib/plugins/refnotes/conf/notes.dat
new file mode 100644
index 0000000..c24e182
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/conf/notes.dat
@@ -0,0 +1 @@
+a:4:{s:6:":fixme";a:2:{s:4:"text";s:29:"This part has to be reworked.";s:6:"inline";b:0;}s:5:":todo";a:6:{s:4:"text";s:51:"%%[%%//[[:missing_citation|citation needed]]//%%]%%";s:6:"inline";b:1;s:18:"use-reference-base";b:1;s:25:"use-reference-font-weight";b:1;s:24:"use-reference-font-style";b:0;s:20:"use-reference-format";b:0;}s:10:":cite:todo";a:6:{s:4:"text";s:37:"[[:missing_citation|citation needed]]";s:6:"inline";b:1;s:18:"use-reference-base";b:1;s:25:"use-reference-font-weight";b:1;s:24:"use-reference-font-style";b:1;s:20:"use-reference-format";b:1;}s:9:":ref:todo";a:6:{s:4:"text";s:53:"%%[%%//[[:missing_reference|reference needed]]//%%]%%";s:6:"inline";b:1;s:18:"use-reference-base";b:1;s:25:"use-reference-font-weight";b:0;s:24:"use-reference-font-style";b:0;s:20:"use-reference-format";b:0;}} \ No newline at end of file
diff --git a/platform/www/lib/plugins/refnotes/config.php b/platform/www/lib/plugins/refnotes/config.php
new file mode 100644
index 0000000..80b0925
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/config.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Plugin RefNotes: Configuration
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+class refnotes_configuration {
+
+ private static $section = array();
+ private static $setting = array(
+ 'replace-footnotes' => array('general', false),
+ 'reference-db-enable' => array('general', false),
+ 'reference-db-namespace' => array('general', ':refnotes:')
+ );
+
+ /**
+ *
+ */
+ public static function getSetting($name) {
+ $result = null;
+
+ if (array_key_exists($name, self::$setting)) {
+ $sectionName = self::$setting[$name][0];
+ $result = self::$setting[$name][1];
+
+ if (!array_key_exists($sectionName, self::$section)) {
+ self::$section[$sectionName] = self::load($sectionName);
+ }
+
+ if (array_key_exists($name, self::$section[$sectionName])) {
+ $result = self::$section[$sectionName][$name];
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public static function load($sectionName) {
+ $fileName = DOKU_CONF . 'refnotes.' . $sectionName . '.local.dat';
+
+ if (!file_exists($fileName)) {
+ // TODO: This backward compatibility fix should be eventually removed
+ $pluginRoot = DOKU_PLUGIN . 'refnotes/';
+ $fileName = $pluginRoot . $sectionName . '.local.dat';
+ if (!file_exists($fileName)) {
+ $fileName = $pluginRoot . 'conf/' . $sectionName . '.dat';
+ if (!file_exists($fileName)) {
+ $fileName = '';
+ }
+ }
+ }
+
+ if ($fileName != '') {
+ $result = unserialize(io_readFile($fileName, false));
+ }
+ else {
+ $result = array();
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public static function save($sectionName, $config) {
+ $fileName = DOKU_CONF . 'refnotes.' . $sectionName . '.local.dat';
+
+ return io_saveFile($fileName, serialize($config));
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/core.php b/platform/www/lib/plugins/refnotes/core.php
new file mode 100644
index 0000000..a3ce28d
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/core.php
@@ -0,0 +1,483 @@
+<?php
+
+/**
+ * Plugin RefNotes: Core functionality
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+require_once(DOKU_PLUGIN . 'refnotes/locale.php');
+require_once(DOKU_PLUGIN . 'refnotes/config.php');
+require_once(DOKU_PLUGIN . 'refnotes/refnote.php');
+require_once(DOKU_PLUGIN . 'refnotes/reference.php');
+require_once(DOKU_PLUGIN . 'refnotes/note.php');
+require_once(DOKU_PLUGIN . 'refnotes/namespace.php');
+require_once(DOKU_PLUGIN . 'refnotes/scope.php');
+require_once(DOKU_PLUGIN . 'refnotes/rendering.php');
+require_once(DOKU_PLUGIN . 'refnotes/database.php');
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_parser_core {
+
+ private static $instance = NULL;
+
+ private $context;
+ private $lexer;
+ private $handler;
+
+ /**
+ *
+ */
+ public static function getInstance() {
+ if (self::$instance == NULL) {
+ self::$instance = new refnotes_parser_core();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ /* Default context. Should never be used, but just in case... */
+ $this->context = array(new refnotes_parsing_context());
+ $this->lexer = NULL;
+ $this->handler = NULL;
+ }
+
+ /**
+ *
+ */
+ public function registerLexer($lexer) {
+ $this->lexer = $lexer;
+ }
+
+ /**
+ *
+ */
+ public function enterParsingContext() {
+ $this->context[] = new refnotes_parsing_context();
+ }
+
+ /**
+ *
+ */
+ public function exitParsingContext($handler) {
+ $this->handler = $handler;
+
+ unset($this->context[count($this->context) - 1]);
+ }
+
+ /**
+ *
+ */
+ public function getInstructions($text) {
+ $this->callWriter = new refnotes_nested_call_writer($this->handler->getCallWriter(), $this->handler);
+
+ $this->callWriter->connect();
+ $this->lexer->parse($text);
+ $this->callWriter->disconnect();
+
+ return $this->callWriter->calls;
+ }
+
+ /**
+ *
+ */
+ private function getCurrentContext() {
+ return end($this->context);
+ }
+
+ /**
+ *
+ */
+ public function canHandle($state) {
+ return $this->getCurrentContext()->canHandle($state);
+ }
+
+ /**
+ *
+ */
+ public function enterReference($name, $data) {
+ $this->getCurrentContext()->enterReference($name, $data);
+ }
+
+ /**
+ *
+ */
+ public function exitReference() {
+ return $this->getCurrentContext()->exitReference();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_parsing_context {
+
+ private $handling;
+ private $reference;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->reset();
+ }
+
+ /**
+ *
+ */
+ private function reset() {
+ $this->handling = false;
+ $this->reference = NULL;
+ }
+
+ /**
+ *
+ */
+ public function canHandle($state) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $result = !$this->handling;
+ break;
+
+ case DOKU_LEXER_EXIT:
+ $result = $this->handling;
+ break;
+
+ default:
+ $result = false;
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function enterReference($name, $data) {
+ $this->handling = true;
+ $this->reference = new refnotes_parser_reference($name, $data);
+ }
+
+ /**
+ *
+ */
+ public function exitReference() {
+ $reference = $this->reference;
+
+ $this->reset();
+
+ return $reference;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+abstract class refnotes_core {
+
+ protected $presetStyle;
+ protected $namespace;
+ protected $mapping;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->presetStyle = refnotes_configuration::load('namespaces');
+ $this->namespace = array();
+ $this->mapping = array();
+ }
+
+ /**
+ *
+ */
+ public function getNamespaceCount() {
+ return count($this->namespace);
+ }
+
+ /**
+ * Returns a namespace given it's name. The namespace is created if it doesn't exist yet.
+ */
+ public function getNamespace($name) {
+ $result = $this->findNamespace($name);
+
+ if ($result == NULL) {
+ $result = $this->createNamespace($name);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Finds a namespace given it's name
+ */
+ protected function findNamespace($name) {
+ $result = NULL;
+
+ if (array_key_exists($name, $this->namespace)) {
+ $result = $this->namespace[$name];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Finds a namespace or it's parent
+ */
+ public function findParentNamespace($name) {
+ while (($name != '') && !array_key_exists($name, $this->namespace)) {
+ $name = refnotes_namespace::getParentName($name);
+ }
+
+ return ($name != '') ? $this->namespace[$name] : NULL;
+ }
+
+ /**
+ *
+ */
+ public function styleNamespace($namespaceName, $style) {
+ $namespace = $this->getNamespace($namespaceName);
+
+ if (array_key_exists('inherit', $style)) {
+ $source = $this->getNamespace($style['inherit']);
+ $namespace->inheritStyle($source);
+ }
+
+ $namespace->setStyle($style);
+ }
+
+ /**
+ *
+ */
+ public function setNamespaceMapping($namespaceName, $map) {
+ foreach ($map as $ns) {
+ $this->mapping[$ns] = $namespaceName;
+ }
+ }
+
+ /**
+ *
+ */
+ protected function clearNamespaceMapping($namespaceName) {
+ $this->mapping = array_diff($this->mapping, array($namespaceName));
+ }
+
+ /**
+ *
+ */
+ protected function createNamespace($name) {
+ if ($name != ':') {
+ $parentName = refnotes_namespace::getParentName($name);
+ $parent = $this->getNamespace($parentName);
+ $this->namespace[$name] = new refnotes_namespace($name, $parent);
+ }
+ else {
+ $this->namespace[$name] = new refnotes_namespace($name);
+ }
+
+ if (array_key_exists($name, $this->presetStyle)) {
+ $this->namespace[$name]->setStyle($this->presetStyle[$name]);
+ }
+
+ return $this->namespace[$name];
+ }
+
+ /**
+ *
+ */
+ protected function getNote($namespaceName, $noteName) {
+ $scope = $this->getNamespace($namespaceName)->getActiveScope();
+ $note = $scope->findNote($namespaceName, $noteName);
+
+ if (($note == NULL) && array_key_exists($namespaceName, $this->mapping)) {
+ $scope = $this->getNamespace($this->mapping[$namespaceName])->getActiveScope();
+ $note = $scope->findNote($namespaceName, $noteName);
+ }
+
+ if ($note == NULL) {
+ if (!is_int($noteName)) {
+ $note = $this->createNote($scope, $namespaceName, $noteName);
+
+ $scope->addNote($note);
+ }
+ else {
+ $note = new refnotes_note_mock();
+ }
+ }
+
+ return $note;
+ }
+
+ /**
+ *
+ */
+ abstract protected function createNote($scope, $namespaceName, $noteName);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_renderer_core extends refnotes_core {
+
+ private static $instance = NULL;
+
+ /**
+ * Renderer core is used by both references and notes syntax plugins during the rendering
+ * stage. The instance has to be shared between the plugins, and since there should be no
+ * more than one rendering pass during a DW page request, a single instance of the syntax
+ * core should be enough.
+ */
+ public static function getInstance() {
+ if (self::$instance == NULL) {
+ self::$instance = new refnotes_renderer_core();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ *
+ */
+ public function addReference($attributes, $data) {
+ $note = $this->getNote($attributes['ns'], $attributes['name']);
+ $reference = new refnotes_renderer_reference($note, $attributes, $data);
+
+ $note->addReference($reference);
+
+ return $reference;
+ }
+
+ /**
+ *
+ */
+ public function renderNotes($mode, $namespaceName, $limit) {
+ $this->clearNamespaceMapping($namespaceName);
+
+ $html = '';
+
+ if ($namespaceName == '*') {
+ foreach ($this->namespace as $namespace) {
+ $html .= $namespace->renderNotes($mode);
+ }
+ }
+ else {
+ $namespace = $this->findNamespace($namespaceName);
+ if ($namespace != NULL) {
+ $html = $namespace->renderNotes($mode, $limit);
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function createNote($scope, $namespaceName, $noteName) {
+ return new refnotes_renderer_note($scope, $namespaceName, $noteName);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_action_core extends refnotes_core {
+
+ private $styleStash;
+ private $mappingStash;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->styleStash = new refnotes_namespace_style_stash($this);
+ $this->mappingStash = new refnotes_namespace_mapping_stash();
+ }
+
+ /**
+ *
+ */
+ public function markScopeStart($namespaceName, $callIndex) {
+ $this->getNamespace($namespaceName)->markScopeStart($callIndex);
+ }
+
+ /**
+ *
+ */
+ public function markScopeEnd($namespaceName, $callIndex) {
+ $this->getNamespace($namespaceName)->markScopeEnd($callIndex);
+ }
+
+ /**
+ * Collect styling information from the page
+ */
+ public function addStyle($namespaceName, $style) {
+ $this->styleStash->add($this->getNamespace($namespaceName), $style);
+ }
+
+ /**
+ *
+ */
+ public function getStyles() {
+ return $this->styleStash;
+ }
+
+ /**
+ * Collect mapping information from the page
+ */
+ public function addMapping($namespaceName, $map) {
+ $this->mappingStash->add($this->getNamespace($namespaceName), $map);
+ }
+
+ /**
+ *
+ */
+ public function getMappings() {
+ return $this->mappingStash;
+ }
+
+ /**
+ *
+ */
+ public function reset() {
+ $this->namespace = array();
+ }
+
+ /**
+ *
+ */
+ public function addReference($attributes, $data, $call) {
+ $note = $this->getNote($attributes['ns'], $attributes['name']);
+ $reference = new refnotes_action_reference($note, $attributes, $data, $call);
+
+ $note->addReference($reference);
+
+ return $reference;
+ }
+
+ /**
+ *
+ */
+ public function rewriteReferences($namespaceName, $limit) {
+ $this->clearNamespaceMapping($namespaceName);
+
+ if ($namespaceName == '*') {
+ foreach ($this->namespace as $namespace) {
+ $namespace->rewriteReferences();
+ }
+ }
+ else {
+ $namespace = $this->findNamespace($namespaceName);
+ if ($namespace != NULL) {
+ $namespace->rewriteReferences($limit);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ protected function createNote($scope, $namespaceName, $noteName) {
+ return new refnotes_action_note($scope, $namespaceName, $noteName);
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/database.php b/platform/www/lib/plugins/refnotes/database.php
new file mode 100644
index 0000000..47388a2
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/database.php
@@ -0,0 +1,563 @@
+<?php
+
+/**
+ * Plugin RefNotes: Reference database
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_reference_database {
+
+ private static $instance = NULL;
+
+ private $note;
+ private $key;
+ private $page;
+ private $namespace;
+ private $enabled;
+
+ /**
+ *
+ */
+ public static function getInstance() {
+ if (self::$instance == NULL) {
+ self::$instance = new refnotes_reference_database();
+
+ /* Loading has to be separated from construction to prevent infinite recursion */
+ self::$instance->load();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->page = array();
+ $this->namespace = array();
+ $this->enabled = true;
+ }
+
+ /**
+ *
+ */
+ private function load() {
+ $this->loadNotesFromConfiguration();
+
+ if (refnotes_configuration::getSetting('reference-db-enable')) {
+ $this->loadKeys();
+ $this->loadPages();
+ $this->loadNamespaces();
+ }
+ }
+
+ /**
+ *
+ */
+ private function loadNotesFromConfiguration() {
+ $note = refnotes_configuration::load('notes');
+
+ foreach ($note as $name => $data) {
+ $this->note[$name] = new refnotes_reference_database_note('{configuration}', $data);
+ }
+ }
+
+ /**
+ *
+ */
+ private function loadKeys() {
+ $locale = refnotes_localization::getInstance();
+ foreach ($locale->getByPrefix('dbk') as $key => $text) {
+ $this->key[$this->normalizeKeyText($text)] = $key;
+ }
+ }
+
+ /**
+ *
+ */
+ public function getKey($text) {
+ $result = '';
+ $text = $this->normalizeKeyText($text);
+
+ if (in_array($text, $this->key)) {
+ $result = $text;
+ }
+ elseif (array_key_exists($text, $this->key)) {
+ $result = $this->key[$text];
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ private function normalizeKeyText($text) {
+ return preg_replace('/\s+/', ' ', utf8_strtolower(trim($text)));
+ }
+
+ /**
+ *
+ */
+ private function loadPages() {
+ global $conf;
+
+ if (file_exists($conf['indexdir'] . '/page.idx')) {
+ require_once(DOKU_INC . 'inc/indexer.php');
+
+ $pageIndex = idx_getIndex('page', '');
+ $namespace = refnotes_configuration::getSetting('reference-db-namespace');
+ $namespacePattern = '/^' . trim($namespace, ':') . ':/';
+ $cache = new refnotes_reference_database_cache();
+
+ foreach ($pageIndex as $pageId) {
+ $pageId = trim($pageId);
+
+ if ((preg_match($namespacePattern, $pageId) == 1) && file_exists(wikiFN($pageId))) {
+ $this->enabled = false;
+ $this->page[$pageId] = new refnotes_reference_database_page($this, $cache, $pageId);
+ $this->enabled = true;
+ }
+ }
+
+ $cache->save();
+ }
+ }
+
+ /**
+ *
+ */
+ private function loadNamespaces() {
+ foreach ($this->page as $pageId => $page) {
+ foreach ($page->getNamespaces() as $ns) {
+ $this->namespace[$ns][] = $pageId;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public function findNote($name) {
+ if (!$this->enabled) {
+ return NULL;
+ }
+
+ $found = array_key_exists($name, $this->note);
+
+ if (!$found) {
+ list($namespace, $temp) = refnotes_namespace::parseName($name);
+
+ if (array_key_exists($namespace, $this->namespace)) {
+ $this->loadNamespaceNotes($namespace);
+
+ $found = array_key_exists($name, $this->note);
+ }
+ }
+
+ return $found ? $this->note[$name] : NULL;
+ }
+
+ /**
+ *
+ */
+ private function loadNamespaceNotes($namespace) {
+ foreach ($this->namespace[$namespace] as $pageId) {
+ if (array_key_exists($pageId, $this->page)) {
+ $this->enabled = false;
+ $this->note = array_merge($this->note, $this->page[$pageId]->getNotes());
+ $this->enabled = true;
+
+ unset($this->page[$pageId]);
+ }
+ }
+
+ unset($this->namespace[$namespace]);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_reference_database_page {
+
+ private $database;
+ private $id;
+ private $fileName;
+ private $namespace;
+ private $note;
+
+ /**
+ * Constructor
+ */
+ public function __construct($database, $cache, $id) {
+ $this->database = $database;
+ $this->id = $id;
+ $this->fileName = wikiFN($id);
+ $this->namespace = array();
+ $this->note = array();
+
+ if ($cache->isCached($this->fileName)) {
+ $this->namespace = $cache->getNamespaces($this->fileName);
+ }
+ else {
+ $this->parse();
+
+ $cache->update($this->fileName, $this->namespace);
+ }
+ }
+
+ /**
+ *
+ */
+ private function parse() {
+ $text = io_readWikiPage($this->fileName, $this->id);
+ $call = p_cached_instructions($this->fileName);
+ $calls = count($call);
+
+ for ($c = 0; $c < $calls; $c++) {
+ if ($call[$c][0] == 'table_open') {
+ $c = $this->parseTable($call, $calls, $c, $text);
+ }
+ elseif ($call[$c][0] == 'code') {
+ $this->parseCode($call[$c]);
+ }
+ elseif (($call[$c][0] == 'plugin') && ($call[$c][1][0] == 'data_entry')) {
+ $this->parseDataEntry($call[$c][1][1]);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseTable($call, $calls, $c, $text) {
+ $row = 0;
+ $column = 0;
+ $columns = 0;
+ $valid = true;
+
+ for ( ; $c < $calls; $c++) {
+ switch ($call[$c][0]) {
+ case 'tablerow_open':
+ $column = 0;
+ break;
+
+ case 'tablerow_close':
+ if ($row == 0) {
+ $columns = $column;
+ }
+ else {
+ if ($column != $columns) {
+ $valid = false;
+ break 2;
+ }
+ }
+ $row++;
+ break;
+
+ case 'tablecell_open':
+ case 'tableheader_open':
+ $cellOpen = $call[$c][2];
+ break;
+
+ case 'tablecell_close':
+ case 'tableheader_close':
+ $table[$row][$column] = trim(substr($text, $cellOpen, $call[$c][2] - $cellOpen), "^| ");
+ $column++;
+ break;
+
+ case 'table_close':
+ break 2;
+ }
+ }
+
+ if ($valid && ($row > 1) && ($columns > 1)) {
+ $this->handleTable($table, $columns, $row);
+ }
+
+ return $c;
+ }
+
+ /**
+ *
+ */
+ private function handleTable($table, $columns, $rows) {
+ $key = array();
+ for ($c = 0; $c < $columns; $c++) {
+ $key[$c] = $this->database->getKey($table[0][$c]);
+ }
+
+ if (!in_array('', $key)) {
+ $this->handleDataSheet($table, $columns, $rows, $key);
+ }
+ else {
+ if ($columns == 2) {
+ $key = array();
+ for ($r = 0; $r < $rows; $r++) {
+ $key[$r] = $this->database->getKey($table[$r][0]);
+ }
+
+ if (!in_array('', $key)) {
+ $this->handleDataCard($table, $rows, $key);
+ }
+ }
+ }
+ }
+
+ /**
+ * The data is organized in rows, one note per row. The first row contains the caption.
+ */
+ private function handleDataSheet($table, $columns, $rows, $key) {
+ for ($r = 1; $r < $rows; $r++) {
+ $data = array();
+
+ for ($c = 0; $c < $columns; $c++) {
+ $data[$key[$c]] = $table[$r][$c];
+ }
+
+ $this->handleNote($data);
+ }
+ }
+
+ /**
+ * Every note is stored in a separate table. The first column of the table contains
+ * the caption, the second one contains the data.
+ */
+ private function handleDataCard($table, $rows, $key) {
+ $data = array();
+
+ for ($r = 0; $r < $rows; $r++) {
+ $data[$key[$r]] = $table[$r][1];
+ }
+
+ $this->handleNote($data);
+ }
+
+ /**
+ *
+ */
+ private function parseCode($call) {
+ switch ($call[1][1]) {
+ case 'bibtex':
+ $this->parseBibtex($call[1][0]);
+ break;
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseBibtex($text) {
+ foreach (refnotes_bibtex_parser::getInstance()->parse($text) as $data) {
+ $this->handleNote($data);
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseDataEntry($pluginData) {
+ if (preg_match('/\brefnotes\b/', $pluginData['classes'])) {
+ $data = array();
+
+ foreach ($pluginData['data'] as $key => $value) {
+ if (is_array($value)) {
+ $data[$key . 's'] = implode(', ', $value);
+ }
+ else {
+ $data[$key] = $value;
+ }
+ }
+
+ $this->handleNote($data);
+ }
+ }
+
+ /**
+ *
+ */
+ private function handleNote($data) {
+ $note = new refnotes_reference_database_note($this->id, $data);
+
+ list($namespace, $name) = $note->getNameParts();
+
+ if ($name != '') {
+ if (!in_array($namespace, $this->namespace)) {
+ $this->namespace[] = $namespace;
+ }
+
+ $this->note[$namespace . $name] = $note;
+ }
+ }
+
+ /**
+ *
+ */
+ public function getNamespaces() {
+ return $this->namespace;
+ }
+
+ /**
+ *
+ */
+ public function getNotes() {
+ if (empty($this->note)) {
+ $this->parse();
+ }
+
+ return $this->note;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_reference_database_note extends refnotes_refnote {
+
+ private $nameParts;
+
+ /**
+ * Constructor
+ */
+ public function __construct($source, $data) {
+ parent::__construct();
+
+ $this->nameParts = array('', '');
+
+ if ($source == '{configuration}') {
+ $this->initializeConfigNote($data);
+ }
+ else {
+ $this->initializePageNote($data);
+ }
+
+ $this->attributes['source'] = $source;
+ }
+
+ /**
+ *
+ */
+ public function initializeConfigNote($data) {
+ $this->data['note-text'] = $data['text'];
+
+ unset($data['text']);
+
+ $this->attributes = $data;
+ }
+
+ /**
+ *
+ */
+ public function initializePageNote($data) {
+ if (isset($data['note-name'])) {
+ if (preg_match('/^' . refnotes_note::getNamePattern('full-extended') . '$/', $data['note-name']) == 1) {
+ $this->nameParts = refnotes_namespace::parseName($data['note-name']);
+ }
+
+ unset($data['note-name']);
+ }
+
+ $this->data = $data;
+ }
+
+ /**
+ *
+ */
+ public function getNameParts() {
+ return $this->nameParts;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_reference_database_cache {
+
+ private $fileName;
+ private $cache;
+ private $requested;
+ private $updated;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ global $conf;
+
+ $this->fileName = $conf['cachedir'] . '/refnotes.database.dat';
+
+ $this->load();
+ }
+
+ /**
+ *
+ */
+ private function load() {
+ $this->cache = array();
+ $this->requested = array();
+
+ if (file_exists($this->fileName)) {
+ $this->cache = unserialize(io_readFile($this->fileName, false));
+ }
+
+ foreach (array_keys($this->cache) as $fileName) {
+ $this->requested[$fileName] = false;
+ }
+
+ $this->updated = false;
+ }
+
+ /**
+ *
+ */
+ public function isCached($fileName) {
+ $result = false;
+
+ if (array_key_exists($fileName, $this->cache)) {
+ if ($this->cache[$fileName]['time'] == @filemtime($fileName)) {
+ $result = true;
+ }
+ }
+
+ $this->requested[$fileName] = true;
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function getNamespaces($fileName) {
+ return $this->cache[$fileName]['ns'];
+ }
+
+ /**
+ *
+ */
+ public function update($fileName, $namespace) {
+ $this->cache[$fileName] = array('ns' => $namespace, 'time' => @filemtime($fileName));
+ $this->updated = true;
+ }
+
+ /**
+ *
+ */
+ public function save() {
+ $this->removeOldPages();
+
+ if ($this->updated) {
+ io_saveFile($this->fileName, serialize($this->cache));
+ }
+ }
+
+ /**
+ *
+ */
+ private function removeOldPages() {
+ foreach ($this->requested as $fileName => $requested) {
+ if (!$requested && array_key_exists($fileName, $this->cache)) {
+ unset($this->cache[$fileName]);
+
+ $this->updated = true;
+ }
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/instructions.php b/platform/www/lib/plugins/refnotes/instructions.php
new file mode 100644
index 0000000..7711a27
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/instructions.php
@@ -0,0 +1,318 @@
+<?php
+
+/**
+ * Plugin RefNotes: Handling of instruction array
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_instruction {
+
+ protected $data;
+
+ /**
+ * Constructor
+ */
+ public function __construct($name, $data, $offset = -1) {
+ $this->data = array($name, $data, $offset);
+ }
+
+ /**
+ *
+ */
+ public function getData() {
+ return $this->data;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_nest_instruction extends refnotes_instruction {
+
+ /**
+ * Constructor
+ */
+ public function __construct($data) {
+ parent::__construct('nest', array($data));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_plugin_instruction extends refnotes_instruction {
+
+ /**
+ * Constructor
+ */
+ public function __construct($name, $data, $type, $text, $offset = -1) {
+ parent::__construct('plugin', array($name, $data, $type, $text), $offset);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_notes_instruction extends refnotes_plugin_instruction {
+
+ /**
+ * Constructor
+ */
+ public function __construct($type, $attributes, $data = NULL) {
+ $pluginData[0] = $type;
+ $pluginData[1] = $attributes;
+
+ if (!empty($data)) {
+ $pluginData[2] = $data;
+ }
+
+ parent::__construct('refnotes_notes', $pluginData, DOKU_LEXER_SPECIAL, '');
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_notes_style_instruction extends refnotes_notes_instruction {
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace, $data) {
+ parent::__construct('style', array('ns' => $namespace), $data);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_notes_map_instruction extends refnotes_notes_instruction {
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace, $data) {
+ parent::__construct('map', array('ns' => $namespace), $data);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_notes_render_instruction extends refnotes_notes_instruction {
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace) {
+ parent::__construct('render', array('ns' => $namespace));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_instruction_reference {
+
+ private $list;
+ private $data;
+ private $index;
+ private $name;
+
+ /**
+ * Constructor
+ */
+ public function __construct($list, &$data, $index) {
+ $this->list = $list;
+ $this->data =& $data;
+ $this->index = $index;
+ $this->name = ($data[0] == 'plugin') ? 'plugin_' . $data[1][0] : $data[0];
+ }
+
+ /**
+ *
+ */
+ public function getIndex() {
+ return $this->index;
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ *
+ */
+ public function getData($index) {
+ return $this->data[1][$index];
+ }
+
+ /**
+ *
+ */
+ public function getPluginData($index) {
+ return $this->data[1][1][$index];
+ }
+
+ /**
+ *
+ */
+ public function setPluginData($index, $data) {
+ $this->data[1][1][$index] = $data;
+ }
+
+ /**
+ *
+ */
+ public function unsetPluginData($index) {
+ unset($this->data[1][1][$index]);
+ }
+
+ /**
+ *
+ */
+ public function getRefnotesAttribute($name) {
+ return array_key_exists($name, $this->data[1][1][1]) ? $this->data[1][1][1][$name] : '';
+ }
+
+ /**
+ *
+ */
+ public function setRefnotesAttribute($name, $value) {
+ $this->data[1][1][1][$name] = $value;
+ }
+
+ /**
+ *
+ */
+ public function unsetRefnotesAttribute($name) {
+ unset($this->data[1][1][1][$name]);
+ }
+
+ /**
+ *
+ */
+ public function getPrevious() {
+ return $this->list->getAt($this->index - 1);
+ }
+
+ /**
+ *
+ */
+ public function insertBefore($call) {
+ return $this->list->insert($this->index, $call);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_instruction_list implements Iterator {
+
+ private $event;
+ private $index;
+ private $extraCalls;
+
+ /**
+ * Constructor
+ */
+ public function __construct($event) {
+ $this->event = $event;
+ $this->index = 0;
+ $this->extraCalls = array();
+ }
+
+ /**
+ * Implementation of Iterator interface
+ */
+ public function rewind() {
+ $this->index = 0;
+ }
+
+ /**
+ * Implementation of Iterator interface
+ */
+ public function current() {
+ return new refnotes_instruction_reference($this, $this->event->data->calls[$this->index], $this->index);
+ }
+
+ /**
+ * Implementation of Iterator interface
+ */
+ public function key() {
+ return $this->index;
+ }
+
+ /**
+ * Implementation of Iterator interface
+ */
+ public function next() {
+ ++$this->index;
+ }
+
+ /**
+ * Implementation of Iterator interface
+ */
+ public function valid() {
+ return array_key_exists($this->index, $this->event->data->calls);
+ }
+
+ /**
+ *
+ */
+ public function getAt($index) {
+ return new refnotes_instruction_reference($this, $this->event->data->calls[$index], $index);
+ }
+
+ /**
+ *
+ */
+ public function insert($index, $call) {
+ $this->extraCalls[$index][] = $call;
+ }
+
+ /**
+ *
+ */
+ public function append($call) {
+ $this->extraCalls[count($this->event->data->calls)][] = $call;
+ }
+
+ /**
+ *
+ */
+ public function applyChanges() {
+ if (empty($this->extraCalls)) {
+ return;
+ }
+
+ ksort($this->extraCalls);
+
+ $calls = array();
+ $prevIndex = 0;
+
+ foreach ($this->extraCalls as $index => $extraCalls) {
+ if ($prevIndex < $index) {
+ $slice = array_slice($this->event->data->calls, $prevIndex, $index - $prevIndex);
+ $calls = array_merge($calls, $slice);
+ }
+
+ foreach ($extraCalls as $call) {
+ $calls[] = $call->getData();
+ }
+
+ $prevIndex = $index;
+ }
+
+ $callCount = count($this->event->data->calls);
+
+ if ($prevIndex < $callCount) {
+ $slice = array_slice($this->event->data->calls, $prevIndex, $callCount - $prevIndex);
+ $calls = array_merge($calls, $slice);
+ }
+
+ $offset = $this->event->data->calls[$callCount - 1][2];
+
+ for ($i = count($calls) - 1; $i >= 0; --$i) {
+ if ($calls[$i][2] == -1) {
+ $calls[$i][2] = $offset;
+ }
+ else {
+ $offset = $calls[$i][2];
+ }
+ }
+
+ $this->event->data->calls = $calls;
+ $this->extraCalls = array();
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/lang/en/__template.txt b/platform/www/lib/plugins/refnotes/lang/en/__template.txt
new file mode 100644
index 0000000..2022a92
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/lang/en/__template.txt
@@ -0,0 +1,82 @@
+====== References ======
+
+On this page you can define commonly used notes for [[doku>plugin:refnotes|RefNotes plugin]]. Every note is defined as collection of data //fields//. There are number of ways to organize the note definitions:
+
+ * Group a number of notes into single data table (//sheet//). The first row of the table is used to specify which field the corresponding column contains.
+ * Use separate table for each note (//card//). The table should have two columns where the first column is used to specify the field names.
+ * Define notes using [[wp>BibTeX]] syntax. BibTeX entries have to be wrapped into a ''<code>'' section. The key of BibTeX entry serves as name of the note. The namespace can be either specified as part of the key or in a separate comment (see example below) for all entries that follow.
+ * Store one note per page as [[doku>plugin:data|Data plugin]] entry. This way the notes are stored in a database, which allows to make queries against the bibliography data, for example, see all books of a certain author. Unfortunately Data plugin allows only one ''dataentry'' section per page.
+
+For sheets and cards plugin does not make a distinction between normal table cells and header cells. The field name cells are identified only based on their content. The names are case insensitive and can also be specified using locale-specific labels. BibTeX and ''dataentry'' sections support only field names. The full list of field names is provided in [[doku>plugin:refnotes:refdb|reference database documentation]].
+
+
+===== Note sheet example =====
+
+^ Note name ^ Note text ^
+^ :ref:sample1 | A sample reference. |
+^ :ref:sample2 | A sample reference with //some// **formatting**. |
+^ :ref:sample3 | A sample reference with a [[http://www.dokuwiki.org/|link.]] |
+
+
+===== Note card example =====
+
+^ Note name ^ :ref:knuth-aop-2 ^
+^ Author | Donald Knuth |
+^ Title | The Art of Computer Programming, Volume 2: Seminumerical Algorithms |
+^ Edition | Third Edition |
+^ Published | 1997 |
+^ Publisher | Addison-Wesley |
+^ Pages | xiv + 762 pp. |
+^ ISBN | 0-201-89684-2 |
+^ URL | http://en.wikipedia.org/wiki/The_Art_of_Computer_Programming |
+
+
+===== BibTeX example =====
+
+<code bibtex>
+@Comment{refnotes,
+ namespace = "ref:prog"
+}
+
+@Book{GangOfFour,
+ author = "Erich {Gamma} and Richard {Helm} and Ralph {Johnson} and John {Vlissides}",
+ author-ref = "Gamma, et al.",
+ title = "Design Patterns: Elements of Reusable Object-Oriented Software",
+ publisher = "Addison-Wesley",
+ year = 1994,
+ address = "Reading, Mass.",
+ pages = 395,
+ isbn = "0-201-63361-2",
+ url = "http://en.wikipedia.org/wiki/Design_Patterns"
+}
+
+@Article{:ref:Knuth-LCE-1985,
+ author = "Donald Knuth",
+ title = "Deciphering a linear congruential encryption",
+ journal = "IEEE Transactions on Information Theory",
+ volume = "31(1)",
+ year = 1985,
+ month = "Jan",
+ publisher = "IEEE",
+ pages = "49-52",
+ issn = "0018-9448",
+ url = "http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=1056997"
+}
+</code>
+
+===== Dataentry example =====
+
+The dataentry below is wrapped into ''<code>'' section to be properly presented in absence of [[doku>plugin:data|Data plugin]].
+
+<code>
+---- dataentry refnotes ----
+note-name : :ref:prog:Hunt&Thomas(1999)
+authors : Andrew Hunt, David Thomas
+title : The Pragmatic Programmer: From Journeyman to Master
+published : 1999
+publisher : Addison-Wesley Professional
+pages : 352
+isbn : 0-201-61622-X
+url : http://en.wikipedia.org/wiki/The_Pragmatic_Programmer
+----
+</code>
diff --git a/platform/www/lib/plugins/refnotes/lang/en/intro.txt b/platform/www/lib/plugins/refnotes/lang/en/intro.txt
new file mode 100644
index 0000000..42f5829
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/lang/en/intro.txt
@@ -0,0 +1,15 @@
+====== RefNotes Configuration ======
+
+On this page you modify the configuration settings used by [[doku>plugin:refnotes|RefNotes plugin]]. There are three sections that group related settings:
+
+ * **General settings**
+ * //Use footnotes syntax// --- if enabled the RefNotes plugin will be used to handle native DokuWiki footnotes along with it's own syntax.
+ * //Enable reference database// --- if enabled the plugin will load predefined notes form the [[doku>plugin:refnotes:refdb|reference database]].
+ * //Reference database namespace// --- DokuWiki namespace for the reference database.
+ * **Namespaces** --- in this section you can specify rendering style for predefined namespaces. For a detailed explanation of each style see the [[doku>plugin:refnotes:style|style reference]].
+ * **Notes** --- this section is used to predefine commonly used notes.
+ * //Inline// --- specifies if the note text should be [[doku>plugin:refnotes:syntax#inline_notes|inlined]]. For inline notes you can configure if various reference styles are applied or not.
+
+Settings that are shown on a blue background are the default values. In the //Namespaces// section settings that are shown on a green background are inherited from the parent namespace.
+
+Remember to press the **Save** button on the bottom of this page before leaving otherwise your changes will be lost.
diff --git a/platform/www/lib/plugins/refnotes/lang/en/lang.php b/platform/www/lib/plugins/refnotes/lang/en/lang.php
new file mode 100644
index 0000000..4106ce1
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/lang/en/lang.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Plugin RefNotes: English language file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+// Settings must be present and set appropriately for the language
+$lang['encoding'] = 'utf-8';
+$lang['direction'] = 'ltr';
+
+// Reference database keys
+$lang['dbk_author'] = 'Author';
+$lang['dbk_authors'] = 'Authors';
+$lang['dbk_chapter'] = 'Chapter';
+$lang['dbk_edition'] = 'Edition';
+$lang['dbk_isbn'] = 'ISBN';
+$lang['dbk_issn'] = 'ISSN';
+$lang['dbk_journal'] = 'Journal';
+$lang['dbk_month'] = 'Month';
+$lang['dbk_note-name'] = 'Note name';
+$lang['dbk_note-page'] = 'Note page';
+$lang['dbk_note-pages'] = 'Note pages';
+$lang['dbk_note-text'] = 'Note text';
+$lang['dbk_page'] = 'Page';
+$lang['dbk_pages'] = 'Pages';
+$lang['dbk_published'] = 'Published';
+$lang['dbk_publisher'] = 'Publisher';
+$lang['dbk_ref-author'] = 'Reference author';
+$lang['dbk_ref-authors'] = 'Reference authors';
+$lang['dbk_title'] = 'Title';
+$lang['dbk_url'] = 'URL';
+$lang['dbk_volume'] = 'Volume';
+$lang['dbk_year'] = 'Year';
+
+$lang['txt_in_cap'] = 'In';
+$lang['txt_page_abbr'] = 'p.';
+$lang['txt_pages_abbr'] = 'pp.';
+
+// For admin plugins, the menu prompt to be displayed in the admin menu
+// if set here, the plugin doesn't need to override the getMenuText() method
+$lang['menu'] = 'RefNotes Configuration';
+
+$lang['noscript'] = 'Sorry, this page requires JavaScript to function properly. Please enable it or get a decent browser.';
+
+$lang['sec_general'] = 'General settings';
+$lang['sec_namespaces'] = 'Namespaces';
+$lang['sec_notes'] = 'Notes';
+
+$lang['lbl_replace-footnotes'] = 'Use footnotes syntax';
+$lang['lbl_reference-db-enable'] = 'Enable reference database';
+$lang['lbl_reference-db-namespace'] = 'Reference database namespace';
+
+$lang['lbl_refnote-id'] = 'Reference/note identifier style';
+$lang['lbl_reference-base'] = 'Reference baseline';
+$lang['lbl_reference-font-weight'] = 'Reference font weight';
+$lang['lbl_reference-font-style'] = 'Reference font style';
+$lang['lbl_reference-format'] = 'Reference formatting';
+$lang['lbl_reference-group'] = 'Reference grouping';
+$lang['lbl_reference-render'] = 'Reference rendering';
+$lang['lbl_multi-ref-id'] = 'Multi-reference identifier';
+$lang['lbl_note-preview'] = 'Note preview';
+$lang['lbl_notes-separator'] = 'Notes block separator';
+$lang['lbl_note-text-align'] = 'Note text alignment';
+$lang['lbl_note-font-size'] = 'Note font size';
+$lang['lbl_note-render'] = 'Note rendering';
+$lang['lbl_note-id-base'] = 'Note identifier baseline';
+$lang['lbl_note-id-font-weight'] = 'Note identifier font weight';
+$lang['lbl_note-id-font-style'] = 'Note identifier font style';
+$lang['lbl_note-id-format'] = 'Note identifier formatting';
+$lang['lbl_back-ref-caret'] = 'Back reference circumflex';
+$lang['lbl_back-ref-base'] = 'Back reference base line';
+$lang['lbl_back-ref-font-weight'] = 'Back reference font weight';
+$lang['lbl_back-ref-font-style'] = 'Back reference font style';
+$lang['lbl_back-ref-format'] = 'Back reference formatting';
+$lang['lbl_back-ref-separator'] = 'Back reference separator';
+$lang['lbl_scoping'] = 'Scoping behavior';
+
+$lang['lbl_inline'] = 'Inline';
+$lang['lbl_use-reference-base'] = 'Apply reference baseline';
+$lang['lbl_use-reference-font-weight'] = 'Apply reference font weight';
+$lang['lbl_use-reference-font-style'] = 'Apply reference font style';
+$lang['lbl_use-reference-format'] = 'Apply reference formatting';
+
+$lang['opt_arrow'] = 'Up arrow';
+$lang['opt_basic'] = 'Plain text';
+$lang['opt_bold'] = 'Bold';
+$lang['opt_brackets'] = 'Brackets';
+$lang['opt_caret'] = 'Circumflex';
+$lang['opt_comma'] = 'Comma';
+$lang['opt_dot'] = 'Dot';
+$lang['opt_group-comma'] = 'Separate with comma';
+$lang['opt_group-none'] = 'Don\'t group';
+$lang['opt_group-semicolon'] = 'Separate with semicolon';
+$lang['opt_harvard'] = 'Harvard system of referencing';
+$lang['opt_inherit'] = 'Inherit';
+$lang['opt_italic'] = 'Italic';
+$lang['opt_justify'] = 'Justify';
+$lang['opt_latin'] = 'Latin';
+$lang['opt_latin-lower'] = 'Latin lower case';
+$lang['opt_latin-upper'] = 'Latin upper case';
+$lang['opt_left'] = 'Left';
+$lang['opt_merge'] = 'Merge with back references';
+$lang['opt_none'] = 'None';
+$lang['opt_normal'] = 'Normal';
+$lang['opt_normal-text'] = 'Normal text';
+$lang['opt_note-counter'] = 'Note counter';
+$lang['opt_note-id'] = 'Note identifier';
+$lang['opt_note-name'] = 'Note name';
+$lang['opt_numeric'] = 'Numeric';
+$lang['opt_parents'] = 'Parentheses';
+$lang['opt_popup'] = 'Static pop-up';
+$lang['opt_prefix'] = 'Prefix back references';
+$lang['opt_ref-counter'] = 'Reference counter';
+$lang['opt_reset'] = 'New scope after each notes block';
+$lang['opt_right-bracket'] = 'Right bracket';
+$lang['opt_right-parent'] = 'Right parenthesis';
+$lang['opt_roman-lower'] = 'Roman lower case';
+$lang['opt_roman-upper'] = 'Roman upper case';
+$lang['opt_single'] = 'Use single scope';
+$lang['opt_small'] = 'Small';
+$lang['opt_stars'] = 'Stars';
+$lang['opt_super'] = 'Superscript';
+$lang['opt_tooltip'] = 'Tooltip';
+
+$lang['btn_add'] = 'Add';
+$lang['btn_rename'] = 'Rename';
+$lang['btn_delete'] = 'Delete';
+$lang['btn_save'] = 'Save';
+
+$lang['js_status'] = 'Server communication status.';
+$lang['js_loading'] = 'Loading configuration settings from the server...';
+$lang['js_loaded'] = 'Configuration settings are successfully loaded from the server.';
+$lang['js_loading_failed'] = 'Failed to load configuration settings from the server ({1}).';
+$lang['js_invalid_data'] = 'Configuration settings loaded from the server are invalid or corrupted ({1}).';
+$lang['js_saving'] = 'Saving configuration settings on the server...';
+$lang['js_saved'] = 'Configuration settings are successfully saved on the server.';
+$lang['js_saving_failed'] = 'Failed to save configuration settings on the server ({1}).';
+$lang['js_invalid_ns_name'] = 'The specified namespace name is invalid.';
+$lang['js_ns_name_exists'] = 'The {1} namespace already exists.';
+$lang['js_delete_ns'] = 'Are you sure you want to delete {1} namespace?';
+$lang['js_invalid_note_name'] = 'The specified note name is invalid.';
+$lang['js_note_name_exists'] = 'The {1} note already exists.';
+$lang['js_delete_note'] = 'Are you sure you want to delete {1} note?';
+$lang['js_unsaved'] = 'Your changes to the configuration settings are not saved.';
diff --git a/platform/www/lib/plugins/refnotes/locale.php b/platform/www/lib/plugins/refnotes/locale.php
new file mode 100644
index 0000000..8950621
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/locale.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Plugin RefNotes: Localization
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+/**
+ * Plugins that rely on refnotes_localization should use this trait.
+ */
+trait refnotes_localization_plugin {
+ /**
+ *
+ */
+ public function getRawLang() {
+ return $this->lang;
+ }
+}
+
+class refnotes_localization {
+
+ private static $instance = NULL;
+
+ private $plugin;
+
+ /**
+ *
+ */
+ public static function initialize($plugin) {
+ if (self::$instance == NULL) {
+ self::$instance = new refnotes_localization($plugin);
+ }
+ }
+
+ /**
+ *
+ */
+ public static function getInstance() {
+ if (self::$instance == NULL) {
+ throw new Exception('Shared refnotes_localization instance is not properly initialized.');
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ */
+ private function __construct($plugin) {
+ $this->plugin = $plugin;
+ }
+
+ /**
+ *
+ */
+ public function getLang($id) {
+ return $this->plugin->getLang($id);
+ }
+
+ /**
+ *
+ */
+ public function getFileName($id) {
+ return $this->plugin->localFN($id);
+ }
+
+ /**
+ *
+ */
+ public function getByPrefix($prefix, $strip = true) {
+ $this->plugin->setupLocale();
+
+ if ($strip) {
+ $pattern = '/^' . $prefix . '_(.+)$/';
+ }
+ else {
+ $pattern = '/^(' . $prefix . '_.+)$/';
+ }
+
+ $result = array();
+
+ foreach ($this->plugin->getRawLang() as $key => $value) {
+ if (preg_match($pattern, $key, $match) == 1) {
+ $result[$match[1]] = $value;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/namespace.php b/platform/www/lib/plugins/refnotes/namespace.php
new file mode 100644
index 0000000..c3fa4b5
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/namespace.php
@@ -0,0 +1,463 @@
+<?php
+
+/**
+ * Plugin RefNotes: Namespace heplers
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+abstract class refnotes_namespace_data_stash {
+
+ protected $index;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->index = array();
+ }
+
+ /**
+ *
+ */
+ abstract public function add($namespace, $data);
+
+ /**
+ *
+ */
+ public function getCount() {
+ return count($this->index);
+ }
+
+ /**
+ *
+ */
+ public function getIndex() {
+ return array_keys($this->index);
+ }
+
+ /**
+ *
+ */
+ public function getAt($index) {
+ return array_key_exists($index, $this->index) ? $this->index[$index] : array();
+ }
+
+ /**
+ *
+ */
+ public function sort() {
+ ksort($this->index);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_namespace_data {
+
+ protected $namespace;
+ protected $data;
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace, $data) {
+ $this->namespace = $namespace;
+ $this->data = $data;
+ }
+
+ /**
+ *
+ */
+ public function getNamespace() {
+ return $this->namespace->getName();
+ }
+
+ /**
+ *
+ */
+ public function getData() {
+ return $this->data;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_namespace_style_stash extends refnotes_namespace_data_stash {
+
+ private $page;
+
+ /**
+ * Constructor
+ */
+ public function __construct($page) {
+ parent::__construct();
+
+ $this->page = $page;
+ }
+
+ /**
+ *
+ */
+ public function add($namespace, $data) {
+ $style = new refnotes_namespace_style_info($namespace, $data);
+ $parent = $style->getInheritedNamespace();
+
+ if (($parent == '') && ($namespace->getScopesCount() == 1)) {
+ /* Default inheritance for the first scope */
+ $parent = refnotes_namespace::getParentName($namespace->getName());
+ }
+
+ $index = $namespace->getStyleIndex($this->page->findParentNamespace($parent));
+
+ $this->index[$index][] = $style;
+ }
+ /**
+ * Sort the style blocks so that the namespaces with inherited style go after
+ * the namespaces they inherit from.
+ */
+ public function sort() {
+ parent::sort();
+
+ $this->sortByDefaultInheritance();
+ $this->sortByExplicitInheritance();
+ }
+
+ /**
+ *
+ */
+ private function sortByDefaultInheritance() {
+ foreach ($this->index as &$index) {
+ $namespace = array();
+
+ foreach ($index as $style) {
+ $namespace[] = $style->getNamespace();
+ }
+
+ array_multisort($namespace, SORT_ASC, $index);
+ }
+ }
+
+ /**
+ *
+ */
+ private function sortByExplicitInheritance() {
+ foreach ($this->index as &$index) {
+ $derived = array();
+ $sorted = array();
+
+ foreach ($index as $style) {
+ if ($style->isDerived()) {
+ $derived[] = $style;
+ }
+ else {
+ $sorted[] = $style;
+ }
+ }
+
+ $derivedCount = count($derived);
+
+ if ($derivedCount > 0) {
+ if ($derivedCount == 1) {
+ $sorted[] = $derived[0];
+ }
+ else {
+ /* Perform simplified topological sorting */
+ $target = array();
+ $source = array();
+
+ for ($i = 0; $i < $derivedCount; $i++) {
+ $target[$i] = $derived[$i]->getNamespace();
+ $source[$i] = $derived[$i]->getInheritedNamespace();
+ }
+
+ for ($j = 0; $j < $derivedCount; $j++) {
+ foreach ($source as $i => $s) {
+ if (!in_array($s, $target)) {
+ break;
+ }
+ }
+
+ $sorted[] = $derived[$i];
+
+ unset($target[$i]);
+ unset($source[$i]);
+ }
+ }
+ }
+
+ $index = $sorted;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_namespace_style_info extends refnotes_namespace_data {
+
+ /**
+ *
+ */
+ public function isDerived() {
+ return array_key_exists('inherit', $this->data);
+ }
+
+ /**
+ *
+ */
+ public function getInheritedNamespace() {
+ return $this->isDerived() ? $this->data['inherit'] : '';
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_namespace_mapping_stash extends refnotes_namespace_data_stash {
+
+ /**
+ *
+ */
+ public function add($namespace, $data) {
+ $this->index[$namespace->getMappingIndex()][] = new refnotes_namespace_data($namespace, $data);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_namespace {
+
+ private $name;
+ private $style;
+ private $renderer;
+ private $scope;
+ private $newScope;
+
+ /**
+ *
+ */
+ public static function getNamePattern($type) {
+ $result = '(?:(?:' . refnotes_note::getNamePattern('strict') . ')?:)*';
+
+ if ($type == 'required') {
+ $result .= '(?::|' . refnotes_note::getNamePattern('strict') . '):*';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns canonic name for a namespace
+ */
+ public static function canonizeName($name) {
+ return preg_replace('/:{2,}/', ':', ':' . $name . ':');
+ }
+
+ /**
+ * Returns name of the parent namespace
+ */
+ public static function getParentName($name) {
+ return preg_replace('/\w*:$/', '', $name);
+ }
+
+ /**
+ * Splits full note name into namespace and name components
+ */
+ public static function parseName($name) {
+ $pos = strrpos($name, ':');
+ if ($pos !== false) {
+ $namespace = self::canonizeName(substr($name, 0, $pos));
+ $name = substr($name, $pos + 1);
+ }
+ else {
+ $namespace = ':';
+ }
+
+ return array($namespace, $name);
+ }
+
+ /**
+ * Constructor
+ */
+ public function __construct($name, $parent = NULL) {
+ $this->name = $name;
+ $this->style = array();
+ $this->renderer = NULL;
+ $this->scope = array();
+ $this->newScope = true;
+
+ if ($parent != NULL) {
+ $this->style = $parent->style;
+ }
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ *
+ */
+ public function getScopesCount() {
+ return count($this->scope);
+ }
+
+ /**
+ *
+ */
+ public function inheritStyle($source) {
+ $this->style = $source->style;
+ $this->renderer = NULL;
+ }
+
+ /**
+ *
+ */
+ public function setStyle($style) {
+ $this->style = array_merge($this->style, $style);
+ $this->renderer = NULL;
+ }
+
+ /**
+ *
+ */
+ public function getStyle($name) {
+ return array_key_exists($name, $this->style) ? $this->style[$name] : '';
+ }
+
+ /**
+ * Defer creation of renderer until namespace style is set.
+ */
+ public function getRenderer() {
+ if ($this->renderer == NULL) {
+ $this->renderer = new refnotes_renderer($this);
+ }
+
+ return $this->renderer;
+ }
+
+ /**
+ *
+ */
+ private function getScope($index) {
+ $index = count($this->scope) + $index;
+
+ return ($index >= 0) ? $this->scope[$index] : new refnotes_scope_mock();
+ }
+
+ /**
+ *
+ */
+ private function getPreviousScope() {
+ return $this->getScope(-2);
+ }
+
+ /**
+ *
+ */
+ private function getCurrentScope() {
+ return $this->getScope(-1);
+ }
+
+ /**
+ *
+ */
+ public function getActiveScope() {
+ if ($this->newScope) {
+ $this->scope[] = new refnotes_scope($this, count($this->scope) + 1);
+ $this->newScope = false;
+ }
+
+ return $this->getCurrentScope();
+ }
+
+ /**
+ *
+ */
+ public function markScopeStart($callIndex) {
+ if (!$this->getCurrentScope()->isOpen()) {
+ $this->scope[] = new refnotes_scope(NULL, 0, $callIndex);
+ }
+ }
+
+ /**
+ *
+ */
+ public function markScopeEnd($callIndex) {
+ /* Create an empty scope if there is no open one */
+ $this->markScopeStart($callIndex - 1);
+ $this->getCurrentScope()->getLimits()->end = $callIndex;
+ }
+
+
+ /**
+ * Find last scope end within specified range
+ */
+ private function findScopeEnd($start, $end) {
+ for ($i = count($this->scope) - 1; $i >= 0; $i--) {
+ $scopeEnd = $this->scope[$i]->getLimits()->end;
+
+ if (($scopeEnd > $start) && ($scopeEnd < $end)) {
+ return $scopeEnd;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ *
+ */
+ public function getStyleIndex($parent) {
+ $previousEnd = $this->getPreviousScope()->getLimits()->end;
+ $currentStart = $this->getCurrentScope()->getLimits()->start;
+ $parentEnd = ($parent != NULL) ? $parent->findScopeEnd($previousEnd, $currentStart) : -1;
+
+ return max($parentEnd, $previousEnd) + 1;
+ }
+
+ /**
+ *
+ */
+ public function getMappingIndex() {
+ return $this->getPreviousScope()->getLimits()->end + 1;
+ }
+
+ /**
+ *
+ */
+ public function rewriteReferences($limit = '') {
+ $this->resetScope();
+
+ if (count($this->scope) > 0) {
+ $html = $this->getCurrentScope()->rewriteReferences($limit);
+ }
+ }
+
+ /**
+ *
+ */
+ public function renderNotes($mode, $limit = '') {
+ $this->resetScope();
+ $doc = '';
+
+ if (count($this->scope) > 0) {
+ $doc = $this->getCurrentScope()->renderNotes($mode, $limit);
+ }
+
+ return $doc;
+ }
+
+ /**
+ *
+ */
+ private function resetScope() {
+ switch ($this->getStyle('scoping')) {
+ case 'single':
+ break;
+
+ default:
+ $this->newScope = true;
+ break;
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/note.php b/platform/www/lib/plugins/refnotes/note.php
new file mode 100644
index 0000000..8379168
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/note.php
@@ -0,0 +1,336 @@
+<?php
+
+/**
+ * Plugin RefNotes: Note
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_note_block_iterator extends FilterIterator {
+
+ private $note;
+ private $limit;
+ private $count;
+
+ /**
+ * Constructor
+ */
+ public function __construct($note, $limit) {
+ $this->note = new ArrayObject($note);
+ $this->limit = $this->getBlockLimit($limit);
+ $this->count = 0;
+
+ parent::__construct($this->note->getIterator());
+ }
+
+ /**
+ *
+ */
+ function accept() {
+ $result = $this->current()->isValid();
+
+ if ($result) {
+ ++$this->count;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ function valid() {
+ return parent::valid() && (($this->limit == 0) || ($this->count <= $this->limit));
+ }
+
+ /**
+ *
+ */
+ private function getBlockLimit($limit) {
+ if (preg_match('/(\/?)(\d+)/', $limit, $match) == 1) {
+ if ($match[1] != '') {
+ $devider = intval($match[2]);
+ $result = ceil($this->getValidCount() / $devider);
+ }
+ else {
+ $result = intval($match[2]);
+ }
+ }
+ else {
+ $result = 0;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ private function getValidCount() {
+ $result = 0;
+
+ foreach ($this->note as $note) {
+ if ($note->isValid()) {
+ ++$result;
+ }
+ }
+
+ return $result;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_note_mock {
+
+ /**
+ *
+ */
+ public function getScope() {
+ return new refnotes_scope_mock();
+ }
+
+ /**
+ *
+ */
+ public function setText($text) {
+ }
+
+ /**
+ *
+ */
+ public function addReference($reference) {
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_note extends refnotes_refnote {
+
+ protected $scope;
+ protected $namespaceName;
+ protected $id;
+ protected $name;
+ protected $inline;
+ protected $reference;
+ protected $text;
+ protected $processed;
+
+ /**
+ *
+ */
+ public static function getNamePattern($type) {
+ if (($type == 'full-extended') || ($type == 'extended')) {
+ $result = ($type == 'full-extended') ? refnotes_namespace::getNamePattern('optional') : '';
+ $result .= '[[:alpha:]\d][\w.&\(\)\[\]{}+-]*';
+ }
+ else {
+ $result = '[[:alpha:]]\w*';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Constructor
+ */
+ public function __construct($scope, $namespaceName, $name) {
+ parent::__construct();
+
+ $this->scope = $scope;
+ $this->namespaceName = $namespaceName;
+ $this->id = -1;
+ $this->name = $name;
+ $this->inline = false;
+ $this->reference = array();
+ $this->text = '';
+ $this->processed = false;
+ }
+
+ /**
+ *
+ */
+ private function initId() {
+ $this->id = $this->scope->getNoteId();
+
+ if ($this->name == '') {
+ $this->name = '#' . $this->id;
+ }
+ }
+
+ /**
+ *
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ *
+ */
+ public function getNamespaceName() {
+ return $this->namespaceName;
+ }
+
+ /**
+ *
+ */
+ public function getScope() {
+ return $this->scope;
+ }
+
+ /**
+ *
+ */
+ public function setText($text) {
+ if ($this->text == '' || !$this->inline) {
+ $this->text = $text;
+ }
+ }
+
+ /**
+ *
+ */
+ public function getText() {
+ return $this->text;
+ }
+
+ /**
+ *
+ */
+ public function addReference($reference) {
+ if ($this->id == -1 && !$this->inline) {
+ $this->inline = $reference->isInline();
+
+ if (!$this->inline) {
+ $this->initId();
+ }
+ }
+
+ if ($reference->isBackReferenced()) {
+ $this->reference[] = $reference;
+ $this->processed = false;
+ }
+ }
+
+ /**
+ * Checks if the note should be processed
+ */
+ public function isValid() {
+ return !$this->processed && !empty($this->reference) && $this->text != '';
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_renderer_note extends refnotes_note {
+
+ /**
+ *
+ */
+ public function getMinReferenceId() {
+ $result = -1;
+
+ /* References are added in ascending order, so the first valid id should be minimal. */
+ foreach ($this->reference as $reference) {
+ if ($reference->getId() != -1) {
+ $result = $reference->getId();
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function getAnchorName() {
+ $result = 'refnotes';
+ $result .= $this->scope->getName();
+ $result .= ':note' . $this->id;
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function render($mode) {
+ $doc = $this->scope->getRenderer()->renderNote($mode, $this, $this->reference);
+
+ $this->reference = array();
+ $this->processed = true;
+
+ return $doc;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_action_note extends refnotes_note {
+
+ /**
+ * Constructor
+ */
+ public function __construct($scope, $namespaceName, $name) {
+ parent::__construct($scope, $namespaceName, $name);
+
+ $this->loadDatabaseDefinition();
+
+ $this->inline = $this->getAttribute('inline', false);
+ }
+
+ /**
+ *
+ */
+ private function loadDatabaseDefinition() {
+ $name = $this->namespaceName . $this->name;
+ $note = refnotes_reference_database::getInstance()->findNote($name);
+
+ if ($note != NULL) {
+ $this->attributes = $note->getAttributes();
+ $this->data = $note->getData();
+ }
+ }
+
+ /**
+ *
+ */
+ public function addReference($reference) {
+ parent::addReference($reference);
+
+ $exclude = $this->scope->getRenderer()->getReferencePrivateDataSet();
+ $data = array_diff_key($reference->getData(), array_flip($exclude));
+ $this->data = array_merge($this->data, $data);
+ }
+
+ /**
+ * Checks if the note should be processed. Simple notes are also reported as valid so that
+ * scope limits will produce note blocks identical to ones during rendering.
+ */
+ public function isValid() {
+ return !$this->processed && !empty($this->reference) && ($this->text != '' || $this->hasData());
+ }
+
+ /**
+ * Inject reference database data into references so that they can be properly rendered.
+ * Inject note text into the first reference.
+ */
+ public function rewriteReferences() {
+ if ($this->text == '' && $this->hasData()) {
+ foreach ($this->reference as $reference) {
+ $reference->rewrite($this->attributes, $this->data);
+ }
+
+ $this->reference[0]->setNoteText($this->scope->getRenderer()->renderNoteText($this));
+ }
+
+ $this->processed = true;
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/plugin.info.txt b/platform/www/lib/plugins/refnotes/plugin.info.txt
new file mode 100644
index 0000000..5f2d881
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/plugin.info.txt
@@ -0,0 +1,7 @@
+base refnotes
+author Mykola Ostrovskyy
+email dwpforge@gmail.com
+date 2021-05-23
+name RefNotes Plugin
+desc Extended syntax for footnotes and references.
+url http://www.dokuwiki.org/plugin:refnotes
diff --git a/platform/www/lib/plugins/refnotes/reference.php b/platform/www/lib/plugins/refnotes/reference.php
new file mode 100644
index 0000000..6a5f82a
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/reference.php
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * Plugin RefNotes: Reference
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_parser_reference extends refnotes_refnote {
+
+ /**
+ * Constructor
+ */
+ public function __construct($name, $data) {
+ list($namespace, $name) = refnotes_namespace::parseName($name);
+
+ if (preg_match('/(?:@@FNT|#)(\d+)/', $name, $match) == 1) {
+ $name = intval($match[1]);
+ }
+
+ parent::__construct(array('ns' => $namespace, 'name' => $name));
+
+ if ($data != '') {
+ $this->parseStructuredData($data);
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseStructuredData($data) {
+ if (preg_match('/^\s*\|/', $data) == 1) {
+ preg_match_all('/\|\s*([-\w]+)\s*=\s*([^|]+)/', $data, $match, PREG_SET_ORDER);
+
+ foreach ($match as $m) {
+ $this->data[$m[1]] = preg_replace('/\s+/', ' ', trim($m[2]));
+ }
+ }
+ else {
+ preg_match_all('/([-\w]+)\s*:\s*(.+?)\s*?(?:(?<!\\\\);|\n|$)/', $data, $match, PREG_SET_ORDER);
+
+ foreach ($match as $m) {
+ $this->data[$m[1]] = str_replace('\\;', ';', $m[2]);
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_reference extends refnotes_refnote {
+
+ protected $inline;
+ protected $hidden;
+ protected $note;
+ protected $id;
+
+ /**
+ * Constructor
+ */
+ public function __construct($note, $attributes, $data) {
+ parent::__construct($attributes, $data);
+
+ $this->inline = $this->getAttribute('inline', false);
+ $this->hidden = $this->getAttribute('hidden', false);
+ $this->note = $note;
+ $this->id = -1;
+
+ if ($this->isBackReferenced()) {
+ $this->id = $this->note->getScope()->getReferenceId();
+ }
+ }
+
+ /**
+ *
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+ /**
+ *
+ */
+ public function getNote() {
+ return $this->note;
+ }
+
+ /**
+ *
+ */
+ public function isInline() {
+ return $this->inline;
+ }
+
+ /**
+ *
+ */
+ public function isBackReferenced() {
+ return !$this->inline && !$this->hidden;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_renderer_reference extends refnotes_reference {
+
+ /**
+ *
+ */
+ public function getAnchorName() {
+ $result = 'refnotes';
+ $result .= $this->note->getScope()->getName();
+ $result .= ':ref' . $this->id;
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function render($mode) {
+ $doc = '';
+
+ if (!$this->hidden) {
+ $doc = $this->note->getScope()->getRenderer()->renderReference($mode, $this);
+ }
+
+ return $doc;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_action_reference extends refnotes_reference {
+
+ private $call;
+
+ /**
+ * Constructor
+ */
+ public function __construct($note, $attributes, $data, $call) {
+ parent::__construct($note, $attributes, $data);
+
+ $this->call = $call;
+ }
+
+ /**
+ *
+ */
+ private function updateAttributes($attributes) {
+ static $key = array('inline', 'use-reference-base', 'use-reference-font-weight', 'use-reference-font-style', 'use-reference-format', 'source');
+
+ foreach ($key as $k) {
+ if (array_key_exists($k, $attributes)) {
+ $this->attributes[$k] = $attributes[$k];
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function updateData($data) {
+ $include = $this->note->getScope()->getRenderer()->getReferenceSharedDataSet();
+ $data = array_intersect_key($data, array_flip($include));
+ $this->data = array_merge($data, $this->data);
+ }
+
+ /**
+ *
+ */
+ public function rewrite($attributes, $data) {
+ $this->updateAttributes($attributes);
+ $this->updateData($data);
+
+ $this->call->setPluginData(1, $this->attributes);
+
+ if ($this->hasData()) {
+ $this->call->setPluginData(2, $this->data);
+ }
+ }
+
+ /**
+ *
+ */
+ public function setNoteText($text) {
+ if ($text != '') {
+ $calls = refnotes_parser_core::getInstance()->getInstructions($text);
+
+ $this->call->insertBefore(new refnotes_nest_instruction($calls));
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/refnote.php b/platform/www/lib/plugins/refnotes/refnote.php
new file mode 100644
index 0000000..127cd18
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/refnote.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Plugin RefNotes: Common base class for references and notes
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_refnote {
+
+ protected $attributes;
+ protected $data;
+
+ /**
+ * Constructor
+ */
+ public function __construct($attributes = array(), $data = array()) {
+ $this->attributes = $attributes;
+ $this->data = $data;
+ }
+
+ /**
+ *
+ */
+ public function getAttributes() {
+ return $this->attributes;
+ }
+
+ /**
+ *
+ */
+ public function getAttribute($name, $default = '') {
+ return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ *
+ */
+ public function getData() {
+ return $this->data;
+ }
+
+ /**
+ *
+ */
+ public function hasData() {
+ return !empty($this->data);
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/rendering.php b/platform/www/lib/plugins/refnotes/rendering.php
new file mode 100644
index 0000000..22f7c9f
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/rendering.php
@@ -0,0 +1,1272 @@
+<?php
+
+/**
+ * Plugin RefNotes: Renderer
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_renderer_mock {
+
+ /**
+ *
+ */
+ public function renderReference($reference) {
+ return '';
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+abstract class refnotes_renderer_base {
+
+ protected $namespace;
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace) {
+ $this->namespace = $namespace;
+ }
+
+ /**
+ *
+ */
+ protected function getStyle($name) {
+ return $this->namespace->getStyle($name);
+ }
+
+ /**
+ * Returns an array of keys for data that is shared between references and notes.
+ */
+ abstract public function getReferenceSharedDataSet();
+
+ /**
+ * Returns an array of keys for data that is specific to references.
+ */
+ abstract public function getReferencePrivateDataSet();
+
+ /**
+ *
+ */
+ abstract public function renderReference($mode, $reference);
+
+ /**
+ *
+ */
+ abstract public function renderNoteText($note);
+
+ /**
+ *
+ */
+ abstract public function renderNote($mode, $note, $reference);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_renderer extends refnotes_renderer_base {
+
+ private $referenceRenderer;
+ private $noteRenderer;
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace) {
+ parent::__construct($namespace);
+
+ $this->referenceRenderer = $this->createRenderer($this->getStyle('reference-render'));
+ $this->noteRenderer = $this->createRenderer($this->getStyle('note-render'));
+ }
+
+ /**
+ *
+ */
+ private function createRenderer($style) {
+ switch ($style) {
+ case 'harvard':
+ $renderer = new refnotes_harvard_renderer($this->namespace);
+ break;
+
+ default:
+ $renderer = new refnotes_basic_renderer($this->namespace);
+ break;
+ }
+
+ return $renderer;
+ }
+
+ /**
+ *
+ */
+ public function getReferenceSharedDataSet() {
+ return $this->referenceRenderer->getReferenceSharedDataSet();
+ }
+
+ /**
+ *
+ */
+ public function getReferencePrivateDataSet() {
+ return $this->referenceRenderer->getReferencePrivateDataSet();
+ }
+
+ /**
+ *
+ */
+ public function renderReference($mode, $reference) {
+ return $this->referenceRenderer->renderReference($mode, $reference);
+ }
+
+ /**
+ *
+ */
+ public function renderNotesSeparator() {
+ $html = '';
+ $style = $this->getStyle('notes-separator');
+ if ($style != 'none') {
+ if ($style != '') {
+ $style = ' style="width: '. $style . '"';
+ }
+ $html = '<hr' . $style . '>' . DOKU_LF;
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ public function renderNoteText($note) {
+ return $this->noteRenderer->renderNoteText($note);
+ }
+
+ /**
+ *
+ */
+ public function renderNote($mode, $note, $reference) {
+ return $this->noteRenderer->renderNote($mode, $note, $reference);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_renderer_data {
+
+ private $data;
+
+ /**
+ * Constructor
+ */
+ public function __construct($data) {
+ $this->data = $data;
+ }
+
+ /**
+ *
+ */
+ public function has($key) {
+ if (func_num_args() > 1) {
+ $result = false;
+
+ foreach (func_get_args() as $key) {
+ if (array_key_exists($key, $this->data)) {
+ $result = true;
+ break;
+ }
+ }
+
+ return $result;
+ }
+ else {
+ return array_key_exists($key, $this->data);
+ }
+ }
+
+ /**
+ *
+ */
+ public function get($key) {
+ if (func_num_args() > 1) {
+ $result = '';
+
+ foreach (func_get_args() as $key) {
+ if (array_key_exists($key, $this->data)) {
+ $result = $this->data[$key];
+ break;
+ }
+ }
+
+ return $result;
+ }
+ else {
+ return array_key_exists($key, $this->data) ? $this->data[$key] : '';
+ }
+ }
+
+ /**
+ *
+ */
+ public function getLongest() {
+ $result = '';
+
+ if (func_num_args() > 0) {
+ foreach (func_get_args() as $key) {
+ if (array_key_exists($key, $this->data) && (strlen($result) < strlen($this->data[$key]))) {
+ $result = $this->data[$key];
+ }
+ }
+ }
+ else {
+ foreach ($this->data as $value) {
+ if (strlen($result) < strlen($value)) {
+ $result = $value;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function isPositive($key)
+ {
+ static $lookup = array('y', 'yes', 'on', 'true', '1');
+
+ return in_array(strtolower($this->get($key)), $lookup);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_basic_renderer extends refnotes_renderer_base {
+
+ protected $renderedNoteId = array();
+
+ /**
+ *
+ */
+ public function getReferenceSharedDataSet() {
+ return array();
+ }
+
+ /**
+ *
+ */
+ public function getReferencePrivateDataSet() {
+ return array();
+ }
+
+ /**
+ *
+ */
+ public function renderReference($mode, $reference) {
+ if ($reference->isInline()) {
+ $doc = $this->renderInlineReference($reference);
+ }
+ else {
+ $doc = $this->renderRegularReference($mode, $reference);
+ }
+
+ return $doc;
+ }
+
+ /**
+ *
+ */
+ public function renderNoteText($note) {
+ $data = new refnotes_renderer_data($note->getData());
+
+ $text = $data->get('note-text', 'title');
+
+ if ($text == '') {
+ $text = $data->getLongest();
+ }
+
+ if ($url = $data->get('url')) {
+ $text = '[[' . $url . '|' . $text . ']]';
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ public function renderNote($mode, $note, $reference) {
+ $doc = '';
+
+ switch ($mode) {
+ case 'xhtml':
+ $doc = $this->renderNoteXhtml($note, $reference);
+ break;
+
+ case 'odt':
+ $doc = $this->renderNoteOdt($note, $reference);
+ break;
+ }
+
+ return $doc;
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteXhtml($note, $reference) {
+ $html = '<div class="' . $this->renderNoteClass() . '">' . DOKU_LF;
+ $html .= $this->renderBackReferences($note, $reference);
+ $html .= '<span id="' . $note->getAnchorName() . ':text">' . DOKU_LF;
+ $html .= $note->getText() . DOKU_LF;
+ $html .= '</span></div>' . DOKU_LF;
+
+ $this->rendered = true;
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteOdt($note, $reference) {
+ $this->rendered = true;
+
+ return '';
+ }
+
+ /**
+ *
+ */
+ protected function getInlineReferenceStyle($reference, $name, $default) {
+ return ($reference->getAttribute('use-' . $name) === false) ? $default : $this->getStyle($name);
+ }
+
+ /**
+ *
+ */
+ protected function renderInlineReference($reference) {
+ $baseStyle = $this->getInlineReferenceStyle($reference, 'reference-base', 'text');
+ $fontWeightStyle = $this->getInlineReferenceStyle($reference, 'reference-font-weight', 'normal');
+ $fontStyle = $this->getInlineReferenceStyle($reference, 'reference-font-style', 'normal');
+ $formatStyle = $this->getInlineReferenceStyle($reference, 'reference-format', 'none');
+
+ list($baseOpen, $baseClose) = $this->renderBase($baseStyle);
+ list($fontOpen, $fontClose) = $this->renderFont($fontWeightStyle, 'normal', $fontStyle);
+ list($formatOpen, $formatClose) = $this->renderFormat($formatStyle);
+
+ $html = $baseOpen . $fontOpen . $formatOpen;
+ $html .= $reference->getNote()->getText();
+ $html .= $formatClose . $fontClose . $baseClose;
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderRegularReference($mode, $reference) {
+ $doc = '';
+
+ switch ($mode) {
+ case 'xhtml':
+ $doc = $this->renderRegularReferenceXhtml($reference);
+ break;
+
+ case 'odt':
+ $doc = $this->renderRegularReferenceOdt($reference);
+ break;
+ }
+
+ return $doc;
+ }
+
+ /**
+ *
+ */
+ protected function renderRegularReferenceXhtml($reference) {
+ $noteName = $reference->getNote()->getAnchorName();
+ $referenceName = $reference->getAnchorName();
+ $class = $this->renderReferenceClass();
+
+ list($baseOpen, $baseClose) = $this->renderReferenceBase();
+ list($fontOpen, $fontClose) = $this->renderReferenceFont();
+ list($formatOpen, $formatClose) = $this->renderReferenceFormat($reference);
+
+ $html = $baseOpen . $fontOpen;
+ $html .= '<a href="#' . $noteName . '" name="' . $referenceName . '" class="' . $class . '">';
+ $html .= $formatOpen . $this->renderReferenceId($reference) . $formatClose;
+ $html .= '</a>';
+ $html .= $fontClose . $baseClose;
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderRegularReferenceOdt($reference) {
+ $xmlOdt = '';
+ $note = $reference->getNote();
+ $noteId = $note->getId();
+ $refId = $reference->getId();
+
+ // Check to see if this note has been seen before
+
+ if (array_search($noteId, $this->renderedNoteId) === false) {
+ // new note, add it to the $renderedNoteId array
+ $this->renderedNoteId[] = $noteId;
+
+ $xmlOdt .= '<text:note text:id="refnote' . $refId . '" text:note-class="footnote">';
+ $xmlOdt .= '<text:note-citation text:label="' . $refId . '">' . $refId . '</text:note-citation>';
+ $xmlOdt .= '<text:note-body>';
+ $xmlOdt .= '<text:p>' . $note->getText();
+ $xmlOdt .= '</text:p>';
+ $xmlOdt .= '</text:note-body>';
+ $xmlOdt .= '</text:note>';
+ }
+ else {
+ // Seen this one before - just reference it FIXME: style isn't correct
+ $xmlOdt = '<text:note-ref text:note-class="footnote" text:ref-name="refnote' . $noteId . '">';
+ $xmlOdt .= $refId;
+ $xmlOdt .= '</text:note-ref>';
+ }
+
+ return $xmlOdt;
+ }
+
+ /**
+ *
+ */
+ protected function renderBackReferences($note, $reference) {
+ $references = count($reference);
+ $singleReference = ($references == 1);
+ $nameAttribute = ' name="' . $note->getAnchorName() .'"';
+ $backRefFormat = $this->getStyle('back-ref-format');
+ $backRefCaret = '';
+ $html = '';
+
+ list($formatOpen, $formatClose) = $this->renderNoteIdFormat();
+
+ if (($backRefFormat != 'note') && ($backRefFormat != '')) {
+ list($baseOpen, $baseClose) = $this->renderNoteIdBase();
+ list($fontOpen, $fontClose) = $this->renderNoteIdFont();
+
+ $html .= $baseOpen . $fontOpen;
+ $html .= '<a' . $nameAttribute .' class="nolink">';
+ $html .= $formatOpen . $this->renderNoteId($note) . $formatClose;
+ $html .= '</a>';
+ $html .= $fontClose . $baseClose . DOKU_LF;
+
+ $nameAttribute = '';
+ $formatOpen = '';
+ $formatClose = '';
+ $backRefCaret = $this->renderBackRefCaret($singleReference);
+ }
+
+ if ($backRefFormat != 'none') {
+ $separator = $this->renderBackRefSeparator();
+
+ list($baseOpen, $baseClose) = $this->renderBackRefBase();
+ list($fontOpen, $fontClose) = $this->renderBackRefFont();
+
+ $html .= $baseOpen . $backRefCaret;
+
+ for ($r = 0; $r < $references; $r++) {
+ $referenceName = $reference[$r]->getAnchorName();
+
+ if ($r > 0) {
+ $html .= $separator . DOKU_LF;
+ }
+
+ $html .= $fontOpen;
+ $html .= '<a href="#' . $referenceName . '"' . $nameAttribute .' class="backref">';
+ $html .= $formatOpen . $this->renderBackRefId($reference[$r], $r, $singleReference) . $formatClose;
+ $html .= '</a>';
+ $html .= $fontClose;
+
+ $nameAttribute = '';
+ }
+
+ $html .= $baseClose . DOKU_LF;
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceClass() {
+ switch ($this->getStyle('note-preview')) {
+ case 'tooltip':
+ $result = 'refnotes-ref note-tooltip';
+ break;
+
+ case 'none':
+ $result = 'refnotes-ref';
+ break;
+
+ default:
+ $result = 'refnotes-ref note-popup';
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceBase() {
+ return $this->renderBase($this->getStyle('reference-base'));
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceFont() {
+ return $this->renderFont('reference-font-weight', 'normal', 'reference-font-style');
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceFormat($reference) {
+ $result = $this->renderFormat($this->getStyle('reference-format'));
+
+ if ($this->getReferenceGrouping($reference)) {
+ switch ($reference->getAttribute('group')) {
+ case 'open':
+ $result = array($result[0], $this->renderReferenceGroupSeparator());
+ break;
+
+ case 'hold':
+ $result = array('', $this->renderReferenceGroupSeparator());
+ break;
+
+ case 'close':
+ $result = array('', $result[1]);
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function getReferenceGrouping($reference) {
+ $group = $reference->getAttribute('group');
+ return !empty($group) && in_array($group, array('open', 'hold', 'close')) &&
+ in_array($this->getStyle('reference-group'), array(',', 's'));
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceGroupSeparator() {
+ $style = $this->getStyle('reference-group');
+ switch ($style) {
+ case ',':
+ $result = ',';
+ break;
+
+ case 's':
+ $result = ';';
+ break;
+
+ default:
+ $result = '';
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceId($reference) {
+ $idStyle = $this->getStyle('refnote-id');
+ if ($idStyle == 'name') {
+ $html = $reference->getNote()->getName();
+ }
+ else {
+ switch ($this->getStyle('multi-ref-id')) {
+ case 'note':
+ $id = $reference->getNote()->getId();
+ break;
+
+ default:
+ $id = $reference->getId();
+ break;
+ }
+
+ $html = $this->convertToStyle($id, $idStyle);
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteClass() {
+ $result = 'note';
+
+ switch ($this->getStyle('note-font-size')) {
+ case 'small':
+ $result .= ' small';
+ break;
+ }
+
+ switch ($this->getStyle('note-text-align')) {
+ case 'left':
+ $result .= ' left';
+ break;
+
+ default:
+ $result .= ' justify';
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteIdBase() {
+ return $this->renderBase($this->getStyle('note-id-base'));
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteIdFont() {
+ return $this->renderFont('note-id-font-weight', 'normal', 'note-id-font-style');
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteIdFormat() {
+ $style = $this->getStyle('note-id-format');
+ switch ($style) {
+ case '.':
+ $result = array('', '.');
+ break;
+
+ default:
+ $result = $this->renderFormat($style);
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function renderNoteId($note) {
+ $idStyle = $this->getStyle('refnote-id');
+ if ($idStyle == 'name') {
+ $html = $note->getName();
+ }
+ else {
+ $html = $this->convertToStyle($note->getId(), $idStyle);
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderBackRefCaret($singleReference) {
+ switch ($this->getStyle('back-ref-caret')) {
+ case 'prefix':
+ $result = '^ ';
+ break;
+
+ case 'merge':
+ $result = $singleReference ? '' : '^ ';
+ break;
+
+ default:
+ $result = '';
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function renderBackRefBase() {
+ return $this->renderBase($this->getStyle('back-ref-base'));
+ }
+
+ /**
+ *
+ */
+ protected function renderBackRefFont() {
+ return $this->renderFont('back-ref-font-weight', 'bold', 'back-ref-font-style');
+ }
+
+ /**
+ *
+ */
+ protected function renderBackRefSeparator() {
+ static $html = array('' => ',', 'none' => '');
+
+ $style = $this->getStyle('back-ref-separator');
+ if (!array_key_exists($style, $html)) {
+ $style = '';
+ }
+
+ return $html[$style];
+ }
+
+ /**
+ *
+ */
+ protected function renderBackRefId($reference, $index, $singleReference) {
+ $style = $this->getStyle('back-ref-format');
+ switch ($style) {
+ case 'a':
+ $result = $this->convertToLatin($index + 1, $style);
+ break;
+
+ case '1':
+ $result = $index + 1;
+ break;
+
+ case 'caret':
+ $result = '^';
+ break;
+
+ case 'arrow':
+ $result = '&uarr;';
+ break;
+
+ default:
+ $result = $this->renderReferenceId($reference);
+ break;
+ }
+
+ if ($singleReference && ($this->getStyle('back-ref-caret') == 'merge')) {
+ $result = '^';
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function renderBase($style) {
+ static $html = array(
+ '' => array('<sup>', '</sup>'),
+ 'text' => array('', '')
+ );
+
+ if (!array_key_exists($style, $html)) {
+ $style = '';
+ }
+
+ return $html[$style];
+ }
+
+ /**
+ *
+ */
+ protected function renderFont($weight, $defaultWeight, $style) {
+ list($weightOpen, $weightClose) = $this->renderFontWeight($this->getStyle($weight), $defaultWeight);
+ list($styleOpen, $styleClose) = $this->renderFontStyle($this->getStyle($style));
+
+ return array($weightOpen . $styleOpen, $styleClose . $weightClose);
+ }
+
+ /**
+ *
+ */
+ protected function renderFontWeight($style, $default) {
+ static $html = array(
+ 'normal' => array('', ''),
+ 'bold' => array('<b>', '</b>')
+ );
+
+ if (!array_key_exists($style, $html)) {
+ $style = $default;
+ }
+
+ return $html[$style];
+ }
+
+ /**
+ *
+ */
+ protected function renderFontStyle($style) {
+ static $html = array(
+ '' => array('', ''),
+ 'italic' => array('<i>', '</i>')
+ );
+
+ if (!array_key_exists($style, $html)) {
+ $style = '';
+ }
+
+ return $html[$style];
+ }
+
+ /**
+ *
+ */
+ protected function renderFormat($style) {
+ static $html = array(
+ '' => array('', ')'),
+ '()' => array('(', ')'),
+ ']' => array('', ']'),
+ '[]' => array('[', ']'),
+ 'none' => array('', '')
+ );
+
+ if (!array_key_exists($style, $html)) {
+ $style = '';
+ }
+
+ return $html[$style];
+ }
+
+ /**
+ *
+ */
+ protected function convertToStyle($id, $style) {
+ switch ($style) {
+ case 'a':
+ case 'A':
+ $result = $this->convertToLatin($id, $style);
+ break;
+
+ case 'i':
+ case 'I':
+ $result = $this->convertToRoman($id, $style);
+ break;
+
+ case '*':
+ $result = str_repeat('*', $id);
+ break;
+
+ default:
+ $result = $id;
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function convertToLatin($number, $case) {
+ static $alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ $result = '';
+
+ while ($number > 0) {
+ --$number;
+ $digit = $number % 26;
+ $result = $alpha[$digit] . $result;
+ $number = intval($number / 26);
+ }
+
+ if ($case == 'a') {
+ $result = strtolower($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ protected function convertToRoman($number, $case) {
+ static $lookup = array(
+ 'M' => 1000, 'CM' => 900, 'D' => 500, 'CD' => 400,
+ 'C' => 100, 'XC' => 90, 'L' => 50, 'XL' => 40,
+ 'X' => 10, 'IX' => 9, 'V' => 5, 'IV' => 4, 'I' => 1
+ );
+
+ $result = '';
+
+ foreach ($lookup as $roman => $value) {
+ $matches = intval($number / $value);
+
+ if ($matches > 0) {
+ $result .= str_repeat($roman, $matches);
+ $number = $number % $value;
+ }
+ }
+
+ if ($case == 'i') {
+ $result = strtolower($result);
+ }
+
+ return $result;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_harvard_renderer extends refnotes_basic_renderer {
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace) {
+ parent::__construct($namespace);
+ }
+
+ /**
+ *
+ */
+ public function getReferenceSharedDataSet() {
+ static $key = array('ref-authors', 'ref-author', 'authors', 'author', 'published', 'month', 'year');
+
+ return $key;
+ }
+
+ /**
+ *
+ */
+ public function getReferencePrivateDataSet() {
+ static $key = array('direct', 'pages', 'page');
+
+ return $key;
+ }
+
+ /**
+ *
+ */
+ public function renderNoteText($note) {
+ $data = new refnotes_renderer_data($note->getData());
+
+ if (!$data->has('title')) {
+ return parent::renderNoteText($note);
+ }
+
+ // authors, published. //[[url|title.]]// edition. publisher, pages, isbn.
+ // authors, published. chapter In //[[url|title.]]// edition. publisher, pages, isbn.
+ // authors, published. [[url|title.]] //journal//, volume, publisher, pages, issn.
+ // authors, published. [[url|title.]] //booktitle//, publisher, pages, issn.
+
+ $title = $this->renderTitle($data);
+
+ // authors, published. //$title// edition. publisher, pages, isbn.
+ // authors, published. chapter In //$title// edition. publisher, pages, isbn.
+ // authors, published. $title //journal//, volume, publisher, pages, issn.
+ // authors, published. $title //booktitle//, publisher, pages, issn.
+
+ $authors = $this->renderAuthors($data);
+
+ // $authors? //$title// edition. publisher, pages, isbn.
+ // $authors? chapter In //$title// edition. publisher, pages, isbn.
+ // $authors? $title //journal//, volume, publisher, pages, issn.
+ // $authors? $title //booktitle//, publisher, pages, issn.
+
+ $publication = $this->renderPublication($data, $authors != '');
+
+ if ($data->has('journal')) {
+ // $authors? $title //journal//, volume, $publication?
+
+ $subtitle = $this->renderJournal($data);
+ }
+ elseif ($data->has('booktitle')) {
+ // $authors? $title //booktitle//, $publication?
+
+ $subtitle = $this->renderBookTitle($data);
+ }
+
+ if (!empty($subtitle)) {
+ // $authors? $title $subtitle?, $publication?
+
+ $text = $title . ' ' . $subtitle;
+
+ // $authors? $text, $publication?
+
+ $text .= ($publication != '') ? ',' : '.';
+ }
+ else {
+ // $authors? //$title// edition. $publication?
+ // $authors? chapter In //$title// edition. $publication?
+
+ $text = $this->renderBook($data, $title);
+ }
+
+ // $authors? $text $publication?
+
+ if ($authors != '') {
+ $text = $authors . ' ' . $text;
+ }
+
+ if ($publication != '') {
+ $text .= ' ' . $publication;
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderTitle($data) {
+ $text = $data->get('title') . '.';
+
+ if ($url = $data->get('url')) {
+ $text = '[[' . $url . '|' . $text . ']]';
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderAuthors($data) {
+ $text = $data->get('authors', 'author');
+
+ if ($text != '') {
+ if ($published = $this->renderPublished($data)) {
+ $text .= ', ' . $published;
+ }
+
+ $text .= '.';
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderPublished($data, $useMonth = true) {
+ $text = $data->get('published');
+
+ if ($text == '') {
+ if ($text = $data->get('year')) {
+ if ($useMonth && $month = $data->get('month')) {
+ $text = $month . ' ' . $text;
+ }
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderPublication($data, $authors) {
+ $part = array();
+
+ $address = $data->get('address');
+ $publisher = $data->get('publisher');
+
+ if ($address && $publisher) {
+ $part[] = $address . ': ' . $publisher;
+ }
+ else {
+ if ($address || $publisher) {
+ $part[] = $address . $publisher;
+ }
+ }
+
+ if (!$authors && ($published = $this->renderPublished($data))) {
+ $part[] = $published;
+ }
+
+ if ($pages = $this->renderPages($data, array('note-pages', 'note-page', 'pages', 'page'))) {
+ $part[] = $pages;
+ }
+
+ if ($isbn = $data->get('isbn')) {
+ $part[] = 'ISBN ' . $isbn;
+ }
+ elseif ($issn = $data->get('issn')) {
+ $part[] = 'ISSN ' . $issn;
+ }
+
+ $text = implode(', ', $part);
+
+ if ($text != '') {
+ $text = rtrim($text, '.') . '.';
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderPages($data, $key) {
+ $text = '';
+
+ foreach ($key as $k) {
+ if ($text = $data->get($k)) {
+ if (preg_match("/^[0-9]/", $text)) {
+ $abbr_key = (substr($k, -1) == 's') ? 'txt_pages_abbr' : 'txt_page_abbr';
+ $text = refnotes_localization::getInstance()->getLang($abbr_key) . $text;
+ }
+ break;
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderJournal($data) {
+ $text = '//' . $data->get('journal') . '//';
+
+ if ($volume = $data->get('volume')) {
+ $text .= ', ' . $volume;
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderBook($data, $title) {
+ $text = '//' . $title . '//';
+
+ if ($chapter = $data->get('chapter')) {
+ $text = $chapter . '. ' . refnotes_localization::getInstance()->getLang('txt_in_cap') . ' ' . $text;
+ }
+
+ if ($edition = $data->get('edition')) {
+ $text .= ' ' . $edition . '.';
+ }
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ protected function renderBookTitle($data) {
+ return '//' . $data->get('booktitle') . '//';
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceId($reference) {
+ $data = new refnotes_renderer_data($reference->getData());
+
+ if (!$this->checkReferenceData($data)) {
+ return $this->renderBasicReferenceId($reference);
+ }
+
+ $authors = $data->get('ref-authors', 'ref-author', 'authors', 'author');
+ $html = $this->renderReferenceExtra($data);
+
+ list($formatOpen, $formatClose) = $this->renderReferenceParentheses();
+
+ if ($data->isPositive('direct')) {
+ $html = $authors . ' ' . $formatOpen . $html . $formatClose;
+
+ if ($this->getReferenceGrouping($reference)) {
+ switch ($reference->getAttribute('group')) {
+ case 'open':
+ case 'hold':
+ $html .= $this->renderReferenceGroupSeparator();
+ break;
+ }
+ }
+ }
+ else {
+ if ($this->getReferenceGrouping($reference)) {
+ switch ($reference->getAttribute('group')) {
+ case 'open':
+ $formatClose = $this->renderReferenceGroupSeparator();
+ break;
+
+ case 'hold':
+ $formatOpen = '';
+ $formatClose = $this->renderReferenceGroupSeparator();
+ break;
+
+ case 'close':
+ $formatOpen = '';
+ break;
+ }
+ }
+
+ $html = $formatOpen . $authors . ', ' . $html . $formatClose;
+ }
+
+ return htmlspecialchars($html);
+ }
+
+ /**
+ *
+ */
+ protected function renderBasicReferenceId($reference) {
+ list($formatOpen, $formatClose) = parent::renderReferenceFormat($reference);
+
+ return $formatOpen . parent::renderReferenceId($reference) . $formatClose;
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceExtra($data) {
+ $html = '';
+
+ if ($published = $this->renderPublished($data, false)) {
+ $html .= $published;
+ }
+
+ if ($pages = $this->renderPages($data, array('page', 'pages'))) {
+ if ($html != '') {
+ $html .= ', ';
+ }
+
+ $html .= $pages;
+ }
+
+ return $html;
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceParentheses() {
+ $style = $this->getStyle('reference-format');
+ $style = (($style == '[]') || ($style == ']')) ? '[]' : '()';
+
+ return $this->renderFormat($style);
+ }
+
+ /**
+ *
+ */
+ protected function renderReferenceFormat($reference) {
+ return array('', '');
+ }
+
+ /**
+ *
+ */
+ protected function checkReferenceData($data) {
+ $authors = $data->has('ref-authors', 'ref-author', 'authors', 'author');
+ $year = $data->has('published', 'year');
+ $page = $data->has('page', 'pages');
+
+ return $authors && ($year || $page);
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/scope.php b/platform/www/lib/plugins/refnotes/scope.php
new file mode 100644
index 0000000..b955635
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/scope.php
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * Plugin RefNotes: Scope
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_scope_limits {
+ public $start;
+ public $end;
+
+ /**
+ * Constructor
+ */
+ public function __construct($start, $end = -1000) {
+ $this->start = $start;
+ $this->end = $end;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_scope_mock {
+
+ /**
+ *
+ */
+ public function getLimits() {
+ return new refnotes_scope_limits(-1, -1);
+ }
+
+ /**
+ *
+ */
+ public function isOpen() {
+ return false;
+ }
+
+ /**
+ *
+ */
+ public function getRenderer() {
+ return new refnotes_renderer_mock();
+ }
+
+ /**
+ *
+ */
+ public function getReferenceId() {
+ return 0;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_scope {
+
+ private $namespace;
+ private $id;
+ private $limits;
+ private $note;
+ private $notes;
+ private $references;
+
+ /**
+ * Constructor
+ */
+ public function __construct($namespace, $id, $start = -1, $end = -1000) {
+ $this->namespace = $namespace;
+ $this->id = $id;
+ $this->limits = new refnotes_scope_limits($start, $end);
+ $this->note = array();
+ $this->notes = 0;
+ $this->references = 0;
+ }
+
+ /**
+ *
+ */
+ public function getName() {
+ return $this->namespace->getName() . $this->id;
+ }
+
+ /**
+ *
+ */
+ public function getLimits() {
+ return $this->limits;
+ }
+
+ /**
+ *
+ */
+ public function isOpen() {
+ return $this->limits->end == -1000;
+ }
+
+ /**
+ *
+ */
+ public function getRenderer() {
+ return $this->namespace->getRenderer();
+ }
+
+ /**
+ *
+ */
+ public function getNoteId() {
+ return ++$this->notes;
+ }
+
+ /**
+ *
+ */
+ public function getReferenceId() {
+ return ++$this->references;
+ }
+
+ /**
+ *
+ */
+ public function addNote($note) {
+ $this->note[] = $note;
+ }
+
+ /**
+ *
+ */
+ public function rewriteReferences($limit) {
+ $block = new refnotes_note_block_iterator($this->note, $limit);
+
+ foreach ($block as $note) {
+ $note->rewriteReferences();
+ }
+ }
+
+ /**
+ *
+ */
+ public function renderNotes($mode, $limit) {
+ $minReferenceId = array();
+
+ foreach ($this->note as $note) {
+ $minReferenceId[] = $note->getMinReferenceId();
+ }
+
+ array_multisort($minReferenceId, $this->note);
+
+ $block = new refnotes_note_block_iterator($this->note, $limit);
+ $doc = '';
+
+ foreach ($block as $note) {
+ $doc .= $note->render($mode);
+ }
+
+ if ($mode == 'xhtml' && $doc != '') {
+ $open = $this->getRenderer()->renderNotesSeparator() . '<div class="notes">' . DOKU_LF;
+ $close = '</div>' . DOKU_LF;
+ $doc = $open . $doc . $close;
+ }
+
+ return $doc;
+ }
+
+ /**
+ * Finds a note given it's name or id
+ */
+ public function findNote($namespaceName, $noteName) {
+ $result = NULL;
+
+ if ($noteName != '') {
+ if (is_int($noteName)) {
+ $getter = 'getId';
+ }
+ else {
+ $getter = 'getName';
+ }
+
+ foreach ($this->note as $note) {
+ if (($note->getNamespaceName() == $namespaceName) && ($note->$getter() == $noteName)) {
+ $result = $note;
+ break;
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/script.js b/platform/www/lib/plugins/refnotes/script.js
new file mode 100644
index 0000000..79e3482
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/script.js
@@ -0,0 +1,114 @@
+(function () {
+ let floater = null;
+ let tracking = false;
+ let timer = null;
+
+ function createFloater() {
+ return jQuery('<div id="insitu__fn" />')
+ .addClass('insitu-footnote JSpopup')
+ .css({ visibility : 'hidden', left : '0px', top : '0px' })
+ .mouseleave(function () { jQuery(this).hide(); })
+ .appendTo('.dokuwiki:first');
+ }
+
+ function getFloater() {
+ if (!floater) {
+ floater = jQuery('#insitu__fn');
+ if (floater.length == 0) {
+ floater = createFloater();
+ }
+ }
+
+ return floater;
+ }
+
+ let preview = {
+ setNoteId(id) {
+ // locate the note span element
+ let note = jQuery('#' + id.replace(/:/g, '\\:') + '\\:text');
+ if (note.length == 0) {
+ return false;
+ }
+
+ // remove any element ids from the content to ensure that they remain unique
+ // and display hidden tooltip so we can move it around
+ getFloater()
+ .html(note.html().replace(/\bid\s*=\s*".*?"/gi, ''))
+ .css('visibility', 'hidden')
+ .show();
+
+ return true;
+ },
+
+ show() {
+ getFloater()
+ .css('visibility', 'visible')
+ .show();
+ },
+
+ hide() {
+ // prevent creation of the floater and re-hiding it on window.scroll()
+ if (floater && floater.is(':visible')) {
+ floater.hide();
+ }
+ },
+
+ move(event, dx, dy) {
+ getFloater().position({
+ my : 'left top',
+ of : event,
+ offset : dx + ' ' + dy,
+ collision : 'flip'
+ });
+ }
+ };
+
+ function getNoteId(event) {
+ return event.target.href.replace(/^.*?#([\w:]+)$/gi, '$1');
+ }
+
+ plugin_refnotes = {
+ popup : {
+ show(event) {
+ plugin_refnotes.tooltip.hide(event);
+ if (preview.setNoteId(getNoteId(event))) {
+ preview.move(event, 2, 2);
+ preview.show();
+ }
+ }
+ },
+
+ tooltip : {
+ show(event) {
+ plugin_refnotes.tooltip.hide(event);
+ if (preview.setNoteId(getNoteId(event))) {
+ timer = setTimeout(function () { preview.show(); }, 500);
+ tracking = true;
+ }
+ },
+
+ hide(event) {
+ if (tracking) {
+ clearTimeout(timer);
+ tracking = false;
+ }
+ preview.hide();
+ },
+
+ track(event) {
+ if (tracking) {
+ preview.move(event, 10, 12);
+ }
+ }
+ }
+ };
+})();
+
+jQuery(function () {
+ jQuery('a.refnotes-ref.note-popup').mouseenter(plugin_refnotes.popup.show);
+ jQuery('a.refnotes-ref.note-tooltip')
+ .mouseenter(plugin_refnotes.tooltip.show)
+ .mouseleave(plugin_refnotes.tooltip.hide);
+ jQuery(document).mousemove(plugin_refnotes.tooltip.track);
+ jQuery(window).scroll(plugin_refnotes.tooltip.hide);
+});
diff --git a/platform/www/lib/plugins/refnotes/style.css b/platform/www/lib/plugins/refnotes/style.css
new file mode 100644
index 0000000..1aba434
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/style.css
@@ -0,0 +1,48 @@
+div.dokuwiki a.refnotes-ref {
+}
+
+div.dokuwiki div.refnotes {
+ clear: both;
+ margin: 0 0 1em 0;
+}
+
+div.dokuwiki div.refnotes hr {
+ text-align: left;
+ margin-bottom: 0.2em;
+}
+
+div.dokuwiki div.refnotes div.notes {
+ padding-left: 1em;
+ margin-bottom: 1em;
+}
+
+div.dokuwiki div.refnotes div.note {
+ font-size: 90%;
+}
+
+div.dokuwiki div.refnotes div.note.small {
+ font-size: 70%;
+}
+
+div.dokuwiki div.refnotes div.note.justify {
+ text-align: justify;
+}
+
+div.dokuwiki div.refnotes div.note.left {
+ text-align: left;
+}
+
+div.dokuwiki div.refnotes a.nolink {
+}
+
+div.dokuwiki div.refnotes a.backref {
+}
+
+/* HACK: Fix compatibility problem with Sidebar plugin (issue 6) */
+.sidebar_inside_left div.insitu-footnote,
+.sidebar_inside_right div.insitu-footnote,
+.sidebar_outside_left div.insitu-footnote,
+.sidebar_outside_right div.insitu-footnote {
+ background: __background_other__;
+ padding: 4px;
+}
diff --git a/platform/www/lib/plugins/refnotes/syntax/notes.php b/platform/www/lib/plugins/refnotes/syntax/notes.php
new file mode 100644
index 0000000..32263ac
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/syntax/notes.php
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * Plugin RefNotes: Note renderer
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+require_once(DOKU_PLUGIN . 'refnotes/core.php');
+
+class syntax_plugin_refnotes_notes extends DokuWiki_Syntax_Plugin {
+
+ private $mode;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->mode = substr(get_class($this), 7);
+ }
+
+ /**
+ * What kind of syntax are we?
+ */
+ public function getType() {
+ return 'substition';
+ }
+
+ public function getPType() {
+ return 'block';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ public function getSort() {
+ return 150;
+ }
+
+ public function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('~~REFNOTES.*?~~', $mode, $this->mode);
+ $this->Lexer->addSpecialPattern('<refnotes[^>]*?\/>', $mode, $this->mode);
+ $this->Lexer->addSpecialPattern('<refnotes(?:[^>]*?[^/>])?>.*?<\/refnotes>', $mode, $this->mode);
+ }
+
+ /**
+ * Handle the match
+ */
+ public function handle($match, $state, $pos, Doku_Handler $handler) {
+ switch ($match[0]) {
+ case '~':
+ return $this->handleBasic($match);
+
+ case '<':
+ return $this->handleExtended($match);
+ }
+
+ return false;
+ }
+
+ /**
+ * Create output
+ */
+ public function render($mode, Doku_Renderer $renderer, $data) {
+ try {
+ if($mode == 'xhtml') {
+ switch ($data[0]) {
+ case 'style':
+ refnotes_renderer_core::getInstance()->styleNamespace($data[1]['ns'], $data[2]);
+ break;
+
+ case 'map':
+ refnotes_renderer_core::getInstance()->setNamespaceMapping($data[1]['ns'], $data[2]);
+ break;
+
+ case 'render':
+ $this->renderNotes($mode, $renderer, $data[1]);
+ break;
+ }
+
+ return true;
+ }
+ elseif ($mode == 'odt') {
+ switch ($data[0]) {
+ case 'render':
+ $this->renderNotes($mode, $renderer, $data[1]);
+ break;
+ }
+
+ return true;
+ }
+ }
+ catch (Exception $error) {
+ msg($error->getMessage(), -1);
+ }
+
+ return false;
+ }
+
+ /**
+ *
+ */
+ private function handleBasic($syntax) {
+ preg_match('/~~REFNOTES(.*?)~~/', $syntax, $match);
+
+ return array('render', $this->parseAttributes($match[1]));
+ }
+
+ /**
+ *
+ */
+ private function handleExtended($syntax) {
+ preg_match('/<refnotes(.*?)(?:\/>|>(.*?)<\/refnotes>)/s', $syntax, $match);
+ $attribute = $this->parseAttributes($match[1]);
+ $style = array();
+
+ if ($match[2] != '') {
+ $style = $this->parseStyles($match[2]);
+ }
+
+ if (count($style) > 0) {
+ return array('split', $attribute, $style);
+ }
+ else {
+ return array('render', $attribute);
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseAttributes($syntax) {
+ $propertyMatch = array(
+ 'ns' => '/^' . refnotes_namespace::getNamePattern('required') . '$/',
+ 'limit' => '/^\/?\d+$/'
+ );
+
+ $attribute = array();
+ $token = preg_split('/\s+/', $syntax, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($token as $t) {
+ foreach ($propertyMatch as $name => $pattern) {
+ if (preg_match($pattern, $t) == 1) {
+ $attribute[$name][] = $t;
+ break;
+ }
+ }
+ }
+
+ if (array_key_exists('ns', $attribute)) {
+ /* Ensure that namespaces are in canonic form */
+ $attribute['ns'] = array_map('refnotes_namespace::canonizeName', $attribute['ns']);
+
+ if (count($attribute['ns']) > 1) {
+ $attribute['map'] = array_slice($attribute['ns'], 1);
+ }
+
+ $attribute['ns'] = $attribute['ns'][0];
+ }
+ else {
+ $attribute['ns'] = ':';
+ }
+
+ if (array_key_exists('limit', $attribute)) {
+ $attribute['limit'] = end($attribute['limit']);
+ }
+
+ return $attribute;
+ }
+
+ /**
+ *
+ */
+ private function parseStyles($syntax) {
+ $style = array();
+ preg_match_all('/([-\w]+)\s*:[ \t]*([^\s\n;].*?)[ \t]*?(?:[\n;]|$)/', $syntax, $match, PREG_SET_ORDER);
+ foreach ($match as $m) {
+ $style[$m[1]] = $m[2];
+ }
+
+ /* Validate direct-to-html styles */
+ if (array_key_exists('notes-separator', $style)) {
+ if (preg_match('/(?:\d+\.?|\d*\.\d+)(?:%|em|px)|none/', $style['notes-separator'], $match) == 1) {
+ $style['notes-separator'] = $match[0];
+ }
+ else {
+ $style['notes-separator'] = '';
+ }
+ }
+
+ /* Ensure that namespaces are in canonic form */
+ if (array_key_exists('inherit', $style)) {
+ $style['inherit'] = refnotes_namespace::canonizeName($style['inherit']);
+ }
+
+ return $style;
+ }
+
+ /**
+ *
+ */
+ private function renderNotes($mode, $renderer, $attribute) {
+ $limit = array_key_exists('limit', $attribute) ? $attribute['limit'] : '';
+ $doc = refnotes_renderer_core::getInstance()->renderNotes($mode, $attribute['ns'], $limit);
+
+ if ($doc != '') {
+ if ($mode == 'xhtml') {
+ $open = '<div class="refnotes">' . DOKU_LF;
+ $close = '</div>' . DOKU_LF;
+ }
+ else {
+ $open = '';
+ $close = '';
+ }
+
+ $renderer->doc .= $open;
+ $renderer->doc .= $doc;
+ $renderer->doc .= $close;
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/refnotes/syntax/references.php b/platform/www/lib/plugins/refnotes/syntax/references.php
new file mode 100644
index 0000000..d4f8931
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/syntax/references.php
@@ -0,0 +1,348 @@
+<?php
+
+/**
+ * Plugin RefNotes: Reference collector/renderer
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+require_once(DOKU_PLUGIN . 'refnotes/core.php');
+require_once(DOKU_PLUGIN . 'refnotes/bibtex.php');
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class syntax_plugin_refnotes_references extends DokuWiki_Syntax_Plugin {
+ use refnotes_localization_plugin;
+
+ private $mode;
+ private $entryPattern;
+ private $exitPattern;
+ private $handlePattern;
+ private $noteCapture;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ refnotes_localization::initialize($this);
+
+ $this->mode = substr(get_class($this), 7);
+ $this->noteCapture = new refnotes_note_capture();
+
+ $this->initializePatterns();
+ }
+
+ /**
+ *
+ */
+ private function initializePatterns() {
+ /* Introduces changes to achive the format. Yet not perfect but similar to Pandoc (https://pandoc.org/MANUAL.html#footnotes):
+
+ This is the text[^n01].
+
+ [^n01:] this is the note.]
+
+ */
+ if (refnotes_configuration::getSetting('replace-footnotes')) {
+ $entry = '(?:\(\(|\[\()';
+ $exit = '(?:\)\)|\)\])';
+ $id = '@@FNT\d+|#\d+';
+ }
+ else {
+ $entry = '\[\^';
+ $exit = '\]';
+ $exit2 = '\.';
+ $id = '#\d+';
+ }
+
+ $strictName = refnotes_note::getNamePattern('strict');
+ $extendedName = refnotes_note::getNamePattern('extended');
+ $namespace = refnotes_namespace::getNamePattern('optional');
+
+ $text = '.*?';
+
+ $strictName = '(?:' . $id . '|' . $strictName . ')';
+ $fullName = '\s*(?:' . $namespace . $strictName . '|:' . $namespace . $extendedName . ')\s*';
+ $lookaheadExit = '(?=' . $exit . ')';
+ $nameEntry = $fullName . $lookaheadExit;
+
+ $extendedName = '(?:' . $id . '|' . $extendedName . ')';
+ $optionalFullName = $extendedName . '?';
+ $structuredEntry = '\s*' . $optionalFullName . '\s*>>' . $text . $lookaheadExit;
+
+ $define = '\s*' . $optionalFullName . '\s*:]\s*';
+ $optionalDefine = '(?:' . $define . ')?';
+ $lookaheadExit = '(?=' . $text . $exit . ')';
+ $defineEntry = $optionalDefine . $lookaheadExit;
+
+ $this->entryPattern = $entry . '(?:' . $nameEntry . '|' . $structuredEntry . '|' . $defineEntry . ')';
+ $this->exitPattern = $exit;
+ $this->handlePattern = '/' . $entry . '\s*(' . $optionalFullName . ')\s*(?:>>(.*))?(.*)/s';
+ }
+
+
+ /**
+ * What kind of syntax are we?
+ */
+ public function getType() {
+ return 'formatting';
+ }
+
+ /**
+ * What modes are allowed within our mode?
+ */
+ public function getAllowedTypes() {
+ return array (
+ 'formatting',
+ 'substition',
+ 'protected',
+ 'disabled'
+ );
+ }
+
+ /**
+ * Where to sort in?
+ */
+ public function getSort() {
+ return 145;
+ }
+
+ public function connectTo($mode) {
+ refnotes_parser_core::getInstance()->registerLexer($this->Lexer);
+
+ $this->Lexer->addEntryPattern($this->entryPattern, $mode, $this->mode);
+ }
+
+ public function postConnect() {
+ $this->Lexer->addExitPattern($this->exitPattern, $this->mode);
+ }
+
+ /**
+ * Handle the match
+ */
+ public function handle($match, $state, $pos, Doku_Handler $handler) {
+ $result = refnotes_parser_core::getInstance()->canHandle($state);
+
+ if ($result) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $result = $this->handleEnter($match);
+ break;
+
+ case DOKU_LEXER_EXIT:
+ $result = $this->handleExit();
+ break;
+ }
+ }
+
+ if ($result === false) {
+ $handler->addCall('cdata', array($match), $pos);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create output
+ */
+ public function render($mode, Doku_Renderer $renderer, $data) {
+ $result = false;
+
+ try {
+ switch ($mode) {
+ case 'xhtml':
+ case 'odt':
+ $result = $this->renderReferences($mode, $renderer, $data);
+ break;
+
+ case 'metadata':
+ $result = $this->renderMetadata($renderer, $data);
+ break;
+ }
+ }
+ catch (Exception $error) {
+ msg($error->getMessage(), -1);
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ private function handleEnter($syntax) {
+ if (preg_match($this->handlePattern, $syntax, $match) == 0) {
+ return false;
+ }
+
+ refnotes_parser_core::getInstance()->enterReference($match[1], $match[2]);
+
+ return array('start');
+ }
+
+ /**
+ *
+ */
+ private function handleExit() {
+ $reference = refnotes_parser_core::getInstance()->exitReference();
+
+ if ($reference->hasData()) {
+ return array('render', $reference->getAttributes(), $reference->getData());
+ }
+ else {
+ return array('render', $reference->getAttributes());
+ }
+ }
+
+ /**
+ *
+ */
+ public function renderReferences($mode, $renderer, $data) {
+ switch ($data[0]) {
+ case 'start':
+ $this->noteCapture->start($renderer);
+ break;
+
+ case 'render':
+ $this->renderReference($mode, $renderer, $data[1], (count($data) > 2) ? $data[2] : array());
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Stops renderer output capture and renders the reference link
+ */
+ private function renderReference($mode, $renderer, $attributes, $data) {
+ $reference = refnotes_renderer_core::getInstance()->addReference($attributes, $data);
+ $text = $this->noteCapture->stop();
+
+ if ($text != '') {
+ $reference->getNote()->setText($text);
+ }
+
+ $renderer->doc .= $reference->render($mode);
+ }
+
+ /**
+ *
+ */
+ public function renderMetadata($renderer, $data) {
+ if ($data[0] == 'render') {
+ $source = '';
+
+ if (array_key_exists('source', $data[1])) {
+ $source = $data[1]['source'];
+ }
+
+ if (($source != '') && ($source != '{configuration}')) {
+ $renderer->meta['plugin']['refnotes']['dbref'][wikiFN($source)] = true;
+ }
+ }
+
+ return true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_note_capture {
+
+ private $renderer;
+ private $note;
+ private $doc;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->initialize();
+ }
+
+ /**
+ *
+ */
+ private function initialize() {
+ $this->renderer = NULL;
+ $this->doc = '';
+ }
+
+ /**
+ *
+ */
+ private function resetCapture() {
+ $this->renderer->doc = '';
+ }
+
+ /**
+ *
+ */
+ public function start($renderer) {
+ $this->renderer = $renderer;
+ $this->doc = $renderer->doc;
+
+ $this->resetCapture();
+ }
+
+ /**
+ *
+ */
+ public function restart() {
+ $text = trim($this->renderer->doc);
+
+ $this->resetCapture();
+
+ return $text;
+ }
+
+ /**
+ *
+ */
+ public function stop() {
+ $text = trim($this->renderer->doc);
+
+ $this->renderer->doc = $this->doc;
+
+ $this->initialize();
+
+ return $text;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_nested_call_writer extends \dokuwiki\Parsing\Handler\Nest {
+
+ private $handler;
+ private $callWriterBackup;
+
+ /**
+ * Constructor
+ *
+ * HACK: Fix compatibility with PHP versions before 7.2 by passing handler as second optional
+ * argument. This makes constructor signature compatible with one defined in ReWriterInterface.
+ * Starting from PHP 7.2 this is not needed because arguments without type hint are compatible
+ * with any type since they have a wider type (any type).
+ * https://wiki.php.net/rfc/parameter-no-type-variance
+ */
+ public function __construct(\dokuwiki\Parsing\Handler\CallWriterInterface $callWriter, $handler = NULL) {
+ $this->handler = $handler;
+
+ parent::__construct($this->handler->getCallWriter());
+ }
+
+ /**
+ *
+ */
+ public function connect() {
+ $this->callWriterBackup = $this->handler->getCallWriter();
+
+ $this->handler->setCallWriter($this);
+ }
+
+ /**
+ *
+ */
+ public function disconnect() {
+ $this->handler->setCallWriter($this->callWriterBackup);
+ }
+}