diff --git a/phpdotnet/phd/Index.php b/phpdotnet/phd/Index.php index 24be8ab6..f0e9cf15 100644 --- a/phpdotnet/phd/Index.php +++ b/phpdotnet/phd/Index.php @@ -83,7 +83,7 @@ class Index extends Format 'phpdoc' => 'PI_PHPDOCHandler', ); - private $db; + private IndexRepository $indexRepository; private $currentchunk; private $ids = array(); private $currentid; @@ -91,7 +91,6 @@ class Index extends Format private $isChunk = array(); protected $nfo = array(); private $isSectionChunk = array(); - private $log = ''; private $previousId = ""; private $inChangelog = false; private $currentChangelog = array(); @@ -101,6 +100,11 @@ class Index extends Format private $POST_REPLACEMENT_INDEXES = array(); private $POST_REPLACEMENT_VALUES = array(); + public function __construct(IndexRepository $indexRepository) { + $this->indexRepository = $indexRepository; + parent::__construct(); + } + public function transformFromMap($open, $tag, $name, $attrs, $props) { } public function TEXT($value) { @@ -112,50 +116,6 @@ public function createLink($for, &$desc = null, $type = Format::SDESC) { public function appendData($data) { } - /** - * Checks if indexing is needed. - * - * This is determined the following way: - * 0. If no index file exists, indexing is required. - * 1. If the config option --no-index is supplied, nothing is indexed - * 2. If the config option --force-index is supplied, indexing is required - * 3. If no option is given, the file modification time of the index and - * the manual docbook file are compared. If the index is older than - * the docbook file, indexing will be done. - * - * @return boolean True if indexing is required. - */ - final static public function requireIndexing() - { - $indexfile = Config::output_dir() . 'index.sqlite'; - if (!file_exists($indexfile)) { - return true; - } - - if (Config::no_index()) { - return false; - } - - if (Config::force_index()) { - return true; - } - - $db = new \SQLite3($indexfile); - $indexingCount = $db->query('SELECT COUNT(time) FROM indexing') - ->fetchArray(SQLITE3_NUM); - if ($indexingCount[0] == 0) { - return true; - } - - $indexing = $db->query('SELECT time FROM indexing') - ->fetchArray(SQLITE3_ASSOC); - $xmlLastModification = filemtime(Config::xml_file()); - if ($indexing['time'] > $xmlLastModification) { - return false; - } - return true; - } - public function update($event, $value = null) { switch($event) { @@ -172,58 +132,24 @@ public function update($event, $value = null) break; case Render::INIT: if ($value) { - if (Config::memoryindex()) { - $db = new \SQLite3(":memory:"); - } else { - $db = new \SQLite3(Config::output_dir() . 'index.sqlite'); - $db->exec('DROP TABLE IF EXISTS ids'); - $db->exec('DROP TABLE IF EXISTS indexing'); - $db->exec('DROP TABLE IF EXISTS changelogs'); - } - - $create = <<exec('PRAGMA default_synchronous=OFF'); - $db->exec('PRAGMA count_changes=OFF'); - $db->exec('PRAGMA cache_size=100000'); - $db->exec($create); - - if (Config::memoryindex()) { - Config::set_indexcache($db); - } - - $this->db = $db; + $this->indexRepository->init(); $this->chunks = array(); } else { print_r($this->chunks); } break; case Render::FINALIZE: - $retval = $this->db->exec("BEGIN TRANSACTION; INSERT INTO indexing (time) VALUES ('" . time() . "'); COMMIT"); - $this->commit(); - if ($this->db->lastErrorCode()) { - trigger_error($this->db->lastErrorMsg(), E_USER_WARNING); + $this->indexRepository->saveIndexingTime(time()); + if ($this->indexRepository->commit( + $this->commit, + $this->POST_REPLACEMENT_INDEXES, + $this->POST_REPLACEMENT_VALUES, + $this->changelog, + )) { + $this->commit = []; + } + if ($this->indexRepository->lastErrorCode()) { + trigger_error($this->indexRepository->lastErrorMsg(), E_USER_WARNING); } break; } @@ -266,7 +192,7 @@ protected function storeInfo($elm, $id, $filename, $isChunk = true) { ); // Append "next" to the previously inserted row if ($isChunk) { - $this->POST_REPLACEMENT_VALUES[$this->previousId] = $this->db->escapeString($id); + $this->POST_REPLACEMENT_VALUES[$this->previousId] = $id; $this->previousId = $id; } } @@ -285,34 +211,33 @@ public function appendID() { $sdesc = $lastChunk["sdesc"]; } - $this->commit[++$idx] = sprintf( - "INSERT INTO ids (docbook_id, filename, parent_id, sdesc, ldesc, element, previous, next, chunk) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d);\n", - $this->db->escapeString($lastChunkId), - $this->db->escapeString($lastChunk["filename"]), - $this->db->escapeString($this->currentchunk), - $this->db->escapeString($sdesc), - $this->db->escapeString($lastChunk["ldesc"]), - $this->db->escapeString($lastChunk["element"]), - $this->db->escapeString($lastChunk["previous"]), - $this->db->escapeString($lastChunk["chunk"] ? "POST-REPLACEMENT" : ""), - $this->db->escapeString($lastChunk["chunk"]) - ); + $this->commit[++$idx] = [ + "docbook_id" => $lastChunkId, + "filename" => $lastChunk["filename"], + "parent_id" => $this->currentchunk, + "sdesc" => $sdesc, + "ldesc" => $lastChunk["ldesc"], + "element" => $lastChunk["element"], + "previous" => $lastChunk["previous"], + "next" => ($lastChunk["chunk"] ? "POST-REPLACEMENT" : ""), + "chunk" => $lastChunk["chunk"], + ]; if ($lastChunk["chunk"]) { $this->POST_REPLACEMENT_INDEXES[] = array("docbook_id" => $lastChunkId, "idx" => $idx); } if ($array === true) { foreach($lastChunk["sdesc"] as $sdesc) { - $this->commit[++$idx] = sprintf( - "INSERT INTO ids (docbook_id, filename, parent_id, sdesc, ldesc, element, previous, next, chunk) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', 0);\n", - $this->db->escapeString($lastChunkId), - $this->db->escapeString($lastChunk["filename"]), - $this->db->escapeString($this->currentchunk), - $this->db->escapeString($sdesc), - $this->db->escapeString($lastChunk["ldesc"]), - $this->db->escapeString($lastChunk["element"]), - $this->db->escapeString($lastChunk["previous"]), - $this->db->escapeString($lastChunk["chunk"] ? "POST-REPLACEMENT" : "") - ); + $this->commit[++$idx] = [ + "docbook_id" => $lastChunkId, + "filename" => $lastChunk["filename"], + "parent_id" => $this->currentchunk, + "sdesc" => $sdesc, + "ldesc" => $lastChunk["ldesc"], + "element" => $lastChunk["element"], + "previous" => $lastChunk["previous"], + "next" => ($lastChunk["chunk"] ? "POST-REPLACEMENT" : ""), + "chunk" => 0, + ]; $this->POST_REPLACEMENT_INDEXES[] = array("docbook_id" => $lastChunkId, "idx" => $idx); } } @@ -475,44 +400,6 @@ public function format_row($open, $name, $attrs, $props) { } } - - - public function commit() { - if (isset($this->commit) && $this->commit) { - $search = $this->db->escapeString("POST-REPLACEMENT"); - $none = $this->db->escapeString(""); - - foreach($this->POST_REPLACEMENT_INDEXES as $a) { - if (isset($this->POST_REPLACEMENT_VALUES[$a["docbook_id"]])) { - $replacement = $this->POST_REPLACEMENT_VALUES[$a["docbook_id"]]; - $this->commit[$a["idx"]] = str_replace($search, $replacement, $this->commit[$a["idx"]]); - } else { - // If there are still post replacement, then they don't have - // any 'next' page - $this->commit[$a["idx"]] = str_replace($search, $none, $this->commit[$a["idx"]]); - } - } - - $this->db->exec('BEGIN TRANSACTION; '.implode("", $this->commit).' COMMIT'); - $log = ""; - foreach($this->changelog as $id => $arr) { - foreach($arr as $entry) { - $log .= sprintf( - "INSERT INTO changelogs (membership, docbook_id, parent_id, version, description) VALUES('%s', '%s', '%s', '%s', '%s');\n", - $this->db->escapeString($entry[0] ?? ''), - $this->db->escapeString($id), - $this->db->escapeString($entry[1]), - $this->db->escapeString($entry[2]), - $this->db->escapeString($entry[3]) - ); - } - } - $this->db->exec('BEGIN TRANSACTION; ' . $log. ' COMMIT'); - $this->log = ""; - $this->commit = array(); - } - } - public function processFilename() { static $dbhtml = null; if ($dbhtml == null) { diff --git a/phpdotnet/phd/IndexRepository.php b/phpdotnet/phd/IndexRepository.php new file mode 100644 index 00000000..24758006 --- /dev/null +++ b/phpdotnet/phd/IndexRepository.php @@ -0,0 +1,119 @@ +db->exec('DROP TABLE IF EXISTS ids'); + $this->db->exec('DROP TABLE IF EXISTS indexing'); + $this->db->exec('DROP TABLE IF EXISTS changelogs'); + $this->db->exec('PRAGMA default_synchronous=OFF'); + $this->db->exec('PRAGMA count_changes=OFF'); + $this->db->exec('PRAGMA cache_size=100000'); + $this->db->exec($create); + } + + public function saveIndexingTime(int $time): void { + $this->db->exec("BEGIN TRANSACTION; INSERT INTO indexing (time) VALUES ('" . $time . "'); COMMIT"); + } + + public function commit( + array $commitList, + array $postReplacementIndexes, + array $postReplacementValues, + array $changelog, + ): bool { + if (!$commitList) { + return false; + } + + foreach ($postReplacementValues as $key => $postReplacementValue) { + $postReplacementValues[$key] = $this->db->escapeString($postReplacementValue); + } + + foreach ($commitList as $key => $commit) { + $commitList[$key] = sprintf( + "INSERT INTO ids (docbook_id, filename, parent_id, sdesc, ldesc, element, previous, next, chunk) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d);\n", + $this->db->escapeString($commit["docbook_id"]), + $this->db->escapeString($commit["filename"]), + $this->db->escapeString($commit["parent_id"]), + $this->db->escapeString($commit["sdesc"]), + $this->db->escapeString($commit["ldesc"]), + $this->db->escapeString($commit["element"]), + $this->db->escapeString($commit["previous"]), + $this->db->escapeString($commit["next"]), + $this->db->escapeString($commit["chunk"]) + ); + } + + $search = $this->db->escapeString("POST-REPLACEMENT"); + $none = $this->db->escapeString(""); + + foreach($postReplacementIndexes as $a) { + if (isset($postReplacementValues[$a["docbook_id"]])) { + $replacement = $postReplacementValues[$a["docbook_id"]]; + $commitList[$a["idx"]] = str_replace($search, $replacement, $commitList[$a["idx"]]); + } else { + // If there are still post replacement, then they don't have + // any 'next' page + $commitList[$a["idx"]] = str_replace($search, $none, $commitList[$a["idx"]]); + } + } + + $this->db->exec('BEGIN TRANSACTION; '.implode("", $commitList).' COMMIT'); + $this->saveChangelogs($changelog); + return true; + } + + private function saveChangelogs(array $changelog): void { + $log = ""; + foreach($changelog as $id => $arr) { + foreach($arr as $entry) { + $log .= sprintf( + "INSERT INTO changelogs (membership, docbook_id, parent_id, version, description) VALUES('%s', '%s', '%s', '%s', '%s');\n", + $this->db->escapeString($entry[0] ?? ''), + $this->db->escapeString($id), + $this->db->escapeString($entry[1]), + $this->db->escapeString($entry[2]), + $this->db->escapeString($entry[3]) + ); + } + } + $this->db->exec('BEGIN TRANSACTION; ' . $log. ' COMMIT'); + } + + public function lastErrorCode(): int { + return $this->db->lastErrorCode(); + } + + public function lastErrorMsg(): string { + return $this->db->lastErrorMsg(); + } +} diff --git a/phpdotnet/phd/TestRender.php b/phpdotnet/phd/TestRender.php index d026150b..047dfeec 100644 --- a/phpdotnet/phd/TestRender.php +++ b/phpdotnet/phd/TestRender.php @@ -10,7 +10,7 @@ public function __construct( ) {} public function run() { - if ($this->index && $this->index::requireIndexing()) { + if ($this->index && requireIndexing($this->config)) { if (!file_exists($this->config->output_dir())) { mkdir($this->config->output_dir(), 0755); } diff --git a/phpdotnet/phd/functions.php b/phpdotnet/phd/functions.php index 0d443f1f..013d2d2d 100644 --- a/phpdotnet/phd/functions.php +++ b/phpdotnet/phd/functions.php @@ -182,4 +182,49 @@ function errh($errno, $msg, $file, $line, $ctx = null) { set_error_handler(__NAMESPACE__ . '\\errh'); /* }}} */ +/** + * Checks if indexing is needed. + * + * This is determined the following way: + * 0. If no index file exists, indexing is required. + * 1. If the config option --no-index is supplied, nothing is indexed + * 2. If the config option --force-index is supplied, indexing is required + * 3. If no option is given, the file modification time of the index and + * the manual docbook file are compared. If the index is older than + * the docbook file, indexing will be done. + * + * @return boolean True if indexing is required. + */ +function requireIndexing(Config $config, ?\SQLite3 $db = null): bool { + if ($db === null) { + $indexfile = $config->output_dir() . 'index.sqlite'; + if (!file_exists($indexfile)) { + return true; + } + } + + if ($config->no_index()) { + return false; + } + + if ($config->force_index()) { + return true; + } + if ($db === null) { + $db = new \SQLite3($indexfile); + } + $indexingCount = $db->query('SELECT COUNT(time) FROM indexing') + ->fetchArray(SQLITE3_NUM); + if ($indexingCount[0] == 0) { + return true; + } + + $indexing = $db->query('SELECT time FROM indexing') + ->fetchArray(SQLITE3_ASSOC); + $xmlLastModification = filemtime($config->xml_file()); + if ($indexing['time'] > $xmlLastModification) { + return false; + } + return true; +} diff --git a/render.php b/render.php index c12ea23a..52c80303 100644 --- a/render.php +++ b/render.php @@ -95,16 +95,32 @@ function make_reader() { $readerOpts |= LIBXML_XINCLUDE; } +if (file_exists(Config::output_dir() . 'index.sqlite')) { + $db = new \SQLite3(Config::output_dir() . 'index.sqlite'); +} else { + $db = null; +} + // Indexing -if (Index::requireIndexing()) { +if (requireIndexing(new Config, $db)) { v("Indexing...", VERBOSE_INDEXING); + if (Config::memoryindex()) { + $db = new \SQLite3(":memory:"); + } else { + $db = $db ?? new \SQLite3(Config::output_dir() . 'index.sqlite'); + } // Create indexer - $format = new Index; + $indexRepository = new IndexRepository($db); + $format = new Index($indexRepository); $render->attach($format); $reader->open(Config::xml_file(), NULL, $readerOpts); $render->execute($reader); + if (Config::memoryindex()) { + Config::set_indexcache($db); + } + $render->detach($format); v("Indexing done", VERBOSE_INDEXING); diff --git a/tests/index/bug_GH-98.phpt b/tests/index/bug_GH-98.phpt index dcc9e434..2a14ee96 100644 --- a/tests/index/bug_GH-98.phpt +++ b/tests/index/bug_GH-98.phpt @@ -13,7 +13,10 @@ Config::init([ "xml_file" => $xml_file, ]); -$index = new TestIndex; +$indexRepository = new IndexRepository(new \SQLite3(":memory:")); +$indexRepository->init(); + +$index = new TestIndex($indexRepository); $render = new TestRender(new Reader, new Config, null, $index); $render->run(); diff --git a/tests/index/data/indexing_001.xml b/tests/index/data/indexing_001.xml new file mode 100644 index 00000000..2559d690 --- /dev/null +++ b/tests/index/data/indexing_001.xml @@ -0,0 +1,120 @@ + + + + PHP Manual + + + + + + AB + + + + + + 1997- + the PHP Documentation Group + + + + Copyright + + If you are interested in redistribution or republishing of this document + in whole or in part, either modified or unmodified, and you have questions, + please contact the Copyright holders at + doc-license@lists.php.net. + Note that this address is mapped to a publicly archived mailing list. + + + + + + PHP Manual + + + + Preface + + + PHP, which stands for "PHP: Hypertext + Preprocessor" is a widely-used Open Source general-purpose + scripting language + + + + +
+ Authors and Contributors + +
+ Authors and Editors + + The following contributors should be + recognized for the impact they have made and/or continue to make by adding + content to the manual: + +
+
+
+
+ + + Installing/Configuring +
+ Requirements + + As of version 1.16.0, the driver requires PHP 7.2 or higher. Previous + versions of the driver allow compatibility with older PHP versions. + +
+
+ + + + Introduction + +
+ What is PHP? + + PHP (recursive acronym for PHP: Hypertext + Preprocessor) is a widely-used open source general-purpose + scripting language that is especially suited for web + development and can be embedded into HTML. + +
+
+
+ + + Predefined Constants + + The constants below are defined by this extension, and + will only be available when the extension has either + been compiled into PHP or dynamically loaded at runtime. + + + + + Predefined Interfaces and Classes + + + See also the SPL Interfaces and reserved classes. + + + + + + The <interfacename>Traversable</interfacename> interface + Traversable + +
+ Changelog + + Changes + +
+
+
+ +
diff --git a/tests/index/indexing_001.phpt b/tests/index/indexing_001.phpt new file mode 100644 index 00000000..92c24dd4 --- /dev/null +++ b/tests/index/indexing_001.phpt @@ -0,0 +1,63 @@ +--TEST-- +Indexing 001 - Basic indexing +--FILE-- + true, + "xml_file" => $xml_file, +]); + +$indexRepository = new IndexRepository(new \SQLite3(":memory:")); +$indexRepository->init(); + +$index = new TestIndex($indexRepository); +$render = new TestRender(new Reader, new Config, null, $index); + +$render->run(); + +$indexes = array_keys($index->getNfo()); + +echo "Indexes stored:\n"; + +var_dump($indexes); +?> +--EXPECT-- +Indexes stored: +array(15) { + [0]=> + string(5) "index" + [1]=> + string(8) "bookinfo" + [2]=> + string(7) "authors" + [3]=> + string(9) "copyright" + [4]=> + string(6) "manual" + [5]=> + string(7) "preface" + [6]=> + string(12) "contributors" + [7]=> + string(13) "mongodb.setup" + [8]=> + string(20) "mongodb.requirements" + [9]=> + string(13) "chapterInBook" + [10]=> + string(12) "introduction" + [11]=> + string(12) "intro-whatis" + [12]=> + string(14) "apcu.constants" + [13]=> + string(19) "reserved.interfaces" + [14]=> + string(17) "class.traversable" +}