summaryrefslogtreecommitdiff
path: root/platform/www/lib/plugins/refnotes/namespace.php
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/lib/plugins/refnotes/namespace.php')
-rw-r--r--platform/www/lib/plugins/refnotes/namespace.php463
1 files changed, 463 insertions, 0 deletions
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;
+ }
+ }
+}