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