Skip to content
Extraits de code Groupes Projets
CiteProc.php 56,7 ko
Newer Older
<?php
/**
 *   CiteProc-PHP
 *
 *   Copyright (C) 2010 - 2011  Ron Jerome, all rights reserved
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

class citeproc {
  public    $bibliography;
  public    $citation;
  public    $style;
  protected $macros;
  private   $info;
  protected $locale;
  protected $style_locale;
  private   $mapper = NULL;

  function __construct($csl = NULL, $lang = 'en') {
    if ($csl) {
      $this->init($csl, $lang);
    }
  }

  function init($csl, $lang) {
    // define field values appropriate to your data in the csl_mapper class and un-comment the next line.
    $this->mapper = new csl_mapper();

    $csl_doc = new DOMDocument();

    if ($csl_doc->loadXML($csl)) {

      $style_nodes = $csl_doc->getElementsByTagName('style');
      if ($style_nodes) {
        foreach ($style_nodes as $style) {
          $this->style = new csl_style($style);
        }
      }

      $info_nodes = $csl_doc->getElementsByTagName('info');
      if ($info_nodes) {
        foreach ($info_nodes as $info) {
          $this->info = new csl_info($info);
        }
      }

      $this->locale = new csl_locale($lang);
      $this->locale->set_style_locale($csl_doc);


      $macro_nodes = $csl_doc->getElementsByTagName('macro');
      if ($macro_nodes) {
        $this->macros = new csl_macros($macro_nodes, $this);
      }

      $citation_nodes = $csl_doc->getElementsByTagName('citation');
      foreach ($citation_nodes as $citation) {
        $this->citation = new csl_citation($citation, $this);
      }

      $bibliography_nodes = $csl_doc->getElementsByTagName('bibliography');
      foreach ($bibliography_nodes as $bibliography) {
        $this->bibliography = new csl_bibliography($bibliography, $this);
      }
    }
  }
  function render($data, $mode = NULL) {
    $text = '';
    switch ($mode) {
      case 'citation':
        $text .=  (isset($this->citation))? $this->citation->render($data) : '';
        break;
      case  'bibliography':
      default:
        $text .=  (isset($this->bibliography))? $this->bibliography->render($data) : '';
        break;
    }
    return $text;
  }

  function render_macro($name, $data, $mode) {
    return $this->macros->render_macro($name, $data, $mode);
  }

  function get_locale($type, $arg1, $arg2 = NULL, $arg3 = NULL) {
    return $this->locale->get_locale($type, $arg1, $arg2, $arg3);
  }

  function map_field($field) {
    if ($this->mapper) {
      return $this->mapper->map_field($field);
    }
    return ($field);
  }
  function map_type($field) {
    if ($this->mapper) {
      return $this->mapper->map_type($field);
    }
    return ($field);
  }
}

class csl_factory {
  public static function create($dom_node, $citeproc = NULL) {
    $class_name =  'csl_' . str_replace('-', '_', $dom_node->nodeName);
    if (class_exists($class_name)) {
      return new $class_name($dom_node, $citeproc);
    }
    else {
      return NULL;
    }
  }
}

class csl_collection {
  protected  $elements = array();

  function add_element($elem) {
    if (isset($elem)) $this->elements[] = $elem;
  }

  function render($data, $mode = NULL) {}

  function format($text) {return $text;}

}

class csl_element extends csl_collection {
  protected $attributes = array();
  protected $citeproc;

  function __construct($dom_node = NULL, $citeproc = NULL) {

    $this->citeproc   = &$citeproc;
    $this->set_attributes($dom_node);
    $this->init($dom_node, $citeproc);

  }

  function init($dom_node, $citeproc) {
    if (!$dom_node) return;

    foreach ($dom_node->childNodes as $node) {
      if ($node->nodeType == 1) {
        $this->add_element(csl_factory::create($node, $citeproc));
      }
    }
  }

  function __set($name, $value) {
    $this->attributes[$name] = $value;
  }

  function __isset($name) {
    return isset($this->attributes[$name]);
  }

  function __unset($name) {
    unset($this->attributes[$name]);
  }

  function &__get($name = NULL) {
    $null = NULL;
    if (array_key_exists($name, $this->attributes)) {
      return $this->attributes[$name];
    }
    return $null;

  }

  function set_attributes($dom_node) {
    $att = array();
    $element_name = $dom_node->nodeName;
    if (isset($dom_node->attributes->length)) {
      for ($i=0; $i < $dom_node->attributes->length; $i++) {
        $value = $dom_node->attributes->item($i)->value;
        $name  = str_replace(' ', '_', $dom_node->attributes->item($i)->name);
        if ($name == 'type' ) {
          $value = $this->citeproc->map_type($value);
        }

        if (($name == 'variable'  || $name == 'is-numeric') && $element_name != 'label') {
          $value = $this->citeproc->map_field($value);
        }
        $this->{$name}  = $value;
      }
    }
  }

  function get_attributes() {
      return $this->attributes;
  }

  function get_hier_attributes() {
    $hier_attr = array();
    $hier_names = array('and', 'delimiter-precedes-last', 'et-al-min', 'et-al-use-first',
                        'et-al-subsequent-min', 'et-al-subsequent-use-first', 'initialize-with',
                        'name-as-sort-order', 'sort-separator', 'name-form', 'name-delimiter',
                        'names-delimiter');
    foreach ($hier_names as $name) {
      if (isset($this->attributes[$name])) {
        $hier_attr[$name] = $this->attributes[$name];
      }
    }
    return $hier_attr;
  }

  function name($name = NULL) {
    if ($name) {
      $this->name = $name;
    }
    else {
      return str_replace(' ', '_', $this->name);
    }
  }

}

class csl_rendering_element extends csl_element {

  function render($data, $mode = NULL) {
    $text = '';
    $text_parts = array();

    $delim = isset($this->delimiter) ? $this->delimiter : '';
    foreach ($this->elements as $element) {
      $text_parts[] = $element->render($data, $mode);
    }
    $text = implode($delim, $text_parts); // insert the delimiter if supplied.

    return $this->format($text);
  }

}

class csl_format extends csl_rendering_element {
  protected $no_op;
  protected $format;

  function __construct($dom_node = NULL, $citeproc = NULL) {
    parent::__construct($dom_node, $citeproc);
    $this->init_formatting();
  }

  function init_formatting() {
    $this->no_op = TRUE;
    $this->format  = '';
    if (isset($this->quotes)) {
      $this->quotes = array();
      $this->quotes['punctuation-in-quote'] = $this->citeproc->get_locale('style_option', 'punctuation-in-quote');
      $this->quotes['open-quote'] = $this->citeproc->get_locale('term', 'open-quote');
      $this->quotes['close-quote'] = $this->citeproc->get_locale('term', 'close-quote');
      $this->quotes['open-inner-quote'] = $this->citeproc->get_locale('term', 'open-inner-quote');
      $this->quotes['close-inner-quote'] = $this->citeproc->get_locale('term', 'close-inner-quote');
      $this->no_op = FALSE;
    }
    if (isset($this->{'prefix'})) $this->no_op = FALSE;
    if (isset($this->{'suffix'})) $this->no_op = FALSE;
    if (isset($this->{'display'})) $this->no_op = FALSE;

    $this->format .= (isset($this->{'font-style'}))      ? 'font-style: ' . $this->{'font-style'} . ';' : '';
    $this->format .= (isset($this->{'font-family'}))     ? 'font-family: ' . $this->{'font-family'} . ';' : '';
    $this->format .= (isset($this->{'font-weight'}))     ? 'font-weight: ' . $this->{'font-weight'} . ';' : '';
    $this->format .= (isset($this->{'font-variant'}))    ? 'font-variant: ' . $this->{'font-variant'} . ';' : '';
    $this->format .= (isset($this->{'text-decoration'})) ? 'text-decoration: ' . $this->{'text-decoration'} . ';' : '';
    $this->format .= (isset($this->{'vertical-align'}))  ? 'vertical-align: ' . $this->{'vertical-align'} . ';' : '';
    // $this->format .= (isset($this->{'display'})  && $this->{'display'}  == 'indent')  ? 'padding-left: 25px;' : '';

    if (isset($this->{'text-case'}) ||
        !empty($this->format) ||
        !empty($this->span_class) ||
        !empty($this->div_class)) {
          $this->no_op = FALSE;
        }

  }

  function format($text) {

    if (empty($text) || $this->no_op) return $text;
    if (isset($this->{'text-case'})) {
      switch ($this->{'text-case'}) {
        case 'uppercase':
          $text = mb_strtoupper($text);
          break;
        case 'lowercase':
          $text = mb_strtolower($text);
          break;
        case 'capitalize-all':
        case 'title':
          $text = mb_convert_case($text, MB_CASE_TITLE);
          break;
        case 'capitalize-first':
          $chr1 = mb_strtoupper(mb_substr($text, 0, 1));
          $text = $chr1 . mb_substr($text, 1);
          break;
      }
    }

    $prefix = $this->prefix ;
    if (isset($this->quotes['open-quote'])) {
        $prefix .= $this->quotes['open-quote'];
    }
    if (isset($this->quotes['close-quote']) && !empty($suffix) && isset($this->quotes['punctuation-in-quote'])) {
      if (strpos($suffix, '.') !== FALSE || strpos($suffix, ',') !== FALSE) {
        $suffix =  $suffix . $this->quotes['close-quote'];
      }
    }
    elseif (isset($this->quotes['close-quote'])) {
      $suffix =  $this->quotes['close-quote'] . $suffix;
    }
    if (!empty($suffix)) { // gaurd against repeaded suffixes...
      $no_tags = strip_tags($text);
      if (strlen($no_tags) && ($no_tags[(strlen($no_tags) - 1)] == $suffix[0]) ) {
        $suffix = substr($suffix, 1);
      }
    }

    if (!empty($this->format) || !empty($this->span_class)) {
      $style = (!empty($this->format)) ? 'style="' . $this->format . '" ' : '';
      $class = (!empty($this->span_class)) ? 'class="' . $this->span_class . '"' : '';
      $text = '<span ' . $class . $style . '>' . $text . '</span>';
    }
    $div_class = $div_style = '';
    if (!empty($this->div_class)) {
       $div_class = (!empty($this->div_class)) ? 'class="' . $this->div_class . '"' : '';
    }
    if ($this->display  == 'indent') {
       $div_style =  'style="text-indent: 0px; padding-left: 45px;"';
    }
    if ($div_class || $div_style) {
      return '<div ' . $div_class . $div_style . '>' . $prefix . $text . $suffix . '</div>';
    }

    return $prefix . $text . $suffix;
  }

}

class csl_info {
  public $title;
  public $id;
  public $authors = array();
  public $links = array();

  function __construct($dom_node) {
    $name = array();
    foreach ($dom_node->childNodes as $node) {
      if ($node->nodeType == 1) {
        switch ($node->nodeName) {
          case 'author':
          case 'contributor':
            foreach ($node->childNodes as $authnode) {
              if ($node->nodeType == 1) {
                $name[$authnode->nodeName] = $authnode->nodeValue;
              }
            }
            $this->authors[] = $name;
            break;
          case 'link':
            foreach ($node->attributes as $attribute) {
              $this->links[] = $attribute->value;
            }
            break;
          default:
            $this->{$node->nodeName} = $node->nodeValue;
        }
      }
    }

  }
}

class csl_terms {

}

class csl_name extends csl_format {
  private $name_parts = array();
  private $attr_init = FALSE;

  function __construct($dom_node, $citeproc = NULL) {

    $tags = $dom_node->getElementsByTagName('name-part');
    if ($tags) {
      foreach ($tags as $tag) {
        $name_part = $tag->getAttribute('name');
        $tag->removeAttribute('name');
        for ($i=0; $i < $tag->attributes->length; $i++) {
          $value = $tag->attributes->item($i)->value;
          $name  = str_replace(' ', '_', $tag->attributes->item($i)->name);
          $this->name_parts[$name_part][$name]  = $value;
        }
      }
    }

    parent::__construct($dom_node, $citeproc);
  }

  function init_formatting() {
    $this->no_op = array();
    $this->format = array();
    $this->base = $this->get_attributes();
    $this->format['base']  = '';
    $this->format['family']  = '';
    $this->format['given']  = '';
    $this->no_op['base'] = TRUE;
    $this->no_op['family'] = TRUE;
    $this->no_op['given'] = TRUE;

    if (isset($this->prefix)) {
      $this->no_op['base'] = FALSE;
    }
    if (isset($this->suffix)) {
      $this->no_op['base'] = FALSE;
    }
    $this->init_format($this->base);


    if (!empty($this->name_parts)) {
      foreach ($this->name_parts as $name => $formatting) {
        $this->init_format($formatting, $name);
      }
    }
  }

  function init_attrs($mode) {
 //   $and = $this->get_attributes('and');
    if (isset($this->and)) {
      if ($this->and == 'text') {
        $this->and = $this->citeproc->get_locale('term', 'and');
       }
       elseif ($this->and == 'symbol') {
        $this->and = '&';
       }
    }
    if (isset($this->citeproc)) {
      $style_attrs = $this->citeproc->style->get_hier_attributes();
      $mode_attrs = $this->citeproc->{$mode}->get_hier_attributes();
      $this->attributes = array_merge($style_attrs, $mode_attrs, $this->attributes);
    }
    if (!isset($this->delimiter)) {
      $this->delimiter =  $this->{'name-delimiter'} ;
    }
    if (!isset($this->alnum)) {
      list($this->alnum, $this->alpha, $this->cntrl, $this->dash,
      $this->digit, $this->graph, $this->lower, $this->print,
      $this->punct, $this->space, $this->upper, $this->word,
      $this->patternModifiers) = $this->get_regex_patterns();
    }
    $this->dpl = $this->{'delimiter-precedes-last'};
    $this->sort_separator = isset($this->{'sort-separator'}) ? $this->{'sort-separator'} : ', ';
    $this->form = isset($this->form) ? $this->form : 'long';
    $this->attr_init = $mode;
  }

  function init_format($attribs, $part = 'base') {
    if (!isset($this->{$part})) {
      $this->{$part} = array();
    }
    if (isset($attribs['quotes'])) {
      $this->{$part}['open-quote'] = $this->citeproc->get_locale('term', 'open-quote');
      $this->{$part}['close-quote'] = $this->citeproc->get_locale('term', 'close-quote');
      $this->{$part}['open-inner-quote'] = $this->citeproc->get_locale('term', 'open-inner-quote');
      $this->{$part}['close-inner-quote'] = $this->citeproc->get_locale('term', 'close-inner-quote');
      $this->no_op[$part] = FALSE;
    }

    if (isset($attribs['prefix']))  $this->{$part}['prefix'] = $attribs['prefix'];
    if (isset($attribs['suffix']))  $this->{$part}['suffix'] = $attribs['suffix'];

    $this->format[$part] .= (isset($attribs['font-style']))      ? 'font-style: ' . $attribs['font-style'] . ';' : '';
    $this->format[$part] .= (isset($attribs['font-family']))     ? 'font-family: ' . $attribs['font-family'] . ';' : '';
    $this->format[$part] .= (isset($attribs['font-weight']))     ? 'font-weight: ' . $attribs['font-weight'] . ';' : '';
    $this->format[$part] .= (isset($attribs['font-variant']))    ? 'font-variant: ' . $attribs['font-variant'] . ';' : '';
    $this->format[$part] .= (isset($attribs['text-decoration'])) ? 'text-decoration: ' . $attribs['text-decoration'] . ';' : '';
    $this->format[$part] .= (isset($attribs['vertical-align']))  ? 'vertical-align: ' . $attribs['vertical-align'] . ';' : '';

    if (isset($attribs['text-case']))  {
      $this->no_op[$part] = FALSE;
      $this->{$part}['text-case'] = $attribs['text-case'];
    }
    if (!empty($this->format[$part])) $this->no_op[$part] = FALSE;

  }

  function format($text, $part = 'base') {

    if (empty($text) || $this->no_op[$part]) return $text;
    if (isset($this->{$part}['text-case'])) {
      switch ($this->{$part}['text-case']) {
        case 'uppercase':
          $text = mb_strtoupper($text);
          break;
        case 'lowercase':
          $text = mb_strtolower($text);
          break;
        case 'capitalize-all':
          $text = mb_convert_case($text, MB_CASE_TITLE);
          break;
        case 'capitalize-first':
          $chr1 = mb_strtoupper(mb_substr($text, 0, 1));
          $text = $chr1 . mb_substr($text, 1);
          break;
      }
    }
    $open_quote = isset($this->{$part}['open-quote']) ? $this->{$part}['open-quote'] : '';
    $close_quote = isset($this->{$part}['close-quote']) ? $this->{$part}['close-quote'] : '';
    $prefix =  isset($this->{$part}['prefix']) ? $this->{$part}['prefix'] : '';
    $suffix = isset($this->{$part}['suffix']) ? $this->{$part}['suffix'] : '';
    if ($text[(strlen($text) -1)] == $suffix) unset($suffix);
    if (!empty($this->format[$part])) {
      $text = '<span style="' . $this->format[$part] . '">' . $text . '</span>';
    }
    return $prefix . $open_quote . $text . $close_quote . $suffix;
  }

  function render($names, $mode = NULL) {
    $text = '';
    $authors = array();
    $count = 0;
    $auth_count = 0;
    $et_al_triggered = FALSE;
    $initialize_with = $this->{'initialize-with'};

    if (!$this->attr_init || $this->attr_init != $mode) $this->init_attrs($mode);

    foreach ($names as $rank => $name) {
      $count++;
      //$given = (!empty($name->firstname)) ? $name->firstname : '';
      if (!empty($name->given) && isset($initialize_with)) {
          $name->given = preg_replace("/([$this->upper])[$this->lower]+/$this->patternModifiers", '\\1', $name->given);
          $name->given = preg_replace("/(?<=[-$this->upper]) +(?=[-$this->upper])/$this->patternModifiers", "", $name->given);
          if(isset($name->initials)) {
            $name->initials = $name->given .  $name->initials;
          }
          $name->initials = $name->given;
      }
      if (isset($name->initials)) {
        // within initials, remove any dots:
        $name->initials = preg_replace("/([$this->upper])\.+/$this->patternModifiers", "\\1", $name->initials);
        // within initials, remove any spaces *between* initials:
        $name->initials = preg_replace("/(?<=[-$this->upper]) +(?=[-$this->upper])/$this->patternModifiers", "", $name->initials);
        if ($this->citeproc->style->{'initialize-with-hyphen'} == 'false') {
          $name->initials = preg_replace("/-/", '', $name->initials);
        }
        // within initials, add a space after a hyphen, but only if ...
        if (preg_match("/ $/", $initialize_with)) {// ... the delimiter that separates initials ends with a space
          $name->initials = preg_replace("/-(?=[$this->upper])/$this->patternModifiers", "- ", $name->initials);
        }
        // then, separate initials with the specified delimiter:
        $name->initials = preg_replace("/([$this->upper])(?=[^$this->lower]+|$)/$this->patternModifiers", "\\1$initialize_with", $name->initials);

        //      $shortenInitials = (isset($options['numberOfInitialsToKeep'])) ? $options['numberOfInitialsToKeep'] : FALSE;
        //      if ($shortenInitials) $given = drupal_substr($given, 0, $shortenInitials);

        if (isset($initialize_with) ) {
          $name->given = $name->initials;
        }
        elseif(!empty($name->given)) {
          $name->given = $name->given.' '.$name->initials;
        }
        elseif(empty($name->given)) {
          $name->given = $name->initials;
        }
      }

      $ndp = (isset($name->{'non-dropping-particle'})) ? $name->{'non-dropping-particle'} . ' ' : '';
      $suffix = (isset($name->{'suffix'})) ? ' ' . $name->{'suffix'}  : '';

      if (isset($name->given)) {
        $given = $this->format($name->given, 'given');
      }
      if(isset($name->family)) {
        $name->family = $this->format($name->family, 'family');
        if ($this->form == 'short') {
          $text = $ndp . $name->family;
        }
        else {
          switch ($this->{'name-as-sort-order'}) {
            case 'first':
            case 'all':
              $text = $ndp . $name->family . $this->sort_separator . $given;
              break;
            default:
              $text = $given .' '. $ndp . $name->family . $suffix;
          }
        }
        $authors[] = trim($this->format($text));
      }
      if (isset($this->{'et-al-min'}) && $count >= $this->{'et-al-min'}) break;
    }
    if (isset($this->{'et-al-min'}) &&
      $count >= $this->{'et-al-min'} &&
      isset($this->{'et-al-use-first'}) &&
      $count >= $this->{'et-al-use-first'} &&
      count($names) >  $this->{'et-al-use-first'}) {
      if ($this->{'et-al-use-first'} < $this->{'et-al-min'}) {
        for ($i = $this->{'et-al-use-first'}; $i < $count; $i++) {
          unset($authors[$i]);
        }
      }
      if ($this->etal) {
        $authors[] = $this->etal->render();
      }
      else {
        $authors[] = $this->citeproc->get_locale('term', 'et-al');
      }
      $et_al_triggered = TRUE;
    }

    if (!empty($authors) && !$et_al_triggered) {
      $auth_count = count($authors);
      if (isset($this->and) && $auth_count > 1) {
        $authors[$auth_count-1] = $this->and . ' ' . $authors[$auth_count-1]; //stick an "and" in front of the last author if "and" is defined
      }
    }
    $text = implode($this->delimiter, $authors);

   if ($this->form == 'count') {
     if (!$et_al_triggered) {
       return (int)count($authors);
     }
     else {
       return (int)(count($authors) - 1);
     }
   }
    // strip out the last delimiter if not required
    if (isset($this->and) && $auth_count > 1) {
      $last_delim =  strrpos($text, $this->delimiter . $this->and);
      switch ($this->dpl) { //dpl == delimiter proceeds last
        case 'always':
          return $text;
          break;
        case 'never':
          return substr_replace($text, ' ', $last_delim, strlen($this->delimiter));
          break;
        case 'contextual':
        default:
          if ($auth_count < 3) {
            return substr_replace($text, ' ', $last_delim, strlen($this->delimiter));
          }
      }
    }
   return  $text ;
  }

  function get_regex_patterns() {
    // Checks if PCRE is compiled with UTF-8 and Unicode support
    if (!@preg_match('/\pL/u', 'a')) {
      // probably a broken PCRE library
      return $this->get_latin1_regex();
    }
    else {
      // Unicode safe filter for the value
      return $this->get_utf8_regex();
    }
  }

  function get_latin1_regex() {
    $alnum = "[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß";
    // Matches ISO-8859-1 letters:
    $alpha = "[:alpha:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß";
    // Matches ISO-8859-1 control characters:
    $cntrl = "[:cntrl:]";
    // Matches ISO-8859-1 dashes & hyphens:
    $dash = "-–";
    // Matches ISO-8859-1 digits:
    $digit = "[\d]";
    // Matches ISO-8859-1 printing characters (excluding space):
    $graph = "[:graph:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß";
    // Matches ISO-8859-1 lower case letters:
    $lower = "[:lower:]äåáàâãçéèêëñöøóòôõüúùûíìîïæÿß";
    // Matches ISO-8859-1 printing characters (including space):
    $print = "[:print:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß";
    // Matches ISO-8859-1 punctuation:
    $punct = "[:punct:]";
    // Matches ISO-8859-1 whitespace (separating characters with no visual representation):
    $space = "[\s]";
    // Matches ISO-8859-1 upper case letters:
    $upper = "[:upper:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆ";
    // Matches ISO-8859-1 "word" characters:
    $word = "_[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß";
    // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables:
    // More info: <http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php>
    $patternModifiers = "";

    return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower,
                 $print, $punct, $space, $upper, $word, $patternModifiers);

  }
  function get_utf8_regex() {
    // Matches Unicode letters & digits:
    $alnum = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:alnum:]"
    // Matches Unicode letters:
    $alpha = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}"; // Unicode-aware equivalent of "[:alpha:]"
    // Matches Unicode control codes & characters not in other categories:
    $cntrl = "\p{C}"; // Unicode-aware equivalent of "[:cntrl:]"
    // Matches Unicode dashes & hyphens:
    $dash = "\p{Pd}";
    // Matches Unicode digits:
    $digit = "\p{Nd}"; // Unicode-aware equivalent of "[:digit:]"
    // Matches Unicode printing characters (excluding space):
    $graph = "^\p{C}\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:graph:]"
    // Matches Unicode lower case letters:
    $lower = "\p{Ll}\p{M}"; // Unicode-aware equivalent of "[:lower:]"
    // Matches Unicode printing characters (including space):
    $print = "\P{C}"; // same as "^\p{C}", Unicode-aware equivalent of "[:print:]"
    // Matches Unicode punctuation (printing characters excluding letters & digits):
    $punct = "\p{P}"; // Unicode-aware equivalent of "[:punct:]"
    // Matches Unicode whitespace (separating characters with no visual representation):
    $space = "\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:space:]"
    // Matches Unicode upper case letters:
    $upper = "\p{Lu}\p{Lt}"; // Unicode-aware equivalent of "[:upper:]"
    // Matches Unicode "word" characters:
    $word = "_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:word:]" (or "[:alnum:]" plus "_")
    // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables:
    // More info: <http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php>
    $patternModifiers = "u"; // the "u" (PCRE_UTF8) pattern modifier causes PHP/PCRE to treat pattern strings as UTF-8
    return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower,
                 $print, $punct, $space, $upper, $word, $patternModifiers);
  }

}

class csl_names extends csl_format {
  private $substitutes;

  function init_formatting() {
 //   $this->span_class = 'authors';
    parent::init_formatting();
  }

  function init($dom_node, $citeproc) {
    $etal = '';
    $tag = $dom_node->getElementsByTagName('substitute')->item(0);
    if ($tag) {
      $this->substitutes = csl_factory::create($tag, $citeproc);
      $dom_node->removeChild($tag);
    }

    $tag = $dom_node->getElementsByTagName('et-al')->item(0);
    if ($tag) {
      $etal = csl_factory::create($tag, $citeproc);
      $dom_node->removeChild($tag);
    }

    $var = $dom_node->getAttribute('variable');
    foreach ($dom_node->childNodes as $node) {
      if ($node->nodeType == 1) {
        $element = csl_factory::create($node, $citeproc);
        if (($element instanceof csl_label)) $element->variable = $var;
        if (($element instanceof csl_name) && $etal) {
          $element->etal = $etal;
        }
        $this->add_element($element);
      }
    }
  }

  function render($data, $mode = NULL) {
    $matches = 0;
    $variable_parts = array();
    if (!isset($this->delimiter)) {
      $style_delimiter = $this->citeproc->style->{'names-delimiter'};
      $mode_delimiter = $this->citeproc->{$mode}->{'names-delimiter'};
      $this->delimiter = (isset($mode_delimiter)) ? $mode_delimiter : (isset($style_delimiter) ? $style_delimiter : '');
    }

    $variables  = explode(' ', $this->variable);
    foreach ($variables as $var) {
      if (isset($data->{$var}) && (!empty($data->{$var}))) {
        $matches++;
        break;
      }
    }

    if (!$matches) { // we don't have any primary suspects, so lets check the substitutes...
      if (isset($this->substitutes)) {
        foreach ($this->substitutes->elements as $element) {
          if (($element instanceof csl_names)) { //test to see if any of the other names variables has content
            $variables  = explode(' ', $element->variable);
            foreach ($variables as $var) {
              //list($contributor, $type) = explode(':', $var);
              if (isset($data->{$var}) ) {
                $matches++;
                break;
              }
            }
          }
          else { // if it's not a "names" element, just render it
            return $element->render($data, $mode);
          }
        }
      }
    }

    foreach ($variables as $var) {
      $text = '';
      if (!empty($data->{$var})) {
        foreach ($this->elements as $element) {
            if(is_a($element, 'csl_label')) {
              $data->{$var}['variable'] = $var;
            }
           $text .= $element->render($data->{$var}, $mode);
        }
      }
      if (!empty($text)) $variable_parts[] = $text;
    }

    if (!empty($variable_parts)) {
      $text = implode($this->delimiter, $variable_parts);
      return $this->format($text);
    }

    return ;
  }
}

class csl_date extends csl_format {

  function init($dom_node, $citeproc) {
    $locale_elements = array();

    if ($form = $this->form) {
      $local_date = $this->citeproc->get_locale('date_options', $form);
      $dom_elem = dom_import_simplexml($local_date[0]);
      if ($dom_elem) {
        foreach ($dom_elem->childNodes as $node) {
          if ($node->nodeType == 1) {
            $locale_elements[] = csl_factory::create($node, $citeproc);
          }
        }
      }
      foreach ($dom_node->childNodes as $node) {
        if ($node->nodeType == 1) {
          $element = csl_factory::create($node, $citeproc);

          foreach ($locale_elements as $key => $locale_element) {
            if ($locale_element->name == $element->name) {
              $locale_elements[$key]->attributes = array_merge($locale_element->attributes, $element->attributes);
              $locale_elements[$key]->format =  $element->format;
              break;
            }

            else {
              $locale_elements[] = $element;
            }
          }
        }
      }
      if ($date_parts = $this->{'date-parts'}) {
        $parts = explode('-', $date_parts);
        foreach ($locale_elements as $key => $element) {
          if (array_search($element->name, $parts) === FALSE) {
            unset($locale_elements[$key]);
          }
        }
        if (count($locale_elements) != count($parts)) {
          foreach ($parts as $part) {
            $element = new csl_date_part();
            $element->name = $part;
            $locale_elements[] = $element;
          }
        }
        // now re-order the elements
        foreach ($parts as $part) {
          foreach ($locale_elements as $key => $element)
          if ($element->name == $part) {
            $this->elements[] = $element;
            unset($locale_elements[$key]);
          }
        }

      }
      else {
        $this->elements = $locale_elements;
      }
    }
    else {
      parent::init($dom_node, $citeproc);
    }


  }

  function render($data, $mode = NULL) {
    $date_parts = array();
    $date = '';
    $text = '';

    if (($var = $this->variable) && isset($data->{$var})) {
      $date = $data->{$var}->{'date-parts'}[0];
      foreach ($this->elements as $element) {
        $date_parts[] = $element->render($date, $mode);
      }
      $text = implode('', $date_parts);
    }else {
      $text = $this->citeproc->get_locale('term', 'no date');
    }

    return $this->format($text);
  }
}

class csl_date_part extends csl_format {

  function render($date, $mode = NULL) {
    $text = '';

    switch ($this->name) {
      case 'year':
        $text = (isset($date[0])) ? $date[0] : '';
        if ($text > 0 && $text < 500) {
          $text = $text . $this->citeproc->get_locale('term', 'ad');
        }
        elseif ($text < 0) {
          $text = $text * -1;
          $text = $text . $this->citeproc->get_locale('term', 'bc');
        }
        //return ((isset($this->prefix))? $this->prefix : '') . $date[0] . ((isset($this->suffix))? $this->suffix : '');
        break;
      case 'month':
        $text = (isset($date[1])) ? $date[1] : '';
        if (empty($text) || $text < 1 || $text > 12) return;
       // $form = $this->form;
        switch ($this->form) {
          case 'numeric': break;
          case 'numeric-leading-zeros':
            if ($text < 10) {
              $text = '0' . $text;
              break;
            }
            break;
          case 'short':
            $month = 'month-' . sprintf('%02d', $text);
            $text = $this->citeproc->get_locale('term', $month, 'short');
            break;
          default:
            $month = 'month-' . sprintf('%02d', $text);
            $text = $this->citeproc->get_locale('term', $month);
            break;
        }
        break;
      case 'day':
        $text = (isset($date[2])) ? $date[2] : '';
        break;
    }

    return $this->format($text);
  }
}

class csl_number extends csl_format {

  function render($data, $mode = NULL) {
    $var = $this->variable;

    if (!$var || empty($data->$var)) return;

 //   $form = $this->form;

    switch ($this->form) {
      case 'ordinal':
        $text = $this->ordinal($data->$var);
        break;
      case 'long-ordinal':
        $text = $this->long_ordinal($data->$var);
        break;
      case 'roman':
        $text = $this->roman($data->$var);
        break;
      case 'numeric':
      default:
        $text = $data->$var;
        break;
    }
    return $this->format($text);
  }

  function ordinal($num) {
    if ( ($num/10)%10 == 1) {
      $num .= $this->citeproc->get_locale('term', 'ordinal-04');
    }
    elseif ( $num%10 == 1) {
      $num .= $this->citeproc->get_locale('term', 'ordinal-01');