diff options
Diffstat (limited to '')
-rw-r--r-- | modules/doc/library/Doc/Search/DocSearch.php | 95 | ||||
-rw-r--r-- | modules/doc/library/Doc/Search/DocSearchIterator.php | 114 | ||||
-rw-r--r-- | modules/doc/library/Doc/Search/DocSearchMatch.php | 215 |
3 files changed, 424 insertions, 0 deletions
diff --git a/modules/doc/library/Doc/Search/DocSearch.php b/modules/doc/library/Doc/Search/DocSearch.php new file mode 100644 index 0000000..20493e4 --- /dev/null +++ b/modules/doc/library/Doc/Search/DocSearch.php @@ -0,0 +1,95 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Doc\Search; + +/** + * Search documentation for a given search string + */ +class DocSearch +{ + /** + * Search string + * + * @var string + */ + protected $input; + + /** + * Search criteria + * + * @var array + */ + protected $search; + + /** + * Create a new doc search from the given search string + * + * @param string $search + */ + public function __construct($search) + { + $this->input = $search = (string) $search; + $criteria = array(); + if (preg_match_all('/"(?P<search>[^"]*)"/', $search, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $unquoted = array(); + $offset = 0; + foreach ($matches as $match) { + $fullMatch = $match[0]; + $searchMatch = $match['search']; + $unquoted[] = substr($search, $offset, $fullMatch[1] - $offset); + $offset = $fullMatch[1] + strlen($fullMatch[0]); + if (strlen($searchMatch[0]) > 0) { + $criteria[] = $searchMatch[0]; + } + } + $unquoted[] = substr($search, $offset); + $search = implode(' ', $unquoted); + } + $this->search = array_map( + 'strtolower', + array_unique(array_merge($criteria, array_filter(explode(' ', trim($search))))) + ); + } + + /** + * Get the search criteria + * + * @return array + */ + public function getCriteria() + { + return $this->search; + } + + /** + * Get the search string + * + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * Search in the given line + * + * @param string $line + * + * @return DocSearchMatch|null + */ + public function search($line) + { + $match = new DocSearchMatch(); + $match->setLine($line); + foreach ($this->search as $criteria) { + $offset = 0; + while (($position = stripos($line, $criteria, $offset)) !== false) { + $match->appendMatch(substr($line, $position, strlen($criteria)), $position); + $offset = $position + 1; + } + } + return $match->isEmpty() ? null : $match; + } +} diff --git a/modules/doc/library/Doc/Search/DocSearchIterator.php b/modules/doc/library/Doc/Search/DocSearchIterator.php new file mode 100644 index 0000000..f262b5d --- /dev/null +++ b/modules/doc/library/Doc/Search/DocSearchIterator.php @@ -0,0 +1,114 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Doc\Search; + +use Icinga\Module\Doc\DocSection; +use RecursiveFilterIterator; +use RecursiveIteratorIterator; +use Icinga\Data\Tree\TreeNodeIterator; + +/** + * Iterator over doc sections that match a given search criteria + */ +class DocSearchIterator extends RecursiveFilterIterator +{ + /** + * Search criteria + * + * @var DocSearch + */ + protected $search; + + /** + * Current search matches + * + * @var DocSearchMatch[]|null + */ + protected $matches; + + /** + * Create a new iterator over doc sections that match the given search criteria + * + * @param TreeNodeIterator $iterator + * @param DocSearch $search + */ + public function __construct(TreeNodeIterator $iterator, DocSearch $search) + { + $this->search = $search; + parent::__construct($iterator); + } + + /** + * Accept sections that match the search + * + * @return bool Whether the current element of the iterator is acceptable + * through this filter + */ + public function accept(): bool + { + /** @var $section DocSection */ + $section = $this->current(); + $matches = array(); + if (($match = $this->search->search($section->getTitle())) !== null) { + $matches[] = $match->setMatchType(DocSearchMatch::MATCH_HEADER); + } + foreach ($section->getContent() as $lineno => $line) { + if (($match = $this->search->search($line)) !== null) { + $matches[] = $match + ->setMatchType(DocSearchMatch::MATCH_CONTENT) + ->setLineno($lineno); + } + } + if (! empty($matches)) { + $this->matches = $matches; + return true; + } + if ($section->hasChildren()) { + $this->matches = null; + return true; + } + return false; + } + + /** + * Get the search criteria + * + * @return DocSearch + */ + public function getSearch() + { + return $this->search; + } + + public function getChildren(): self + { + return new static($this->getInnerIterator()->getChildren(), $this->search); + } + + /** + * Whether the search did not yield any match + * + * @return bool + */ + public function isEmpty() + { + $iter = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST); + foreach ($iter as $section) { + if ($iter->getInnerIterator()->getMatches() !== null) { + return false; + } + } + return true; + } + + /** + * Get current matches + * + * @return DocSearchMatch[]|null + */ + public function getMatches() + { + return $this->matches; + } +} diff --git a/modules/doc/library/Doc/Search/DocSearchMatch.php b/modules/doc/library/Doc/Search/DocSearchMatch.php new file mode 100644 index 0000000..0f21748 --- /dev/null +++ b/modules/doc/library/Doc/Search/DocSearchMatch.php @@ -0,0 +1,215 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Doc\Search; + +use UnexpectedValueException; +use Icinga\Application\Icinga; +use Icinga\Web\View; + +/** + * A doc search match + */ +class DocSearchMatch +{ + /** + * CSS class for highlighting matches + * + * @var string + */ + const HIGHLIGHT_CSS_CLASS = 'search-highlight'; + + /** + * Header match + * + * @var int + */ + const MATCH_HEADER = 1; + + /** + * Content match + * + * @var int + */ + const MATCH_CONTENT = 2; + + /** + * Line + * + * @var string + */ + protected $line; + + /** + * Line number + * + * @var int + */ + protected $lineno; + + /** + * Type of the match + * + * @var int + */ + protected $matchType; + + /** + * Matches + * + * @var array + */ + protected $matches = array(); + + /** + * View + * + * @var View|null + */ + protected $view; + + /** + * Set the line + * + * @param string $line + * + * @return $this + */ + public function setLine($line) + { + $this->line = (string) $line; + return $this; + } + + /** + * Get the line + * + * @return string + */ + public function getLine() + { + return $this->line; + } + + /** + * Set the line number + * + * @param int $lineno + * + * @return $this + */ + public function setLineno($lineno) + { + $this->lineno = (int) $lineno; + return $this; + } + + /** + * Set the match type + * + * @param int $matchType + * + * @return $this + */ + public function setMatchType($matchType) + { + $matchType = (int) $matchType; + if ($matchType !== static::MATCH_HEADER && $matchType !== static::MATCH_CONTENT) { + throw new UnexpectedValueException(); + } + $this->matchType = $matchType; + return $this; + } + + /** + * Get the match type + * + * @return int + */ + public function getMatchType() + { + return $this->matchType; + } + + /** + * Append a match + * + * @param string $match + * @param int $position + * + * @return $this + */ + public function appendMatch($match, $position) + { + $this->matches[(int) $position] = (string) $match; + return $this; + } + + /** + * Get the matches + * + * @return array + */ + public function getMatches() + { + return $this->matches; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Get the line having matches highlighted + * + * @return string + */ + public function highlight() + { + $highlighted = ''; + $offset = 0; + $matches = $this->getMatches(); + ksort($matches); + foreach ($matches as $position => $match) { + $highlighted .= $this->getView()->escape(substr($this->line, $offset, $position - $offset)) + . '<span class="' . static::HIGHLIGHT_CSS_CLASS .'">' + . $this->getView()->escape($match) + . '</span>'; + $offset = $position + strlen($match); + } + $highlighted .= $this->getView()->escape(substr($this->line, $offset)); + return $highlighted; + } + + /** + * Whether the match is empty + * + * @return bool + */ + public function isEmpty() + { + return empty($this->matches); + } +} |