phpgtk__legacy/include/PhpGtkDoc/Search2/Index.php

312 lines
No EOL
12 KiB
PHP

<?php
/**
* Class with all functionality needed to create the index for the
* php-gtk2-doc search class.
*
*
* Index explanation:
* - Only full words are found. If you search by "wind", "window" will *not* be found.
* - up to 2 with "_" connected full words are found, e.g. when searching for "this_is",
* the file "this_is_a_long_name" will be found, but searching for "this_is_a" will not
* bring the former result, as only 2 directly connected words are indexed.
* Note that full method names are indexed, too: "this_is_a_long_name" will be found
* by searching for "this_is_a_long_name"
* - The index is prioritized. This means that results are sorted by priority:
* (Example search: "window")
* - class names will be first
* found: gtk.gtkwindow.php, gdk.gdkwindow.php
* - methods which contain the search word in method name are second
* found: gdk.gdkdragcontext.property.dest_window.php
* - methods with the search word not in the direct method name are third
* found: gdk.gdkwindow.method.lower.php
*
* @author Christian Weiske <cweiske@php.net>
*/
class PhpGtkDoc_Search2_Index
{
protected static $arReserved = array(
'atk', 'gtk', 'gdk', 'scn', 'pango', 'method',
'property', 'prop', 'field', 'enum', 'signal', 'constructor'
);
protected static $arReservedMethods = array(
'get', 'set'
);
/**
* Creates the search index.
* Required parameters are the documentation directory
* and the index file, as which the index shall be stored.
*/
public static function createIndex($strDocumentationDirectory, $strIndexFile)
{
if (!file_exists($strDocumentationDirectory) || !is_dir($strDocumentationDirectory)) {
throw new Exception('Documentation directory does not exist: ' . $strDocumentationDirectory);
}
if ((file_exists($strIndexFile) && !is_writable($strIndexFile))
|| (!file_exists($strIndexFile) && !is_writable(dirname($strIndexFile)))) {
throw new Exception('Index file is not writable: ' . $strIndexFile);
}
file_put_contents($strIndexFile,
serialize(
self::buildIndexFromFiles(
self::getFiles($strDocumentationDirectory),
$strDocumentationDirectory
)
)
);
}//public static function createIndex($strDocumentationDirectory, $strIndexFile)
/**
* Creates an index array from the given files.
* The filenames are meant to be relative to the doc directory,
* so that e.g. "gdk/gdk.functions.html" or
* "gdk/gdk.gdkcolormap.method.get_screen.html" are in it.
*
* The index array has the following structure:
* [keyword]
* - [1] priority level
* - [doc file 1]
* - [doc file 2]
* - [2] priority level
* - [doc file 1]
* - [doc file 2]
* - [3] priority level
* - [doc file 1]
* - [doc file 2]
*
* Priorities:
* 1 class names, tutorial names, ...
* 2 methods, signals, enums
* 3 methods which have the keyword in the class name
*
* @param array $arFiles Array with all the files
* @param string $strDocumentationDirectory The directory which the file names are relative to
*
* @return array The index array, can be used with PhpGtkDoc_Search2
*/
protected static function buildIndexFromFiles($arFiles, $strDocumentationDirectory)
{
$arIndex = array();
$arCamelCaseWords = self::getCamelCaseWords(
self::getTitles(
self::getTitleFiles(
$arFiles
),
$strDocumentationDirectory
)
);
foreach ($arFiles as $strFile) {
$strBaseFile = basename($strFile);
$strBaseFile = substr($strBaseFile, 0, strrpos($strBaseFile, '.'));
$arPieces = explode('.', $strBaseFile);
//remove reserved words so that they are not indexed
//$arPieces = array_diff($arPieces, self::$arReserved);
//when uncommenting the last line, change all "$nCountWords > 2" to "$nCountWords > 1"
$arNewPieces = array();
$nCountWords = count($arPieces);
$nWordPos = -1;
foreach ($arPieces as $strWord) {
$nWordPos++;//the indices do not have constant values (array_diff)
if ($nWordPos == $nCountWords - 1 && $nCountWords > 2) {
//last word in the filename
$nPriority = 2;
} else {
//not the last word in the filename
$nPriority = 1;
}
$arNewPieces[$nPriority][] = $strWord;//the word itself
if (isset($arCamelCaseWords[$strWord])) {
$arNewPieces[$nPriority] = array_merge($arNewPieces[$nPriority], $arCamelCaseWords[$strWord]);
}
/*
$strPrefix = substr($strWord, 0, 3);
if ($strPrefix == 'gtk' || $strPrefix == 'gdk' || $strPrefix == 'atk' || $strPrefix == 'pan') {
//pango is 5 chars, all others are 3
$nCutPos = $strPrefix == 'pan' ? 5 : 3;
//classes have gtk or gdk at the beginning, e.g. gtkfixed or gtkoptionmenu
$arNewPieces[$nPriority][] = substr($strWord, $nCutPos);
if (isset($arCamelCaseWords[$strWord])) {
$arNewPieces[$nPriority] = array_merge($arNewPieces[$nPriority], $arCamelCaseWords[$strWord]);
}
}
*/
$arMethodPieces = explode( '_', $strWord);
if (count($arMethodPieces) > 1) {
//if you want to remove "get" and "set" from the index, uncomment the following line
//$arMethodPieces = array_diff( $arMethodPieces, self::$arReservedMethods);
$arNewPieces[2] = array_merge($arNewPieces[2], $arMethodPieces);
if (count( $arMethodPieces) > 2) {
//that we have some partly connections like do_this from do_this_thing
foreach ($arMethodPieces as $nId => $strPiece) {
if ($nId < count($arMethodPieces) - 1) {
$arNewPieces[2][] = $strPiece . '_' . $arMethodPieces[$nId + 1];
}
}
}
}
}//foreach piece
//append the search words to the index array
foreach ($arNewPieces as $nPriority => $arPriorityPieces) {
foreach ($arPriorityPieces as $strPiece) {
$arIndex[$strPiece][$nPriority][] = $strFile;
}
}
}
//sort the index | should speed up searching and is nice for debugging
ksort($arIndex);
return $arIndex;
}//protected static function buildIndexFromFiles($arFiles)
/**
* Returns an array of file names from the documentation directory.
* The file names are relative to the doc directory
*
* @param string $strDocumentationDirectory The directory of the compiled manual
* @return array All the files in there
*/
protected static function getFiles($strDocumentationDirectory)
{
$strDir = getcwd();
chdir($strDocumentationDirectory);
//php-gtk-web specific
#$arFiles = glob('*/*.{html,php}', GLOB_BRACE);
$arFiles = glob('*.php');
chdir($strDir);
if (count($arFiles) == 0) {
throw new Exception('No files found in ' . $strDocumentationDirectory);
}
return $arFiles;
}//protected static function getFiles($strDocumentationDirectory)
/**
* Returns an array of filenames that should contain title tags
* needed for the camelCase title splitter
*
* @param array $arFiles Array with files that (@see getFiles())
* @return array Array with files that should have needed titles
*/
protected static function getTitleFiles($arFiles)
{
$nFiles = count($arFiles);
for ($nA = 0; $nA < $nFiles; $nA++) {
//class files (gtk.gtktreeview.html) or enums (gtk.enum.selectionmode.html)
if (!preg_match('/^[a-z0-9]+\\.(enum\\.)?[a-z0-9]+\\.[a-z]+$/', basename($arFiles[$nA]))) {
unset($arFiles[$nA]);
}
}
return $arFiles;
}//protected static function getTitleFiles($arFiles)
/**
* Returns an array with the contents of the html title tags
* in the given files
*
* @param array $arFiles The files to check
* @param string $strDocumentationDirectory The directory the file names are relative to
*
* @return array Array of titles.
*/
protected static function getTitles($arFiles, $strDocumentationDirectory)
{
$arTitles = array();
foreach ($arFiles as $strFile) {
if (substr($strFile, -4) === '.php') {
//.php files (make phpweb) don't have a title header
if (preg_match('/manualHeader\\(\"(.+)\"\\,/', file_get_contents($strDocumentationDirectory . '/' . $strFile), $arMatches)) {
$arTitles[] = $arMatches[1];
}
} else {
if (preg_match('/<title>(.+)<\\/title>/', file_get_contents($strDocumentationDirectory . '/' . $strFile), $arMatches)) {
$arTitles[] = $arMatches[1];
}
}
}
return $arTitles;
}//protected static function getTitles($arFiles, $strDocumentationDirectory)
/**
* Splits all the titles from camelCase into several
* words (camel and case).
*
* @param array $arTitles The titles from the files
* @return array Array with strtolower(word) => split words array
*/
protected static function getCamelCaseWords($arTitles)
{
$arSplit = array();
foreach ($arTitles as $strTitle) {
if (strpos($strTitle, ' ') !== false) {
//will be tutorial title or "Gtk functions"
//we don't want this now.
continue;
}
$arSplit[strtolower($strTitle)] = self::varyWords(self::splitCamelCaseWord($strTitle));
}
return $arSplit;
}//protected static function getCamelCaseWords($arTitles)
/**
* Splits a camelCaseWord into words (camel, case, word)
*
* @param string $strWord The word to split
* @return array Array with lowercase words
*/
protected static function splitCamelCaseWord($strWord)
{
$strWords = preg_replace('/([A-Z])/', ' \\1', $strWord);
return explode(' ', strtolower($strWords));
}//protected static function splitCamelCaseWord($strWord)
/**
* Makes variations of an array of words.
* E.g. array(Gtk, Tree, View, Column) will get the variations
* gtktree, gtktreeview, treeview, treeviewcolumn, viewcolumn
* added to the word list
*
* @param string $arWords Array with words
* @return array Array with words and their variations
*/
protected static function varyWords($arWords)
{
$arVariations = $arWords;
for ($nA = 0; $nA < count($arWords); $nA++) {
$strVariation = '';
for ($nB = $nA; $nB < count($arWords); $nB++) {
$strVariation .= $arWords[$nB];
$arVariations[] = $strVariation;
}
}
return array_unique($arVariations);
}//protected static function varyWords($arWords)
}//class PhpGtkDoc_Search2_Index
?>