diff --git a/bibtexbrowser.php b/bibtexbrowser.php index 5d5b555..c43244f 100755 --- a/bibtexbrowser.php +++ b/bibtexbrowser.php @@ -4,6 +4,7 @@ URL: http://www.monperrus.net/martin/bibtexbrowser/ Feedback & Bug Reports: martin.monperrus@gnieh.org (C) 2012-2014 Github contributors +(C) 2014 Markus Jochim (C) 2006-2014 Martin Monperrus (C) 2013 Matthieu Guillaumin (C) 2005-2006 The University of Texas at El Paso / Joel Garcia, Leonardo Ruiz, and Yoonsik Cheon @@ -100,6 +101,9 @@ function bibtexbrowser_configure($key, $value) { // do we add [gsid] links (Google Scholar)? @define('BIBTEXBROWSER_GSID_LINKS',true); +// should pdf, doi, url, gsid links be opened in a new window? +@define('BIBTEXBROWSER_LINKS_IN_NEW_WINDOW',true); + // should authors be linked to [none/homepage/resultpage] // none: nothing // their homepage if defined as @strings @@ -112,9 +116,15 @@ function bibtexbrowser_configure($key, $value) { // for ordered_list, the index is given by HTML directly (in increasing order) @define('BIBTEXBROWSER_LAYOUT','table'); +// Which is the first html level that should be used in embedded mode? +@define('BIBTEXBROWSER_HTMLHEADINGLEVEL', 2); + +@define('BIBTEXBROWSER_ACADEMIC_TOC', true); + @define('BIBTEXBROWSER_DEBUG',false); @define('COMMA_NAMES',false);// do have authors in a comma separated form? +@define('FORCE_NAMELIST_SEPARATOR', ''); // if non-empty, use this to separate multiple names regardless of COMMA_NAMES @define('TYPES_SIZE',10); // number of entry types per table @define('YEAR_SIZE',20); // number of years per table @define('AUTHORS_SIZE',30); // number of authors per table @@ -122,6 +132,9 @@ function bibtexbrowser_configure($key, $value) { @define('READLINE_LIMIT',1024); @define('Q_YEAR', 'year'); @define('Q_YEAR_PAGE', 'year_page'); +@define('Q_YEAR_INPRESS', 'in press'); +@define('Q_YEAR_ACCEPTED', 'accepted'); +@define('Q_YEAR_SUBMITTED', 'submitted'); @define('Q_FILE', 'bib'); @define('Q_AUTHOR', 'author'); @define('Q_AUTHOR_PAGE', 'author_page'); @@ -139,6 +152,7 @@ function bibtexbrowser_configure($key, $value) { @define('Q_ACADEMIC', 'academic'); @define('Q_DB', 'bibdb'); @define('Q_LATEST', 'latest'); +@define('Q_RANGE', 'range'); @define('AUTHOR', 'author'); @define('EDITOR', 'editor'); @define('SCHOOL', 'school'); @@ -151,6 +165,15 @@ function bibtexbrowser_configure($key, $value) { @define('METADATA_DC',true); @define('METADATA_EPRINTS',false); +// define sort order for special values in 'year' field +// highest number is sorted first +// don't exceed 0 though, since the values are added to PHP_INT_MAX +@define('ORDER_YEAR_INPRESS', -0); +@define('ORDER_YEAR_ACCEPTED', -1); +@define('ORDER_YEAR_SUBMITTED', -2); +@define('ORDER_YEAR_OTHERNONINT', -3); + + // in embedded mode, we still need a URL for displaying bibtex entries alone // this is usually resolved to bibtexbrowser.php // but can be overridden in bibtexbrowser.local.php @@ -907,6 +930,7 @@ function latex2html($line) { $line = char2html($line,'"','o',"uml"); $line = char2html($line,'"','u',"uml"); $line = char2html($line,'"','y',"uml"); + $line = char2html($line,'"','s',"zlig"); $line = char2html($line,'^','a',"circ"); $line = char2html($line,'^','e',"circ"); @@ -1094,11 +1118,21 @@ class BibEntry { if ($altlabel==NULL) { $altlabel=$bibfield; } $str = $this->getIconOrTxt($altlabel,$iconurl); if ($this->hasField($bibfield)) { - return ''.$str.''; + return ''.$str.''; } return ''; } + /** returns a "[bib]" link */ + function getBibLink($iconurl=NULL) { + $bibstr = $this->getIconOrTxt('bibtex',$iconurl); + $href = 'href="'.$this->getURL().'"'; + // we add biburl and title to be able to retrieve this important information + // using Xpath expressions on the XHTML source + $link = "getKey()."\" {$href}>$bibstr"; + return $link; + } + /** returns a "[pdf]" link if relevant. modified to exploit the new method, while keeping backward compatibility */ function getUrlLink($iconurl = NULL, $label = 'pdf') { if ($this->hasField('url')) { @@ -1114,34 +1148,22 @@ class BibEntry { } } - /** returns a "[bib]" link if relevant */ - function getBibLink($iconurl=NULL) { - if (BIBTEXBROWSER_BIBTEX_LINKS) { - $bibstr = $this->getIconOrTxt('bibtex',$iconurl); - $href = 'href="'.$this->getURL().'"'; - $link = "getKey()."\" {$href}>$bibstr"; - return $link; - } else { - return ''; - } - } /** DOI are a special kind of links, where the url depends on the doi */ function getDoiLink($iconurl=NULL) { $str = $this->getIconOrTxt('doi',$iconurl); - if (BIBTEXBROWSER_DOI_LINKS && $this->hasField('doi')) { - return ''.$str.''; + if ($this->hasField('doi')) { + return ''.$str.''; } return ''; } - /** GS are a special kind of links, where the url depends on the google scholar id */ + /** GS (Google Scholar) are a special kind of links, where the url depends on the google scholar id */ function getGSLink($iconurl=NULL) { $str = $this->getIconOrTxt('cites',$iconurl); - // Google Scholar ID - if (BIBTEXBROWSER_GSID_LINKS && $this->hasField('gsid')) { - return ' '.$str.''; + if ($this->hasField('gsid')) { + return ' '.$str.''; } return ''; } @@ -1298,6 +1320,7 @@ class BibEntry { } if (COMMA_NAMES) {$sep = '; ';} else {$sep = ', ';} + if (FORCE_NAMELIST_SEPARATOR !== '') {$sep = FORCE_NAMELIST_SEPARATOR;} return implode($sep ,$array_authors); } @@ -1370,11 +1393,15 @@ class BibEntry { $editors[]=$this->addHomepageLink($this->formatAuthor($editor)); } if (COMMA_NAMES) {$sep = '; ';} else {$sep = ', ';} + if (FORCE_NAMELIST_SEPARATOR !== '') {$sep = FORCE_NAMELIST_SEPARATOR;} return implode($sep, $editors).', '.(count($editors)>1?'eds.':'ed.'); } /** Returns the year of this entry? */ function getYear() { + return __(strtolower($this->getField('year'))); + } + function getYearRaw() { return $this->getField('year'); } @@ -1601,10 +1628,16 @@ class BibEntry { $result = ""; $result .= '
'; // pre is nice when it is embedded with no CSS available
     $entry = htmlspecialchars($this->getFullText());
-    if ($this->hasField('url')) {
-      $url = $this->getField('url');
-      // this is not a parsing but a simple replacement
-      $entry = str_replace($url,''.$url.'', $entry);
+
+    // Fields that should be hyperlinks
+    $hyperlinks = array('url' => '%O', 'file' => '%O', 'pdf' => '%O', 'doi' => 'http://dx.doi.org/%O', 'gsid' => 'http://scholar.google.com/scholar?cites=%O');
+
+    foreach ($hyperlinks as $field => $url) {
+      if ($this->hasField($field)) {
+        $href = str_replace('%O', $this->getField($field), $url);
+        // this is not a parsing but a simple replacement
+        $entry = str_replace($this->getField($field), ''.$this->getField($field).'', $entry);
+      }
     }
 
     $result .=  $entry;
@@ -1658,28 +1691,22 @@ function get_HTML_tag_for_layout() {
  *  e.g. [bibtex] [doi][pdf]
  */
 function bib2links_default(&$bibentry) {
-  $href = 'href="'.$bibentry->getURL().'"';
-
   $str = '';
 
   if (BIBTEXBROWSER_BIBTEX_LINKS) {
-    // we add biburl and title to be able to retrieve this important information
-    // using Xpath expressions on the XHTML source
-    $str .= "getKey()."\" {$href}>[bibtex]";
+    $str .= ' '.$bibentry->getBibLink();
   }
 
   if (BIBTEXBROWSER_PDF_LINKS) {
-    // returns an empty string if no url present
     $str .= ' '.$bibentry->getUrlLink();
   }
 
-  if (BIBTEXBROWSER_DOI_LINKS && $bibentry->hasField('doi')) {
-    $str .= ' [doi]';
+  if (BIBTEXBROWSER_DOI_LINKS) {
+    $str .= ' '.$bibentry->getDoiLink();
   }
 
-  // Google Scholar ID
-  if (BIBTEXBROWSER_GSID_LINKS && $bibentry->hasField('gsid')) {
-    $str .= ' [cites]';
+  if (BIBTEXBROWSER_GSID_LINKS) {
+    $str .= ' '.$bibentry->getGSLink();
   }
 
   $str .= '';
@@ -1690,6 +1717,7 @@ function bib2links_default(&$bibentry) {
 
 /** prints the header of a layouted HTML, depending on BIBTEXBROWSER_LAYOUT e.g.  */
 function print_header_layout() {
+  if (BIBTEXBROWSER_LAYOUT == 'list') return;
   echo '<' . get_HTML_tag_for_layout() . ' class="result">'."\n";
 }
 
@@ -1727,7 +1755,47 @@ function compare_bib_entry_by_mtime($a, $b)
  */
 function compare_bib_entry_by_year($a, $b)
 {
-  return -strcmp($a->getYear(),$b->getYear());
+  $yearA = (int) $a->getYear();
+  $yearB = (int) $b->getYear();
+
+  if ($yearA === 0) {
+    switch (strtolower($a->getYearRaw())) {
+      case Q_YEAR_INPRESS:
+        $yearA = PHP_INT_MAX + ORDER_YEAR_INPRESS;
+	break;
+      case Q_YEAR_ACCEPTED:
+        $yearA = PHP_INT_MAX + ORDER_YEAR_ACCEPTED;
+	break;
+      case Q_YEAR_SUBMITTED:
+        $yearA = PHP_INT_MAX + ORDER_YEAR_SUBMITTED;
+	break;
+      default:
+        $yearA = PHP_INT_MAX + ORDER_YEAR_OTHERNONINT;
+    }
+  }
+
+  if ($yearB === 0) {
+    switch (strtolower($b->getYearRaw())) {
+      case Q_YEAR_INPRESS:
+        $yearB = PHP_INT_MAX + ORDER_YEAR_INPRESS;
+	break;
+      case Q_YEAR_ACCEPTED:
+        $yearB = PHP_INT_MAX + ORDER_YEAR_ACCEPTED;
+	break;
+      case Q_YEAR_SUBMITTED:
+        $yearB = PHP_INT_MAX + ORDER_YEAR_SUBMITTED;
+	break;
+      default:
+        $yearB = PHP_INT_MAX + ORDER_YEAR_OTHERNONINT;
+    }
+  }
+
+  if ($yearA === $yearB)
+    return 0;
+  else if ($yearA > $yearB)
+    return -1;
+  else
+    return 1;
 }
 
 /** compares two instances of BibEntry by title
@@ -1744,6 +1812,26 @@ function compare_bib_entry_by_raw_abbrv($a, $b)
   return strcmp($a->getRawAbbrv(),$b->getRawAbbrv());
 }
 
+/** compares two instances of BibEntry by author or editor
+ */
+function compare_bib_entry_by_name($a, $b)
+{
+  if ($a->hasField(AUTHOR))
+    $namesA = $a->getAuthor();
+  else if ($a->hasField(EDITOR))
+    $namesA = $a->getField(EDITOR);
+  else
+    $namesA = __('No author');
+
+  if ($b->hasField(AUTHOR))
+    $namesB = $b->getAuthor();
+  else if ($b->hasField(EDITOR))
+    $namesB = $b->getField(EDITOR);
+  else
+    $namesB = __('No author');
+
+  return strcmp($namesA, $namesB);
+}
 
 /** compares two instances of BibEntry by month
  * @author Jan Geldmacher
@@ -2401,9 +2489,13 @@ else $page = 1;
     $index = 0;
     foreach ($items as $key => $item) {
       if ($index >= $startIndex && $index < $endIndex) {
- $href = makeHref(array($queryKey => $key));
- echo ''. $item ."\n";
- echo "
\n"; + if ($queryKey === 'year') { + $href = makeHref(array($queryKey => __($item))); + } else { + $href = makeHref(array($queryKey => $key)); + } + echo ''. $item ."\n"; + echo "
\n"; } $index++; } @@ -2423,6 +2515,12 @@ function query2title(&$query) { $v = preg_replace('/[$^]/','',$v); } if($k == Q_KEYS) { $v=json_encode(array_values($v)); } + if($k == Q_RANGE) { + foreach ($v as &$range) { + $range = $range[0].'-'.$range[1]; + } + $v = join($v, ','); + } $headers[$k] = __(ucwords($k)).': '.ucwords(htmlspecialchars($v)); } // special cases @@ -2541,6 +2639,14 @@ class SimpleDisplay { var $options = array(); + var $headingLevel = BIBTEXBROWSER_HTMLHEADINGLEVEL; + function incHeadingLevel ($by=1) { + $this->headingLevel += $by; + } + function decHeadingLevel ($by=1) { + $this->headingLevel -= $by; + } + function setDB(&$bibdatabase) { $this->setEntries($bibdatabase->bibdb); } @@ -2591,6 +2697,15 @@ class SimpleDisplay { echo 'Options: '.@implode(',',$this->options).'
'; } + if ($this->headingLevel == BIBTEXBROWSER_HTMLHEADINGLEVEL) { + echo "\n".''; + if (count($this->entries) == 1) { + echo count ($this->entries).' '.__('result'); + } else if (count($this->entries) != 0) { + echo count ($this->entries).' '.__('results'); + } + echo "\n"; + } print_header_layout(); $count = count($this->entries); @@ -2598,7 +2713,7 @@ class SimpleDisplay { $pred = NULL; foreach ($this->entries as $bib) { if ($this->changeSection($pred, $bib)) { - echo $this->sectionHeader($bib); + echo $this->sectionHeader($bib, $pred); } // by default, index are in decreasing order // so that when you add a publicaton recent , the indices of preceding publications don't change @@ -2621,7 +2736,7 @@ class SimpleDisplay { return $f($pred, $bib) != 0; } - function sectionHeader($bib) { + function sectionHeader($bib, $pred) { switch(BIBTEXBROWSER_LAYOUT) { case 'table': return ''."\n"; @@ -2629,6 +2744,15 @@ class SimpleDisplay { case 'definition': return '
'.$bib->getYear().'
'."\n"; break; + case 'list': + $string = ''; + if ($pred) $string .= "\n"; + if ($bib->hasField(YEAR)) + $year = $bib->getYear(); + else + $year = __('No date'); + return $string.'headingLevel.'>'.$year."headingLevel.">\n
    \n"; + break; default: return ''; } @@ -2652,7 +2776,7 @@ class NotFoundDisplay { function setTitle($title) { $this->title = $title; return $this; } function getTitle() { return @$this->title ; } function display() { - echo 'no result found, sorry.'; + echo ''.__('No results').''; } } /** displays the publication records sorted by publication types (as configured by constant BIBLIOGRAPHYSECTIONS). @@ -2698,11 +2822,45 @@ class AcademicDisplay { $this->db = createBibDataBase(); $this->db->bibdb = $this->entries; - foreach (_DefaultBibliographySections() as $section) { - $this->search2html($section['query'],$section['title']); + if (BIBTEXBROWSER_ACADEMIC_TOC != true) { + foreach (_DefaultBibliographySections() as $section) { + $this->search2html($section['query'],$section['title']); + } + } else { + $sections = array(); + echo "
      "; + + foreach (_DefaultBibliographySections() as $section) { + $entries = $this->db->multisearch($section['query']); + + if (count($entries)>0) { + $anchor = preg_replace("/[^a-zA-Z]/", "", $section['title']); + echo "
    • ".$section['title']." (".count($entries).")
    • "; + + $display = createBasicDisplay(); + $display->incHeadingLevel(); + $display->setEntries($entries); + $display->headerCSS = 'theader'; + + $sections[] = array ( + 'display' => $display, + 'anchor' => $anchor, + 'title' => $section['title'], + 'count' => count($entries) + ); + } + } + echo "
    "; + + foreach ($sections as $section) { + echo "\n"; + echo ""; + echo $section['title']." (".$section['count'].")"; + echo "\n", + $section['display']->display(); + } } } - } @@ -3147,10 +3305,31 @@ class BibDataBase { $result = array(); foreach ($this->bibdb as $bib) { if (!$bib->hasField("year")) continue; - $year = $bib->getField("year"); - $result[$year] = $year; + $year = strtolower($bib->getYearRaw()); + $yearInt = (int) $year; + + // Allow for ordering of non-string values ('in press' etc.) + switch ($year) { + case (string) $yearInt: // Sorry for this hacky type-casting + $key = $year; + break; + case Q_YEAR_INPRESS: + $key = PHP_INT_MAX + ORDER_YEAR_INPRESS; + break; + case Q_YEAR_ACCEPTED: + $key = PHP_INT_MAX + ORDER_YEAR_ACCEPTED; + break; + case Q_YEAR_SUBMITTED: + $key = PHP_INT_MAX + ORDER_YEAR_SUBMITTED; + break; + default: + $key = PHP_INT_MAX + ORDER_YEAR_OTHERNONINT; } - arsort($result); + + $result[$key] = $year; + } + + krsort($result); return $result; } @@ -3215,6 +3394,25 @@ class BibDataBase { break; } } + else if ($field==Q_RANGE) { + $year = $bib->getYear(); + $withinRange = false; + + foreach ($query[Q_RANGE] as $elements) { + if ($elements[0] === "" && $elements[1] === "") + $withinRange = true; + else if ($elements[0] === "" && $year <= $elements[1]) + $withinRange = true; + else if ($elements[1] === "" && $year >= $elements[0]) + $withinRange = true; + else if ($year <= $elements[1] && $year >= $elements[0]) { + $withinRange = true; + } + } + + if (!$withinRange) + $entryisselected = false; + } else { if (!$bib->hasPhrase($fragment, $field)) { $entryisselected = false; @@ -3854,6 +4052,68 @@ class Dispatcher { function type() { $this->query[Q_TYPE]= $_GET[Q_TYPE]; } + /** + * Allow the user to search for a range of dates + * + * The query string can comprise several elements separated by commas and + * optionally white-space. + * Each element can either be one number (a year) or two numbers + * (a range of years) separated by anything non-numerical. + * + */ + function range() { + $ranges = explode(',', $_GET[Q_RANGE]); + $result = array(); + + $nextYear = 1 + (int) date('Y'); + $nextYear2D = $nextYear % 100; + $thisCentury = $nextYear - $nextYear2D; + + foreach ($ranges as $range) { + $range = trim($range); + preg_match('/([0-9]*)([^0-9]*)([0-9]*)/', $range, $matches); + array_shift($matches); + + // If the number is empty, leave it empty - dont put it to 0 + // If the number is two-digit, assume it to be within the last century or next year + if ($matches[0] === "") { + $lower = ""; + } else if ($matches[0] < 100) { + if ($matches[0] > $nextYear2D) { + $lower = $thisCentury + $matches[0] - 100; + } else { + $lower = $thisCentury + $matches[0]; + } + } else { + $lower = $matches[0]; + } + + // If no separator to indicate a range of years was supplied, + // the upper and lower boundaries are the same. + // + // Otherwise, again: + // If the number is empty, leave it empty - dont put it to 0 + // If the number is two-digit, assume it to be within the last century or next year + if ($matches[1] === "") + $upper = $lower; + else { + if ($matches[2] === "") { + $upper = ""; + } else if ($matches[2] < 100) { + if ($matches[2] > $nextYear2D) { + $upper = $thisCentury + $matches[2] - 100; + } else { + $upper = $thisCentury + $matches[2]; + } + } else { + $upper = $matches[2]; + } + } + + $result[] = array($lower, $upper); + } + $this->query[Q_RANGE] = $result; + } function menu() { $menu = createMenuManager();
'.$bib->getYear().'