From 85e16a4a010edf0f3d467e42471a7d5aae231a2d Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 11 Sep 2007 22:00:00 +0000 Subject: [PATCH] Initial revision --- bibtexbrowser.php | 1224 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1224 insertions(+) create mode 100755 bibtexbrowser.php diff --git a/bibtexbrowser.php b/bibtexbrowser.php new file mode 100755 index 0000000..1b9dc69 --- /dev/null +++ b/bibtexbrowser.php @@ -0,0 +1,1224 @@ +/bibtexbrowser.php +* displays the menu and all entries without filtering from the $filename hardcoded in the script +* +* /bibtexbrowser.php?bib=bibfile.bib +* displays the menu and all entries without filtering from the file bibfile.bib +* +* /bibtexbrowser.php?bib=bibfile.bib&all +* displays all entries +* +* /bibtexbrowser.php?bib=bibfile.bib&year=2004 +* displays all entries from year 2004 +* +* /bibtexbrowser.php?bib=bibfile.bib&author="James+Russel" +* displays all entries from author James Russel +* +* /bibtexbrowser.php?bib=bibfile.bib&tag=france +* displays all entries with tag france +* @book{discours-de-la-methode, +* author = "René Descartes", +* title = "Discours de la M{\'{e}}thode", +* year = 1637, +* tag="france and seventeenth century" +* } +*/ + +define('READLINE_LIMIT',1024); +define('PAGE_SIZE',25); + +define('YEAR_SIZE',10); +define('Q_YEAR', 'year'); +define('Q_YEAR_PAGE', 'year_page'); + +define('Q_FILE', 'bib'); + +define('AUTHORS_SIZE',20); +define('Q_AUTHOR', 'author'); +define('Q_AUTHOR_PAGE', 'author_page'); + +define('TAGS_SIZE',20); +define('Q_TAG', 'tag'); +define('Q_TAG_PAGE', 'tag_page'); + +define('Q_TYPE', 'type'); +define('Q_TYPE_PAGE', 'type_page'); + +define('Q_ALL', 'all'); +define('Q_ENTRY', 'entry'); +define('Q_SEARCH', 'search'); +define('Q_RESULT', 'result'); + +define('AUTHOR', 'author'); +define('EDITOR', 'editor'); +define('TITLE', 'title'); +define('YEAR', 'year'); + +session_start(); + +// default bib file, if no file is specified in the query string. +$filename = "uml.bib"; + +// retrieve the filename sent as query or hidden data, if exists. +if (isset($_GET[Q_FILE])) { + $filename = urldecode($_GET[Q_FILE]); +} + +// parse a new bib file, if requested +if (($filename == $_SESSION[Q_FILE]) && isset($_SESSION['main'])) { + $dispmgr = $_SESSION['main']; +} else { + $dispmgr = new DisplayManager(new BibDataBase($filename)); +} + +$_SESSION[Q_FILE] = $filename; +// stores the information in the session for performance +$_SESSION['main'] = & $dispmgr; + + + + +//////////////////////////////////////////////////////// + + +/** + * Class to parse a bibtex file. + */ +class BibParser { + + /** A hashtable from IDs (number) to bib entries (BibEntry). */ + var $bibdb; + + /** Parses the given bibtex file and stores all entries to $bibdb. */ + //@ assignable $bibdb; + function BibParser($filename) { + $file = fopen($filename, 'r'); + $entry =$this->parseEntry($file); + while ($entry) { + $this->bibdb[$entry->getId()] = $entry; + //if ($entry->getId() >= 500) { return; } // !FIXME! + $entry =$this->parseEntry($file); + //print_r($entry); + } + fclose($file); + //print_r($this->bibdb); + } + + /** Returns the array of parsed bib entires. */ + function getEntries() { + return $this->bibdb; + } + + /** Parses and returns the next bib entry from the fiven file. If + * no more entry exist, NULL is returned. */ + function parseEntry($file) { + // parse bib type, e.g., @BOOK{ + while (true) { + $raw_line = $this->nextLine($file); + //echo 'RAW: ' . $raw_line; + if (!$raw_line) { // EOF? + return NULL; + } + $line = trim($raw_line); + if (ereg("^@.*{", $line)) { + //echo 'NEW: ' . $line . "\n"; + $type = trim(substr($line, 1, strpos($line,'{') - 1)); + $fields = array(); + $raw_bib = $raw_line; + break; + } + } + + // parse fields, if any + $raw_line = $this->nextLine($file); + while ($raw_line) { + $raw_bib .= $raw_line; + + $line = trim($raw_line); + if (ereg("^.*=", $line)) { // new field? + //echo 'FIELD: ' . $line . "\n"; + //get the field type + $ps = strpos($line, '='); + $fkey = strtolower(trim(substr($line,0,$ps))); + $fval = $this->extractFieldValue($line); + if (strlen($fval)) { + $fields[$fkey] = $fval; + } + if (ereg(",[:space:]*}$",$line)) return $this->makeBibEntry($type, $fields, $raw_bib); + if (ereg("=[^{]*}$",$line)) return $this->makeBibEntry($type, $fields, $raw_bib); + } else if ($line == "}") { // end of entry? + //echo 'END: ' . $line . "\n"; + return $this->makeBibEntry($type, $fields, $raw_bib); + } else { // continued field? + $fval = $this->extractFieldValue($line); + if (strlen($fval) > 0) { + if (!isset($fields[$fkey])) { // no value seen so far? + // remove starting " if exists + if ($fval[0] == '"') { + $fval = ltrim(substr($fval, 1)); + } + } + if (strlen($fval)) { + if (isset($fields[$fkey])) { + $fields[$fkey] = $fields[$fkey].' '.$fval; + } else { + $fields[$fkey] = $fval; + } + } + } + } + + $raw_line = $this->nextLine($file); + } + + // entry ended without a closing brace + return $this->makeBibEntry($type, $fields, $raw_bib); + } + + /** Creates and return a bib entry by doing any postprogessing to + * the arguments. E.g., canonical rep. of type names. */ + function makeBibEntry($type, $fields, $raw_bib) { + // remove a trailing comma, if exists. + foreach ($fields as $name => $value) { + $fields[$name] = rtrim($value, ','); + } + return new BibEntry($this->stdType($type), $fields, $raw_bib); + } + + /** Returns the canonical representation of the given type name. */ + function stdType($type) { + static $types = array(); + foreach ($types as $t) { + if (strcasecmp($t, $type) == 0) { + return $t; + } + } + $type = ucfirst($type); + $types[] = $type; + return $type; + } + + /** Extracts and returns a field value from the given line. */ + function extractFieldValue($line) { + + $result = ereg_replace("^[^=]*=[ :space:]*", '', $line); + // clean out tex stuff + $result = str_replace('}','', $result); + $result = str_replace('{','', $result); + $result = str_replace(',','', $result); + $result = str_replace("\'", '', $result); // e.g., \'{e} + $result = str_replace('\`', '', $result); + $result = str_replace('\^', '', $result); + $result = str_replace('"', '', $result); + $result = str_replace('\~', '', $result); + $result = str_replace('\.', '', $result); + $result = str_replace('\u', '', $result); + $result = str_replace('\v', '', $result); + $result = str_replace('\H', '', $result); + $result = str_replace('\t', '', $result); + $result = str_replace('\c', '', $result); + $result = str_replace('\d', '', $result); + $result = str_replace('\b', '', $result); + $result = str_replace('\i', 'i', $result); + $result = str_replace('\j`', 'j', $result); + $result = str_replace('\j`', 'j', $result); + $result = str_replace('\ ', ' ',$result); // space + + return trim($result); + } + + /** Returns the next non-empty line; returns NULL upon end-of-file. */ + function nextLine($file) { + $rawline = fgets($file, READLINE_LIMIT); + while (!feof($file)) { + //echo "RAW: " . $rawline; + $line = trim($rawline); + if (strpos($line, '@string') === false // !FIXME!@string ignored! + && strlen($line) != 0 && $line[0] != '%') { + return $rawline; + } + $rawline = fgets($file, READLINE_LIMIT); + } + return NULL; + } +} + + +/** A class to parse a single bib entry starting from the given + * source code line. */ +class SingleEntryParser extends BibParser { + function SingleEntryParser() { + } + + /** Parses and returns an entry. */ + function parse($filename, $startIndex) { + $file = fopen($filename, 'r'); + while ($startIndex > 0) { + fgets($file, READLINE_LIMIT); + $startIndex--; + } + $e =& $this->parseEntry($file); + fclose($file); + return $e; + } +} + +// ---------------------------------------------------------------------- +// BIB ENTRIES +// ---------------------------------------------------------------------- + +/** + * Class to represent a bibliographic entry. + */ +class BibEntry { + + /** The type (e.g., article and book) of this bib entry. */ + var $type; + + /** The fields (fieldName -> value) of this bib entry. */ + var $fields; + + /** The verbatim copy (i.e., whole text) of this bib entry. */ + var $text; + + /** Creates a new bib entry. Each bib entry is assigned a unique + * identification number. */ + function BibEntry($type, $fields = array(), $text = '') { + static $id = 0; + $this->id = $id++; + $this->type = $type; + $this->fields =& $fields; + $this->text =& $text; + } + + /** Returns the type of this bib entry. */ + function getType() { + return $this->type; + } + + /** Has this entry the given field? */ + function hasField($name) { + return array_key_exists($name, $this->fields); + } + + /** Returns the authors of this entry. If no author field exists, + * returns the editors. If none of authors and editors exists, + * return a string 'Unknown'. */ + function getAuthor() { + if (array_key_exists(AUTHOR, $this->fields)) { + return $this->fields[AUTHOR]; + } + if (array_key_exists(EDITOR, $this->fields)) { + return $this->fields[EDITOR]; + } + return 'Unknown'; + } + + /** Returns the title of this entry? */ + function getTitle() { + return $this->getField('title'); + } + + /** Returns the value of the given field? */ + function getField($name) { + if ($this->hasField($name)) + {return $this->fields[$name];} + else return 'missing '.$name; + } + + + + /** Returns the fields */ + function getFields() { + return $this->fields; + } + + /** Returns the identification number. */ + function getId() { + return $this->id; + } + + /** Returns the verbatim text of this bib entry. */ + function getText() { + return $this->text; + } + + /** Returns true if this bib entry contains the given phrase + * in the given fields. The argument $fields is an array of + * field names; if null, all fields are considered. */ + function hasPhrase($phrase, $fields = null) { + if (!$fields) { + return strpos(strtolower($this->getText()), $phrase) !== false; + } + foreach ($fields as $f) { + if ($this->hasField($f) && + strpos(strtolower($this->getField($f)), $phrase) !== false) { + return true; + } + } + return false; + } +} + +// ---------------------------------------------------------------------- +// DISPLAY MANAGEMENT +// ---------------------------------------------------------------------- + +/** + * Given a query, an array of key value pairs, returns a href string + * of the form: href="bibtex.php?bib=testing.bib&search=JML. + */ +function makeHref($query = NULL) { + global $filename; + + $qstring = Q_FILE .'='. urlencode($filename); + if ($query) { + foreach ($query as $key => $val) { + $qstring .= '&'. $key .'='. $val; + } + } + return 'href="'. $_SERVER['PHP_SELF'] .'?'. $qstring .'"'; +} + +/** + * Returns the formated author name. The argument is assumed to be + * , and the return value is . + */ +function formatAuthor($author){ + $author = trim($author); + return trim(!strrchr($author, ' ') ? + $author : + strrchr($author, ' ') . ', ' + .substr($author, 0, strrpos($author, " "))); +} + +/** + * Returns a compacted string form of author names by throwing away + * all author names except for the first one and appending ", et al." + */ +function compactAuthor($author){ + $authors = explode(" and ", $author); + $etal = count($authors) > 1 ? ', et al.' : ''; + return formatAuthor($authors[0]) . $etal; +} + +/** + * A class providing GUI views and controllers. In general, the views + * are tables that can be incorporated into bigger GUI tables. + */ +class DisplayManager { + + /** The bibliographic database, an instance of class BibDataBase. */ + var $db; + + /** Creates a new display manager that uses the given bib database. */ + function DisplayManager(&$db) { + $this->db =& $db; + } + + /** Displays the title in a table. */ + function titleView() { + global $filename; + ?> + + + + +
Generated from
+ +
+ + +
+ +
+
+ + + + + + + + + db->getTypes() as $type) { + $types[$type] = $type; + } + + // retreive or calculate page number to display + if (isset($_GET[Q_TYPE_PAGE])) { + $page = $_GET[Q_TYPE_PAGE]; + } + else $page = 1; + + $this->displayMenu('Types', $types, $page, 10, Q_TYPE_PAGE, Q_TYPE); + } + + /** Displays and controls the authors menu in a table. */ + function authorVC() { + // retrieve authors list to display + $authors = $this->db->authorIndex(); + + // determine the authors page to display + if (isset($_GET[Q_AUTHOR_PAGE])) { + $page = $_GET[Q_AUTHOR_PAGE]; + } + else $page = 1; + + + $this->displayMenu('Authors', $authors, $page, AUTHORS_SIZE, Q_AUTHOR_PAGE, + Q_AUTHOR); + } + + /** Displays and controls the tag menu in a table. */ + function tagVC() { + // retrieve authors list to display + $tags = $this->db->tagIndex(); + + // determine the authors page to display + if (isset($_GET[Q_TAG_PAGE])) { + $page = $_GET[Q_TAG_PAGE]; + } else $page = 1; + + + $this->displayMenu('Tags', $tags, $page, TAGS_SIZE, Q_TAG_PAGE, + Q_TAG); + } + + /** Displays and controls the tag menu in a table. */ + function yearVC() { + // retrieve authors list to display + $years = $this->db->yearIndex(); + + // determine the authors page to display + if (isset($_GET[Q_YEAR_PAGE])) { + $page = $_GET[Q_YEAR_PAGE]; + } +else $page = 1; + + + $this->displayMenu('Years', $years, $page, YEAR_SIZE, Q_YEAR_PAGE, + Q_YEAR); + } + + + /** Displays and controls the main contents (or result) view. */ + function mainVC() { + $result = null; + if (isset($_GET[Q_ENTRY])){ + $result = new SingleResultDisplay( + $this->db->getEntry( + $_GET[Q_ENTRY])); + } else if (isset($_GET[Q_SEARCH])){ // search? + $to_find = $_GET[Q_SEARCH]; + $searched = $this->db->search($to_find); + $header = 'Search: ' . trim($to_find); + $result = new ResultDisplay($searched, $header,array(Q_SEARCH => $to_find)); + // clicking an author, a menu item from the authors menu? + } else if (isset($_GET[Q_AUTHOR])) { + $to_find = urldecode($_GET[Q_AUTHOR]); + $searched = $this->db->search($to_find, array('author')); + $header = 'Author: ' . ucwords($to_find); + $result = new ResultDisplay($searched, $header,array(Q_AUTHOR => $to_find)); + // clicking a type, a menu item from the types menu? + } else if(isset($_GET[Q_TAG])) { + $to_find = $_GET[Q_TAG]; + $searched = $this->db->search($to_find, array('tag')); + $header = 'Tag: ' . ucwords($to_find); + $result = new ResultDisplay($searched, $header,array(Q_TAG => $to_find)); + } + else if(isset($_GET[Q_YEAR])) { + $to_find = $_GET[Q_YEAR]; + $searched = $this->db->search($to_find, array('year')); + $header = 'Year: ' . ucwords($to_find); + $result = new ResultDisplay($searched, $header,array(Q_YEAR => $to_find)); + } + else if(isset($_GET[Q_TYPE])) { + $to_find = $_GET[Q_TYPE]; + $searched = $this->db->searchType($to_find); + $header = 'Type: ' . ucwords($to_find); + $result = new ResultDisplay($searched, $header,array(Q_TYPE => $to_find)); + } + else if(isset($_GET[Q_ALL])) { + $to_find = $_GET[Q_ALL]; + $searched = array_values($this->db->bibdb); + $header = 'All'; + $result = new ResultDisplay($searched, $header,array(Q_ALL =>'')); + } + + // requesting a different page of the result view? + if (isset($_GET[Q_RESULT])) { + $result->setPage($_GET[Q_RESULT]); + // requesting a differen page of type or author menus? + } + + // display + return $result; + } + + /** Displays a list menu in a table. + * + * $title: title of the menu (string) + * $list: list of menu items (string) + * $page: page number to display (number) + * $pageSize: size of each page + * $pageKey: URL query name to send the page number to the server + * $targetKey: URL query name to send the target of the menu item + */ + function displayMenu($title, $list, $page, $pageSize, $pageKey, + $targetKey) { + $numEntries = count($list); + $startIndex = ($page - 1) * $pageSize; + $endIndex = $startIndex + $pageSize; + ?> + + + + + + + + + 0) { + $href = makeHref(array($queryKey => $page - 2,'menu'=>'')); + $result .= '«\n"; + } + + // (1 page) reverse (<) + if ($start > 0) { + $href = makeHref(array($queryKey => $page - 1,'menu'=>'')); + $result .= '<\n"; + } + + // (1 page) forward (>) + if ($end < $numEntries) { + $href = makeHref(array($queryKey => $page + 1,'menu'=>'')); + $result .= '>\n"; + } + + // fast (2 pages) forward (>>) + if (($end + $pageSize) < $numEntries) { + $href = makeHref(array($queryKey => $page + 2,'menu'=>'')); + $result .= '»\n"; + } + return $result; + } + + /** + * Displays menu items (anchors) from the start index (inclusive) to + * the end index (exclusive). For each menu, the following form of + * string is printed: + * + * + * Cheon, Yoonsik + *
+ */ + function displayMenuItems($items, $startIndex, $endIndex, $queryKey) { + $index = 0; + foreach ($items as $key => $item) { + if ($index >= $startIndex && $index < $endIndex) { + $href = makeHref(array($queryKey => urlencode($key))); + echo ''. $item ."\n"; + echo "
\n"; + } + $index++; + } + } +} + +/** Class to display a search result, a list of bib entries. */ +class ResultDisplay { + /** the bib entries to display. */ + var $result; + + /** the header string. */ + var $header; + + /** the page number to display. */ + var $page; + + /** the original filter author, year, etc */ + var $filter; + + + /** Creates an instance with the given entries and header. */ + function ResultDisplay(&$result, $header,$filter) { + $this->result =& $result; + $this->header = $header; + $this->page = 1; + $this->filter = $filter; + } + + /** Sets the page number to display. */ + function setPage($page) { + $this->page = $page; + } + + /** Displays the entries preceded with the header. */ + function display() { + $this->displayHeader($this->header); + $this->displayContents(); + } + + /** Displays the header stringg. */ + function displayHeader() { + $header = $this->header ? $this->header : " "; + echo "
$header

\n"; + } + + /** + * Displays the summary information of each bib entries of the + * current page. For each entry, this method displays the author, + * title, and type; the bib entries are displayed grouped by the + * publication years. If the bib list is empty, an error message is + * displayed. + */ + function displayContents() { + $biblist =& $this->result; + $page = $this->page; + + // print error message if no entry. + if (empty($biblist)) { + echo "No match found!\n"; + return; + } + + // print a page bar, a list of clickable page numbers + $pageSize = PAGE_SIZE; // no. of entries per page + $noPages = ceil(count($biblist) / $pageSize); + $this->displayPageBar($noPages, $page); + + // create a year -> entries map to display by years + $years = array(); + foreach ($biblist as $e) { + $y = trim($e->getField(YEAR)); + $years[$y][] = $e; + } + krsort($years); + + $startIndex = ($page - 1) * $pageSize; + $endIndex = $startIndex + $pageSize; + $index = 0; + ?> + + + $entries) { + if ($index >= $endIndex) { break; } + + if ($index >= $startIndex && $index < $endIndex) { + ?> + + + + = $startIndex && $index < $endIndex) { + $author = compactAuthor($bib->getAuthor()); + $id = $bib->getId(); + $title = $bib->getField(TITLE); + $type = $bib->getType(); + $href = makeHref(array(Q_ENTRY => $id)); + ?> + + + + + +
+ + >
+ () +
+
+ 0 ? $page - $barSize : 1; + $end = min($noPages, $start + $barSize * 2); + + echo '
'; + } +} + + +/** Class to display a single bibentry. */ +class SingleResultDisplay extends ResultDisplay { + + /** Creates an instance with the given bib entry and header. + * It the object is an instance of BibIndexEntry, it may be + * mutated to read the rest of the fields. + */ + function SingleResultDisplay(&$bibentry) { + $this->result =& $bibentry; + $this->header = $this->result->getTitle(); + } + + /** Displays the bib entry, both in the formatted and raw text. + * The object may be mutated. */ + function displayContents() { + $entry =& $this->result; + if ($entry) { + $this->displayEntryFormatted($entry); + echo '
BibTeX entry:
'; + $this->displayEntryUnformatted($entry); + } + } + + /** + * Displays a unformated (verbatim) text of the given bib entry. + * The text is displayed in
tag. + * The object may be mutated to read the rest of the fields. + */ + function displayEntryUnformatted(&$entry) { + $text =$entry->getText(); + ?> + +
+ getAuthor(); + $type = $bib->getType(); + + // display heading: author (type) + ?> + + + + + + getFields() as $name => $value) { + if ($name == 'key') { continue; } // skip the key field + // make href if URL + $dval = $name == 'url' ? "$value" : $value; + ?> + + + + + +
+ () +
+ bibdb =$parser->getEntries(); + } + + /** Returns all entries as an array. Each entry is an instance of + * class BibEntry. */ + function getEntries() { + return $this->bibdb; + } + + /** Returns all entries categorized by types. The returned value is + * a hashtable from types to arrays of bib entries. + */ + function getEntriesByTypes() { + $result = array(); + foreach ($this->bibdb as $b) { + $result[$b->getType()][] = $b; + } + return $result; + } + + /** Given its ID, return the bib entry. */ + function getEntry($id) { + return $this->bibdb[$id]; + } + + /** Returns an array containing all the bib types (strings). */ + function getTypes() { + $result = array(); + foreach ($this->bibdb as $b) { + $result[$b->getType()] = 1; + } + $result = array_keys($result); + return $result; + } + + /** Generates and returns an array consisting of all authors. + * The returned array is a hash table with keys + * and values . + */ + function authorIndex(){ + $result = array(); + foreach ($this->bibdb as $bib) { + $authors =explode(' and ', $bib->getAuthor()); + foreach($authors as $a){ + $ta = trim($a); + if (!array_key_exists($ta, $result)) { + $result[$ta] = formatAuthor($ta); + } + } + } + asort($result); + return $result; + } + + /** Generates and returns an array consisting of all tags. + */ + function tagIndex(){ + $result = array(); + foreach ($this->bibdb as $bib) { + $tags =explode(' and ', $bib->getField("tag")); + foreach($tags as $a){ + $ta = trim($a); + $result[$ta] = $ta; + } + } + asort($result); + return $result; + } + + /** Generates and returns an array consisting of all years. + */ + function yearIndex(){ + $result = array(); + foreach ($this->bibdb as $bib) { + $tags =explode(' and ', $bib->getField("year")); + foreach($tags as $a){ + $ta = trim($a); + $result[$ta] = $ta; + } + } + arsort($result); + return $result; + } + + /** + * Returns an array containing all bib entries matching the given + * type. + */ + function searchType($type){ + $result = array(); + foreach($this->bibdb as $bib) { + if($bib->getType() == $type) + $result[] = $bib; + } + return $result; + } + + /** Returns an array of bib entries (BibEntry) that contains the + * given phrase in the given fields. If the fields are empty, all + * fields are searched. */ + function search($phrase, $fields = NULL) { + $phrase = strtolower(trim($phrase)); + if (empty($phrase)) { + return array(); + } + + $result = array(); + foreach ($this->bibdb as $bib) { + if ($bib->hasPhrase($phrase, $fields)) { + $result[] = $bib; + } + } + //print_r($result); + return $result; + } +} + + + +?> + + + + + + +mainVC(); +?> + + +<? +if ($result != null) echo $result->header.' in file '.$filename; +else echo 'bibtexbrowser: dynamic bibtex to HTML'; +?> + + + + +'; + echo $dispmgr->searchView(); + echo $dispmgr->typeVC().'
'; + echo $dispmgr->yearVC().'
'; + echo $dispmgr->authorVC().'
'; + echo $dispmgr->tagVC().'
'; + echo ''; +} // end isset($_GET['menu'] +else { + ?> + '; + $result->display(); + echo '
Powered by bibtexbrowser
'; + echo ''; + } + else { + ?> + + + + + + + \ No newline at end of file