1<?php /* bibtexbrowser: publication lists with bibtex and PHP
2<!--this is version from commit __GITHUB__ -->
3URL: http://www.monperrus.net/martin/bibtexbrowser/
4Questions & Bug Reports: https://github.com/monperrus/bibtexbrowser/issues
5
6(C) 2012-2020 Github contributors
7(C) 2006-2020 Martin Monperrus
8(C) 2014 Markus Jochim
9(C) 2013 Matthieu Guillaumin
10(C) 2005-2006 The University of Texas at El Paso / Joel Garcia, Leonardo Ruiz, and Yoonsik Cheon
11This program is free software; you can redistribute it and/or
12modify it under the terms of the GNU General Public License as
13published by the Free Software Foundation; either version 2 of the
14License, or (at your option) any later version.
15
16*/
17
18// it is be possible to include( 'bibtexbrowser.php' ); several times in the same script
19// added on Wednesday, June 01 2011, bug found by Carlos Bras
20if (!defined('BIBTEXBROWSER')) {
21// this if block ends at the very end of this file, after all class and function declarations.
22define('BIBTEXBROWSER','v__GITHUB__');
23
24// support for configuration
25// set with bibtexbrowser_configure, get with config_value
26// you may have bibtexbrowser_configure('foo', 'bar') in bibtexbrowser.local.php
27global $CONFIGURATION;
28$CONFIGURATION = array();
29function bibtexbrowser_configure($key, $value) {
30  global $CONFIGURATION;
31  $CONFIGURATION[$key]=$value;
32  if (!defined($key)) { define($key, $value); } // for backward compatibility
33}
34function bibtexbrowser_configuration($key) {
35  global $CONFIGURATION;
36  if (isset($CONFIGURATION[$key])) {return $CONFIGURATION[$key];}
37  if (defined($key)) {return constant($key);}
38  throw new Exception('no such configuration parameter: '.$key);
39}
40function c($key) { // shortcut
41  return bibtexbrowser_configuration($key);
42}
43
44// *************** CONFIGURATION
45// I recommend to put your changes in bibtexbrowser.local.php
46// it will help you to upgrade the script with a new version
47// the changes that require existing bibtexbrowser symbols should be in bibtexbrowser.after.php (included at the end of this file)
48// per bibtex file configuration
49@include('bibtexbrowser.local.php');
50
51// the encoding of your bibtex file
52@define('BIBTEX_INPUT_ENCODING','UTF-8');//@define('BIBTEX_INPUT_ENCODING','iso-8859-1');//define('BIBTEX_INPUT_ENCODING','windows-1252');
53// the encoding of the HTML output
54@define('OUTPUT_ENCODING','UTF-8');
55
56// print a warning if deprecated variable is used
57if (defined('ENCODING')) {
58  echo 'ENCODING has been replaced by BIBTEX_INPUT_ENCODING and OUTPUT_ENCODING';
59}
60
61// number of bib items per page
62// we use the same parameter 'num' as Google
63@define('PAGE_SIZE',isset($_GET['num'])?(preg_match('/^\d+$/',$_GET['num'])?$_GET['num']:10000):14);
64
65// bibtexbrowser uses a small piece of Javascript to improve the user experience
66// see http://en.wikipedia.org/wiki/Progressive_enhancement
67// if you don't like it, you can be disable it by adding in bibtexbrowser.local.php
68// @define('BIBTEXBROWSER_USE_PROGRESSIVE_ENHANCEMENT',false);
69@define('BIBTEXBROWSER_USE_PROGRESSIVE_ENHANCEMENT',true);
70@define('BIBLIOGRAPHYSTYLE','DefaultBibliographyStyle');// this is the name of a function
71@define('BIBLIOGRAPHYSECTIONS','DefaultBibliographySections');// this is the name of a function
72@define('BIBLIOGRAPHYTITLE','DefaultBibliographyTitle');// this is the name of a function
73
74// shall we load MathJax to render math in $…$ in HTML?
75@define('BIBTEXBROWSER_RENDER_MATH', true);
76@define('MATHJAX_URI', '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/config/TeX-AMS_HTML.js?V=2.7.1');
77
78// the default jquery URI
79@define('JQUERY_URI', '//code.jquery.com/jquery-3.6.4.min.js');
80
81// can we load bibtex files on external servers?
82@define('BIBTEXBROWSER_LOCAL_BIB_ONLY', true);
83
84// the default view in {SimpleDisplay,AcademicDisplay,RSSDisplay,BibtexDisplay}
85@define('BIBTEXBROWSER_DEFAULT_DISPLAY','SimpleDisplay');
86
87// the default template
88@define('BIBTEXBROWSER_DEFAULT_TEMPLATE','HTMLTemplate');
89
90// the target frame of menu links
91@define('BIBTEXBROWSER_MENU_TARGET','main'); // might be define('BIBTEXBROWSER_MENU_TARGET','_self'); in bibtexbrowser.local.php
92
93@define('ABBRV_TYPE','index');// may be year/x-abbrv/key/none/index/keys-index
94
95// are robots allowed to crawl and index bibtexbrowser generated pages?
96@define('BIBTEXBROWSER_ROBOTS_NOINDEX',false);
97
98//the default view in the "main" (right hand side) frame
99@define('BIBTEXBROWSER_DEFAULT_FRAME','year=latest'); // year=latest,all and all valid bibtexbrowser queries
100
101// Wrapper to use when we are included by another script
102@define('BIBTEXBROWSER_EMBEDDED_WRAPPER', 'NoWrapper');
103
104// Main class to use
105@define('BIBTEXBROWSER_MAIN', 'Dispatcher');
106
107// default order functions
108// Contract Returns < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal.
109// can be @define('ORDER_FUNCTION','compare_bib_entry_by_title');
110// can be @define('ORDER_FUNCTION','compare_bib_entry_by_bibtex_order');
111@define('ORDER_FUNCTION','compare_bib_entry_by_year');
112@define('ORDER_FUNCTION_FINE','compare_bib_entry_by_month');
113
114// only displaying the n newest entries
115@define('BIBTEXBROWSER_NEWEST',5);
116
117@define('BIBTEXBROWSER_NO_DEFAULT', false);
118
119// BIBTEXBROWSER_LINK_STYLE defines which function to use to display the links of a bibtex entry
120@define('BIBTEXBROWSER_LINK_STYLE','bib2links_default'); // can be 'nothing' (a function that does nothing)
121
122// do we add [bibtex] links ?
123@define('BIBTEXBROWSER_BIBTEX_LINKS',true);
124// do we add [pdf] links ?
125// if the file extention is not .pdf, the field name (pdf, url, or file) is used instead
126@define('BIBTEXBROWSER_PDF_LINKS',true);
127// do we add [doi] links ?
128@define('BIBTEXBROWSER_DOI_LINKS',true);
129// do we add [gsid] links (Google Scholar)?
130@define('BIBTEXBROWSER_GSID_LINKS',true);
131
132// should pdf, doi, url, gsid links be opened in a new window?
133@define('BIBTEXBROWSER_LINKS_TARGET','_self');// can be _blank (new window), _top (with frames)
134
135// should authors be linked to [none/homepage/resultpage]
136// none: nothing
137// their homepage if defined as @strings
138// their publication lists according to this bibtex
139@define('BIBTEXBROWSER_AUTHOR_LINKS','homepage');
140
141// BIBTEXBROWSER_LAYOUT defines the HTML rendering layout of the produced HTML
142// may be table/list/ordered_list/definition/none (for <table>, <ol>, <dl>, nothing resp.).
143// for list/ordered_list, the abbrevations are not taken into account (see ABBRV_TYPE)
144// for ordered_list, the index is given by HTML directly (in increasing order)
145@define('BIBTEXBROWSER_LAYOUT','table');
146
147// should the original bibtex be displayed or a reconstructed one with filtering
148// values: original/reconstructed
149// warning, with reconstructed, the latex markup for accents/diacritics is lost
150@define('BIBTEXBROWSER_BIBTEX_VIEW','original');
151// a list of fields that will not be shown in the bibtex view if BIBTEXBROWSER_BIBTEX_VIEW=reconstructed
152@define('BIBTEXBROWSER_BIBTEX_VIEW_FILTEREDOUT','comment|note|file');
153
154// should Latex macros be executed (e.g. \'e -> é
155@define('BIBTEXBROWSER_USE_LATEX2HTML',true);
156
157// Which is the first html <hN> level that should be used in embedded mode?
158@define('BIBTEXBROWSER_HTMLHEADINGLEVEL', 2);
159
160@define('BIBTEXBROWSER_ACADEMIC_TOC', false);
161
162@define('BIBTEXBROWSER_DEBUG',false);
163
164// should we cache the parsed bibtex file?
165// ref: https://github.com/monperrus/bibtexbrowser/issues/128
166@define('BIBTEXBROWSER_USE_CACHE',true);
167
168// how to print authors names?
169// default => as in the bibtex file
170// USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT = true => "Meyer, Herbert"
171// USE_INITIALS_FOR_NAMES = true => "Meyer H"
172// USE_FIRST_THEN_LAST => Herbert Meyer
173@define('USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT',false);// output authors in a comma separated form, e.g. "Meyer, H"?
174@define('USE_INITIALS_FOR_NAMES',false); // use only initials for all first names?
175@define('USE_FIRST_THEN_LAST',false); // put first names before last names?
176@define('FORCE_NAMELIST_SEPARATOR', ''); // if non-empty, use this to separate multiple names regardless of USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT
177@define('LAST_AUTHOR_SEPARATOR',' and ');
178@define('USE_OXFORD_COMMA',false); // adds an additional separator in addition to LAST_AUTHOR_SEPARATOR if there are more than two authors
179
180@define('TYPES_SIZE',10); // number of entry types per table
181@define('YEAR_SIZE',20); // number of years per table
182@define('AUTHORS_SIZE',30); // number of authors per table
183@define('TAGS_SIZE',30); // number of keywords per table
184@define('READLINE_LIMIT',1024);
185@define('Q_YEAR', 'year');
186@define('Q_YEAR_PAGE', 'year_page');
187@define('Q_YEAR_INPRESS', 'in press');
188@define('Q_YEAR_ACCEPTED', 'accepted');
189@define('Q_YEAR_SUBMITTED', 'submitted');
190@define('Q_FILE', 'bib');
191@define('Q_AUTHOR', 'author');
192@define('Q_AUTHOR_PAGE', 'author_page');
193@define('Q_TAG', 'keywords');
194@define('Q_TAG_PAGE', 'keywords_page');
195@define('Q_TYPE', 'type');// used for queries
196@define('Q_TYPE_PAGE', 'type_page');
197@define('Q_ALL', 'all');
198@define('Q_ENTRY', 'entry');
199@define('Q_KEY', 'key');
200@define('Q_KEYS', 'keys'); // filter entries using a url-encoded, JSON-encoded array of bibtex keys
201@define('Q_SEARCH', 'search');
202@define('Q_EXCLUDE', 'exclude');
203@define('Q_RESULT', 'result');
204@define('Q_ACADEMIC', 'academic');
205@define('Q_DB', 'bibdb');
206@define('Q_LATEST', 'latest');
207@define('Q_RANGE', 'range');
208@define('AUTHOR', 'author');
209@define('EDITOR', 'editor');
210@define('SCHOOL', 'school');
211@define('TITLE', 'title');
212@define('BOOKTITLE', 'booktitle');
213@define('YEAR', 'year');
214@define('BUFFERSIZE',100000);
215@define('MULTIPLE_BIB_SEPARATOR',';');
216@define('METADATA_COINS',true); // see https://en.wikipedia.org/wiki/COinS
217@define('METADATA_GS',false); // metadata google scholar, see http://www.monperrus.net/martin/accurate+bibliographic+metadata+and+google+scholar
218@define('METADATA_DC',true); // see http://dublincore.org/
219@define('METADATA_OPENGRAPH',true);  // see http://ogp.me/
220@define('METADATA_EPRINTS',false); // see https://wiki.eprints.org/w/Category:EPrints_Metadata_Fields
221
222// define sort order for special values in 'year' field
223// highest number is sorted first
224// don't exceed 0 though, since the values are added to PHP_INT_MAX
225@define('ORDER_YEAR_INPRESS', 0);
226@define('ORDER_YEAR_ACCEPTED', 1);
227@define('ORDER_YEAR_SUBMITTED', 2);
228@define('ORDER_YEAR_OTHERNONINT', 3);
229
230
231// in embedded mode, we still need a URL for displaying bibtex entries alone
232// this is usually resolved to bibtexbrowser.php
233// but can be overridden in bibtexbrowser.local.php
234// for instance with @define('BIBTEXBROWSER_URL',''); // links to the current page with ?
235@define('BIBTEXBROWSER_URL',basename(__FILE__));
236
237// Specify the location of the cache file for servers that need temporary files written in a specific location
238@define('CACHE_DIR','');
239
240// Specify the location of the bib file for servers that need do not allow slashes in URLs,
241// where the bib file and bibtexbrowser.php are in different directories.
242@define('DATA_DIR','');
243
244// *************** END CONFIGURATION
245
246define('Q_INNER_AUTHOR', '_author');// internally used for representing the author
247define('Q_INNER_TYPE', 'x-bibtex-type');// used for representing the type of the bibtex entry internally
248@define('Q_INNER_KEYS_INDEX', '_keys-index');// used for storing indices in $_GET[Q_KEYS] array
249
250define('Q_NAME', 'name');// used to allow for exact last name matches in multisearch
251define('Q_AUTHOR_NAME', 'author_name');// used to allow for exact last name matches in multisearch
252define('Q_EDITOR_NAME', 'editor_name');// used to allow for exact last name matches in multisearch
253
254// for clean search engine links
255// we disable url rewriting
256// ... and hope that your php configuration will accept one of these
257@ini_set("session.use_only_cookies",1);
258@ini_set("session.use_trans_sid",0);
259@ini_set("url_rewriter.tags","");
260
261function nothing() {}
262
263function config_value($key) {
264  global $CONFIGURATION;
265  if (isset($CONFIGURATION[$key])) { return $CONFIGURATION[$key]; }
266  if (defined($key)) { return constant($key); }
267  die('no such configuration: '.$key);
268}
269
270/** parses $_GET[Q_FILE] and puts the result (an object of type BibDataBase) in $_GET[Q_DB].
271See also zetDB().
272  */
273function setDB() {
274  list($db, $parsed, $updated, $saved) = _zetDB(@$_GET[Q_FILE]);
275  $_GET[Q_DB] = $db;
276  return $updated;
277}
278
279/** parses the $bibtex_filenames (usually semi-column separated) and returns an object of type BibDataBase.
280See also setDB()
281*/
282function zetDB($bibtex_filenames) {
283  list($db, $parsed, $updated, $saved) = _zetDB($bibtex_filenames);
284  return $db;
285}
286
287/** @nodoc */
288function default_message() {
289
290  if (config_value('BIBTEXBROWSER_NO_DEFAULT')==true) { return; }
291
292  ?>
293  <div id="bibtexbrowser_message">
294  Congratulations! bibtexbrowser is correctly installed!<br/>
295  Now you have to pass the name of the bibtex file as parameter (e.g. bibtexbrowser.php?bib=mybib.php)<br/>
296  You may browse:<br/>
297  <?php
298  foreach (glob("*.bib") as $bibfile) {
299    $url="?bib=".$bibfile; echo '<a href="'.$url.'" rel="nofollow">'.$bibfile.'</a><br/>';
300  }
301  echo "</div>";
302}
303
304/** returns the target of links */
305function get_target() {
306  if (c('BIBTEXBROWSER_LINKS_TARGET')!='_self') {
307    return " target=\"".c('BIBTEXBROWSER_LINKS_TARGET')."\"";
308  }
309  else return "";
310}
311
312/** @nodoc */
313function _zetDB($bibtex_filenames) {
314
315  $db = null;
316
317  // default bib file, if no file is specified in the query string.
318  if (!isset($bibtex_filenames) || $bibtex_filenames == "") {
319    default_message();
320    exit;
321  }
322
323  // first does the bibfiles exist:
324  // $bibtex_filenames can be urlencoded for instance if they contain slashes
325  // so we decode it
326  $bibtex_filenames = urldecode($bibtex_filenames);
327
328  // ---------------------------- HANDLING unexistent files
329  foreach(explode(MULTIPLE_BIB_SEPARATOR, $bibtex_filenames) as $bib) {
330
331    // get file extension to only allow .bib files
332    $ext = pathinfo($bib, PATHINFO_EXTENSION);
333    // this is a security protection
334    if (BIBTEXBROWSER_LOCAL_BIB_ONLY && (!file_exists(DATA_DIR.$bib) || strcasecmp($ext, 'bib') != 0)) {
335      // to automate dectection of faulty links with tools such as webcheck
336      header('HTTP/1.1 404 Not found');
337      // escape $bib to prevent XSS
338      $escapedBib = htmlEntities($bib, ENT_QUOTES);
339      die('<b>the bib file '.$escapedBib.' does not exist !</b>');
340    }
341  } // end for each
342
343  // ---------------------------- HANDLING HTTP If-modified-since
344  // testing with $ curl -v --header "If-Modified-Since: Fri, 23 Oct 2010 19:22:47 GMT" "... bibtexbrowser.php?key=wasylkowski07&bib=..%252Fstrings.bib%253B..%252Fentries.bib"
345  // and $ curl -v --header "If-Modified-Since: Fri, 23 Oct 2000 19:22:47 GMT" "... bibtexbrowser.php?key=wasylkowski07&bib=..%252Fstrings.bib%253B..%252Fentries.bib"
346
347  // save bandwidth and server cpu
348  // (imagine the number of requests from search engine bots...)
349  $bib_is_unmodified = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ;
350  foreach(explode(MULTIPLE_BIB_SEPARATOR, $bibtex_filenames) as $bib) {
351      $bib_is_unmodified =
352                    $bib_is_unmodified
353                    &&  (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>filemtime($bib));
354  } // end for each
355  if ( $bib_is_unmodified && !headers_sent()) {
356    header("HTTP/1.1 304 Not Modified");
357    exit;
358  }
359
360
361  $parse=true;
362  $updated = false;
363
364  if (config_value('BIBTEXBROWSER_USE_CACHE')==true) {
365
366  // ---------------------------- HANDLING caching of compiled bibtex files
367  // for sake of performance, once the bibtex file is parsed
368  // we try to save a "compiled" in a txt file
369  $compiledbib = CACHE_DIR.'bibtexbrowser_'.md5($bibtex_filenames).'.dat';
370
371  $parse=filemtime(__FILE__)>@filemtime($compiledbib);
372
373  // do we have a compiled version ?
374  if (is_file($compiledbib)
375     && is_readable($compiledbib)
376     && filesize($compiledbib)>0
377   ) {
378    $f = fopen($compiledbib,'r+'); // some Unix seem to consider flock as a writing operation
379    //we use a lock to avoid that a call to bibbtexbrowser made while we write the object loads an incorrect object
380    if (flock($f,LOCK_EX)) {
381      $s = filesize($compiledbib);
382      $ser = fread($f,$s);
383      $db = @unserialize($ser);
384      flock($f,LOCK_UN);
385    } else { die('could not get the lock'); }
386    fclose($f);
387    // basic test
388    // do we have an correct version of the file
389    if (!is_a($db,'BibDataBase')) {
390      unlink($compiledbib);
391      if (BIBTEXBROWSER_DEBUG) { die('$db not a BibDataBase. please reload.'); }
392      $parse=true;
393    }
394  } else {$parse=true;}
395  }
396
397  // we don't have a compiled version
398  if ($parse) {
399    //echo '<!-- parsing -->';
400    // then parsing the file
401    $db = createBibDataBase();
402    foreach(explode(MULTIPLE_BIB_SEPARATOR, $bibtex_filenames) as $bib) {
403      $db->load($bib);
404    }
405  }
406
407  // now we may update the database
408  if (!file_exists($compiledbib)) {
409    @touch($compiledbib);
410    $updated = true; // limit case
411  } else foreach(explode(MULTIPLE_BIB_SEPARATOR, $bibtex_filenames) as $bib) {
412      // is it up to date ? wrt to the bib file and the script
413    // then upgrading with a new version of bibtexbrowser triggers a new compilation of the bib file
414    if (filemtime($bib)>filemtime($compiledbib) || filemtime(__FILE__)>filemtime($compiledbib)) {
415//       echo "updating  ".$bib;
416      $db->update($bib);
417      $updated = true;
418    }
419  }
420
421//   echo var_export($parse);
422//   echo var_export($updated);
423
424  $saved = false;
425  // are we able to save the compiled version ?
426  // note that the compiled version is saved in the current working directory
427  if ( ($parse || $updated ) && is_writable($compiledbib)) {
428    // we use 'a' because the file is not locked between fopen and flock
429    $f = fopen($compiledbib,'a');
430    //we use a lock to avoid that a call to bibbtexbrowser made while we write the object loads an incorrect object
431    if (flock($f,LOCK_EX)) {
432//       echo '<!-- saving -->';
433      ftruncate($f,0);
434      fwrite($f,serialize($db));
435      flock($f,LOCK_UN);
436      $saved = true;
437    } else { die('could not get the lock'); }
438    fclose($f);
439  } // end saving the cached verions
440  //else echo '<!-- please chmod the directory containing the bibtex file to be able to keep a compiled version (much faster requests for large bibtex files) -->';
441
442
443  return array($db, $parse, $updated, $saved);
444} // end function setDB
445
446// internationalization
447if (!function_exists('__')){
448  function __($msg) {
449    global $BIBTEXBROWSER_LANG;
450    if (isset($BIBTEXBROWSER_LANG[$msg])) {
451      return $BIBTEXBROWSER_LANG[$msg];
452    }
453    return $msg;
454  }
455}
456
457// factories
458// may be overridden in bibtexbrowser.local.php
459if (!function_exists('createBibDataBase')) {
460  /** factory method for openness @nodoc */
461  function createBibDataBase() { $x = new BibDataBase(); return $x;}
462}
463if (!function_exists('createBibEntry')) {
464  /** factory method for openness @nodoc */
465  function createBibEntry() { $x = new BibEntry(); return $x;}
466}
467if (!function_exists('createBibDBBuilder')) {
468  /** factory method for openness @nodoc */
469  function createBibDBBuilder() { $x = new BibDBBuilder(); return $x;}
470}
471if (!function_exists('createBasicDisplay')) {
472  /** factory method for openness @nodoc */
473  function createBasicDisplay() { $x = new SimpleDisplay(); return $x;}
474}
475if (!function_exists('createBibEntryDisplay')) {
476  /** factory method for openness @nodoc */
477  function createBibEntryDisplay() { $x = new BibEntryDisplay(); return $x;}
478}
479if (!function_exists('createMenuManager')) {
480  /** factory method for openness @nodoc */
481  function createMenuManager() { $x = new MenuManager(); return $x;}
482}
483
484
485////////////////////////////////////////////////////////
486
487/** is a generic parser of bibtex files.
488usage:
489<pre>
490  $delegate = new XMLPrettyPrinter();// or another delegate such as BibDBBuilder
491  $parser = new StateBasedBibtexParser($delegate);
492  $parser->parse(fopen('bibacid-utf8.bib','r'));
493</pre>
494notes:
495 - It has no dependencies, it can be used outside of bibtexbrowser
496 - The delegate is expected to have some methods, see classes BibDBBuilder and XMLPrettyPrinter
497 */
498class StateBasedBibtexParser {
499
500  var $delegate;
501
502  function __construct($delegate) {
503    $this->delegate = $delegate;
504  }
505
506  function parse($handle) {
507    if (gettype($handle) == 'string') { throw new Exception('oops'); }
508    $delegate = $this->delegate;
509    // STATE DEFINITIONS
510    @define('NOTHING',1);
511    @define('GETTYPE',2);
512    @define('GETKEY',3);
513    @define('GETVALUE',4);
514    @define('GETVALUEDELIMITEDBYQUOTES',5);
515    @define('GETVALUEDELIMITEDBYQUOTES_ESCAPED',6);
516    @define('GETVALUEDELIMITEDBYCURLYBRACKETS',7);
517    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_ESCAPED',8);
518    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL',9);
519    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL_ESCAPED',10);
520    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL',11);
521    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL_ESCAPED',12);
522    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL',13);
523    @define('GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL_ESCAPED',14);
524
525
526    $state=NOTHING;
527    $entrytype='';
528    $entrykey='';
529    $entryvalue='';
530    $fieldvaluepart='';
531    $finalkey='';
532    $entrysource='';
533
534    // metastate
535    $isinentry = false;
536
537    $delegate->beginFile();
538
539    // if you encounter this error "Allowed memory size of xxxxx bytes exhausted"
540    // then decrease the size of the temp buffer below
541    $bufsize=BUFFERSIZE;
542    while (!feof($handle)) {
543    $sread=fread($handle,$bufsize);
544    //foreach(str_split($sread) as $s) {
545    for ( $i=0; $i < strlen( $sread ); $i++) { $s=$sread[$i];
546
547    if ($isinentry) $entrysource.=$s;
548
549    if ($state==NOTHING) {
550      // this is the beginning of an entry
551      if ($s=='@') {
552      $delegate->beginEntry();
553      $state = GETTYPE;
554      $isinentry = true;
555      $entrysource='@';
556      }
557    }
558
559    else if ($state==GETTYPE) {
560      // this is the beginning of a key
561      if ($s=='{') {
562      $state = GETKEY;
563      $delegate->setEntryType($entrytype);
564      $entrytype='';
565      }
566      else   $entrytype=$entrytype.$s;
567    }
568
569    else if ($state==GETKEY) {
570      // now we get the value
571      if ($s=='=') {
572        $state = GETVALUE;
573        $fieldvaluepart='';
574        $finalkey=$entrykey;
575        $entrykey='';
576      }
577      // oups we only have the key :-) anyway
578      else if ($s=='}') {
579      $state = NOTHING;$isinentry = false;$delegate->endEntry($entrysource);
580      $entrykey='';
581      }
582      // OK now we look for values
583      else if ($s==',') {
584      $state=GETKEY;
585      $delegate->setEntryKey($entrykey);
586      $entrykey='';}
587      else { $entrykey=$entrykey.$s; }
588      }
589      // we just got a =, we can now receive the value, but we don't now whether the value
590      // is delimited by curly brackets, double quotes or nothing
591      else if ($state==GETVALUE) {
592
593        // the value is delimited by double quotes
594        if ($s=='"') {
595        $state = GETVALUEDELIMITEDBYQUOTES;
596        }
597        // the value is delimited by curly brackets
598        else if ($s=='{') {
599        $state = GETVALUEDELIMITEDBYCURLYBRACKETS;
600	}
601        // the end of the key and no value found: it is the bibtex key e.g. \cite{Descartes1637}
602        else if ($s==',') {
603        $state = GETKEY;
604        $delegate->setEntryField($finalkey,$entryvalue);
605        $entryvalue=''; // resetting the value buffer
606        }
607        // this is the end of the value AND of the entry
608        else if ($s=='}') {
609        $state = NOTHING;
610        $delegate->setEntryField($finalkey,$entryvalue);
611        $isinentry = false;$delegate->endEntry($entrysource);
612        $entryvalue=''; // resetting the value buffer
613        }
614        else if ($s==' ' || $s=="\t"  || $s=="\n" || $s=="\r" ) {
615          // blank characters are not taken into account when values are not in quotes or curly brackets
616        }
617        else {
618          $entryvalue=$entryvalue.$s;
619        }
620      }
621
622
623    /* GETVALUEDELIMITEDBYCURLYBRACKETS* handle entries delimited by curly brackets and the possible nested curly brackets */
624    else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS) {
625
626      if ($s=='\\') {
627      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_ESCAPED;
628      $entryvalue=$entryvalue.$s;}
629      else if ($s=='{') {
630        $state = GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL;
631        $entryvalue=$entryvalue.$s;
632        $delegate->entryValuePart($finalkey,$fieldvaluepart,'CURLYTOP');
633        $fieldvaluepart='';
634      }
635      else if ($s=='}') { // end entry
636        $state = GETVALUE;
637        $delegate->entryValuePart($finalkey,$fieldvaluepart,'CURLYTOP');
638      }
639      else {
640        $entryvalue=$entryvalue.$s;
641        $fieldvaluepart=$fieldvaluepart.$s;
642      }
643    }
644      // handle anti-slashed brackets
645      else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_ESCAPED) {
646        $state = GETVALUEDELIMITEDBYCURLYBRACKETS;
647        $entryvalue=$entryvalue.$s;
648        }
649    // in first level of curly bracket
650    else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL) {
651      if ($s=='\\') {
652      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL_ESCAPED;
653      $entryvalue=$entryvalue.$s;}
654      else if ($s=='{') {
655      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL;$entryvalue=$entryvalue.$s;}
656      else if ($s=='}') {
657        $state = GETVALUEDELIMITEDBYCURLYBRACKETS;
658        $delegate->entryValuePart($finalkey,$fieldvaluepart,'CURLYONE');
659        $fieldvaluepart='';
660        $entryvalue=$entryvalue.$s;
661      }
662      else {
663        $entryvalue=$entryvalue.$s;
664        $fieldvaluepart=$fieldvaluepart.$s;
665      }
666    }
667      // handle anti-slashed brackets
668      else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL_ESCAPED) {
669        $state = GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL;
670        $entryvalue=$entryvalue.$s;
671        }
672
673    // in second level of curly bracket
674    else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL) {
675      if ($s=='\\') {
676      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL_ESCAPED;
677      $entryvalue=$entryvalue.$s;}
678      else if ($s=='{') {
679      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL;$entryvalue=$entryvalue.$s;}
680      else if ($s=='}') {
681      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_1NESTEDLEVEL;$entryvalue=$entryvalue.$s;}
682      else { $entryvalue=$entryvalue.$s;}
683      }
684      // handle anti-slashed brackets
685      else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL_ESCAPED) {
686        $state = GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL;
687        $entryvalue=$entryvalue.$s;
688      }
689
690    // in third level of curly bracket
691    else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL) {
692      if ($s=='\\') {
693      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL_ESCAPED;
694      $entryvalue=$entryvalue.$s;}
695      else if ($s=='}') {
696      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_2NESTEDLEVEL;$entryvalue=$entryvalue.$s;}
697      else { $entryvalue=$entryvalue.$s;}
698    }
699    // handle anti-slashed brackets
700    else if ($state==GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL_ESCAPED) {
701      $state = GETVALUEDELIMITEDBYCURLYBRACKETS_3NESTEDLEVEL;
702      $entryvalue=$entryvalue.$s;
703    }
704
705    /* handles entries delimited by double quotes */
706    else if ($state==GETVALUEDELIMITEDBYQUOTES) {
707
708      if ($s=='\\') {
709      $state = GETVALUEDELIMITEDBYQUOTES_ESCAPED;
710      $entryvalue=$entryvalue.$s;}
711      else if ($s=='"') {
712        $state = GETVALUE;
713      }
714      else {  $entryvalue=$entryvalue.$s;}
715    }
716    // handle anti-double quotes
717    else if ($state==GETVALUEDELIMITEDBYQUOTES_ESCAPED) {
718      $state = GETVALUEDELIMITEDBYQUOTES;
719      $entryvalue=$entryvalue.$s;
720    }
721
722    } // end for
723    } // end while
724    $delegate->endFile();
725    //$d = $this->delegate;print_r($d);
726  } // end function
727} // end class
728
729/** a default empty implementation of a delegate for StateBasedBibtexParser */
730class ParserDelegate {
731
732  function beginFile() {}
733
734  function endFile() {}
735
736  function setEntryField($finalkey,$entryvalue) {}
737
738  function setEntryType($entrytype) {}
739
740  function setEntryKey($entrykey) {}
741
742  function beginEntry() {}
743
744  function endEntry($entrysource) {}
745
746  /** called for each sub parts of type {part} of a field value
747   * for now, only CURLYTOP and CURLYONE events
748  */
749  function entryValuePart($key, $value, $type) {}
750
751} // end class ParserDelegate
752
753
754/** is a possible delegate for StateBasedBibParser.
755usage:
756see snippet of [[#StateBasedBibParser]]
757*/
758class XMLPrettyPrinter extends ParserDelegate {
759  function beginFile() {
760    header('Content-type: text/xml;');
761    print '<?xml version="1.0" encoding="'.OUTPUT_ENCODING.'"?>';
762    print '<bibfile>';
763  }
764
765
766  function endFile() {
767    print '</bibfile>';
768  }
769  function setEntryField($finalkey,$entryvalue) {
770    print "<data>\n<key>".$finalkey."</key>\n<value>".$entryvalue."</value>\n</data>\n";
771  }
772
773  function setEntryType($entrytype) {
774    print '<type>'.$entrytype.'</type>';
775  }
776
777  function setEntryKey($entrykey) {
778    print '<keyonly>'.$entrykey.'</keyonly>';
779  }
780
781  function beginEntry() {
782    print "<entry>\n";
783  }
784
785  function endEntry($entrysource) {
786    print "</entry>\n";
787  }
788} // end class XMLPrettyPrinter
789
790/** represents @string{k=v} */
791class StringEntry {
792  public $filename;
793  public $name;
794  public $value;
795
796  function __construct($k, $v, $filename) {
797    $this->name=$k;
798    $this->value=$v;
799    $this->filename=$filename;
800  }
801
802  function toString() {
803    return '@string{'.$this->name.'={'.$this->value.'}}';
804  }
805} // end class StringEntry
806
807
808
809
810/** builds arrays of BibEntry objects from a bibtex file.
811usage:
812<pre>
813  $empty_array = array();
814  $db = new BibDBBuilder(); // see also factory method createBibDBBuilder
815  $db->build('bibacid-utf8.bib'); // parses bib file
816  print_r($db->builtdb);// an associated array key -> BibEntry objects
817  print_r($db->stringdb);// an associated array key -> strings representing @string
818</pre>
819notes:
820 method build can be used several times, bibtex entries are accumulated in the builder
821*/
822class BibDBBuilder extends ParserDelegate {
823
824  /** A hashtable from keys to bib entries (BibEntry). */
825  var $builtdb  = array();
826
827  /** A hashtable of constant strings */
828  var $stringdb = array();
829
830  var $filename;
831
832  var $currentEntry;
833
834  function build($bibfilename, $handle = NULL) {
835
836    $this->filename = $bibfilename;
837    if ($handle == NULL) {
838      $handle = fopen(DATA_DIR.$bibfilename, "r");
839    }
840
841    if (!$handle) die ('cannot open '.$bibfilename);
842
843    $parser = new StateBasedBibtexParser($this);
844    $parser->parse($handle);
845    fclose($handle);
846    //print_r(array_keys($this->builtdb));
847    //print_r($this->builtdb);
848  }
849
850
851  function getBuiltDb() {
852    //print_r($this->builtdb);
853    return $this->builtdb;
854  }
855
856  function beginFile() {
857  }
858
859  function endFile() {
860    // resolving crossrefs
861    // we are careful with PHP 4 semantics
862    foreach (array_keys($this->builtdb) as $key) {
863      $bib = $this->builtdb[$key];
864      if ($bib->hasField('crossref')) {
865        if (isset($this->builtdb[$bib->getField('crossref')])) {
866          $crossrefEntry = $this->builtdb[$bib->getField('crossref')];
867          $bib->crossref = $crossrefEntry;
868          foreach($crossrefEntry->getFields() as $k => $v) {
869            // copying the fields of the cross ref
870            // only if they don't exist yet
871            if (!$bib->hasField($k)) {
872              $bib->setField($k,$v);
873            }
874          }
875        }
876      }
877    }
878    //print_r($this->builtdb);
879  }
880
881  function setEntryField($fieldkey,$entryvalue) {
882    $fieldkey=trim($fieldkey);
883    // support for Bibtex concatenation
884    // see http://newton.ex.ac.uk/tex/pack/bibtex/btxdoc/node3.html
885    // (?<! is a negative look-behind assertion, see http://www.php.net/manual/en/regexp.reference.assertions.php
886    $entryvalue_array=preg_split('/(?<!\\\\)#/', $entryvalue);
887    foreach ($entryvalue_array as $k=>$v) {
888      // spaces are allowed when using # and they are not taken into account
889      // however # is not itself replaced by a space
890      // warning: @strings are not case sensitive
891      // see http://newton.ex.ac.uk/tex/pack/bibtex/btxdoc/node3.html
892      $stringKey=strtolower(trim($v));
893      if (isset($this->stringdb[$stringKey]))
894      {
895        // this field will be formated later by xtrim and latex2html
896        $entryvalue_array[$k]=$this->stringdb[$stringKey]->value;
897
898        // we keep a trace of this replacement
899        // so as to produce correct bibtex snippets
900        $this->currentEntry->constants[$stringKey]=$this->stringdb[$stringKey]->value;
901      }
902    }
903    $entryvalue=implode('',$entryvalue_array);
904
905    $this->currentEntry->setField($fieldkey,$entryvalue);
906  }
907
908  function setEntryType($entrytype) {
909    $this->currentEntry->setType($entrytype);
910  }
911
912  function setEntryKey($entrykey) {
913    //echo "new entry:".$entrykey."\n";
914    $this->currentEntry->setKey($entrykey);
915  }
916
917  function beginEntry() {
918    $this->currentEntry = createBibEntry();
919    $this->currentEntry->setFile($this->filename);
920  }
921
922  function endEntry($entrysource) {
923
924    // we add a timestamp
925    $this->currentEntry->timestamp();
926
927    // we add a key if there is no key
928    if (!$this->currentEntry->hasField(Q_KEY) && $this->currentEntry->getType()!='string') {
929      $this->currentEntry->setField(Q_KEY,md5($entrysource));
930    }
931
932    // we set the fulltext
933    $this->currentEntry->text = $entrysource;
934
935    // we format the author names in a special field
936    // to enable search
937    if ($this->currentEntry->hasField('author')) {
938      $this->currentEntry->setField(Q_INNER_AUTHOR,$this->currentEntry->getFormattedAuthorsString());
939
940      foreach($this->currentEntry->getCanonicalAuthors() as $author) {
941        $homepage_key = $this->currentEntry->getHomePageKey($author);
942        if (isset($this->stringdb[$homepage_key])) {
943            $this->currentEntry->homepages[$homepage_key] = $this->stringdb[$homepage_key]->value;
944        }
945      }
946    }
947
948    // ignoring jabref comments
949    if (($this->currentEntry->getType()=='comment')) {
950      /* do nothing for jabref comments */
951    }
952
953    // we add it to the string database
954    else if ($this->currentEntry->getType()=='string') {
955      foreach($this->currentEntry->fields as $k => $v) {
956        $k!=Q_INNER_TYPE and $this->stringdb[$k] = new StringEntry($k,$v,$this->filename);
957      }
958    }
959
960    // we add it to the database
961    else {
962      $this->builtdb[$this->currentEntry->getKey()] = $this->currentEntry;
963    }
964  }
965
966} // end class BibDBBuilder
967
968
969
970
971/** is an extended version of the trim function, removes linebreaks, tabs, etc.
972 */
973function xtrim($line) {
974  $line = trim($line);
975  // we remove the unneeded line breaks
976  // this is *required* to correctly split author lists into names
977  // 2010-06-30
978  // bug found by Thomas
979  // windows new line is **\r\n"** and not the other way around!!
980  // according to php.net: Proncess \r\n's first so they aren't converted twice
981  $line = str_replace(array("\r\n", "\r", "\n", "\t"), ' ', $line);
982  // remove superfluous spaces e.g. John+++Bar
983  $line = preg_replace('/ {2,}/',' ', $line);
984  return $line;
985}
986
987/** encapsulates the conversion of a single latex chars to the corresponding HTML entity.
988It expects a **lower-case** char.
989*/
990function char2html($line,$latexmodifier,$char,$entitiyfragment) {
991  $line = char2html_case_sensitive($line,$latexmodifier,strtoupper($char),$entitiyfragment);
992  return char2html_case_sensitive($line,$latexmodifier,strtolower($char),$entitiyfragment);
993}
994
995function char2html_case_sensitive($line,$latexmodifier,$char,$entitiyfragment) {
996  $line = preg_replace('/\\{?\\\\'.preg_quote($latexmodifier,'/').' ?\\{?'.$char.'\\}?/','&'.$char.''.$entitiyfragment.';', $line);
997  return $line;
998}
999
1000/** converts latex chars to HTML entities.
1001(I still look for a comprehensive translation table from late chars to html, better than [[http://isdc.unige.ch/Newsletter/help.html]])
1002 */
1003function latex2html($line, $do_clean_extra_bracket=true) {
1004
1005  $line = preg_replace('/([^\\\\])~/','\\1&nbsp;', $line);
1006
1007  $line = str_replace(array('---', '--'), array('&mdash;', '&ndash;'), $line);
1008
1009  $line = str_replace(array('``', "''"), array('"', '"'), $line);
1010
1011  // performance increases with this test
1012  // bug found by Serge Barral: what happens if we have curly braces only (typically to ensure case in Latex)
1013  // added && strpos($line,'{')===false
1014  if (strpos($line,'\\')===false && strpos($line,'{')===false) return $line;
1015
1016  // handling uppercase
1017  //  echo preg_replace_callback('!\b[a-z]!', 'upper', strtolower($str));
1018  if (!function_exists("strtolowercallback")) {
1019    function strtolowercallback($array) {
1020      return strtolower($array[1]);
1021    }
1022  }
1023  if (!function_exists("strtouppercallback")) {
1024    function strtouppercallback($array) {
1025      return strtoupper($array[1]);
1026    }
1027  }
1028  $line = preg_replace_callback('/\\\\uppercase\{(.*)\}/U',"strtouppercallback", $line);
1029  $line = preg_replace_callback('/\\\\lowercase\{(.*)\}/U',"strtolowercallback", $line);
1030
1031  $maths = array();
1032  $index = 0;
1033  // first we escape the math env
1034  preg_match_all('/\$.*?\$/', $line, $matches);
1035  foreach ($matches[0] as $k) {
1036    $maths[] = $k;
1037    $line = str_replace($k, '__MATH'.$index.'__', $line);
1038    $index++;
1039  }
1040
1041  // we should better replace this before the others
1042  // in order not to mix with the HTML entities coming after (just in case)
1043  $line = str_replace('\\&','&amp;', $line);
1044
1045  $line = str_replace('\_','_',$line);
1046  $line = str_replace('\%','%',$line);
1047
1048  // handling \url{....}
1049  // often used in howpublished for @misc
1050  $line = preg_replace('/\\\\url\{(.*)\}/U','<a href="\\1">\\1</a>', $line);
1051
1052  // Friday, April 01 2011
1053  // added support for accented i
1054  // for instance \`\i
1055  // see http://en.wikibooks.org/wiki/LaTeX/Accents
1056  // " the letters "i" and "j" require special treatment when they are given accents because it is often desirable to replace the dot with the accent. For this purpose, the commands \i and \j can be used to produce dotless letters."
1057  $line = preg_replace('/\\\\([ij])/i','\\1', $line);
1058
1059
1060  $line = char2html($line,"'",'a',"acute");
1061  $line = char2html($line,"'",'c',"acute");
1062  $line = char2html($line,"'",'e',"acute");
1063  $line = char2html($line,"'",'i',"acute");
1064  $line = char2html($line,"'",'o',"acute");
1065  $line = char2html($line,"'",'u',"acute");
1066  $line = char2html($line,"'",'y',"acute");
1067  $line = char2html($line,"'",'n',"acute");
1068
1069  $line = char2html($line,'`','a',"grave");
1070  $line = char2html($line,'`','e',"grave");
1071  $line = char2html($line,'`','i',"grave");
1072  $line = char2html($line,'`','o',"grave");
1073  $line = char2html($line,'`','u',"grave");
1074
1075  $line = char2html($line,'~','a',"tilde");
1076  $line = char2html($line,'~','n',"tilde");
1077  $line = char2html($line,'~','o',"tilde");
1078
1079  $line = char2html($line,'"','a',"uml");
1080  $line = char2html($line,'"','e',"uml");
1081  $line = char2html($line,'"','i',"uml");
1082  $line = char2html($line,'"','o',"uml");
1083  $line = char2html($line,'"','u',"uml");
1084  $line = char2html($line,'"','y',"uml");
1085  $line = char2html($line,'"','s',"zlig");
1086
1087  $line = char2html($line,'^','a',"circ");
1088  $line = char2html($line,'^','e',"circ");
1089  $line = char2html($line,'^','i',"circ");
1090  $line = char2html($line,'^','o',"circ");
1091  $line = char2html($line,'^','u',"circ");
1092
1093  $line = char2html($line,'r','a',"ring");
1094
1095  $line = char2html($line,'c','c',"cedil");
1096  $line = char2html($line,'c','s',"cedil");
1097  $line = char2html($line,'v','s',"caron");
1098
1099  $line = str_replace('\\ae','&aelig;', $line);
1100  $line = str_replace('\\ss','&szlig;', $line);
1101
1102  $line = str_replace('\\o','&oslash;', $line);
1103  $line = str_replace('\\O','&Oslash;', $line);
1104  $line = str_replace('\\aa','&aring;', $line);
1105  $line = str_replace('\\AA','&Aring;', $line);
1106
1107  $line = str_replace('\\l','&#322',$line);
1108  $line = str_replace('\\L','&#321',$line);
1109  $line = str_replace('\\k{a}','&#261',$line);
1110  $line = str_replace('\\\'{c}','&#263',$line);
1111
1112  $line = str_replace('\\v{c}','&#269',$line);
1113  $line = str_replace('\\v{C}','&#268',$line);
1114
1115  // handling \textsuperscript{....} FAILS if there still are nested {}
1116  $line = preg_replace('/\\\\textsuperscript\{(.*)\}/U','<sup>\\1</sup>', $line);
1117
1118  // handling \textsubscript{....} FAILS if there still are nested {}
1119  $line = preg_replace('/\\\\textsubscript\{(.*)\}/U','<sub>\\1</sub>', $line);
1120
1121
1122
1123  if ($do_clean_extra_bracket) {
1124    // clean extra tex curly brackets, usually used for preserving capitals
1125    // must come before the final math replacement
1126    $line = str_replace('}','',$line);
1127    $line = str_replace('{','',$line);
1128  }
1129
1130  // we restore the math env
1131  for($i = 0; $i < count($maths); $i++) {
1132    $line = str_replace('__MATH'.$i.'__', $maths[$i], $line);
1133  }
1134
1135  return $line;
1136}
1137
1138/** encodes strings for Z3988 URLs. Note that & are encoded as %26 and not as &amp. */
1139function s3988($s) {
1140  if ($s == null) return '';
1141  // first remove the HTML entities (e.g. &eacute;) then urlencode them
1142  return urlencode($s);
1143}
1144
1145/**
1146see BibEntry->formatAuthor($author)
1147@deprecated
1148@nodoc
1149*/
1150function formatAuthor() {
1151  die('Sorry, this function does not exist anymore, however, you can simply use $bibentry->formatAuthor($author) instead.');
1152}
1153
1154// ----------------------------------------------------------------------
1155// BIB ENTRIES
1156// ----------------------------------------------------------------------
1157
1158/** represents a bibliographic entry.
1159usage:
1160<pre>
1161  $db = zetDB('bibacid-utf8.bib');
1162  $entry = $db->getEntryByKey('classical');
1163  echo bib2html($entry);
1164</pre>
1165notes:
1166- BibEntry are usually obtained with getEntryByKey or multisearch
1167*/
1168class BibEntry {
1169
1170  /** The fields (fieldName -> value) of this bib entry with Latex macros interpreted and encoded in the desired character set . */
1171  var $fields = array();
1172
1173  /** The raw fields (fieldName -> value) of this bib entry. */
1174  var $raw_fields = array();
1175
1176  /** The constants @STRINGS referred to by this entry */
1177  var $constants = array();
1178
1179  /** The homepages of authors if any */
1180  var $homepages = array();
1181
1182  /** The crossref entry if there is one */
1183  var $crossref;
1184
1185  /** The verbatim copy (i.e., whole text) of this bib entry. */
1186  var $text = '';
1187
1188  /** A timestamp to trace when entries have been created */
1189  var $timestamp;
1190
1191  /** The name of the file containing this entry */
1192  var $filename;
1193
1194  /** The short name of the entry (parameterized by ABBRV_TYPE) */
1195  var $abbrv;
1196
1197  /** The index in a list of publications (e.g. [1] Foo */
1198  var $index = '';
1199
1200  /** The location in the original bibtex file (set by addEntry) */
1201  var $order = -1;
1202
1203
1204  /** returns a debug string representation */
1205  function __toString() {
1206    return $this->getType()." ".$this->getKey();
1207  }
1208
1209  /** Creates an empty new bib entry. Each bib entry is assigned a unique
1210   * identification number. */
1211  function __construct() {
1212  }
1213
1214  /** Sets the name of the file containing this entry */
1215  function setFile($filename) {
1216    $this->filename = $filename;
1217    return $this;
1218  }
1219
1220  /** Adds timestamp to this object */
1221  function timestamp() {
1222    $this->timestamp = time();
1223  }
1224  /** Returns the timestamp of this object */
1225  function getTimestamp() {
1226    return $this->timestamp;
1227  }
1228
1229  /** Returns the type of this bib entry (always lowercase). */
1230  function getType() {
1231    // strtolower is important to be case-insensitive
1232    return strtolower($this->getField(Q_INNER_TYPE));
1233  }
1234
1235  /** Sets the key of this bib entry. */
1236  function setKey($value) {
1237    // Slashes are not allowed in keys because they don't play well with web servers
1238    // if url-rewriting is used
1239    $this->setField(Q_KEY,str_replace('/','-',$value));
1240  }
1241
1242  function transformValue($value) {
1243    if (c('BIBTEXBROWSER_USE_LATEX2HTML'))
1244    {
1245        // trim space
1246        $value = xtrim($value);
1247
1248        // transform Latex markup to HTML entities (easier than a one to one mapping to each character)
1249        // HTML entity is an intermediate format
1250        $value = latex2html($value);
1251
1252        // transform to the target output encoding
1253        $value = html_entity_decode($value, ENT_QUOTES|ENT_XHTML, OUTPUT_ENCODING);
1254    }
1255    return $value;
1256  }
1257
1258  /** removes a field from this bibtex entry */
1259  function removeField($name) {
1260    $name = strtolower($name);
1261    unset($this->raw_fields[$name]);
1262    unset($this->fields[$name]);
1263  }
1264
1265  /** Sets a field of this bib entry. */
1266  function setField($name, $value) {
1267    $name = strtolower($name);
1268    $this->raw_fields[$name] = $value;
1269
1270    // fields that should not be transformed
1271    // we assume that "comment" is never latex code
1272    // but instead could contain HTML code (with links using the character "~" for example)
1273    // so "comment" is not transformed too
1274    if ($name!='url' && $name!='comment'
1275            && !preg_match('/^hp_/',$name) // homepage links should not be transformed with latex2html
1276        ) {
1277          $value = $this->transformValue($value);
1278
1279      // 4. transform existing encoded character in the new format
1280      if (function_exists('mb_convert_encoding') && OUTPUT_ENCODING != BIBTEX_INPUT_ENCODING) {
1281        $value = mb_convert_encoding($value, OUTPUT_ENCODING, BIBTEX_INPUT_ENCODING);
1282      }
1283
1284    } else {
1285      //echo "xx".$value."xx\n";
1286    }
1287
1288
1289
1290    $this->fields[$name] = $value;
1291  }
1292
1293  function clean_top_curly($value) {
1294    $value = preg_replace('/^\{/','', $value);
1295    $value = preg_replace('/\}$/','', $value);
1296    return $value;
1297  }
1298
1299  /** Sets a type of this bib entry. */
1300  function setType($value) {
1301    // 2009-10-25 added trim
1302    // to support space e.g. "@article  {"
1303    // as generated by ams.org
1304    // thanks to Jacob Kellner
1305    $this->fields[Q_INNER_TYPE] = trim($value);
1306  }
1307
1308  function setIndex($index) { $this->index = $index; }
1309
1310  /** Tries to build a good URL for this entry. The URL should be absolute (better for the generated RSS) */
1311  function getURL() {
1312    if (defined('BIBTEXBROWSER_URL_BUILDER')) {
1313      $f = BIBTEXBROWSER_URL_BUILDER;
1314      return $f($this);
1315    }
1316//     echo $this->filename;
1317//     echo $this->getKey();
1318    return BIBTEXBROWSER_URL.'?'.createQueryString(array(Q_KEY=>$this->getKey(), Q_FILE=>$this->filename));
1319  }
1320
1321  /** @see bib2links(), kept for backward compatibility */
1322  function bib2links() {
1323    return bib2links($this);
1324  }
1325
1326  /** Read the bibtex field $bibfield and return a link with icon (if $iconurl is given) or text
1327   * e.g. given the bibtex entry: @article{myarticle, pdf={myarticle.pdf}},
1328   * $bibtexentry->getLink('pdf') creates a link to myarticle.pdf using the text '[pdf]'.
1329   * $bibtexentry->getLink('pdf','pdficon.png') returns &lt;a href="myarticle.pdf">&lt;img src="pdficon.png"/>&lt;/a>
1330   * if you want a label that is different from the bibtex field, add a third parameter.
1331  */
1332  function getLink($bibfield,$iconurl=NULL,$altlabel=NULL) {
1333    $show = true;
1334    if ($altlabel==NULL) { $altlabel=$bibfield; }
1335    $str = $this->getIconOrTxt($altlabel,$iconurl);
1336    if ($this->hasField($bibfield)) {
1337       return '<a'.get_target().' href="'.$this->getField($bibfield).'">'.$str.'</a>';
1338    }
1339    return '';
1340  }
1341
1342  /** returns a "[bib]" link */
1343  function getBibLink($iconurl=NULL) {
1344    $bibstr = $this->getIconOrTxt('bibtex',$iconurl);
1345    $href = 'href="'.$this->getURL().'"';
1346    // we add biburl and title to be able to retrieve this important information
1347    // using Xpath expressions on the XHTML source
1348    $link = '<a'.get_target()." class=\"biburl\" title=\"".$this->getKey()."\" {$href}>$bibstr</a>";
1349    return $link;
1350  }
1351
1352  /** kept for backward compatibility */
1353  function getPdfLink($iconurl = NULL, $label = NULL) {
1354    return $this->getUrlLink($iconurl);
1355  }
1356
1357  /** returns a "[pdf]" link for the entry, if possible.
1358      Tries to get the target URL from the 'pdf' field first, then from 'url' or 'file'.
1359      Performs a sanity check that the file extension is 'pdf' or 'ps' and uses that as link label.
1360      Otherwise (and if no explicit $label is set) the field name is used instead.
1361    */
1362  function getUrlLink($iconurl = NULL) {
1363    if ($this->hasField('pdf')) {
1364      return $this->getAndRenameLink('pdf', $iconurl);
1365    }
1366    if ($this->hasField('url')) {
1367      return $this->getAndRenameLink('url', $iconurl);
1368    }
1369    // Adding link to PDF file exported by Zotero
1370    // ref: https://github.com/monperrus/bibtexbrowser/pull/14
1371    if ($this->hasField('file')) {
1372      return $this->getAndRenameLink('file', $iconurl);
1373    }
1374    return "";
1375  }
1376
1377  /** See description of 'getUrlLink'
1378    */
1379  function getAndRenameLink($bibfield, $iconurl=NULL) {
1380    $extension = strtolower(pathinfo(parse_url($this->getField($bibfield),PHP_URL_PATH),PATHINFO_EXTENSION));
1381    switch ($extension) {
1382      // overriding the label if it's a known extension
1383      case 'html': return $this->getLink($bibfield, $iconurl, 'html'); break;
1384      case 'pdf': return $this->getLink($bibfield, $iconurl, 'pdf'); break;
1385      case 'ps': return $this->getLink($bibfield, $iconurl, 'ps'); break;
1386      default:
1387        return $this->getLink($bibfield, $iconurl, $bibfield);
1388    }
1389  }
1390
1391
1392
1393  /** DOI are a special kind of links, where the url depends on the doi */
1394  function getDoiLink($iconurl=NULL) {
1395    $str = $this->getIconOrTxt('doi',$iconurl);
1396    if ($this->hasField('doi')) {
1397        return '<a'.get_target().' href="https://doi.org/'.$this->getField('doi').'">'.$str.'</a>';
1398    }
1399    return '';
1400  }
1401
1402  /** GS (Google Scholar) are a special kind of links, where the url depends on the google scholar id */
1403  function getGSLink($iconurl=NULL) {
1404    $str = $this->getIconOrTxt('citations',$iconurl);
1405    if ($this->hasField('gsid')) {
1406        return ' <a'.get_target().' href="https://scholar.google.com/scholar?cites='.$this->getField("gsid").'">'.$str.'</a>';
1407    }
1408    return '';
1409  }
1410
1411  /** replace [$ext] with an icon whose url is defined in a string
1412   *  e.g. getIconOrTxt('pdf') will print '[pdf]'
1413   *  or   getIconOrTxt('pdf','http://link/to/icon.png') will use the icon linked by the url, or print '[pdf']
1414   *  if the url does not point to a valid file (using the "alt" property of the "img" html tag)
1415   */
1416  function getIconOrTxt($txt,$iconurl=NULL) {
1417    if ( $iconurl==NULL ) {
1418      $str='['.$txt.']';
1419    } else {
1420      $str='<img class="icon" src="'.$iconurl.'" alt="['.$txt.']" title="'.$txt.'"/>';
1421    }
1422    return $str;
1423  }
1424
1425  /** Reruns the abstract */
1426  function getAbstract() {
1427    if ($this->hasField('abstract')) return $this->getField('abstract');
1428    else return '';
1429  }
1430
1431  /**
1432    * Returns the last name of an author name.
1433    */
1434  function getLastName($author){
1435      list($firstname, $lastname) = splitFullName($author);
1436      return $lastname;
1437  }
1438
1439  /**
1440    * Returns the first name of an author name.
1441    */
1442  function getFirstName($author){
1443      list($firstname, $lastname) = splitFullName($author);
1444      return $firstname;
1445  }
1446
1447  /** Has this entry the given field? */
1448  function hasField($name) {
1449    return isset($this->fields[strtolower($name)]);
1450  }
1451
1452  /** Returns the authors of this entry. If "author" is not given,
1453   * return a string 'Unknown'. */
1454  function getAuthor() {
1455    if (array_key_exists(AUTHOR, $this->fields)) {
1456      return $this->getFormattedAuthorsString();
1457    }
1458    // 2010-03-02: commented the following, it results in misleading author lists
1459    // issue found by Alan P. Sexton
1460    //if (array_key_exists(EDITOR, $this->fields)) {
1461    //  return $this->fields[EDITOR];
1462    //}
1463    return 'Unknown';
1464  }
1465
1466  /** Returns the key of this entry */
1467  function getKey() {
1468    return $this->getField(Q_KEY);
1469  }
1470
1471  /** Returns the title of this entry? */
1472  function getTitle() {
1473    return $this->getField('title');
1474  }
1475
1476   /** Returns the publisher of this entry
1477    * It encodes a specific logic
1478    * */
1479  function getPublisher() {
1480    // citation_publisher
1481    if ($this->hasField("publisher")) {
1482      return $this->getField("publisher");
1483    }
1484    if ($this->getType()=="phdthesis") {
1485      return $this->getField(SCHOOL);
1486    }
1487    if ($this->getType()=="mastersthesis") {
1488      return $this->getField(SCHOOL);
1489    }
1490    if ($this->getType()=="bachelorsthesis") {
1491      return $this->getField(SCHOOL);
1492    }
1493    if ($this->getType()=="techreport") {
1494      return $this->getField("institution");
1495    }
1496    // then we don't know
1497    return '';
1498  }
1499
1500  /** Returns the authors of this entry as an array (split by " and ") */
1501  function getRawAuthors() {
1502    return $this->split_names(Q_AUTHOR);
1503  }
1504
1505  // Previously called split_authors. Made generic to allow call on editors as well.
1506  function split_names($key) {
1507    if (!array_key_exists($key, $this->raw_fields)) return array();
1508
1509    // Sometimes authors/editors are split by line breaks followed by whitespace in bib files.
1510    // In this case we need to replace these with a normal space.
1511    $raw = preg_replace( '/\s+/', ' ', @$this->raw_fields[$key]);
1512    $array = preg_split('/ and( |$)/ims', $raw);
1513
1514    $res = array();
1515    // we merge the remaining ones
1516    for ($i=0; $i < count($array)-1; $i++) {
1517      if (strpos( latex2html($array[$i],false), '{') !== FALSE && strpos(latex2html($array[$i+1],false),'}') !== FALSE) {
1518        $res[] = $this->clean_top_curly(trim($array[$i])." and ".trim($array[$i+1]));
1519        $i = $i + 1;
1520      } else {
1521        $res[] = trim($array[$i]);
1522      }
1523    }
1524    $res[] = trim($array[count($array)-1]);
1525    return $res;
1526  }
1527
1528  /**
1529   * Returns the formated author name w.r.t to the user preference
1530   * encoded in USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT and USE_INITIALS_FOR_NAMES
1531   */
1532  function formatAuthor($author){
1533    $author = $this->transformValue($author);
1534    if (bibtexbrowser_configuration('USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT')) {
1535      return $this->formatAuthorCommaSeparated($author);
1536    }
1537
1538    if (bibtexbrowser_configuration('USE_INITIALS_FOR_NAMES')) {
1539      return $this->formatAuthorInitials($author);
1540    }
1541
1542    if (bibtexbrowser_configuration('USE_FIRST_THEN_LAST')) {
1543      return $this->formatAuthorCanonical($author);
1544    }
1545
1546    return $author;
1547  }
1548
1549  /**
1550  * Returns the formated author name as "FirstName LastName".
1551  */
1552  function formatAuthorCanonical($author){
1553      list($firstname, $lastname) = splitFullName($author);
1554      if ($firstname!='') return $firstname.' '.$lastname;
1555      else return $lastname;
1556  }
1557
1558  /**
1559  * Returns the formated author name as "LastName, FirstName".
1560  */
1561  function formatAuthorCommaSeparated($author){
1562      list($firstname, $lastname) = splitFullName($author);
1563      if ($firstname!='') return $lastname.', '.$firstname;
1564      else return $lastname;
1565  }
1566
1567  /**
1568  * Returns the formated author name as "LastName Initials".
1569  * e.g. for Vancouver-style used by PubMed.
1570  */
1571  function formatAuthorInitials($author){
1572      list($firstname, $lastname) = splitFullName($author);
1573      if ($firstname!='') return $lastname.' '.preg_replace("/(\p{Lu})\w*[- ]*/Su","$1", $firstname);
1574      else return $lastname;
1575  }
1576
1577
1578  /** @deprecated */
1579  function formattedAuthors() {  return $this->getFormattedAuthorsString(); }
1580  /** @deprecated */
1581  function getFormattedAuthors() {  return $this->getFormattedAuthorsArray(); }
1582  /** @deprecated */
1583  function getFormattedAuthorsImproved() {  return $this->getFormattedAuthorsString(); }
1584
1585
1586  /** Returns the authors as an array of strings (one string per author).
1587   */
1588  function getFormattedAuthorsArray() {
1589    $array_authors = array();
1590
1591
1592    // first we use formatAuthor
1593    foreach ($this->getRawAuthors() as $author) {
1594      $array_authors[]=$this->formatAuthor($author);
1595    }
1596
1597    if (BIBTEXBROWSER_AUTHOR_LINKS=='homepage') {
1598      foreach ($array_authors as $k => $author) {
1599        $array_authors[$k]=$this->addHomepageLink($author);
1600      }
1601    }
1602
1603    if (BIBTEXBROWSER_AUTHOR_LINKS=='resultpage') {
1604      foreach ($array_authors as $k => $author) {
1605        $array_authors[$k]=$this->addAuthorPageLink($author);
1606      }
1607    }
1608
1609    return $array_authors;
1610  }
1611
1612  /** Adds to getFormattedAuthors() the home page links and returns a string (not an array). Is configured with BIBTEXBROWSER_AUTHOR_LINKS and USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT.
1613  */
1614  function getFormattedAuthorsString() {
1615    return $this->implodeAuthors($this->getFormattedAuthorsArray());
1616  }
1617
1618  function implodeAuthors($authors) {
1619    if (count($authors)==0) return '';
1620    if (count($authors)==1) return $authors[0];
1621
1622    $result = '';
1623
1624    if (bibtexbrowser_configuration('USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT')) {$sep = '; ';} else {$sep = ', ';}
1625    if (FORCE_NAMELIST_SEPARATOR !== '') {$sep = FORCE_NAMELIST_SEPARATOR;}
1626    for ($i=0;$i<count($authors)-2;$i++) {
1627      $result .= $authors[$i].$sep;
1628    }
1629    $lastAuthorSeperator = bibtexbrowser_configuration('LAST_AUTHOR_SEPARATOR');
1630    // add Oxford comma if there are more than 2 authors
1631    if (bibtexbrowser_configuration('USE_OXFORD_COMMA') && count($authors)>2) {
1632      $lastAuthorSeperator = $sep.$lastAuthorSeperator;
1633      $lastAuthorSeperator = preg_replace("/ {2,}/", " ", $lastAuthorSeperator); // get rid of double spaces
1634    }
1635    $result .= $authors[count($authors)-2].$lastAuthorSeperator.$authors[count($authors)-1];
1636    return $result;
1637  }
1638
1639  /** adds a link to the author page */
1640  function addAuthorPageLink($author) {
1641    $link = makeHref(array(Q_AUTHOR => $author));
1642    return "<a {$link}>$author</a>";
1643  }
1644
1645
1646  /** Returns the authors of this entry as an array in a canonical form */
1647  function getCanonicalAuthors() {
1648    $authors = array();
1649    foreach ($this->getRawAuthors() as $author) {
1650      $authors[]=$this->formatAuthorCanonical($author);
1651    }
1652    return $authors;
1653  }
1654
1655  /** Returns the authors of this entry as an array in a comma-separated form
1656   * Mostly used to create meta tags (eg <meta>
1657   */
1658  function getArrayOfCommaSeparatedAuthors() {
1659    $authors = array();
1660    foreach ($this->getRawAuthors() as $author) {
1661      $author = $this->transformValue($author);
1662      $authors[]=$this->formatAuthorCommaSeparated($author);
1663    }
1664    return $authors;
1665  }
1666
1667  /**
1668  * Returns a compacted string form of author names by throwing away
1669  * all author names except for the first one and appending ", et al."
1670  */
1671  function getCompactedAuthors(){
1672    $authors = $this->getRawAuthors();
1673    $etal = count($authors) > 1 ? ', et al.' : '';
1674    return $this->formatAuthor($authors[0]) . $etal;
1675  }
1676
1677  function getHomePageKey($author) {
1678
1679    return strtolower('hp_'.preg_replace('/ /', '', $this->formatAuthorCanonical(latex2html($author))));
1680  }
1681
1682  /** add the link to the homepage if it is defined in a string
1683   *  e.g. @string{hp_MartinMonperrus="http://www.monperrus.net/martin"}
1684   *  The string is a concatenation of firstname, lastname, prefixed by hp_
1685   * Warning: by convention @string are case sensitive so please be keep the same case as author names
1686   * @thanks Eric Bodden for the idea
1687   */
1688  function addHomepageLink($author) {
1689    // hp as home page
1690    // accents are normally handled
1691    // e.g. @STRING{hp_Jean-MarcJézéquel="http://www.irisa.fr/prive/jezequel/"}
1692    $homepage = $this->getHomePageKey($author);
1693    if (isset($this->homepages[$homepage]))
1694      $author='<a href="'.$this->homepages[$homepage].'">'.$author.'</a>';
1695    return $author;
1696  }
1697
1698
1699  /** Returns the editors of this entry as an arry */
1700  function getEditors() {
1701    $editors = array();
1702    foreach (preg_split('/ and /i', $this->getField(EDITOR)) as $editor) {
1703      $editors[]=$editor;
1704    }
1705    return $editors;
1706  }
1707
1708
1709  /** Returns the editors of this entry as an array (split by " and ") */
1710  function getRawEditors() {
1711    return $this->split_names(EDITOR);
1712  }
1713
1714  /** Returns the editors of this entry as an arry */
1715  function getFormattedEditors() {
1716    $editors = array();
1717    foreach ($this->getEditors() as $editor) {
1718      $editors[]=$this->formatAuthor($editor);
1719    }
1720    if (bibtexbrowser_configuration('USE_COMMA_AS_NAME_SEPARATOR_IN_OUTPUT')) {$sep = '; ';} else {$sep = ', ';}
1721    if (FORCE_NAMELIST_SEPARATOR !== '') {$sep = FORCE_NAMELIST_SEPARATOR;}
1722    return implode($sep, $editors).', '.(count($editors)>1?'eds.':'ed.');
1723  }
1724
1725  /** Returns the year of this entry? */
1726  function getYear() {
1727    return __(strtolower($this->getYearRaw()));
1728  }
1729  function getYearRaw() {
1730    $r = $this->getField('year');
1731    if ($r == null) return '';
1732    return $r;
1733  }
1734
1735  /** returns the array of keywords */
1736  function getKeywords() {
1737    return preg_split('/[,;\\/]/', $this->getField("keywords"));
1738  }
1739
1740  /** Returns the value of the given field? */
1741  function getField($name) {
1742    // 2010-06-07: profiling showed that this is very costly
1743    // hence returning the value directly
1744    //if ($this->hasField($name))
1745    //    {return $this->fields[strtolower($name)];}
1746    //else return 'missing '.$name;
1747
1748    return @$this->fields[strtolower($name)];
1749  }
1750
1751
1752
1753  /** Returns the fields */
1754  function getFields() {
1755    return $this->fields;
1756  }
1757
1758  /** Returns the raw, undecorated abbreviation depending on ABBRV_TYPE. */
1759  function getRawAbbrv() {
1760    if (c('ABBRV_TYPE') == 'index') return $this->index;
1761    if (c('ABBRV_TYPE') == 'none') return '';
1762    if (c('ABBRV_TYPE') == 'key') return $this->getKey();
1763    if (c('ABBRV_TYPE') == 'year') return $this->getYear();
1764    if (c('ABBRV_TYPE') == 'x-abbrv') {
1765      if ($this->hasField('x-abbrv')) {return $this->getField('x-abbrv');}
1766      return $this->abbrv;
1767    }
1768    if (c('ABBRV_TYPE') == 'keys-index') {
1769      if (isset($_GET[Q_INNER_KEYS_INDEX])) {return $_GET[Q_INNER_KEYS_INDEX][$this->getKey()]; }
1770      return '';
1771    }
1772
1773    // otherwise it is a user-defined function in bibtexbrowser.local.php
1774    $f = c('ABBRV_TYPE');
1775    return $f($this);
1776  }
1777
1778  /** Returns the abbreviation, etc [1] if ABBRV_TYPE='index'. */
1779  function getAbbrv() {
1780    $abbrv = $this->getRawAbbrv();
1781    if ( c('ABBRV_TYPE') != 'none' ) {
1782       $abbrv = '['.$abbrv.']';
1783    }
1784    return $abbrv;
1785  }
1786
1787
1788  /** Sets the abbreviation (e.g. [OOPSLA] or [1]) */
1789  function setAbbrv($abbrv) {
1790    //if (!is_string($abbrv)) { throw new Exception('Illegal argument'); }
1791    $this->abbrv = $abbrv;
1792    return $this;
1793  }
1794
1795
1796  /** Returns the verbatim text of this bib entry. */
1797  function getText() {
1798    if (c('BIBTEXBROWSER_BIBTEX_VIEW') == 'original') {
1799        return $this->text;
1800    }
1801    if (c('BIBTEXBROWSER_BIBTEX_VIEW') == 'reconstructed') {
1802        $result = '@'.$this->getType().'{'.$this->getKey().",\n";
1803        foreach ($this->raw_fields as $k=>$v) {
1804          if ( !preg_match('/^('.c('BIBTEXBROWSER_BIBTEX_VIEW_FILTEREDOUT').')$/i', $k)
1805             && !preg_match('/^(key|'.Q_INNER_AUTHOR.'|'.Q_INNER_TYPE.')$/i', $k) )
1806             {
1807              $result .= ' '.$k.' = {'.$v.'},'."\n";
1808          }
1809        }
1810        $result .= "}\n";
1811        return $result;
1812    }
1813    throw new Exception('incorrect value of BIBTEXBROWSER_BIBTEX_VIEW: '+BIBTEXBROWSER_BIBTEX_VIEW);
1814  }
1815
1816  /** Returns true if this bib entry contains the given phrase (PREG regexp)
1817   * in the given field. if $field is null, all fields are considered.
1818   * Note that this method is NOT case sensitive */
1819  function hasPhrase($phrase, $field = null) {
1820
1821    // we have to search in the formatted fields and not in the raw entry
1822    // i.e. all latex markups are not considered for searches
1823    if (!$field) {
1824      return preg_match('/'.$phrase.'/i',$this->getConstants().' '.join(" ",$this->getFields()));
1825      //return stripos($this->getText(), $phrase) !== false;
1826    }
1827    if ($this->hasField($field) &&  (preg_match('/'.$phrase.'/i',$this->getField($field)) ) ) {
1828    //if ($this->hasField($field) &&  (stripos($this->getField($field), $phrase) !== false) ) {
1829      return true;
1830    }
1831
1832    return false;
1833  }
1834
1835
1836  /** Outputs HTML line according to layout */
1837  function toHTML($wrapped=false) {
1838      $result = '';
1839      if ($wrapped) {
1840      switch(BIBTEXBROWSER_LAYOUT) { // open row
1841        case 'list':
1842          $result .= '<li class="bibline">';
1843          break;
1844        case 'ordered_list':
1845          $result .= '<li class="bibline">';
1846          break;
1847        case 'table':
1848          $result .= '<tr class="bibline"><td class="bibref">';
1849          break;
1850        case 'definition':
1851          $result .= '<dl class="bibline"><dt class="bibref">';
1852          if (c('ABBRV_TYPE')=='none') { die ('Cannot define an empty term!'); }
1853          break;
1854        case 'none':
1855          break;
1856      }
1857      $result .= $this->anchor();
1858      switch(BIBTEXBROWSER_LAYOUT) { // close bibref and open bibitem
1859        case 'table':
1860          $result .= $this->getAbbrv().'</td><td class="bibitem">';
1861          break;
1862        case 'definition':
1863          $result .= $this->getAbbrv().'</dt><dd class="bibitem">';
1864          break;
1865      }
1866      }
1867
1868      // may be overridden using configuration value of BIBLIOGRAPHYSTYLE
1869      $result .= bib2html($this);
1870
1871      // may be overridden using configuration value of BIBTEXBROWSER_LINK_STYLE
1872      $result .= ' '.bib2links($this);
1873
1874      if ($wrapped) {
1875      switch(BIBTEXBROWSER_LAYOUT) { // close row
1876        case 'list':
1877          $result .= '</li>'."\n";
1878          break;
1879        case 'ordered_list':
1880          $result .= '</li>'."\n";
1881          break;
1882        case 'table':
1883          $result .= '</td></tr>'."\n";
1884          break;
1885        case 'definition':
1886          $result .= '</dd></dl>'."\n";
1887          break;
1888        case 'none':
1889          break;
1890      }
1891      }
1892      return $result;
1893  }
1894
1895
1896  /** Outputs an coins URL: see http://ocoins.info/cobg.html
1897   * Used by Zotero, mendeley, etc.
1898  */
1899  function toCoins() {
1900    if (c('METADATA_COINS') == false) {
1901        return;
1902    }
1903    $url_parts=array();
1904    $url_parts[]='ctx_ver=Z39.88-2004';
1905
1906    $type = $this->getType();
1907    if ($type=="book") {
1908      $url_parts[]='rft_val_fmt='.s3988('info:ofi/fmt:kev:mtx:book');
1909      $url_parts[]='rft.btitle='.s3988($this->getTitle());
1910      $url_parts[]='rft.genre=book';
1911    } else if ($type=="inproceedings") {
1912      $url_parts[]='rft_val_fmt='.s3988('info:ofi/fmt:kev:mtx:book');
1913      $url_parts[]='rft.atitle='.s3988($this->getTitle());
1914      $url_parts[]='rft.btitle='.s3988($this->getField(BOOKTITLE));
1915
1916      // zotero does not support with this proceeding and conference
1917      // they give the wrong title
1918      //$url_parts[]='rft.genre=proceeding';
1919      //$url_parts[]='rft.genre=conference';
1920      $url_parts[]='rft.genre=bookitem';
1921    } else if ($type=="incollection" ) {
1922      $url_parts[]='rft_val_fmt='.s3988('info:ofi/fmt:kev:mtx:book');
1923      $url_parts[]='rft.btitle='.s3988($this->getField(BOOKTITLE));
1924      $url_parts[]='rft.atitle='.s3988($this->getTitle());
1925      $url_parts[]='rft.genre=bookitem';
1926    } else if ($type=="article") {
1927      $url_parts[]='rft_val_fmt='.s3988('info:ofi/fmt:kev:mtx:journal');
1928      $url_parts[]='rft.atitle='.s3988($this->getTitle());
1929      $url_parts[]='rft.jtitle='.s3988($this->getField("journal"));
1930      $url_parts[]='rft.volume='.s3988($this->getField("volume"));
1931      $url_parts[]='rft.issue='.s3988($this->getField("issue"));
1932    } else { // techreport, phdthesis
1933      $url_parts[]='rft_val_fmt='.s3988('info:ofi/fmt:kev:mtx:book');
1934      $url_parts[]='rft.btitle='.s3988($this->getTitle());
1935      $url_parts[]='rft.genre=report';
1936    }
1937
1938    $url_parts[]='rft.pub='.s3988($this->getPublisher());
1939
1940    // referent
1941    if ($this->hasField('url')) {
1942      $url_parts[]='rft_id='.s3988($this->getField('url'));
1943    } else if ($this->hasField('doi')) {
1944      $url_parts[]='rft_id='.s3988('info:doi/'.$this->getField("doi"));
1945    }
1946
1947    // referrer, the id of a collection of objects
1948    // see also http://www.openurl.info/registry/docs/pdf/info-sid.pdf
1949    $url_parts[]='rfr_id='.s3988('info:sid/'.@$_SERVER['HTTP_HOST'].':'.basename(@$_GET[Q_FILE]));
1950
1951    $url_parts[]='rft.date='.s3988($this->getYear());
1952
1953    foreach ($this->getFormattedAuthorsArray() as $au) $url_parts[]='rft.au='.s3988($au);
1954
1955
1956    return '<span class="Z3988" title="'.implode('&amp;',$url_parts).'"></span>';
1957
1958  }
1959
1960  /** Returns an anchor for this entry.  */
1961  function anchor() {
1962        return '<a class="bibanchor" name="'.$this->getRawAbbrv().'"></a>';
1963  }
1964
1965   /**
1966   * rebuild the set of constants used if any as a string
1967   */
1968  function getConstants() {
1969    $result='';
1970    foreach ($this->constants as $k=>$v) {
1971      $result.='@string{'.$k.'="'.$v."\"}\n";
1972    }
1973    return $result;
1974  }
1975
1976   /**
1977   * Displays a <pre> text of the given bib entry.
1978   * URLs are replaced by HTML links.
1979   */
1980  function toEntryUnformatted() {
1981    $result = "";
1982    $result .= '<pre class="purebibtex">'; // pre is nice when it is embedded with no CSS available
1983    $entry = htmlspecialchars($this->getFullText(),ENT_NOQUOTES|ENT_XHTML, OUTPUT_ENCODING);
1984
1985    // Fields that should be hyperlinks
1986    // the order matters
1987    $hyperlinks = array('url' => '%O', 'file' => '%O', 'pdf' => '%O', 'doi' => 'https://doi.org/%O', 'gsid' => 'https://scholar.google.com/scholar?cites=%O');
1988
1989    $vals = array();
1990    foreach ($hyperlinks as $field => $url) {
1991      if ($this->hasField($field)) {
1992        $href = str_replace('%O', $this->getField($field), $url);
1993        // this is not a parsing but a simple replacement
1994        $entry = str_replace($this->getField($field), '___'.$field.'___', $entry);
1995        $vals[$field] = $href;
1996      }
1997    }
1998    foreach ($vals as $field => $href) {
1999      if ($this->hasField($field)) {
2000        // this is not a parsing but a simple replacement
2001        $entry = str_replace('___'.$field.'___', '<a'.get_target().' href="'.$href.'">'.$this->getField($field).'</a>', $entry);
2002      }
2003    }
2004
2005    $result .=  $entry;
2006    $result .=  '</pre>';
2007    return $result;
2008   }
2009
2010   /**
2011   * Gets the raw text of the entry (crossref + strings + entry)
2012   */
2013  function getFullText() {
2014    $s = '';
2015    // adding the crossref if necessary
2016    if ($this->crossref!=null) { $s .= $this->crossref->getFullText()."\n";}
2017    $s.=$this->getConstants();
2018    $s.=$this->getText();
2019    return $s;
2020  }
2021
2022  /** returns the first and last page of the entry as an array ([0]->first,  [2]->last) */
2023  function getPages() {
2024    preg_match('/([0-9]+).*?([0-9]+)/',$this->getField('pages'),$matches);
2025    array_shift($matches);
2026    return $matches;
2027  }
2028
2029  /** returns in the citation file format, tailored for github */
2030  function toCFF() {
2031    $result = "";
2032    $result .= "cff-version: 1.2.0"."\n";
2033    $result .= "# CITATION.cff created with https://github.com/monperrus/bibtexbrowser/"."\n";
2034    $result .= "preferred-citation:"."\n";
2035    $result .= "  title: \"".$this->getTitle()."\""."\n";
2036    if ($this->hasField("doi")) {
2037        $result .= "  doi: \"".$this->getField("doi")."\""."\n";
2038    }
2039    if ($this->hasField("year")) {
2040        $result .= "  year: \"".$this->getField("year")."\""."\n";
2041    }
2042    if ($this->hasField("journal")) {
2043        $result .= "  type: article\n";
2044        $result .= "  journal: \"".$this->getField("journal")."\""."\n";
2045    }
2046    if ($this->hasField("booktitle")) {
2047        $result .= "  type: conference-paper\n";
2048        $result .= "  conference: \"".$this->getField("booktitle")."\""."\n";
2049    }
2050    $result .= "  authors:"."\n";
2051    foreach ($this->getFormattedAuthorsArray() as $author) {
2052        $split = splitFullName($author);
2053        $result .= "    - family-names: ".$split[1]."\n";
2054        $result .= "      given-names: ".$split[0]."\n";
2055    }
2056    return $result;
2057  }
2058
2059
2060} // end class BibEntry
2061
2062class RawBibEntry extends BibEntry {
2063  function setField($name, $value) {
2064    $this->fields[$name]=$value;
2065    $this->raw_fields[$name]=$value;
2066  }
2067}
2068
2069/** returns an HTML tag depending on BIBTEXBROWSER_LAYOUT e.g. <TABLE> */
2070function get_HTML_tag_for_layout() {
2071  switch(BIBTEXBROWSER_LAYOUT) { /* switch for different layouts */
2072    case 'list':
2073      $tag='ul';
2074      break;
2075    case 'ordered_list':
2076      $tag='ol';
2077      break;
2078    case 'table':
2079      $tag = 'table';
2080      break;
2081    case 'definition':
2082      $tag = 'div';
2083      break;
2084    default:
2085      die('Unknown BIBTEXBROWSER_LAYOUT');
2086  }
2087  return $tag;
2088}
2089
2090/** returns a collection of links for the given bibtex entry
2091 *  e.g. [bibtex] [doi][pdf]
2092 */
2093function bib2links_default($bibentry) {
2094  $links = array();
2095
2096  if (BIBTEXBROWSER_BIBTEX_LINKS) {
2097    $link = $bibentry->getBibLink();
2098    if ($link != '') { $links[] = $link; };
2099  }
2100
2101  if (BIBTEXBROWSER_PDF_LINKS) {
2102    $link = $bibentry->getUrlLink();
2103    if ($link != '') { $links[] = $link; };
2104  }
2105
2106  if (BIBTEXBROWSER_DOI_LINKS) {
2107    $link = $bibentry->getDoiLink();
2108    if ($link != '') { $links[] = $link; };
2109  }
2110
2111  if (BIBTEXBROWSER_GSID_LINKS) {
2112    $link = $bibentry->getGSLink();
2113    if ($link != '') { $links[] = $link; };
2114  }
2115
2116  return '<span class="bibmenu">'.implode(" ",$links).'</span>';
2117}
2118
2119
2120/** prints the header of a layouted HTML, depending on BIBTEXBROWSER_LAYOUT e.g. <TABLE> */
2121function print_header_layout() {
2122  if (BIBTEXBROWSER_LAYOUT == 'list') return;
2123  echo '<' . get_HTML_tag_for_layout() . ' class="result">'."\n";
2124}
2125
2126/** prints the footer of a layouted HTML, depending on BIBTEXBROWSER_LAYOUT e.g. </TABLE> */
2127function print_footer_layout() {
2128  echo '</' . get_HTML_tag_for_layout() . '>';
2129}
2130
2131/** this function encapsulates the user-defined name for bib to HTML*/
2132function bib2html($bibentry) {
2133  $function = bibtexbrowser_configuration('BIBLIOGRAPHYSTYLE');
2134  return $function($bibentry);
2135}
2136
2137/** this function encapsulates the user-defined name for bib2links */
2138function bib2links($bibentry) {
2139  $function = c('BIBTEXBROWSER_LINK_STYLE');
2140  return $function($bibentry);
2141}
2142
2143/** encapsulates the user-defined sections. @nodoc */
2144function _DefaultBibliographySections() {
2145  $function = BIBLIOGRAPHYSECTIONS;
2146  return $function();
2147}
2148
2149/** encapsulates the user-defined sections. @nodoc */
2150function _DefaultBibliographyTitle($query) {
2151  $function = BIBLIOGRAPHYTITLE;
2152  return $function($query);
2153}
2154
2155function DefaultBibliographyTitle($query) {
2156  $result = 'Publications in '.$_GET[Q_FILE];
2157  if (isset($query['all'])) {
2158    unset($query['all']);
2159  }
2160  if (count($query)>0) {
2161    $result .= ' - '.query2title($query);
2162  }
2163  return $result;
2164}
2165
2166/** compares two instances of BibEntry by modification time
2167 */
2168function compare_bib_entry_by_mtime($a, $b)
2169{
2170  return -($a->getTimestamp()-$b->getTimestamp());
2171}
2172
2173/** compares two instances of BibEntry by order in Bibtex file
2174 */
2175function compare_bib_entry_by_bibtex_order($a, $b)
2176{
2177  return $a->order-$b->order;
2178}
2179
2180/** compares two instances of BibEntry by year
2181 */
2182function compare_bib_entry_by_year($a, $b)
2183{
2184  $yearA = (int) $a->getYear(); // 0 if no year
2185  $yearB = (int) $b->getYear();
2186
2187  if ($yearA === 0) {
2188    switch (strtolower($a->getYearRaw())) {
2189      case Q_YEAR_INPRESS:
2190        $yearA = PHP_INT_MIN + ORDER_YEAR_INPRESS;
2191	break;
2192      case Q_YEAR_ACCEPTED:
2193        $yearA = PHP_INT_MIN + ORDER_YEAR_ACCEPTED;
2194	break;
2195      case Q_YEAR_SUBMITTED:
2196        $yearA = PHP_INT_MIN + ORDER_YEAR_SUBMITTED;
2197	break;
2198      default:
2199        $yearA = PHP_INT_MIN + ORDER_YEAR_OTHERNONINT;
2200    }
2201  }
2202
2203  if ($yearB === 0) {
2204    switch (strtolower($b->getYearRaw())) {
2205      case Q_YEAR_INPRESS:
2206        $yearB = PHP_INT_MIN + ORDER_YEAR_INPRESS;
2207	break;
2208      case Q_YEAR_ACCEPTED:
2209        $yearB = PHP_INT_MIN + ORDER_YEAR_ACCEPTED;
2210	break;
2211      case Q_YEAR_SUBMITTED:
2212        $yearB = PHP_INT_MIN + ORDER_YEAR_SUBMITTED;
2213	break;
2214      default:
2215        $yearB = PHP_INT_MIN + ORDER_YEAR_OTHERNONINT;
2216    }
2217  }
2218
2219  if ($yearA === $yearB)
2220    return 0;
2221  else if ($yearA < $yearB)
2222    return -1;
2223  else
2224    return 1;
2225}
2226
2227/** compares two instances of BibEntry by title
2228 */
2229function compare_bib_entry_by_title($a, $b)
2230{
2231  return strcmp($a->getTitle(),$b->getTitle());
2232}
2233
2234/** compares two instances of BibEntry by undecorated Abbrv
2235 */
2236function compare_bib_entry_by_raw_abbrv($a, $b)
2237{
2238  return strcmp($a->getRawAbbrv(),$b->getRawAbbrv());
2239}
2240
2241/** compares two instances of BibEntry by author or editor
2242 */
2243function compare_bib_entry_by_name($a, $b)
2244{
2245  if ($a->hasField(AUTHOR))
2246    $namesA = $a->getAuthor();
2247  else if ($a->hasField(EDITOR))
2248    $namesA = $a->getField(EDITOR);
2249  else
2250    $namesA = __('No author');
2251
2252  if ($b->hasField(AUTHOR))
2253    $namesB = $b->getAuthor();
2254  else if ($b->hasField(EDITOR))
2255    $namesB = $b->getField(EDITOR);
2256  else
2257    $namesB = __('No author');
2258
2259  return strcmp($namesA, $namesB);
2260}
2261
2262/** compares two instances of BibEntry by month
2263 * @author Jan Geldmacher
2264 */
2265function compare_bib_entry_by_month($a, $b)
2266{
2267  // this was the old behavior
2268  // return strcmp($a->getKey(),$b->getKey());
2269
2270  //bibkey which is used for sorting
2271  $sort_key = 'month';
2272  //desired order of values
2273  $sort_order_values = array('jan','january','feb','february','mar','march','apr','april','may','jun','june','jul','july','aug','august','sep','september','oct','october','nov','november','dec','december');
2274  //order: 1=as specified in $sort_order_values  or -1=reversed
2275
2276
2277  //first check if the search key exists
2278  if (!array_key_exists($sort_key,$a->fields)  && !array_key_exists($sort_key,$b->fields)) {
2279    //neither a nor b have the key -> we compare the keys
2280    $retval=strcmp($a->getKey(),$b->getKey());
2281  }
2282  elseif (!array_key_exists($sort_key,$a->fields)) {
2283    //only b has the field -> b is greater
2284    $retval=-1;
2285  }
2286  elseif  (!array_key_exists($sort_key,$b->fields)) {
2287    //only a has the key -> a is greater
2288    $retval=1;
2289  }
2290  else {
2291    //both have the key, sort using the order given in $sort_order_values
2292
2293    $val_a = array_search(strtolower($a->fields[$sort_key]), $sort_order_values);
2294    $val_b = array_search(strtolower($b->fields[$sort_key]), $sort_order_values);
2295
2296    if (($val_a === FALSE && $val_b === FALSE) || ($val_a === $val_b)) {
2297      //neither a nor b are in the search array or a=b -> both are equal
2298      $retval=0;
2299    }
2300    elseif (($val_a === FALSE) || ($val_a < $val_b)) {
2301      //a is not in the search array or a<b -> b is greater
2302      $retval=-1;
2303    }
2304    elseif (($val_b === FALSE) || (($val_a > $val_b))){
2305      //b is not in the search array or a>b -> a is greater
2306      $retval=1;
2307    }
2308  }
2309
2310  return $retval;
2311}
2312
2313/** is the default sectioning for AcademicDisplay (books, articles, proceedings, etc. ) */
2314function DefaultBibliographySections() {
2315return
2316  array(
2317  // Books
2318    array(
2319      'query' => array(Q_TYPE=>'book|proceedings'),
2320      'title' => __('Books')
2321    ),
2322  // Book chapters
2323    array(
2324      'query' => array(Q_TYPE=>'incollection|inbook'),
2325      'title' => __('Book Chapters')
2326    ),
2327  // Journal / Bookchapters
2328    array(
2329      'query' => array(Q_TYPE=>'article'),
2330      'title' => __('Refereed Articles')
2331    ),
2332  // conference papers
2333    array(
2334      'query' => array(Q_TYPE=>'inproceedings|conference',Q_EXCLUDE=>'workshop'),
2335      'title' => __('Refereed Conference Papers')
2336    ),
2337  // workshop papers
2338    array(
2339      'query' => array(Q_TYPE=>'inproceedings',Q_SEARCH=>'workshop'),
2340      'title' => __('Refereed Workshop Papers')
2341    ),
2342  // misc and thesis
2343    array(
2344      'query' => array(Q_TYPE=>'misc|phdthesis|mastersthesis|bachelorsthesis|techreport'),
2345      'title' => __('Other Publications')
2346    )
2347  );
2348}
2349
2350
2351/** transforms a $bibentry into an HTML string.
2352  It is called by function bib2html if the user did not choose a specific style
2353  the default usable CSS styles are
2354  .bibtitle { font-weight:bold; }
2355  .bibbooktitle { font-style:italic; }
2356  .bibauthor { }
2357  .bibpublisher { }
2358
2359  See http://schema.org/ScholarlyArticle for the metadata
2360*/
2361function DefaultBibliographyStyle($bibentry) {
2362  $title = $bibentry->getTitle();
2363  $type = $bibentry->getType();
2364
2365  // later on, all values of $entry will be joined by a comma
2366  $entry=array();
2367
2368  // title
2369  // usually in bold: .bibtitle { font-weight:bold; }
2370  $title = '<span class="bibtitle"  itemprop="name">'.$title.'</span>';
2371  if ($bibentry->hasField('url')) $title = ' <a'.get_target().' href="'.$bibentry->getField('url').'">'.$title.'</a>';
2372
2373
2374  $coreInfo = $title;
2375
2376  // adding author info
2377  if ($bibentry->hasField('author')) {
2378    $coreInfo .= ' (<span class="bibauthor">';
2379
2380    $authors = array();
2381    foreach ($bibentry->getFormattedAuthorsArray() as $a) {
2382       $authors[]='<span itemprop="author" itemtype="http://schema.org/Person">'.$a.'</span>';
2383    }
2384    $coreInfo .= $bibentry->implodeAuthors($authors);
2385
2386    $coreInfo .= '</span>)';
2387  }
2388
2389  // core info usually contains title + author
2390  $entry[] = $coreInfo;
2391
2392  // now the book title
2393  $booktitle = '';
2394  if ($type=="inproceedings") {
2395      $booktitle = __('In').' '.'<span itemprop="isPartOf">'.$bibentry->getField(BOOKTITLE).'</span>'; }
2396  if ($type=="incollection") {
2397      $booktitle = __('Chapter in').' '.'<span itemprop="isPartOf">'.$bibentry->getField(BOOKTITLE).'</span>';}
2398  if ($type=="inbook") {
2399      $booktitle = __('Chapter in').' '.$bibentry->getField('chapter');}
2400  if ($type=="article") {
2401      $booktitle = __('In').' '.'<span itemprop="isPartOf">'.$bibentry->getField("journal").'</span>';}
2402
2403  //// we may add the editor names to the booktitle
2404  $editor='';
2405  if ($bibentry->hasField(EDITOR)) {
2406    $editor = $bibentry->getFormattedEditors();
2407  }
2408  if ($editor!='') $booktitle .=' ('.$editor.')';
2409  // end editor section
2410
2411  // is the booktitle available
2412  if ($booktitle!='') {
2413    $entry[] = '<span class="bibbooktitle">'.$booktitle.'</span>';
2414  }
2415
2416
2417  $publisher='';
2418  if ($type=="phdthesis") {
2419      $publisher = __('PhD thesis').', '.$bibentry->getField(SCHOOL);
2420  }
2421  if ($type=="mastersthesis") {
2422      $publisher = __('Master\'s thesis').', '.$bibentry->getField(SCHOOL);
2423  }
2424  if ($type=="bachelorsthesis") {
2425      $publisher = __('Bachelor\'s thesis').', '.$bibentry->getField(SCHOOL);
2426  }
2427  if ($type=="techreport") {
2428      $publisher = __('Technical report');
2429      if ($bibentry->hasField("number")) {
2430          $publisher .= ' '.$bibentry->getField("number");
2431      }
2432      $publisher .= ', '.$bibentry->getField("institution");
2433  }
2434
2435  if ($bibentry->hasField("publisher")) {
2436      $publisher = $bibentry->getField("publisher");
2437  }
2438
2439  if ($type=="misc") {
2440      $publisher = $bibentry->getField('howpublished');
2441  }
2442
2443
2444  if ($publisher!='') $entry[] = '<span class="bibpublisher">'.$publisher.'</span>';
2445
2446
2447  if ($bibentry->hasField('volume')) $entry[] =  __('volume').' '.$bibentry->getField("volume");
2448
2449
2450  if ($bibentry->hasField(YEAR)) $entry[] = '<span itemprop="datePublished">'.$bibentry->getYear().'</span>';
2451
2452  $result = implode(", ",$entry).'.';
2453
2454  // add the Coin URL
2455  $result .=  $bibentry->toCoins();
2456
2457  return '<span itemscope="" itemtype="http://schema.org/ScholarlyArticle">'.$result.'</span>';
2458}
2459
2460
2461
2462/** is the Bibtexbrowser style contributed by Janos Tapolcai. It looks like the IEEE transaction style.
2463usage:
2464Add the following line in "bibtexbrowser.local.php"
2465<pre>
2466@define('BIBLIOGRAPHYSTYLE','JanosBibliographyStyle');
2467</pre>
2468*/
2469function JanosBibliographyStyle($bibentry) {
2470  $title = $bibentry->getTitle();
2471  $type = $bibentry->getType();
2472
2473  $entry=array();
2474
2475  // author
2476  if ($bibentry->hasField('author')) {
2477    $entry[] = '<span class="bibauthor">'.$bibentry->getFormattedAuthorsString().'</span>';
2478  }
2479
2480  // title
2481  $title = '"'.'<span class="bibtitle">'.$title.'</span>'.'"';
2482  if ($bibentry->hasField('url')) $title = ' <a'.get_target().' href="'.$bibentry->getField('url').'">'.$title.'</a>';
2483  $entry[] = $title;
2484
2485
2486  // now the origin of the publication is in italic
2487  $booktitle = '';
2488
2489  if (($type=="misc") && $bibentry->hasField("note")) {
2490    $booktitle = $bibentry->getField("note");
2491  }
2492
2493  if ($type=="inproceedings" && $bibentry->hasField(BOOKTITLE)) {
2494      $booktitle = '<span class="bibbooktitle">'.'In '.$bibentry->getField(BOOKTITLE).'</span>';
2495  }
2496
2497  if ($type=="incollection" && $bibentry->hasField(BOOKTITLE)) {
2498      $booktitle = '<span class="bibbooktitle">'.'Chapter in '.$bibentry->getField(BOOKTITLE).'</span>';
2499  }
2500
2501  if ($type=="article" && $bibentry->hasField("journal")) {
2502      $booktitle = '<span class="bibbooktitle">'.''.$bibentry->getField("journal").'</span>';
2503  }
2504
2505
2506
2507  //// ******* EDITOR
2508  $editor='';
2509  if ($bibentry->hasField(EDITOR)) {
2510    $editor = $bibentry->getFormattedEditors();
2511  }
2512
2513  if ($booktitle!='') {
2514    if ($editor!='') $booktitle .=' ('.$editor.')';
2515    $entry[] = '<i>'.$booktitle.'</i>';
2516  }
2517
2518
2519  $publisher='';
2520  if ($type=="phdthesis") {
2521      $publisher = 'PhD thesis, '.$bibentry->getField(SCHOOL);
2522  }
2523
2524  if ($type=="mastersthesis") {
2525      $publisher = 'Master\'s thesis, '.$bibentry->getField(SCHOOL);
2526  }
2527  if ($type=="techreport") {
2528      $publisher = 'Technical report, ';
2529      $publisher .=$bibentry->getField("institution");
2530      if ($bibentry->hasField("number")) {
2531        $publisher .= ' '.$bibentry->getField("number");
2532      }
2533  }
2534  if ($bibentry->hasField("publisher")) {
2535    $publisher = $bibentry->getField("publisher");
2536  }
2537
2538  if ($publisher!='') $entry[] = $publisher;
2539
2540  if ($type=="article") {
2541    if ($bibentry->hasField('volume')) $entry[] =  "vol. ".$bibentry->getField("volume");
2542    if ($bibentry->hasField('number')) $entry[] =  'no. '.$bibentry->getField("number");
2543  }
2544
2545  if ($bibentry->hasField('address')) $entry[] =  $bibentry->getField("address");
2546
2547  if ($bibentry->hasField('pages')) $entry[] = str_replace("--", "-", "pp. ".$bibentry->getField("pages"));
2548
2549
2550  if ($bibentry->hasField(YEAR)) $entry[] = $bibentry->getYear();
2551
2552  $result = implode(", ",$entry).'.';
2553
2554  // add the Coin URL
2555  $result .=  "\n".$bibentry->toCoins();
2556
2557  return '<span itemscope="" itemtype="http://schema.org/ScholarlyArticle">'.$result.'</span>';
2558}
2559
2560
2561/** Bibtexbrowser style producing vancouver style often used in medicine.
2562 *
2563 *  See: Patrias K. Citing medicine: the NLM style guide for authors, editors,
2564 *  and publishers [Internet]. 2nd ed. Wendling DL, technical editor.
2565 *  Bethesda (MD): National Library of Medicine (US); 2007 -
2566 *  [updated 2011 Sep 15; cited 2015 April 18].
2567 *  Available from: http://www.nlm.nih.gov/citingmedicine
2568 *
2569 * usage: Add the following lines to "bibtexbrowser.local.php"
2570<pre>
2571@define('BIBLIOGRAPHYSTYLE','VancouverBibliographyStyle');
2572@define('USE_INITIALS_FOR_NAMES',true);
2573</pre>
2574*/
2575
2576function VancouverBibliographyStyle($bibentry) {
2577  $title = $bibentry->getTitle();
2578  $type = $bibentry->getType();
2579
2580  $entry=array();
2581
2582  // author
2583  if ($bibentry->hasField('author')) {
2584    $entry[] = $bibentry->getFormattedAuthorsString().'. ';
2585  }
2586
2587  // Ensure punctuation mark at title's end
2588  if (strlen(rtrim($title))>0 && strpos(":.;,?!", substr(rtrim($title), -1)) > 0) {
2589    $title = $title . ' ';
2590  } else {
2591    $title = $title . '. ';
2592  }
2593  if ($bibentry->hasField('url')) {
2594    $title = ' <a'.get_target().' href="'.$bibentry->getField('url').'">'.$title.'</a>';
2595  }
2596
2597  $entry[] = $title;
2598
2599  $booktitle = '';
2600
2601  //// ******* EDITOR
2602  $editor='';
2603  if ($bibentry->hasField(EDITOR)) {
2604    $editor = $bibentry->getFormattedEditors() . ' ';
2605  }
2606
2607  if (($type=="misc") && $bibentry->hasField("note")) {
2608    $booktitle = $editor;
2609    $booktitle = $bibentry->getField("note");
2610  } else if ($type=="inproceedings") {
2611      $booktitle = 'In: ' . $editor . $bibentry->getField(BOOKTITLE);
2612  } else if ($type=="incollection") {
2613      $booktitle = 'Chapter in ';
2614      if ($editor!='') $booktitle .= $editor;
2615      $booktitle .= $bibentry->getField(BOOKTITLE);
2616  } else if ($type=="article") {
2617      $booktitle = $bibentry->getField("journal");
2618  }
2619  if ($booktitle!='') {
2620    $entry[] = $booktitle . '. ';
2621  }
2622
2623
2624  $publisher='';
2625  if ($type=="phdthesis") {
2626      $publisher = 'PhD thesis, '.$bibentry->getField(SCHOOL);
2627  } else if ($type=="mastersthesis") {
2628      $publisher = 'Master\'s thesis, '.$bibentry->getField(SCHOOL);
2629  } else if ($type=="techreport") {
2630      $publisher = 'Technical report, '.$bibentry->getField("institution");
2631  }
2632  if ($bibentry->hasField("publisher")) {
2633    $publisher = $bibentry->getField("publisher");
2634  }
2635  if ($publisher!='') {
2636    if ($bibentry->hasField('address')) {
2637      $entry[] =  $bibentry->getField("address").': ';
2638    }
2639    $entry[] = $publisher . "; ";
2640  }
2641
2642
2643  if ($bibentry->hasField(YEAR)) $entry[] = $bibentry->getYear();
2644
2645  if ($bibentry->hasField('volume')) $entry[] =  ";".$bibentry->getField("volume");
2646  if ($bibentry->hasField('number')) $entry[] =  '('.$bibentry->getField("number").')';
2647
2648  if ($bibentry->hasField('pages')) $entry[] = str_replace("--", "-", ":".$bibentry->getField("pages"));
2649
2650  $result = implode($entry).'.';
2651
2652  // some comments (e.g. acceptance rate)?
2653  if ($bibentry->hasField('comment')) {
2654      $result .=  " (".$bibentry->getField("comment").")";
2655  }
2656
2657  // add the Coin URL
2658  $result .=  "\n".$bibentry->toCoins();
2659
2660  return $result;
2661}
2662
2663
2664
2665
2666// ----------------------------------------------------------------------
2667// DISPLAY MANAGEMENT
2668// ----------------------------------------------------------------------
2669/** orders two BibEntry as defined by ORDER_FUNCTION
2670 * (by default compares two instances of BibEntry by year and then month)
2671 */
2672function compare_bib_entries($bib1, $bib2) {
2673  $f1 = ORDER_FUNCTION;
2674  $cmp = $f1($bib1, $bib2);
2675  if ($cmp ==0) {
2676    $f2 = ORDER_FUNCTION_FINE;
2677    $cmp = $f2($bib1, $bib2);
2678  }
2679  return $cmp;
2680}
2681
2682/** creates a query string given an array of parameter, with all specifities of bibtexbrowser_ (such as adding the bibtex file name &bib=foo.bib
2683 */
2684function createQueryString($array_param) {
2685 // then a simple transformation and implode
2686 foreach ($array_param as $key => $val) {
2687      // the inverse transformation should also be implemented into query2title
2688      if($key == Q_INNER_AUTHOR) { $key = Q_AUTHOR; }
2689      if($key == Q_INNER_TYPE) { $key = Q_TYPE; }
2690      if($key == Q_KEYS) { $val = urlencode(json_encode($val)); }
2691      if($key == Q_INNER_KEYS_INDEX) {continue;}
2692      $array_param[$key]=$key .'='. urlencode($val);
2693 }
2694
2695 // adding the bibtex file name is not already there
2696 if (isset($_GET[Q_FILE]) && !isset($array_param[Q_FILE])) {
2697    // first we add the name of the bib file
2698    $array_param[Q_FILE] = Q_FILE .'='. urlencode($_GET[Q_FILE]);
2699  }
2700
2701 return implode("&amp;",$array_param);
2702}
2703
2704/** returns a href string of the form: href="?bib=testing.bib&search=JML.
2705Based on createQueryString.
2706@nodoc
2707 */
2708function makeHref($query = NULL) {
2709  return 'href="'.bibtexbrowser_configuration('BIBTEXBROWSER_URL').'?'. createQueryString($query) .'"';
2710}
2711
2712
2713/** returns the splitted name of an author name as an array. The argument is assumed to be
2714 "FirstName LastName" or "LastName, FirstName".
2715 */
2716function splitFullName($author){
2717    $author = trim($author);
2718    // the author is unsplittable, treat it as lastname
2719    if (strpos($author, '}') !== false) {
2720        $lastname = preg_replace(['/\{/', '/\}/'], '', $author);
2721        $firstname = '';
2722    }
2723    // the author format is "Joe Dupont"
2724    else if (strpos($author,',')===false) {
2725      $parts=explode(' ', $author);
2726      // get the last name
2727      $lastname = array_pop($parts);
2728      $firstname = implode(" ", $parts);
2729    }
2730    // the author format is "Dupont, J."
2731    else {
2732      $parts=explode(',', $author);
2733      // get the last name
2734      $lastname = str_replace(',','',array_shift($parts));
2735      $firstname = implode(" ", $parts);
2736    }
2737  return array(trim($firstname), trim($lastname));
2738}
2739
2740
2741/** outputs an horizontal  year-based menu
2742usage:
2743<pre>
2744  $_GET['library']=1;
2745  $_GET['bib']='bibacid-utf8.bib';
2746  $_GET['all']=1;
2747  include( 'bibtexbrowser.php' );
2748  setDB();
2749  new IndependentYearMenu($_GET[Q_DB]);
2750</pre>
2751 */
2752class IndependentYearMenu  {
2753  function __construct($db) {
2754    $yearIndex = $db->yearIndex();
2755    echo '<div id="yearmenu">Year: ';
2756    $formatedYearIndex = array();
2757    $formatedYearIndex[] = '<a '.makeHref(array(Q_YEAR=>'.*')).'>All</a>';
2758    foreach($yearIndex as $year) {
2759      $formatedYearIndex[] = '<a '.makeHref(array(Q_YEAR=>$year)).'>'.$year.'</a>';
2760    }
2761
2762    // by default the separator is a |
2763    echo implode('|',$formatedYearIndex);
2764    echo '</div>';
2765  }
2766}
2767
2768if (!function_exists('poweredby')) {
2769  /** Returns the powered by part. @nodoc */
2770  function poweredby() {
2771    $poweredby = "\n".'<div style="text-align:right;font-size: xx-small;opacity: 0.6;" class="poweredby">';
2772    $poweredby .= '<!-- If you like bibtexbrowser, thanks to keep the link :-) -->';
2773    $poweredby .= 'Powered by <a href="http://www.monperrus.net/martin/bibtexbrowser/">bibtexbrowser</a><!--v__GITHUB__-->';
2774    $poweredby .= '</div>'."\n";
2775    return $poweredby;
2776  }
2777}
2778
2779if (!function_exists('bibtexbrowser_top_banner')) {
2780  function bibtexbrowser_top_banner() {
2781    return '';
2782  }
2783}
2784
2785/** ^^adds a touch of AJAX in bibtexbrowser to display bibtex entries inline.
2786   It uses the HIJAX design pattern: the Javascript code fetches the normal bibtex HTML page
2787   and extracts the bibtex.
2788   In other terms, URLs and content are left perfectly optimized for crawlers
2789   Note how beautiful is this piece of code thanks to JQuery.^^
2790  */
2791function javascript() {
2792  // we use jquery with the official content delivery URLs
2793  // Microsoft and Google also provide jquery with their content delivery networks
2794?><script type="text/javascript" src="<?php echo JQUERY_URI ?>"></script>
2795<script type="text/javascript" ><!--
2796// Javascript progressive enhancement for bibtexbrowser
2797$('a.biburl').each(function() { // for each url "[bibtex]"
2798  var biburl = $(this);
2799  if (biburl.attr('bibtexbrowser') === undefined)
2800  {
2801  biburl.click(function(ev) { // we change the click semantics
2802    ev.preventDefault(); // no open url
2803    if (biburl.nextAll('pre').length == 0) { // we don't have yet the bibtex data
2804      var bibtexEntryUrl = $(this).attr('href');
2805      $.ajax({url: bibtexEntryUrl,  dataType: 'html', success: function (data) { // we download it
2806        // elem is the element containing bibtex entry, creating a new element is required for Chrome and IE
2807        var elem = $('<pre class="purebibtex"/>');
2808        elem.text($('.purebibtex', data).text()); // both text() are required for IE
2809        // we add a link so that users clearly see that even with AJAX
2810        // there is still one URL per paper.
2811        elem.append(
2812          $('<div class="bibtex_entry_url">%% Bibtex entry URL: <a href="'+bibtexEntryUrl+'">'+bibtexEntryUrl+'</a></div>')
2813          ).appendTo(biburl.parent());
2814      }, error: function() {window.location.href = biburl.attr('href');}});
2815    } else {biburl.nextAll('pre').toggle();}  // we toggle the view
2816  });
2817  biburl.attr('bibtexbrowser','done');
2818  } // end if biburl.bibtexbrowser;
2819});
2820
2821
2822--></script><?php
2823} // end function javascript
2824
2825
2826if (!function_exists('javascript_math')) {
2827  function javascript_math() {
2828    ?>
2829<script>
2830MathJax = {
2831  tex: {
2832    inlineMath: [['$', '$'], ['\\(', '\\)']]
2833  }
2834};
2835</script>
2836<script type="text/javascript" id="MathJax-script" async
2837  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
2838</script>
2839    <?php
2840  }
2841}
2842
2843
2844/** is used for creating menus (by type, by year, by author, etc.).
2845usage:
2846<pre>
2847  $db = zetDB('bibacid-utf8.bib');
2848  $menu = new MenuManager();
2849  $menu->setDB($db);
2850  $menu->year_size=100;// should display all years :)
2851  $menu->display();
2852</pre>
2853 */
2854class MenuManager {
2855
2856  /** The bibliographic database, an instance of class BibDataBase. */
2857  var $db;
2858
2859  var $type_size = TYPES_SIZE;
2860  var $year_size = YEAR_SIZE;
2861  var $author_size = AUTHORS_SIZE;
2862  var $tag_size = TAGS_SIZE;
2863
2864  function __construct() {
2865  }
2866
2867  /** sets the database that is used to create the menu */
2868  function setDB($db) {
2869    $this->db =$db;
2870    return $this;
2871  }
2872
2873  function getTitle() {
2874    return '';
2875  }
2876
2877  function metadata() {
2878    return array(array('robots','noindex'));
2879  }
2880
2881  /** function called back by HTMLTemplate */
2882  function display() {
2883  echo $this->searchView().'<br/>';
2884  echo $this->venueVC().'<br/>';
2885  echo $this->yearVC().'<br/>';
2886  echo $this->authorVC().'<br/>';
2887  echo $this->tagVC().'<br/>';
2888  echo $this->typeVC().'<br/>';
2889  }
2890
2891  /** Displays the title in a table. */
2892  function titleView() {
2893    ?>
2894    <table>
2895      <tr>
2896        <td class="rheader">Generated from <?php echo $_GET[Q_FILE]; ?></td>
2897      </tr>
2898    </table>
2899    <?php
2900  }
2901
2902  /** Displays the search view in a form. */
2903  function searchView() {
2904    ?>
2905    <form action="?" method="get" target="<?php echo BIBTEXBROWSER_MENU_TARGET;?>">
2906      <input type="text" name="<?php echo Q_SEARCH; ?>" class="input_box" size="18"/>
2907      <input type="hidden" name="<?php echo Q_FILE; ?>" value="<?php echo @$_GET[Q_FILE]; ?>"/>
2908      <br/>
2909      <!-- submit button -->
2910      <input type="submit" value="<?php echo __("search"); ?>" class="input_box"/>
2911    </form>
2912    <?php
2913  }
2914
2915  /** Displays and controls the types menu in a table. */
2916  function typeVC() {
2917    $types = array();
2918    foreach ($this->db->getTypes() as $type) {
2919      $types[$type] = __($type);
2920    }
2921    $types['.*'] = __('all types');;
2922    // retreive or calculate page number to display
2923    if (isset($_GET[Q_TYPE_PAGE])) {
2924      $page = $_GET[Q_TYPE_PAGE];
2925    }
2926    else $page = 1;
2927
2928    $this->displayMenu('Types', $types, $page, $this->type_size, Q_TYPE_PAGE, Q_INNER_TYPE);
2929  }
2930
2931
2932  /** Displays and controls the venues. */
2933  function venueVC() {
2934    // retrieve authors list to display
2935    $data = $this->db->venueIndex();
2936
2937    $this->displayMenu('Venues', $data, 1, 100000, Q_SEARCH,
2938                       Q_SEARCH);
2939  }
2940
2941  /** Displays and controls the authors menu in a table. */
2942  function authorVC() {
2943    // retrieve authors list to display
2944      $authors = $this->db->authorIndex();
2945
2946    // determine the authors page to display
2947    if (isset($_GET[Q_AUTHOR_PAGE])) {
2948      $page = $_GET[Q_AUTHOR_PAGE];
2949    }
2950    else $page = 1;
2951
2952
2953    $this->displayMenu('Authors', $authors, $page, $this->author_size, Q_AUTHOR_PAGE,
2954         Q_AUTHOR);
2955  }
2956
2957  /** Displays and controls the tag menu in a table. */
2958  function tagVC() {
2959    // retrieve authors list to display
2960      $tags = $this->db->tagIndex();
2961
2962    // determine the authors page to display
2963    if (isset($_GET[Q_TAG_PAGE])) {
2964      $page = $_GET[Q_TAG_PAGE];
2965    }  else $page = 1;
2966
2967
2968    if (count($tags)>0) $this->displayMenu('Keywords', $tags, $page, $this->tag_size, Q_TAG_PAGE,
2969         Q_TAG);
2970  }
2971
2972  /** Displays and controls the tag menu in a table. */
2973  function yearVC() {
2974    // retrieve authors list to display
2975      $years = $this->db->yearIndex();
2976
2977    // determine the authors page to display
2978    if (isset($_GET[Q_YEAR_PAGE])) {
2979      $page = $_GET[Q_YEAR_PAGE];
2980    }
2981else $page = 1;
2982
2983
2984    $this->displayMenu('Years', $years, $page, $this->year_size, Q_YEAR_PAGE,
2985         Q_YEAR);
2986  }
2987
2988  /** Displays the main contents . */
2989  function mainVC() {
2990      $this->display->display();
2991  }
2992
2993  /** Displays a list menu in a table.
2994   *
2995   * $title: title of the menu (string)
2996   * $list: list of menu items (string)
2997   * $page: page number to display (number)
2998   * $pageSize: size of each page
2999   * $pageKey: URL query name to send the page number to the server
3000   * $targetKey: URL query name to send the target of the menu item
3001   */
3002  function displayMenu($title, $list, $page, $pageSize, $pageKey,
3003         $targetKey) {
3004    $numEntries = count($list);
3005    $startIndex = ($page - 1) * $pageSize;
3006    $endIndex = $startIndex + $pageSize;
3007    ?>
3008    <table style="width:100%"  class="menu">
3009      <tr>
3010        <td>
3011        <!-- this table is used to have the label on the left
3012        and the navigation links on the right -->
3013        <table style="width:100%" border="0" cellspacing="0" cellpadding="0">
3014          <tr class="btb-nav-title">
3015            <td><b><?php echo __($title); ?></b></td>
3016            <td class="btb-nav"><b>
3017                <?php echo $this->menuPageBar($pageKey, $numEntries, $page,
3018           $pageSize, $startIndex, $endIndex);?></b></td>
3019          </tr>
3020        </table>
3021        </td>
3022      </tr>
3023      <tr>
3024        <td class="btb-menu-items">
3025          <?php $this->displayMenuItems($list, $startIndex, $endIndex,
3026     $targetKey); ?>
3027        </td>
3028      </tr>
3029    </table>
3030  <?php
3031  }
3032
3033  /** Returns a string to displays forward and reverse page controls.
3034   *
3035   * $queryKey: key to send the page number as a URL query string
3036   * $page: current page number to display
3037   * $numEntries: number of menu items
3038   * $start: start index of the current page
3039   * $end: end index of the current page
3040   */
3041  function menuPageBar($queryKey, $numEntries, $page, $pageSize,
3042         $start, $end) {
3043
3044    $result = '';
3045
3046    // (1 page) reverse (<)
3047    if ($start > 0) {
3048      $href = makeHref(array($queryKey => $page - 1,'menu'=>''));//menuPageBar
3049      $result .= '<a '. $href ."><b>[prev]</b></a>\n";
3050    }
3051
3052    // (1 page) forward (>)
3053    if ($end < $numEntries) {
3054      $href = makeHref(array($queryKey => $page + 1,'menu'=>''));//menuPageBar
3055      $result .= '<a '. $href ."><b>[next]</b></a>\n";
3056    }
3057
3058    return $result;
3059  }
3060
3061  /**
3062   * Displays menu items (anchors) from the start index (inclusive) to
3063   * the end index (exclusive). For each menu, the following form of
3064   * string is printed:
3065   *
3066   * <a href="...?bib=cheon.bib&author=Yoonsik+Cheon">
3067   *    Cheon, Yoonsik</a>
3068   * <div class="mini_se"></div>
3069   */
3070  function displayMenuItems($items, $startIndex, $endIndex, $queryKey) {
3071    $index = 0;
3072    foreach ($items as $key => $item) {
3073      if ($index >= $startIndex && $index < $endIndex) {
3074        if ($queryKey === 'year') {
3075          $href = makeHref(array($queryKey => __($item)));
3076	} else {
3077          $href = makeHref(array($queryKey => $key));
3078	}
3079        echo '<a '. $href .' target="'.BIBTEXBROWSER_MENU_TARGET.'">'. $item ."</a>\n";
3080        echo "<div class=\"mini_se\"></div>\n";
3081      }
3082      $index++;
3083    }
3084  }
3085}
3086
3087if (!function_exists('query2title')) {
3088/** transforms an array representing a query into a formatted string */
3089function query2title($query) {
3090    $headers = array();
3091    foreach($query as $k=>$v) {
3092      if($k == Q_INNER_AUTHOR) { $k = 'author'; }
3093      if($k == Q_INNER_TYPE) {
3094        // we changed from x-bibtex-type to type
3095        $k = 'type';
3096        // and we remove the regexp modifiers ^ $
3097        $v = preg_replace('/[$^]/','',$v);
3098      }
3099      if($k == Q_KEYS) { $v=json_encode(array_values($v)); }
3100      if($k == Q_RANGE) {
3101        foreach ($v as $range) {
3102	  $range = $range[0].'-'.$range[1];
3103	}
3104	$v = join($v, ',');
3105      }
3106      $headers[$k] = __(ucwords($k)).': '.ucwords(htmlspecialchars($v,ENT_NOQUOTES|ENT_XHTML, OUTPUT_ENCODING));
3107  }
3108  return join(' &amp; ',$headers);
3109}
3110} // if (!function_exists('query2title'))
3111
3112/** displays the latest modified bibtex entries.
3113usage:
3114<pre>
3115  $db = zetDB('bibacid-utf8.bib');
3116  $d = new NewEntriesDisplay();
3117  $d->setDB($db);
3118  $d->setN(7);// optional
3119  $d->display();
3120</pre>
3121 */
3122class NewEntriesDisplay {
3123  var $n=5;
3124  var $db;
3125
3126  function setDB($bibdatabase) {
3127    $this->db = $bibdatabase;
3128  }
3129
3130  function setN($n) {$this->n = $n;return $this;}
3131
3132  /** sets the entries to be shown */
3133  function setEntries($entries) {
3134    $this->db = createBibDataBase();
3135    $this->db->bibdb = $entries;
3136  }
3137
3138    /** Displays a set of bibtex entries in an HTML table */
3139  function display() {
3140    $array = $this->db->getLatestEntries($this->n);
3141    $delegate = createBasicDisplay();
3142    $delegate->setEntries($array);
3143    $delegate->display();
3144  }
3145}
3146
3147
3148/** displays the entries by year in reverse chronological order.
3149usage:
3150<pre>
3151  $db = zetDB('bibacid-utf8.bib');
3152  $d = new YearDisplay();
3153  $d->setDB($db);
3154  $d->display();
3155</pre>
3156*/
3157class YearDisplay {
3158
3159  /** is an array of strings representing years */
3160  var $yearIndex;
3161
3162  function setDB($bibdatabase) {
3163    $this->setEntries($bibdatabase->bibdb);
3164  }
3165
3166  /** creates a YearDisplay */
3167  function setOptions($options) {}
3168
3169  function getTitle() {return '';}
3170
3171  /** sets the entries to be shown */
3172  function setEntries($entries) {
3173    $this->entries = $entries;
3174    $db= createBibDataBase();
3175    $db->bibdb = $entries;
3176    $this->yearIndex = $db->yearIndex();
3177  }
3178
3179  /** Displays a set of bibtex entries in an HTML table */
3180  function display() {
3181    $delegate = createBasicDisplay();
3182    $delegate->setEntries($this->entries);
3183    $index = count($this->entries);
3184    foreach($this->yearIndex as $year) {
3185      $x = array();
3186      uasort($x,'compare_bib_entry_by_month');
3187      foreach($this->entries as $e) {
3188        if ($e->getYear() == $year) {
3189          $x[] = $e;
3190        }
3191      }
3192
3193      if (count($x)>0) {
3194        echo '<div  class="theader">'.$year.'</div>';
3195        $delegate->setEntries($x);
3196        $delegate->display();
3197      }
3198
3199      $index = $index - count($x);
3200    }
3201  }
3202}
3203
3204
3205/** displays the summary information of all bib entries.
3206usage:
3207<pre>
3208  $db = zetDB('bibacid-utf8.bib');
3209  $d = new SimpleDisplay();
3210  $d->setDB($db);
3211  $d->display();
3212</pre>
3213  */
3214class SimpleDisplay  {
3215
3216  var $headerCSS = 'sheader';
3217
3218  var $options = array();
3219
3220  var $entries = array();
3221
3222  var $headingLevel = BIBTEXBROWSER_HTMLHEADINGLEVEL;
3223
3224  function __construct($db = NULL, $query = array()) {
3225    if ($db == NULL) return;
3226    $this->setEntries($db->multisearch($query));
3227  }
3228
3229  function incHeadingLevel ($by=1) {
3230  	$this->headingLevel += $by;
3231  }
3232  function decHeadingLevel ($by=1) {
3233  	$this->headingLevel -= $by;
3234  }
3235
3236  function setDB($bibdatabase) {
3237    $this->setEntries($bibdatabase->bibdb);
3238  }
3239
3240  function metadata() {
3241    if (BIBTEXBROWSER_ROBOTS_NOINDEX) {
3242      return array(array('robots','noindex'));
3243    } else {
3244      return array();
3245    }
3246  }
3247
3248  /** sets the entries to be shown */
3249  function setEntries($entries) {
3250    $this->entries = array_values($entries);
3251  }
3252
3253  function indexUp() {
3254    $index=1;
3255    foreach ($this->entries as $bib) {
3256      $bib->setAbbrv((string)$index++);
3257    } // end foreach
3258    return $this->entries;
3259  }
3260
3261  function newest($entries) {
3262    return array_slice($entries,0,BIBTEXBROWSER_NEWEST);
3263  }
3264
3265  function indexDown() {
3266    $index=count($this->entries);
3267    foreach ($this->entries as $bib) {
3268      $bib->setAbbrv((string)$index--);
3269    } // end foreach
3270    return $this->entries;
3271  }
3272
3273  function setQuery($query) {
3274    $this->query = $query;
3275  }
3276  function getTitle() {
3277    return _DefaultBibliographyTitle($this->query);
3278  }
3279
3280  function setIndices() {
3281    $this->setIndicesInDecreasingOrder();
3282  }
3283
3284  function setIndicesInIncreasingOrderChangingEveryYear() {
3285    $i=1;
3286    $pred = NULL;
3287    foreach ($this->entries as $bib) {
3288      if ($this->changeSection($pred, $bib)) {
3289            $i=1;
3290      }
3291      $bib->setIndex($i++);
3292      $pred = $bib;
3293    } // end foreach
3294  }
3295
3296  function setIndicesInDecreasingOrder() {
3297    $count = count($this->entries);
3298    $i=0;
3299    foreach ($this->entries as $bib) {
3300      // by default, index are in decreasing order
3301      // so that when you add a publicaton recent , the indices of preceding publications don't change
3302      $bib->setIndex($count-($i++));
3303    } // end foreach
3304  }
3305
3306  /** Displays a set of bibtex entries in an HTML table */
3307  function display() {
3308    usort($this->entries, 'compare_bib_entries');
3309
3310    // now that the entries are sorted, setting the index of entries
3311    // this function can be overloaded
3312    $this->setIndices();
3313
3314    if ($this->options) {
3315      foreach($this->options as $fname=>$opt) {
3316        $this->$fname($opt,$entries);
3317      }
3318    }
3319
3320    if (BIBTEXBROWSER_DEBUG) {
3321      echo 'Style: '.bibtexbrowser_configuration('BIBLIOGRAPHYSTYLE').'<br/>';
3322      echo 'Order: '.ORDER_FUNCTION.'<br/>';
3323      echo 'Abbrv: '.c('ABBRV_TYPE').'<br/>';
3324      echo 'Options: '.@implode(',',$this->options).'<br/>';
3325    }
3326
3327//     if ($this->headingLevel == BIBTEXBROWSER_HTMLHEADINGLEVEL) {
3328//       echo "\n".'<span class="count">';
3329//       if (count($this->entries) == 1) {
3330//         echo count ($this->entries).' '.__('result');
3331//       } else if (count($this->entries) != 0) {
3332//         echo count ($this->entries).' '.__('results');
3333//       }
3334//       echo "</span>\n";
3335//     }
3336    print_header_layout();
3337
3338    $pred = NULL;
3339    foreach ($this->entries as $bib) {
3340      if ($this->changeSection($pred, $bib)) {
3341        echo $this->sectionHeader($bib, $pred);
3342      }
3343
3344      echo $bib->toHTML(true);
3345
3346      $pred = $bib;
3347    } // end foreach
3348
3349    print_footer_layout();
3350
3351  } // end function
3352
3353  function changeSection($pred, $bib) {
3354
3355    // for the first one we output the header
3356    if ($pred == NULL) { return true; }
3357
3358    $f = ORDER_FUNCTION;
3359    return $f($pred, $bib) != 0;
3360  }
3361
3362  function sectionHeader($bib, $pred) {
3363    switch(BIBTEXBROWSER_LAYOUT) {
3364      case 'table':
3365        return '<tr><td colspan="2" class="'.$this->headerCSS.'">'.$bib->getYear().'</td></tr>'."\n";
3366        break;
3367      case 'definition':
3368        return '<div class="'.$this->headerCSS.'">'.$bib->getYear().'</div>'."\n";
3369        break;
3370      case 'list':
3371      	$string = '';
3372        if ($pred) $string .= "</ul>\n";
3373	if ($bib->hasField(YEAR))
3374	  $year = $bib->getYear();
3375	else
3376	  $year = __('No date');
3377        return $string.'<h'.$this->headingLevel.'>'.$year."</h".$this->headingLevel.">\n<ul class=\"result\">\n";
3378        break;
3379      default:
3380        return '';
3381    }
3382  }
3383
3384} // end class
3385
3386
3387/** returns an HTTP 404 and displays en error message. */
3388function nonExistentBibEntryError() {
3389  header('HTTP/1.1 404 Not found');
3390  ?>
3391  <b>Sorry, this bib entry does not exist.</b>
3392  <a href="?">Back to bibtexbrowser</a>
3393  <?php
3394  exit;
3395}
3396
3397/** handles queries with no result */
3398class NotFoundDisplay {
3399  function display() {
3400    echo '<span class="count">'.__('Sorry, no results for this query').'</span>';
3401  }
3402}
3403/** displays the publication records sorted by publication types (as configured by constant BIBLIOGRAPHYSECTIONS).
3404usage:
3405<pre>
3406  $db = zetDB('bibacid-utf8.bib');
3407  $d = new AcademicDisplay();
3408  $d->setDB($db);
3409  $d->display();
3410</pre>
3411  */
3412class AcademicDisplay  {
3413  public $db;
3414  public $entries;
3415  public $title;
3416
3417  function getTitle() { return $this->title; }
3418  function setTitle($title) { $this->title = $title; return $this; }
3419
3420  function setDB($bibdatabase) {
3421    $this->setEntries($bibdatabase->bibdb);
3422  }
3423
3424  /** sets the entries to be shown */
3425  function setEntries($entries) {
3426    $this->entries = $entries;
3427  }
3428
3429  /** transforms a query to HTML
3430   * $ query is an array (e.g. array(Q_YEAR=>'2005'))
3431   * $title is a string, the title of the section
3432   */
3433  function search2html($query, $title) {
3434    $entries = $this->db->multisearch($query);
3435    if (count($entries)>0) {
3436      echo "\n".'<div class="sheader">'.$title.'</div>'."\n";
3437    }
3438    $display = createBasicDisplay();
3439    $display->setEntries($entries);
3440    $display->headerCSS = 'theader';
3441    $display->display();
3442
3443  }
3444
3445  function display() {
3446    $this->db = createBibDataBase();
3447    $this->db->bibdb = $this->entries;
3448
3449    if (BIBTEXBROWSER_ACADEMIC_TOC != true) {
3450      foreach (_DefaultBibliographySections() as $section) {
3451        $this->search2html($section['query'],$section['title']);
3452      }
3453    } else {
3454      $sections = array();
3455      echo "<ul>";
3456
3457      foreach (_DefaultBibliographySections() as $section) {
3458        $entries = $this->db->multisearch($section['query']);
3459
3460        if (count($entries)>0) {
3461          $anchor = preg_replace("/[^a-zA-Z]/", "", $section['title']);
3462          echo "<li><a href=\"#".$anchor."\">".$section['title']." (".count($entries).")</a></li>";
3463
3464          $display = createBasicDisplay();
3465          $display->incHeadingLevel();
3466          $display->setEntries($entries);
3467          $display->headerCSS = 'theader';
3468
3469          $sections[] = array (
3470            'display' => $display,
3471            'anchor' => $anchor,
3472            'title' => $section['title'],
3473            'count' => count($entries)
3474          );
3475        }
3476      }
3477      echo "</ul>";
3478
3479      foreach ($sections as $section) {
3480        echo "\n<a name=\"".$section['anchor']."\"></a>";
3481        echo "<h".BIBTEXBROWSER_HTMLHEADINGLEVEL.">";
3482        echo $section['title']." (".$section['count'].")";
3483        echo "</h".BIBTEXBROWSER_HTMLHEADINGLEVEL.">\n",
3484        $section['display']->display();
3485      }
3486    }
3487  }
3488}
3489
3490
3491
3492
3493/** displays a single bib entry.
3494usage:
3495<pre>
3496  $db = zetDB('bibacid-utf8.bib');
3497  $dis = new BibEntryDisplay($db->getEntryByKey('classical'));
3498  $dis->display();
3499</pre>
3500notes:
3501- the top-level header (usually &lt;H1>) must be done by the caller.
3502- this view is optimized for Google Scholar
3503 */
3504class BibEntryDisplay {
3505
3506  /** the bib entry to display */
3507  var $bib;
3508
3509  function __construct($bib=null) {
3510    $this->bib = $bib;
3511  }
3512
3513  function setEntries($entries) {
3514    $this->bib = $entries[0];
3515    //$this->title = $this->bib->getTitle().' (bibtex)'.$this->bib->getUrlLink();
3516  }
3517
3518  /** returns the title */
3519  function getTitle() {
3520    return $this->bib->getTitle().' (bibtex)';
3521  }
3522
3523  /** 2011/10/02: new display, inspired from Tom Zimmermann's home page */
3524  function displayOnSteroids() {
3525      $subtitle = '<div class="bibentry-by">by '.$this->bib->getFormattedAuthorsString().'</div>';
3526
3527      $abstract = '';
3528      if ($this->bib->hasField('abstract')) {
3529        $abstract = '<div class="bibentry-label">Abstract:</div><div class="bibentry-abstract">'.$this->bib->getAbstract().'</div>';
3530      }
3531
3532      $download = '';
3533      if ($this->bib->hasField('url')) {
3534        $download = '<div class="bibentry-document-link"><a href="'.$this->bib->getField('url').'">View PDF</a></div>';
3535      }
3536      $reference= '<div class="bibentry-label">Reference:</div><div class="bibentry-reference">'.strip_tags(bib2html($this->bib)).'</div>';
3537
3538      $bibtex = '<div class="bibentry-label">Bibtex Entry:</div>'.$this->bib->toEntryUnformatted().'';
3539      return $subtitle.$abstract.$download.$reference.$bibtex.$this->bib->toCoins();
3540  }
3541
3542  function display() {
3543    // we encapsulate everything so that the output of display() is still valid XHTML
3544    echo '<div>';
3545    //echo $this->display_old();
3546    echo $this->displayOnSteroids();
3547    echo '</div>';
3548  }
3549
3550  // old display
3551  function display_old() {
3552    return $this->bib->toCoins().$this->bib->toEntryUnformatted();
3553  }
3554
3555  /** Returns a dictionary of metadata. If the same metadata appears multiple times, it is concatenated with ";"
3556   */
3557  function metadata_dict() {
3558    $result = array();
3559    foreach($this->metadata() as $v) {
3560      if (!in_array($v[0], $result)) {
3561        $result[$v[0]] = $v[1];
3562      } else {
3563        $result[$v[0]] .= ';'.$v[1];
3564      }
3565    }
3566    return $result;
3567  }
3568
3569  /** Returns an array containing the metadata for Google Scholar
3570   *    array (array('citation_title', 'foo'), ....)
3571   * @see http://scholar.google.com/intl/en/scholar/inclusion.html
3572   * @see http://www.monperrus.net/martin/accurate+bibliographic+metadata+and+google+scholar
3573   * */
3574  function metadata() {
3575    $result=array();
3576
3577    if (c('BIBTEXBROWSER_ROBOTS_NOINDEX')) {
3578      $result[] = array('robots','noindex');
3579    }
3580
3581    if (c('METADATA_GS')) {
3582      $result = $this->metadata_google_scholar($result);
3583    } // end Google Scholar
3584
3585    // a fallback to essential dublin core
3586    if (c('METADATA_DC')) {
3587      $result = $this->metadata_dublin_core($result);
3588    }
3589
3590    if (c('METADATA_OPENGRAPH')) {
3591      $result = $this->metadata_opengraph($result);
3592    }
3593
3594    if (c('METADATA_EPRINTS')) {
3595      $result = $this->metadata_eprints($result);
3596    }
3597
3598    return $result;
3599  } // end function metadata
3600
3601  function metadata_opengraph($result) {
3602    // Facebook metadata
3603    // see http://ogp.me
3604    // https://developers.facebook.com/tools/debug/og/object/
3605    $result[] = array('og:type','article');
3606    $result[] = array('og:title',$this->bib->getTitle());
3607    foreach($this->bib->getRawAuthors() as $author) {
3608    // opengraph requires a URL as author value
3609    $result[] = array('og:author',"http://".@$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'?bib='.urlencode($this->bib->filename).'&amp;author='.urlencode($author));
3610    }
3611    $result[] = array('og:published_time',$this->bib->getYear());
3612    return $result;
3613  } // end function metadata_opengraph
3614
3615  function metadata_dublin_core($result) {
3616    // Dublin Core should not be used for bibliographic metadata
3617    // according to several sources
3618    //  * Google Scholar: "Use Dublin Core tags (e.g., DC.title) as a last resort - they work poorly for journal papers"
3619    //  * http://reprog.wordpress.com/2010/09/03/bibliographic-data-part-2-dublin-cores-dirty-little-secret/
3620    // however it seems that Google Scholar needs at least DC.Title to trigger referencing
3621    // reference documentation: http://dublincore.org/documents/dc-citation-guidelines/
3622    $result[] = array('DC.Title',$this->bib->getTitle());
3623    foreach($this->bib->getArrayOfCommaSeparatedAuthors() as $author) {
3624      $result[] = array('DC.Creator',$author);
3625    }
3626    $result[] = array('DC.Issued',$this->bib->getYear());
3627    return $result;
3628  }
3629
3630  function metadata_google_scholar($result) {
3631    // the description may mix with the Google Scholar tags
3632    // we remove it
3633    // $result[] = array('description',trim(strip_tags(str_replace('"','',bib2html($this->bib)))));
3634    $result[] = array('citation_title',$this->bib->getTitle());
3635    $authors = $this->bib->getArrayOfCommaSeparatedAuthors();
3636    $result[] = array('citation_authors',implode("; ",$authors));
3637    foreach($authors as $author) {
3638    $result[] = array('citation_author',$author);
3639    }
3640
3641    // the date
3642    $result[] = array('citation_publication_date',$this->bib->getYear());
3643    $result[] = array('citation_date',$this->bib->getYear());
3644    $result[] = array('citation_year',$this->bib->getYear());
3645
3646    if ($this->bib->hasField("publisher")) {
3647    $result[] = array('citation_publisher',$this->bib->getPublisher());
3648    }
3649
3650    // BOOKTITLE: JOURNAL NAME OR PROCEEDINGS
3651    if ($this->bib->getType()=="article") { // journal article
3652    $result[] = array('citation_journal_title',$this->bib->getField("journal"));
3653    $result[] = array('citation_volume',$this->bib->getField("volume"));
3654    if ($this->bib->hasField("number")) {
3655        // in bibtex, the issue number is usually in a field "number"
3656        $result[] = array('citation_issue',$this->bib->getField("number"));
3657    }
3658    if ($this->bib->hasField("issue")) {
3659        $result[] = array('citation_issue',$this->bib->getField("issue"));
3660    }
3661    if ($this->bib->hasField("issn")) {
3662        $result[] = array('citation_issue',$this->bib->getField("issn"));
3663    }
3664    }
3665
3666    if ($this->bib->getType()=="inproceedings" || $this->bib->getType()=="conference") {
3667        $result[] = array('citation_conference_title',$this->bib->getField(BOOKTITLE));
3668        $result[] = array('citation_conference',$this->bib->getField(BOOKTITLE));
3669    }
3670
3671    if ($this->bib->getType()=="phdthesis"
3672        || $this->bib->getType()=="mastersthesis"
3673        || $this->bib->getType()=="bachelorsthesis"
3674        )
3675    {
3676        $result[] = array('citation_dissertation_institution',$this->bib->getField('school'));
3677    }
3678
3679    if ($this->bib->getType()=="techreport"
3680        && $this->bib->hasField("number")
3681        )
3682    {
3683        $result[] = array('citation_technical_report_number',$this->bib->getField('number'));
3684    }
3685
3686    if ($this->bib->getType()=="techreport"
3687        && $this->bib->hasField("institution")
3688        )
3689    {
3690        $result[] = array('citation_technical_report_institution',$this->bib->getField('institution'));
3691    }
3692
3693    // generic
3694    if ($this->bib->hasField("doi")) {
3695    $result[] = array('citation_doi',$this->bib->getField("doi"));
3696    }
3697
3698    if ($this->bib->hasField('url')) {
3699    $result[] = array('citation_pdf_url',$this->bib->getField('url'));
3700    }
3701
3702    if ($this->bib->hasField("pages")) {
3703    $pages = $this->bib->getPages();
3704    if (count($pages)==2) {
3705        $result[] = array('citation_firstpage',$pages[0]);
3706        $result[] = array('citation_lastpage',$pages[1]);
3707    }
3708    }
3709
3710    return $result;
3711  }
3712
3713  function metadata_eprints($result) {
3714    // --------------------------------- BEGIN METADATA EPRINTS
3715    // and now adding eprints metadata
3716    // why adding eprints metadata?
3717    // because eprints is a well known bibliographic software and several crawlers/desktop software
3718    // use their metadata
3719    // unfortunately, the metadata is even less documented than Google Scholar citation_
3720    // reference documentation: the eprints source code (./perl_lib/EPrints/Plugin/Export/Simple.pm)
3721    // examples: conference paper: http://tubiblio.ulb.tu-darmstadt.de/44344/
3722    //           journal paper: http://tubiblio.ulb.tu-darmstadt.de/44344/
3723    $result[] = array('eprints.title',$this->bib->getTitle());
3724    $authors = $this->bib->getArrayOfCommaSeparatedAuthors();
3725    foreach($authors as $author) {
3726      $result[] = array('eprints.creators_name',$author);
3727    }
3728    $result[] = array('eprints.date',$this->bib->getYear());
3729
3730    if ($this->bib->hasField("publisher")) {
3731      $result[] = array('eprints.publisher',$this->bib->getPublisher());
3732    }
3733
3734    if ($this->bib->getType()=="article") { // journal article
3735      $result[] = array('eprints.type','article');
3736      $result[] = array('eprints.publication',$this->bib->getField("journal"));
3737      $result[] = array('eprints.volume',$this->bib->getField("volume"));
3738      if ($this->bib->hasField("issue")) {
3739        $result[] = array('eprints.number',$this->bib->getField("issue"));}
3740    }
3741
3742    if ($this->bib->getType()=="inproceedings" || $this->bib->getType()=="conference") {
3743       $result[] = array('eprints.type','proceeding');
3744       $result[] = array('eprints.book_title',$this->bib->getField(BOOKTITLE));
3745    }
3746
3747    if ($this->bib->getType()=="phdthesis"
3748         || $this->bib->getType()=="mastersthesis"
3749         || $this->bib->getType()=="bachelorsthesis"
3750       )
3751    {
3752       $result[] = array('eprints.type','thesis');
3753       $result[] = array('eprints.institution',$this->bib->getField('school'));
3754    }
3755
3756    if ($this->bib->getType()=="techreport")
3757    {
3758       $result[] = array('eprints.type','monograph');
3759       if ($this->bib->hasField("number")) {
3760         $result[] = array('eprints.number',$this->bib->getField('number'));
3761       }
3762       if ($this->bib->hasField("institution")) {
3763         $result[] = array('eprints.institution',$this->bib->getField('institution'));
3764       }
3765    }
3766
3767    // generic
3768    if ($this->bib->hasField("doi")) {
3769      $result[] = array('eprints.id_number',$this->bib->getField("doi"));
3770    }
3771
3772    if ($this->bib->hasField('url')) {
3773      $result[] = array('eprints.official_url',$this->bib->getField('url'));
3774    }
3775    // --------------------------------- END METADATA EPRINTS
3776    return $result;
3777  } // end method metatada_eprints;
3778} // end class BibEntryDisplay
3779
3780
3781// ----------------------------------------------------------------------
3782// DATABASE MANAGEMENT
3783// ----------------------------------------------------------------------
3784
3785/** represents a bibliographic database that contains a set of bibliographic entries.
3786usage:
3787<pre>
3788$db = new BibDataBase();
3789$db->load('bibacid-utf8.bib');
3790$query = array('author'=>'martin', 'year'=>2008);
3791foreach ($db->multisearch($query) as $bibentry) { echo $bibentry->getTitle(); }
3792</pre>
3793*/
3794class BibDataBase {
3795  /** A hash table from keys (e.g. Goody1994) to bib entries (BibEntry instances). */
3796  var $bibdb;
3797
3798  /** A hashtable of constant strings */
3799  var $stringdb;
3800
3801  /** A list of file names */
3802  var $from_files;
3803
3804  /** Creates a new database by parsing bib entries from the given
3805   * file. (backward compatibility) */
3806  function load($filename) {
3807    $this->from_files[] = $filename;
3808    $this->update($filename);
3809  }
3810
3811  /** Updates a database (replaces the new bibtex entries by the most recent ones) */
3812  function update($filename) {
3813    $this->from_files[] = $filename;
3814    $this->update_internal($filename, NULL);
3815  }
3816
3817  /** returns true if this file is already loaded in this BibDataBase object */
3818  function is_already_loaded($filename) {
3819    return in_array($filename, $this->from_files);
3820  }
3821
3822  /** See update */
3823  function update_internal($resource_name, $resource) {
3824    $empty_array = array();
3825    $db = createBibDBBuilder();
3826    $db->build($resource_name, $resource);
3827
3828    $this->stringdb = array_merge($this->stringdb, $db->stringdb);
3829
3830    $result = $db->builtdb;
3831
3832
3833    foreach ($result as $b) {
3834      // new entries:
3835      if (!isset($this->bibdb[$b->getKey()])) {
3836        //echo 'adding...<br/>';
3837        $this->addEntry($b);
3838      }
3839      // update entry
3840      else if (isset($this->bibdb[$b->getKey()]) && ($b->toHTML() !== $this->bibdb[$b->getKey()]->toHTML())) {
3841        //echo 'replacing...<br/>';
3842        $this->bibdb[$b->getKey()] = $b;
3843      }
3844    }
3845
3846    // some entries have been removed
3847    foreach ($this->bibdb as $e) {
3848      if (!isset($result[$e->getKey()])
3849          && $e->filename==$resource_name // bug reported by Thomas on Dec 4 2012
3850         ) {
3851        //echo 'deleting...<br/>';
3852        unset($this->bibdb[$e->getKey()]);
3853      }
3854    }
3855
3856    // some @string have been removed
3857    foreach ($this->stringdb as $k=>$e) {
3858      if (!isset($db->stringdb[$k])
3859          && $e->filename==$resource_name ) {
3860        //echo 'deleting...<br/>';
3861        unset($this->stringdb[$e->name]);
3862      }
3863    }
3864  }
3865
3866  /** Creates a new empty database */
3867  function __construct() {
3868    $this->bibdb = array();
3869    $this->stringdb = array();
3870  }
3871
3872  /** Returns the $n latest modified bibtex entries/ */
3873  function getLatestEntries($n) {
3874    $order='compare_bib_entry_by_mtime';
3875    $array = $this->bibdb; // array passed by value
3876    uasort($array, $order);
3877    $result = array_slice($array,0,$n);
3878    return $result;
3879  }
3880
3881  /** Returns all entries as an array. Each entry is an instance of
3882   * class BibEntry. */
3883  function getEntries() {
3884    return $this->bibdb;
3885  }
3886  /** tests wheter the database contains a bib entry with $key */
3887  function contains($key) {
3888    return isset($this->bibdb[$key]);
3889  }
3890  /** Returns all entries categorized by types. The returned value is
3891   * a hashtable from types to arrays of bib entries.
3892   */
3893  function getEntriesByTypes() {
3894    $result = array();
3895    foreach ($this->bibdb as $b) {
3896      $result[$b->getType()][] = $b;
3897    }
3898    return $result;
3899  }
3900
3901  /** Returns an array containing all the bib types (strings). */
3902  function getTypes() {
3903    $result = array();
3904    foreach ($this->bibdb as $b) {
3905      $result[$b->getType()] = 1;
3906    }
3907    $result = array_keys($result);
3908    return $result;
3909  }
3910
3911  /** Generates and returns an array consisting of all authors.
3912   * The returned array is a hash table with keys <FirstName LastName>
3913   * and values <LastName, FirstName>.
3914   */
3915  function authorIndex(){
3916    $tmp = array();
3917    foreach ($this->bibdb as $bib) {
3918      foreach($bib->getFormattedAuthorsArray() as $a){
3919        $a = strip_tags($a);
3920        //we use an array because several authors can have the same lastname
3921        @$tmp[$bib->getLastName($a)]=$a;
3922      }
3923    }
3924    ksort($tmp);
3925    $result=array();
3926    foreach ($tmp as $k=>$v) {
3927      $result[$v]=$v;
3928    }
3929
3930    return $result;
3931  }
3932
3933  function venueIndex(){
3934    $tmp = array();
3935    foreach ($this->bibdb as $bib) {
3936      if ($bib->getType()=="article") {
3937        @$tmp[$bib->getField("journal")]++;
3938      }
3939      if ($bib->getType()=="inproceedings") {
3940        @$tmp[$bib->getField("booktitle")]++;
3941      }
3942    }
3943    arsort($tmp);
3944    $result=array();
3945    foreach ($tmp as $k=>$v) {
3946      $result[$k]=$k;
3947    }
3948    return $result;
3949  }
3950
3951  /** Generates and returns an array consisting of all tags.
3952   */
3953  function tagIndex(){
3954    $result = array();
3955    foreach ($this->bibdb as $bib) {
3956      if (!$bib->hasField("keywords")) continue;
3957      $tags = $bib->getKeywords();
3958      foreach($tags as $a){
3959        $ta = trim($a);
3960        $result[$ta] = $ta;
3961      }
3962    }
3963    asort($result);
3964    return $result;
3965  }
3966
3967  /** Generates and returns an array consisting of all years.
3968   */
3969  function yearIndex(){
3970    $result = array();
3971    foreach ($this->bibdb as $bib) {
3972      if (!$bib->hasField("year")) continue;
3973      $year = strtolower($bib->getYearRaw());
3974      $yearInt = (int) $year;
3975
3976      // Allow for ordering of non-string values ('in press' etc.)
3977      switch ($year) {
3978        case (string) $yearInt: // Sorry for this hacky type-casting
3979          $key = $year;
3980          break;
3981        case Q_YEAR_INPRESS:
3982          $key = PHP_INT_MAX + ORDER_YEAR_INPRESS;
3983          break;
3984        case Q_YEAR_ACCEPTED:
3985          $key = PHP_INT_MAX + ORDER_YEAR_ACCEPTED;
3986          break;
3987        case Q_YEAR_SUBMITTED:
3988          $key = PHP_INT_MAX + ORDER_YEAR_SUBMITTED;
3989          break;
3990        default:
3991          $key = PHP_INT_MAX + ORDER_YEAR_OTHERNONINT;
3992      }
3993
3994      $result[$key] = $year;
3995    }
3996
3997    krsort($result);
3998    return $result;
3999  }
4000
4001  /** Given its key, return the bib entry. */
4002  function getEntryByKey($key) {
4003    return $this->bibdb[$key];
4004  }
4005
4006  /** Adds a new bib entry to the database. */
4007  function addEntry($entry) {
4008    if (!$entry->hasField('key')) {
4009      throw new Exception('error: a bibliographic entry must have a key '.$entry->getText());
4010    }
4011    // we keep its insertion order
4012    $entry->order = count($this->bibdb);
4013    $this->bibdb[$entry->getKey()] = $entry;
4014  }
4015
4016
4017  /**
4018   * Returns an array containing all bib entries matching the given
4019   * type.
4020   */
4021  function searchType($type){
4022    $result = array();
4023    foreach($this->bibdb as $bib) {
4024      if($bib->getType() == $type)
4025 $result[] = $bib;
4026    }
4027    return $result;
4028  }
4029
4030  /** Returns an array of bib entries (BibEntry) that satisfy the query
4031   * $query is an hash with entry type as key and searched fragment as value
4032   */
4033  function multisearch($query) {
4034    if (count($query)<1) {return array();}
4035    if (isset($query[Q_ALL])) return array_values($this->bibdb);
4036
4037    $result = array();
4038
4039    foreach ($this->bibdb as $bib) {
4040        $entryisselected = true;
4041        foreach ($query as $field => $fragment) {
4042          $field = strtolower($field);
4043          if ($field==Q_SEARCH) {
4044            // we search in the whole bib entry
4045            if (!$bib->hasPhrase($fragment)) {
4046              $entryisselected = false;
4047              break;
4048            }
4049          }
4050          else if ($field==Q_EXCLUDE) {
4051            if ($bib->hasPhrase($fragment)) {
4052              $entryisselected = false;
4053              break;
4054            }
4055          }
4056          else if ($field==Q_TYPE || $field==Q_INNER_TYPE) {
4057            // types are always exact search
4058            // remarks Ken
4059            // type:"book" should only select book (and not inbook, book, bookchapter)
4060            // this was before in Dispatch:type()
4061            // moved here so that it is also used by AcademicDisplay:search2html()
4062            if (!$bib->hasPhrase('^('.$fragment.')$', Q_INNER_TYPE))  {
4063              $entryisselected = false;
4064              break;
4065            }
4066          }
4067          else if ($field==Q_NAME || $field==Q_AUTHOR_NAME || $field==Q_EDITOR_NAME) {
4068            // Names require exact matching per name. Although a preg_match over the entire author field is possible,
4069            // it's inconvenient and often results in unwanted matches if not done careful. Instead, use
4070            // 'name'=>'(M. Monperrus|Monperrus, M.)' to exact match the name of an author or editor, use
4071            // 'author_name' to match the name of an author, and use 'editor_name' to match the name of an editor.
4072            $names = [];
4073            if ($field==Q_NAME || $field==Q_AUTHOR_NAME)
4074              $names = array_merge($bib->getRawAuthors(), $names);
4075            if ($field==Q_NAME || $field==Q_EDITOR_NAME)
4076              $names = array_merge($bib->getRawEditors(), $names);
4077
4078            if (empty($names)) {
4079              $entryisselected = false;
4080            } else {
4081              foreach ($names as $name) {
4082                $entryisselected = preg_match('/^' . $fragment . '$/', trim($name));
4083                if ($entryisselected) {
4084                  break;
4085                }
4086              }
4087            }
4088            if (!$entryisselected) {
4089              break;
4090            }
4091          }
4092          else if ($field==Q_KEYS) {
4093            if ( ! in_array( $bib->getKey(), $query[Q_KEYS] ) ) {
4094              $entryisselected = false;
4095              break;
4096            }
4097          }
4098	  else if ($field==Q_RANGE) {
4099	    $year = $bib->getYear();
4100	    $withinRange = false;
4101
4102	    foreach ($query[Q_RANGE] as $elements) {
4103	      if ($elements[0] === "" && $elements[1] === "")
4104	        $withinRange = true;
4105              else if ($elements[0] === "" && $year <= $elements[1])
4106	        $withinRange = true;
4107              else if ($elements[1] === "" && $year >= $elements[0])
4108	        $withinRange = true;
4109              else if ($year <= $elements[1] && $year >= $elements[0]) {
4110	        $withinRange = true;
4111              }
4112	    }
4113
4114	    if (!$withinRange)
4115              $entryisselected = false;
4116	  }
4117          else {
4118            if (!$bib->hasPhrase($fragment, $field))  {
4119              $entryisselected = false;
4120              break;
4121            }
4122          }
4123
4124        }
4125        if ($entryisselected) {
4126          $result[] = $bib;
4127        }
4128      }
4129      return $result;
4130  }
4131
4132  /** returns the text of all @String entries of this dabatase */
4133  function stringEntriesText() {
4134    $s = "";
4135    foreach($this->stringdb as $entry) { $s.=$entry->toString()."\n"; }
4136    return $s;
4137  }
4138
4139  /** returns a classical textual Bibtex representation of this database */
4140  function toBibtex() {
4141    $s = "";
4142    $s .= $this->stringEntriesText();
4143    foreach($this->bibdb as $bibentry) { $s.=$bibentry->getText()."\n"; }
4144    return $s;
4145  }
4146
4147} // end class
4148
4149/** returns the default CSS of bibtexbrowser */
4150function bibtexbrowserDefaultCSS() {
4151?>
4152
4153/* title */
4154.bibtitle { font-weight:bold; }
4155/* author */
4156.bibauthor { /* nothing by default */ }
4157/* booktitle (e.g. proceedings title, journal name, etc )*/
4158.bibbooktitle { font-style:italic; }
4159/* publisher */
4160.bibpublisher { /* nothing by default */ }
4161
4162
4163/* 1st level headers, equivalent H1  */
4164.rheader {
4165  color: #003366;
4166  font-size: large;
4167  font-weight: bold;
4168}
4169
4170/* 2nd level headers, equivalent H2  */
4171.sheader {
4172  font-weight: bold;
4173  background-color: #003366;
4174  color: #ffffff;
4175  padding: 2px;
4176  margin-bottom: 0px;
4177  margin-top: 7px;
4178  border-bottom: #ff6633 2px solid;
4179
4180}
4181
4182/* 3rd level headers, equivalent H3  */
4183.theader {
4184  background-color: #995124;
4185  color: #FFFFFF;
4186  padding: 1px 2px 1px 2px;
4187}
4188
4189.btb-nav-title {
4190  background-color: #995124;
4191  color: #FFFFFF;
4192  padding: 1px 2px 1px 2px;
4193}
4194
4195.menu {
4196  font-size: x-small;
4197  background-color: #EFDDB4;
4198  padding: 0px;
4199  border: 1px solid #000000;
4200  margin: 0px;
4201}
4202.menu a {
4203  text-decoration: none;
4204  color: #003366;
4205}
4206.menu a:hover {
4207  color: #ff6633;
4208}
4209
4210dd {
4211  display: inline; /* for <dt> if BIBTEXBROWSER_LAYOUT='definition' */
4212}
4213
4214.bibitem {
4215  margin-left:5px;
4216}
4217
4218.bibref {
4219  padding:7px;
4220  padding-left:15px;
4221  vertical-align:text-top;
4222  display: inline; /* for <dt> if BIBTEXBROWSER_LAYOUT='definition' */
4223}
4224
4225.result {
4226  border: 1px solid #000000;
4227  margin:0px;
4228  background-color: #ffffff;
4229  width:100%;
4230}
4231.result a {
4232  text-decoration: none;
4233  color: #469AF8;
4234}
4235
4236.result a:hover {
4237  color: #ff6633;
4238}
4239
4240.input_box{
4241  margin-bottom : 2px;
4242}
4243.mini_se {
4244  border: none 0;
4245  border-top: 1px dashed #717171;
4246  height: 1px;
4247}
4248.a_name a {
4249  color:#469AF8;
4250  width:130px;
4251}
4252
4253.rsslink {
4254  text-decoration: none;
4255  color:#F88017;
4256/* could be fancy, see : http://www.feedicons.com/ for icons*/
4257  /*background-image: url("rss.png"); text-indent: -9999px;*/
4258}
4259
4260.purebibtex {
4261  font-family: monospace;
4262  font-size: small;
4263  border: 1px solid #DDDDDD;
4264  background: none repeat scroll 0 0 #F5F5F5;
4265  padding:10px;
4266
4267  overflow:auto;
4268  width:600px;
4269
4270  clear:both;
4271}
4272.bibentry-by { font-style: italic; }
4273.bibentry-abstract { margin:15px; }
4274.bibentry-label { margin-top:15px; }
4275.bibentry-reference { margin-bottom:15px; padding:10px; background: none repeat scroll 0 0 #F5F5F5; border: 1px solid #DDDDDD; }
4276
4277.btb-nav { text-align: right; }
4278
4279<?php
4280} // end function bibtexbrowserDefaultCSS
4281
4282/** encapsulates the content of a delegate into full-fledged HTML (&lt;HTML>&lt;BODY> and TITLE)
4283usage:
4284<pre>
4285  $db = zetDB('bibacid-utf8.bib');
4286  $dis = new BibEntryDisplay($db->getEntryByKey('classical'));
4287  HTMLTemplate($dis);
4288</pre>
4289 * $content: an object with methods
4290      display()
4291      getRSS()
4292      getTitle()
4293 * $title: title of the page
4294 */
4295function HTMLTemplate($content) {
4296
4297// when we load a page with AJAX
4298// the HTTP header is taken into account, not the <meta http-equiv>
4299header('Content-type: text/html; charset='.OUTPUT_ENCODING);
4300echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n";
4301
4302?>
4303<html xmlns="http://www.w3.org/1999/xhtml">
4304<head>
4305<meta http-equiv="Content-Type" content="text/html; charset=<?php echo OUTPUT_ENCODING ?>"/>
4306<meta name="generator" content="bibtexbrowser v__GITHUB__" />
4307<?php
4308// if ($content->getRSS()!='') echo '<link rel="alternate" type="application/rss+xml" title="RSS" href="'.$content->getRSS().'&amp;rss" />';
4309?>
4310<?php
4311
4312// we may add new metadata tags
4313$metatags = array();
4314if (method_exists($content, 'metadata')) {
4315  $metatags = $content->metadata();
4316}
4317foreach($metatags as $item) {
4318  list($name,$value) = $item;
4319  echo '<meta name="'.$name.'" property="'.$name.'" content="'.$value.'"/>'."\n";
4320} // end foreach
4321
4322
4323
4324// now the title
4325if (method_exists($content, 'getTitle')) {
4326  echo '<title>'.strip_tags($content->getTitle()).'</title>';
4327}
4328
4329// now the CSS
4330echo '<style type="text/css"><!--  '."\n";
4331
4332if (method_exists($content, 'getCSS')) {
4333  echo $content->getCSS();
4334} else if (is_readable(dirname(__FILE__).'/bibtexbrowser.css')) {
4335  readfile(dirname(__FILE__).'/bibtexbrowser.css');
4336}
4337else {  bibtexbrowserDefaultCSS(); }
4338
4339echo "\n".' --></style>';
4340
4341?>
4342</head>
4343<body>
4344<?php
4345// configuration point to add a banner
4346echo bibtexbrowser_top_banner();
4347?>
4348<?php
4349if (method_exists($content, 'getTitle')) {
4350  echo "<div class=\"rheader\">" . $content->getTitle() . "</div>";
4351}
4352?>
4353<?php
4354  $content->display();
4355  echo poweredby();
4356
4357  if (c('BIBTEXBROWSER_USE_PROGRESSIVE_ENHANCEMENT')) {
4358    javascript();
4359  }
4360
4361  if (BIBTEXBROWSER_RENDER_MATH) {
4362    javascript_math();
4363  }
4364?>
4365</body>
4366</html>
4367<?php
4368//exit;
4369} // end function HTMLTemplate
4370
4371
4372/** does nothing but calls method display() on the content.
4373usage:
4374<pre>
4375  $db = zetDB('bibacid-utf8.bib');
4376  $dis = new SimpleDisplay($db);
4377  NoWrapper($dis);
4378</pre>
4379*/
4380function NoWrapper($content) {
4381  echo $content->display();
4382  if (c('BIBTEXBROWSER_USE_PROGRESSIVE_ENHANCEMENT')) {
4383    javascript();
4384  }
4385}
4386
4387/** is used to create an subset of a bibtex file.
4388usage:
4389<pre>
4390  $db = zetDB('bibacid-utf8.bib');
4391  $query = array('year'=>2005);
4392  $dis = new BibtexDisplay();
4393  $dis->setEntries($db->multisearch($query));
4394  $dis->display();
4395</pre>
4396*/
4397class BibtexDisplay {
4398
4399  function __construct() {}
4400
4401  function setTitle($title) { $this->title = $title; return $this; }
4402
4403  /** sets the entries to be shown */
4404  function setEntries($entries) {
4405    $this->entries = $entries;
4406  }
4407
4408  function setWrapper($x) { $x->wrapper = 'NoWrapper'; }
4409
4410  function display() {
4411    header('Content-type: text/plain; charset='.OUTPUT_ENCODING);
4412    echo '% generated by bibtexbrowser <http://www.monperrus.net/martin/bibtexbrowser/>'."\n";
4413    echo '% '.@$this->title."\n";
4414    echo '% Encoding: '.OUTPUT_ENCODING."\n";
4415    foreach($this->entries as $bibentry) { echo $bibentry->getText()."\n"; }
4416  }
4417
4418}
4419
4420/** creates paged output, e.g: [[http://localhost/bibtexbrowser/testPagedDisplay.php?page=1]]
4421usage:
4422<pre>
4423  $_GET['library']=1;
4424  include( 'bibtexbrowser.php' );
4425  $db = zetDB('bibacid-utf8.bib');
4426  $pd = new PagedDisplay();
4427  $pd->setEntries($db->bibdb);
4428  $pd->display();
4429</pre>
4430*/
4431class PagedDisplay {
4432
4433  var $query = array();
4434
4435  function __construct() {
4436    $this->setPage();
4437  }
4438
4439    /** sets the entries to be shown */
4440  function setEntries($entries) {
4441    uasort($entries, 'compare_bib_entries');
4442    $this->entries = array_values($entries);
4443  }
4444
4445  /** sets $this->page from $_GET, defaults to 1 */
4446  function setPage() {
4447    $this->page = 1;
4448    if (isset($_GET['page'])) {
4449      $this->page = $_GET['page'];
4450    }
4451  }
4452
4453  function setQuery($query) {
4454    $this->query = $query;
4455  }
4456
4457  function getTitle() {
4458    return query2title($this->query). ' - page '.$this->page;
4459  }
4460
4461  function display() {
4462    $less = false;
4463
4464    if ($this->page>1) {$less = true;}
4465
4466    $more = true;
4467
4468    // computing $more
4469    $index = ($this->page)*bibtexbrowser_configuration('PAGE_SIZE');
4470    if (!isset($this->entries[$index])) {
4471      $more = false;
4472    }
4473
4474    $this->menu($less, $more);
4475    print_header_layout();
4476    for ($i = 0; $i < bibtexbrowser_configuration('PAGE_SIZE'); $i++) {
4477      $index = ($this->page-1)*bibtexbrowser_configuration('PAGE_SIZE') + $i;
4478      if (isset($this->entries[$index])) {
4479        $bib = $this->entries[$index];
4480        echo $bib->toHTML(true);
4481
4482      } else {
4483        //break;
4484      }
4485    } // end foreach
4486
4487    print_footer_layout();
4488
4489    $this->menu($less, $more);
4490  }
4491
4492  function menu($less, $more) {
4493
4494    echo '<span class="nav-menu">';
4495
4496    $prev = $this->query;
4497    $prev['page'] = $this->page-1;
4498    if ($less == true) { echo '<a '.makeHref($prev).'>Prev Page</a>'; }
4499
4500    if ($less && $more) { echo '&nbsp;|&nbsp;'; }
4501
4502    $next = $this->query;
4503    $next['page'] = $this->page+1;
4504    if ($more == true) { echo '<a '.makeHref($next).'>Next Page</a>'; }
4505    echo '</span>';
4506
4507  }
4508}
4509
4510/** is used to create an RSS feed.
4511usage:
4512<pre>
4513  $db = zetDB('bibacid-utf8.bib');
4514  $query = array('year'=>2005);
4515  $rss = new RSSDisplay();
4516  $entries = $db->getLatestEntries(10);
4517  $rss->setEntries($entries);
4518  $rss->display();
4519</pre>
4520*/
4521class RSSDisplay {
4522
4523  var $title = 'RSS produced by bibtexbrowser';
4524
4525  function __construct() {
4526    // nothing by default
4527  }
4528
4529  function setTitle($title) { $this->title = $title; return $this; }
4530
4531  /** tries to always output a valid XML/RSS string
4532    * based on OUTPUT_ENCODING, HTML tags, and the transformations
4533    * that happened in latex2html */
4534  function text2rss($desc) {
4535    // first strip HTML tags
4536    $desc = strip_tags($desc);
4537
4538    // some entities may still be here, we remove them
4539    // we replace html entities e.g. &eacute; by nothing
4540    // however XML entities are kept (e.g. &#53;)
4541    $desc = preg_replace('/&\w+;/','',$desc);
4542
4543    // bullet proofing ampersand
4544    $desc = preg_replace('/&([^#])/','&#38;$1',$desc);
4545
4546    // be careful of <
4547    $desc = str_replace('<','&#60;',$desc);
4548
4549    // final test with encoding:
4550    if (function_exists('mb_check_encoding')) { // (PHP 4 >= 4.4.3, PHP 5 >= 5.1.3)
4551      if (!mb_check_encoding($desc,OUTPUT_ENCODING)) {
4552        return 'encoding error: please check the content of OUTPUT_ENCODING';
4553      }
4554    }
4555
4556    return $desc;
4557  }
4558
4559  /** sets the entries to be shown */
4560  function setEntries($entries) {
4561    $this->entries = $entries;
4562  }
4563
4564  function setWrapper($x) { $x->wrapper = 'NoWrapper'; }
4565
4566  function display() {
4567    header('Content-type: application/rss+xml');
4568    echo '<?xml version="1.0" encoding="'.OUTPUT_ENCODING.'"?>';
4569//
4570
4571?>
4572<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
4573   <channel>
4574      <title><?php echo $this->title;?></title>
4575      <link>http://<?php echo @$_SERVER['HTTP_HOST'].htmlentities(@$_SERVER['REQUEST_URI']);?></link>
4576      <atom:link href="http://<?php echo @$_SERVER['HTTP_HOST'].htmlentities(@$_SERVER['REQUEST_URI']);?>" rel="self" type="application/rss+xml" />
4577      <description></description>
4578      <generator>bibtexbrowser v__GITHUB__</generator>
4579
4580<?php
4581      foreach($this->entries as $bibentry) {
4582         ?>
4583         <item>
4584         <title><?php echo $this->text2rss($bibentry->getTitle());?></title>
4585         <link><?php echo $bibentry->getURL();?></link>
4586         <description>
4587          <?php
4588            // we are in XML, so we cannot have HTML entitites
4589            echo $this->text2rss(bib2html($bibentry)."\n".$bibentry->getAbstract());
4590          ?>
4591          </description>
4592         <guid isPermaLink="false"><?php echo urlencode(@$_GET[Q_FILE].'::'.$bibentry->getKey());?></guid>
4593         </item>
4594         <?php } /* end foreach */?>
4595   </channel>
4596</rss>
4597
4598<?php
4599  //exit;
4600  }
4601}
4602
4603
4604
4605/** is responsible for transforming a query string of $_GET[..] into a publication list.
4606usage:
4607<pre>
4608  $_GET['library']=1;
4609  @require('bibtexbrowser.php');
4610  $_GET['bib']='bibacid-utf8.bib';
4611  $_GET['year']='2006';
4612  $x = new Dispatcher();
4613  $x->main();
4614</pre>
4615*/
4616class Dispatcher {
4617
4618  /** this is the query */
4619  var $query = array();
4620
4621  /** the displayer of selected entries. The default is set in BIBTEXBROWSER_DEFAULT_DISPLAY.
4622    *  It could also be an RSSDisplay if the rss keyword is present
4623    */
4624  var $displayer = '';
4625
4626  /** the wrapper of selected entries. The default is an HTML wrapper
4627    *  It could also be a NoWrapper when you include your pub list in your home page
4628    */
4629  var $wrapper = BIBTEXBROWSER_DEFAULT_TEMPLATE;
4630
4631  /** The BibDataBase object */
4632  var $db = null;
4633
4634  function __construct() {}
4635
4636  /** returns the underlying BibDataBase object */
4637  function getDB() {
4638    // by default set it from $_GET[Q_FILE]
4639    // first we set the database (load from disk or parse the bibtex file)
4640    if ($this->db == null) {
4641      list($db, $parsed, $updated, $saved) = _zetDB($_GET[Q_FILE]);
4642      $this->db = $db;
4643    }
4644    return $this->db;
4645  }
4646
4647  function main() {
4648    // are we in test mode, or libray mode
4649    // then this file is just a library
4650    if (isset($_GET['test']) || isset($_GET['library'])) {
4651      // we unset in  order to use the dispatcher afterwards
4652      unset($_GET['test']);
4653      unset($_GET['library']);
4654      return;
4655    }
4656
4657    if (!isset($_GET[Q_FILE])) { die('$_GET[\''.Q_FILE.'\'] is not set!'); }
4658
4659    // is the publication list included in another page?
4660    // strtr is used for Windows where __FILE__ contains C:\toto and SCRIPT_FILENAME contains C:/toto (bug reported by Marco)
4661    // realpath is required if the path contains sym-linked directories (bug found by Mark Hereld)
4662    if (strtr(realpath(__FILE__),"\\","/")!=strtr(realpath($_SERVER['SCRIPT_FILENAME']),"\\","/")) $this->wrapper=BIBTEXBROWSER_EMBEDDED_WRAPPER;
4663
4664    // first pass, we will exit if we encounter key or menu or academic
4665    // other wise we just create the $this->query
4666    foreach($_GET as $keyword=>$value) {
4667      if (method_exists($this,$keyword)) {
4668        // if the return value is END_DISPATCH, we finish bibtexbrowser (but not the whole PHP process in case we are embedded)
4669        if ($this->$keyword()=='END_DISPATCH') return;
4670      }
4671    }
4672
4673    // at this point, we may have a query
4674
4675    if (count($this->query)>0) {
4676
4677       // first test for inconsistent queries
4678       if (isset($this->query[Q_ALL]) && count($this->query)>1) {
4679         // we discard the Q_ALL, it helps in embedded mode
4680         unset($this->query[Q_ALL]);
4681       }
4682
4683       $selectedEntries = $this->getDB()->multisearch($this->query);
4684
4685       if (count($selectedEntries)==0) {
4686         $this->displayer = 'NotFoundDisplay';
4687       }
4688
4689       // default order
4690       uasort($selectedEntries, 'compare_bib_entries');
4691       $selectedEntries = array_values($selectedEntries);
4692
4693       //echo '<pre>';print_r($selectedEntries);echo '</pre>';
4694
4695       if ($this->displayer=='') {
4696         $this->displayer = bibtexbrowser_configuration('BIBTEXBROWSER_DEFAULT_DISPLAY');
4697       }
4698    } // otherwise the query is left empty
4699
4700    // do we have a displayer?
4701    if ($this->displayer!='') {
4702
4703      $options = array();
4704      if (isset($_GET['dopt'])) {
4705        $options = json_decode($_GET['dopt'],true);
4706      }
4707
4708      // required for PHP4 to have this intermediate variable
4709      $x = new $this->displayer();
4710
4711      if (method_exists($x,'setEntries')) {
4712        $x->setEntries($selectedEntries);
4713      }
4714
4715      if (method_exists($x,'setTitle')) {
4716        $x->setTitle(query2title($this->query));
4717      }
4718
4719      if (method_exists($x,'setQuery')) {
4720        $x->setQuery($this->query);
4721      }
4722
4723      // should call method display() on $x
4724      $fun = $this->wrapper;
4725      $fun($x);
4726
4727      $this->clearQuery();
4728    }
4729    else {
4730       // we send a redirection for having the frameset
4731       // if some contents have already been sent, for instance if we are included
4732       // this means doing nothing
4733       if ( headers_sent() == false ) { /* to avoid sending an unnecessary frameset */
4734         header("Location: ".$_SERVER['SCRIPT_NAME']."?frameset&bib=".$_GET[Q_FILE]);
4735       }
4736     }
4737  }
4738
4739  /** clears the query string in $_GET so that bibtexbrowser can be called multiple times */
4740  function clearQuery() {
4741    $params= array(Q_ALL,'rss', 'astext', Q_SEARCH, Q_EXCLUDE, Q_YEAR, EDITOR, Q_TAG, Q_AUTHOR, Q_TYPE, Q_ACADEMIC, Q_KEY);
4742    foreach($params as $p) { unset($_GET[$p]); }
4743  }
4744
4745  function all() {
4746    $this->query[Q_ALL]=1;
4747  }
4748
4749  function display() {
4750    $this->displayer=$_GET['display'];
4751  }
4752
4753  function rss() {
4754    $this->displayer='RSSDisplay';
4755    $this->wrapper='NoWrapper';
4756  }
4757
4758  function astext() {
4759    $this->displayer='BibtexDisplay';
4760    $this->wrapper='NoWrapper';
4761  }
4762
4763  function search() {
4764    if (preg_match('/utf-?8/i',OUTPUT_ENCODING)) {
4765      $_GET[Q_SEARCH] = urldecode($_GET[Q_SEARCH]);
4766    }
4767    $this->query[Q_SEARCH]=$_GET[Q_SEARCH];
4768  }
4769
4770  function exclude() { $this->query[Q_EXCLUDE]=$_GET[Q_EXCLUDE]; }
4771
4772  function year() {
4773    // we may want the latest
4774    if ($_GET[Q_YEAR]=='latest') {
4775      $years = $this->getDB()->yearIndex();
4776      $_GET[Q_YEAR]=array_shift($years);
4777    }
4778    $this->query[Q_YEAR]=$_GET[Q_YEAR];
4779  }
4780
4781  function editor() {  $this->query[EDITOR]=$_GET[EDITOR]; }
4782
4783  function keywords() { $this->query[Q_TAG]=$_GET[Q_TAG]; }
4784
4785  function author() {
4786    // Friday, October 29 2010
4787    // changed from 'author' to '_author'
4788    // in order to search at the same time "Joe Dupont" an "Dupont, Joe"
4789    $this->query[Q_INNER_AUTHOR]=$_GET[Q_AUTHOR];
4790  }
4791
4792  function type() {
4793    $this->query[Q_TYPE]= $_GET[Q_TYPE];
4794  }
4795  /**
4796   * Allow the user to search for a range of dates
4797   *
4798   * The query string can comprise several elements separated by commas and
4799   * optionally white-space.
4800   * Each element can either be one number (a year) or two numbers
4801   * (a range of years) separated by anything non-numerical.
4802   *
4803   */
4804  function range() {
4805    $ranges = explode(',', $_GET[Q_RANGE]);
4806    $result = array();
4807
4808    $nextYear = 1 + (int) date('Y');
4809    $nextYear2D = $nextYear % 100;
4810    $thisCentury = $nextYear - $nextYear2D;
4811
4812    foreach ($ranges as $range) {
4813      $range = trim($range);
4814      preg_match('/([0-9]*)([^0-9]*)([0-9]*)/', $range, $matches);
4815      array_shift($matches);
4816
4817      // If the number is empty, leave it empty - dont put it to 0
4818      // If the number is two-digit, assume it to be within the last century or next year
4819      if ($matches[0] === "") {
4820        $lower = "";
4821      } else if ($matches[0] < 100) {
4822        if ($matches[0] > $nextYear2D) {
4823          $lower = $thisCentury + $matches[0] - 100;
4824	} else {
4825	  $lower = $thisCentury + $matches[0];
4826	}
4827      } else {
4828        $lower = $matches[0];
4829      }
4830
4831      // If no separator to indicate a range of years was supplied,
4832      // the upper and lower boundaries are the same.
4833      //
4834      // Otherwise, again:
4835      // If the number is empty, leave it empty - dont put it to 0
4836      // If the number is two-digit, assume it to be within the last century or next year
4837      if ($matches[1] === "")
4838        $upper = $lower;
4839      else {
4840        if ($matches[2] === "") {
4841          $upper = "";
4842        } else if ($matches[2] < 100) {
4843          if ($matches[2] > $nextYear2D) {
4844            $upper = $thisCentury + $matches[2] - 100;
4845	  } else {
4846	    $upper = $thisCentury + $matches[2];
4847          }
4848        } else {
4849          $upper = $matches[2];
4850        }
4851      }
4852
4853      $result[] = array($lower, $upper);
4854    }
4855    $this->query[Q_RANGE] = $result;
4856  }
4857
4858  function menu() {
4859    $menu = createMenuManager();
4860    $menu->setDB($this->getDB());
4861    $fun = $this->wrapper;
4862    $fun($menu);
4863    return 'END_DISPATCH';
4864  }
4865
4866  /** the academic keyword in URLs switch from a year based viey to a publication type based view */
4867  function academic() {
4868     $this->displayer='AcademicDisplay';
4869
4870
4871     // backward compatibility with old GET API
4872     // this is deprecated
4873     // instead of academic=Martin+Monperrus
4874     // you should use author=Martin+Monperrus&academic
4875     // be careful of the semantics of === and !==
4876     // 'foo bar' == true is true
4877     // 123 == true is true (and whatever number different from 0
4878     // 0 == true is true
4879     // '1'!=1 is **false**
4880     if(!isset($_GET[Q_AUTHOR]) && $_GET[Q_ACADEMIC]!==true && $_GET[Q_ACADEMIC]!=='true' && $_GET[Q_ACADEMIC]!=1 && $_GET[Q_ACADEMIC]!='') {
4881      $_GET[Q_AUTHOR]=$_GET[Q_ACADEMIC];
4882      $this->query[Q_AUTHOR]=$_GET[Q_ACADEMIC];
4883     }
4884
4885  }
4886
4887  function key() {
4888    $entries = array();
4889    // case 1: this is a single key
4890    if ($this->getDB()->contains($_GET[Q_KEY])) {
4891      $entries[] = $this->getDB()->getEntryByKey($_GET[Q_KEY]);
4892      if (isset($_GET['astext'])) {
4893        $bibdisplay = new BibtexDisplay();
4894        $bibdisplay->setEntries($entries);
4895        $bibdisplay->display();
4896      } else {
4897        $bibdisplay = createBibEntryDisplay();
4898        $bibdisplay->setEntries($entries);
4899        $fun = $this->wrapper;
4900        $fun($bibdisplay);
4901      }
4902      return 'END_DISPATCH';
4903    }
4904
4905    // case two: multiple keys
4906    if (preg_match('/[|,]/',$_GET[Q_KEY])) {
4907      $this->query[Q_SEARCH]=str_replace(',','|',$_GET[Q_KEY]);
4908    } else { nonExistentBibEntryError(); }
4909  }
4910
4911  function keys() {
4912    // Create array from list of bibtex entries
4913    if (get_magic_quotes_gpc()) {
4914      $_GET[Q_KEYS] = stripslashes($_GET[Q_KEYS]);
4915    }
4916    $_GET[Q_KEYS] = (array) json_decode(urldecode($_GET[Q_KEYS])); // decode and cast the object into an (associative) array
4917    // Make the array 1-based (keeps the string keys unchanged)
4918    array_unshift($_GET[Q_KEYS],"__DUMMY__");
4919    unset($_GET[Q_KEYS][0]);
4920    // Keep a flipped version for efficient search in getRawAbbrv()
4921    $_GET[Q_INNER_KEYS_INDEX] = array_flip($_GET[Q_KEYS]);
4922    $this->query[Q_KEYS]=$_GET[Q_KEYS];
4923  }
4924
4925  /** is used to remotely analyzed a situation */
4926  function diagnosis() {
4927    header('Content-type: text/plain');
4928    echo "php version: ".phpversion()."\n";
4929    echo "bibtexbrowser version: __GITHUB__\n";
4930    echo "dir: ".decoct(fileperms(dirname(__FILE__)))."\n";
4931    echo "bibtex file: ".decoct(fileperms($_GET[Q_FILE]))."\n";
4932    exit;
4933  }
4934
4935  function frameset() {    ?>
4936
4937
4938    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
4939    <html  xmlns="http://www.w3.org/1999/xhtml">
4940    <head>
4941    <meta name="generator" content="bibtexbrowser v__GITHUB__" />
4942    <meta http-equiv="Content-Type" content="text/html; charset=<?php echo OUTPUT_ENCODING ?>"/>
4943    <title>You are browsing <?php echo htmlentities($_GET[Q_FILE], ENT_QUOTES); ?> with bibtexbrowser</title>
4944    </head>
4945    <frameset cols="15%,*">
4946    <frame name="menu" src="<?php echo '?'.Q_FILE.'='. urlencode($_GET[Q_FILE]).'&amp;menu'; ?>" />
4947    <frame name="main" src="<?php echo '?'.Q_FILE.'='. urlencode($_GET[Q_FILE]).'&amp;'.BIBTEXBROWSER_DEFAULT_FRAME?>" />
4948    </frameset>
4949    </html>
4950
4951    <?php
4952    return 'END_DISPATCH';
4953}
4954
4955} // end class Dispatcher
4956
4957function bibtexbrowser_cli($arguments) {
4958  $db = new BibDataBase();
4959  $db->load($arguments[1]);
4960  $current_entry=NULL;
4961  $current_field=NULL;
4962  for ($i=2;$i<count($arguments); $i++) {
4963    $arg=$arguments[$i];
4964    if ($arg=='--id') {
4965      $current_entry = $db->getEntryByKey($arguments[$i+1]);
4966      $i=$i+1;
4967    }
4968    if (preg_match('/^--set-(.*)/',$arg,$matches)) {
4969      $current_entry->setField($matches[1],$arguments[$i+1]);
4970      $i=$i+1;
4971    }
4972  }
4973  file_put_contents($arguments[1],$db->toBibtex());
4974}
4975
4976} // end if (!defined('BIBTEXBROWSER'))
4977
4978@include(preg_replace('/\.php$/','.after.php',__FILE__));
4979$class = BIBTEXBROWSER_MAIN;// extension point
4980$main = new $class();
4981$main->main();
4982?>
4983