<?php
/*
homepage: http://arc.semsol.org/
license:  http://arc.semsol.org/license

class:    ARC2 SPARQL-enhanced Turtle Parser
author:   Benjamin Nowack
version:  2008-02-08 (Fix: large prologs were not supported)
*/

ARC2::inc('RDFParser');

class ARC2_TurtleParser extends ARC2_RDFParser {

  function __construct($a = '', &$caller) {
    parent::__construct($a, $caller);
  }
  
  function ARC2_TurtleParser($a = '', &$caller) {
    $this->__construct($a, $caller);
  }

  function __init() {/* reader */
    parent::__init();
    $this->state = 0;
    $this->xml = 'http://www.w3.org/XML/1998/namespace';
    $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
    $this->xsd = 'http://www.w3.org/2001/XMLSchema#';
    $this->nsp = array($this->xml => 'xml', $this->rdf => 'rdf', $this->xsd => 'xsd');
    $this->unparsed_code = '';
  }
  
  /*  */

  function x($re, $v, $options = 'si') {
    while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $v, $m)) {/* comment removal */
      $v = $m[2];
    }
    $this->unparsed_code = (strlen($this->unparsed_code) > strlen($v)) ? $v : $this->unparsed_code;
    return ARC2::x($re, $v, $options);
  }

  function createBnodeID(){
    $this->bnode_id++;
    return '_:' . $this->bnode_prefix . $this->bnode_id;
  }

  /*  */
  
  function addT($t) {
    if ($this->skip_dupes) {
      $h = md5(print_r($t, 1));
      if (!isset($this->added_triples[$h])) {
        $this->triples[$this->t_count] = $t;
        $this->t_count++;
        $this->added_triples[$h] = true;
      }
    }
    else {
      $this->triples[$this->t_count] = $t;
      $this->t_count++;
    }
  }

  /*  */

  function getTriples() {
    return $this->v('triples', array());
  }
  
  function countTriples() {
    return $this->t_count;
  }
  
  /*  */
  
  function getUnparsedCode() {
    return $this->v('unparsed_code', '');
  }
  
  /*  */

  function parse($path, $data = '', $iso_fallback = false) {
    /* reader */
    if (!$this->v('reader')) {
      ARC2::inc('Reader');
      $this->reader = & new ARC2_Reader($this->a, $this);
    }
    $this->reader->setAcceptHeader('Accept: application/x-turtle; q=0.9, */*; q=0.1');
    $this->reader->activate($path, $data);
    $this->base = $this->v1('base', $this->reader->base, $this->a);
    $this->prefixes = array(
      'rdf:' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
      'rdfs:' => 'http://www.w3.org/2000/01/rdf-schema#',
      'owl:' => 'http://www.w3.org/2002/07/owl#',
      'xsd:' => 'http://www.w3.org/2001/XMLSchema#',
    );
    $this->r = array('vars' => array());
    /* parse */
    $buffer = '';
    $more_triples = array();
    $sub_v = '';
    $sub_v2 = '';
    $loops = 0;
    $prologue_done = 0;
    while ($d = $this->reader->readStream(0)) {
      $buffer .= $d;
      $sub_v = $buffer;
      do {
        $proceed = 0;
        if (!$prologue_done) {
          $proceed = 1;
          if ((list($sub_r, $sub_v) = $this->xPrologue($sub_v)) && $sub_r) {
            $loops = 0;
            $sub_v .= $this->reader->readStream(0, 128);
            if ($this->x("\@?(base|prefix)", $sub_v)) {/* more prologue to come, use outer loop */
              $proceed = 0;
            }
          }
          else {
            $prologue_done = 1;
          }
        }
        if ($prologue_done && (list($sub_r, $sub_v, $more_triples, $sub_v2) = $this->xTriplesBlock($sub_v)) && is_array($sub_r)) {
          $proceed = 1;
          $loops = 0;
          foreach ($sub_r as $t) {
            $this->addT($t);
          }
        }
      } while ($proceed);
      $loops++;
      $buffer = $sub_v;
      if ($loops > 100) {/* most probably a parser or code bug, might also be a huge object value, though */
        $this->addError('too many loops: ' . $loops);
        break;
      }
    }
    foreach ($more_triples as $t) {
      $this->addT($t);
    }
    $sub_v = count($more_triples) ? $sub_v2 : $sub_v;
    $buffer = $sub_v;
    $this->reader->closeStream();
    return $this->done();
  }

  function xPrologue($v) {
    $r = 0;
    if (!$this->t_count) {
      if ((list($sub_r, $v) = $this->xBaseDecl($v)) && $sub_r) {
        $this->base = $sub_r;
        $r = 1;
      }
      while ((list($sub_r, $v) = $this->xPrefixDecl($v)) && $sub_r) {
        $this->prefixes[$sub_r['prefix']] = $sub_r['iri'];
        $r = 1;
      }
    }
    return array($r, $v);
  }
  
  /* 3 */

  function xBaseDecl($v) {
    if ($r = $this->x("\@?base\s+", $v)) {
      if ((list($r, $sub_v) = $this->xIRI_REF($r[1])) && $r) {
        if ($sub_r = $this->x('\.', $sub_v)) {
          $sub_v = $sub_r[1];
        }
        return array($r, $sub_v);
      }
    }
    return array(0, $v);
  }
  
  /* 4 */
  
  function xPrefixDecl($v) {
    if ($r = $this->x("\@?prefix\s+", $v)) {
      if ((list($r, $sub_v) = $this->xPNAME_NS($r[1])) && $r) {
        $prefix = $r;
        if((list($r, $sub_v) = $this->xIRI_REF($sub_v)) && $r) {
          $iri = $this->calcUri($r, $this->base);
          if ($sub_r = $this->x('\.', $sub_v)) {
            $sub_v = $sub_r[1];
          }
          return array(array('prefix' => $prefix, 'iri_ref' => $r, 'iri' => $iri), $sub_v);
        }
      }
    }
    return array(0, $v);
  }

  /* 21.., 32.. */
  
  function xTriplesBlock($v) {
    $pre_r = array();
    $r = array();
    $state = 1;
    $sub_v = $v;
    $buffer = $sub_v;
    do {
      $proceed = 0;
      if ($state == 1) {/* expecting subject */
        $t = array('type' => 'triple', 's' => '', 'p' => '', 'o' => '', 's_type' => '', 'p_type' => '', 'o_type' => '', 'o_dt' => '', 'o_lang' => '');
        if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) {
          $t['s'] = $sub_r['val'];
          $t['s_type'] = $sub_r['type'];
          $state = 2;
          $proceed = 1;
          if ($sub_r = $this->x('\}', $sub_v)) {
            $this->addError('} after subject found.');
          }
          elseif ($sub_r = $this->x('\.', $sub_v)) {
            $this->addError('DOT after subject found.');
          }
        }
        elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) {
          $t['s'] = $sub_r['id'];
          $t['s_type'] = $sub_r['type'];
          $pre_r = array_merge($pre_r, $sub_r['triples']);
          $state = 2;
          $proceed = 1;
          if ($sub_r = $this->x('\.', $sub_v)) {
            $this->addError('DOT after subject found.');
          }
        }
        elseif ((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) {
          $t['s'] = $sub_r['id'];
          $t['s_type'] = $sub_r['type'];
          $pre_r = array_merge($pre_r, $sub_r['triples']);
          $state = 2;
          $proceed = 1;
        }
        elseif ($sub_r = $this->x('\.', $sub_v)) {
          $this->addError('Subject expected, DOT found.');
        }
      }
      if ($state == 2) {/* expecting predicate */
        if ($sub_r = $this->x('a\s+', $sub_v)) {
          $sub_v = $sub_r[1];
          $t['p'] = $this->rdf . 'type';
          $t['p_type'] = 'iri';
          $state = 3;
          $proceed = 1;
        }
        elseif ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) {
          if ($sub_r['type'] == 'bnode') {
            $this->addError('Blank node used as triple predicate');
          }
          $t['p'] = $sub_r['val'];
          $t['p_type'] = $sub_r['type'];
          $state = 3;
          $proceed = 1;
        }
        elseif ($sub_r = $this->x('\.', $sub_v)) {
          $state = 4;          
        }
        elseif ($sub_r = $this->x('\}', $sub_v)) {
          $buffer = $sub_v;
          $r = array_merge($r, $pre_r);
          $pre_r = array();
          $proceed = 0;
        }
      }
      if ($state == 3) {/* expecting object */
        if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) {
          $t['o'] = $sub_r['val'];
          $t['o_type'] = $sub_r['type'];
          $t['o_lang'] = $this->v('lang', '', $sub_r);
          $t['o_dt'] = $this->v('dt', '', $sub_r);
          $pre_r[] = $t;
          $state = 4;
          $proceed = 1;
        }
        elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) {
          $t['o'] = $sub_r['id'];
          $t['o_type'] = $sub_r['type'];
          $pre_r = array_merge($pre_r, array($t), $sub_r['triples']);
          $state = 4;
          $proceed = 1;
        }
        elseif ((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) {
          $t['o'] = $sub_r['id'];
          $t['o_type'] = $sub_r['type'];
          $pre_r = array_merge($pre_r, array($t), $sub_r['triples']);
          $state = 4;
          $proceed = 1;
        }
      }
      if ($state == 4) {/* expecting . or ; or , or } */
        if ($sub_r = $this->x('\.', $sub_v)) {
          $sub_v = $sub_r[1];
          $buffer = $sub_v;
          $r = array_merge($r, $pre_r);
          $pre_r = array();
          $state = 1;
          $proceed = 1;
        }
        elseif ($sub_r = $this->x('\;', $sub_v)) {
          $sub_v = $sub_r[1];
          $state = 2;
          $proceed = 1;
        }
        elseif ($sub_r = $this->x('\,', $sub_v)) {
          $sub_v = $sub_r[1];
          $state = 3;
          $proceed = 1;
          if ($sub_r = $this->x('\}', $sub_v)) {
            $this->addError('Object expected, } found.');
          }
        }
        if ($sub_r = $this->x('(\}|\{|OPTIONAL|FILTER|GRAPH)', $sub_v)) {
          $buffer = $sub_v;
          $r = array_merge($r, $pre_r);
          $pre_r = array();
          $proceed = 0;
        }
      }
    } while ($proceed);
    return count($r) ? array($r, $buffer, $pre_r, $sub_v) : array(0, $buffer, $pre_r, $sub_v);
  }
  
  /* 39.. */
  
  function xBlankNodePropertyList($v) {
    if ($sub_r = $this->x('\[', $v)) {
      $sub_v = $sub_r[1];
      $s = $this->createBnodeID();
      $r = array('id' => $s, 'type' => 'bnode', 'triples' => array());
      $t = array('type' => 'triple', 's' => $s, 'p' => '', 'o' => '', 's_type' => 'bnode', 'p_type' => '', 'o_type' => '', 'o_dt' => '', 'o_lang' => '');
      $state = 2;
      $closed = 0;
      do {
        $proceed = 0;
        if ($state == 2) {/* expecting predicate */
          if ($sub_r = $this->x('a\s+', $sub_v)) {
            $sub_v = $sub_r[1];
            $t['p'] = $this->rdf . 'type';
            $t['p_type'] = 'iri';
            $state = 3;
            $proceed = 1;
          }
          elseif ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) {
            $t['p'] = $sub_r['val'];
            $t['p_type'] = $sub_r['type'];
            $state = 3;
            $proceed = 1;
          }
        }
        if ($state == 3) {/* expecting object */
          if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) {
            $t['o'] = $sub_r['val'];
            $t['o_type'] = $sub_r['type'];
            $t['o_lang'] = $this->v('lang', '', $sub_r);
            $t['o_dt'] = $this->v('dt', '', $sub_r);
            $r['triples'][] = $t;
            $state = 4;
            $proceed = 1;
          }
          elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) {
            $t['o'] = $sub_r['id'];
            $t['o_type'] = $sub_r['type'];
            $r['triples'] = array_merge($r['triples'], array($t), $sub_r['triples']);
            $state = 4;
            $proceed = 1;
          }
          elseif((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) {
            $t['o'] = $sub_r['id'];
            $t['o_type'] = $sub_r['type'];
            $r['triples'] = array_merge($r['triples'], array($t), $sub_r['triples']);
            $state = 4;
            $proceed = 1;
          }
        }
        if ($state == 4) {/* expecting . or ; or , or ] */
          if ($sub_r = $this->x('\.', $sub_v)) {
            $sub_v = $sub_r[1];
            $state = 1;
            $proceed = 1;
          }
          if ($sub_r = $this->x('\;', $sub_v)) {
            $sub_v = $sub_r[1];
            $state = 2;
            $proceed = 1;
          }
          if ($sub_r = $this->x('\,', $sub_v)) {
            $sub_v = $sub_r[1];
            $state = 3;
            $proceed = 1;
          }
          if ($sub_r = $this->x('\]', $sub_v)) {
            $sub_v = $sub_r[1];
            $proceed = 0;
            $closed = 1;
          }
        }
      } while ($proceed);
      if ($closed) {
        return array($r, $sub_v);
      }
      return array(0, $v);
    }
    return array(0, $v);
  }
  
  /* 40.. */
  
  function xCollection($v) {
    if ($sub_r = $this->x('\(', $v)) {
      $sub_v = $sub_r[1];
      $s = $this->createBnodeID();
      $r = array('id' => $s, 'type' => 'bnode', 'triples' => array());
      $closed = 0;
      do {
        $proceed = 0;
        if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) {
          $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'first', 'o' => $sub_r['val'], 's_type' => 'bnode', 'p_type' => 'iri', 'o_type' => $sub_r['type'], 'o_lang' => $this->v('lang', '', $sub_r), 'o_dt' => $this->v('dt', '', $sub_r));
          $proceed = 1;
        }
        elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) {
          $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'first', 'o' => $sub_r['id'], 's_type' => 'bnode', 'p_type' => 'iri', 'o_type' => $sub_r['type'], 'o_lang' => '', 'o_dt' => '');
          $r['triples'] = array_merge($r['triples'], $sub_r['triples']);
          $proceed = 1;
        }
        elseif((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) {
          $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'first', 'o' => $sub_r['id'], 's_type' => 'bnode', 'p_type' => 'iri', 'o_type' => $sub_r['type'], 'o_lang' => '', 'o_dt' => '');
          $r['triples'] = array_merge($r['triples'], $sub_r['triples']);
          $proceed = 1;
        }
        if ($proceed) {
          if ($sub_r = $this->x('\)', $sub_v)) {
            $sub_v = $sub_r[1];
            $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'rest', 'o' => $this->rdf . 'nil', 's_type' => 'bnode', 'p_type' => 'iri', 'o_type' => 'iri', 'o_lang' => '', 'o_dt' => '');
            $closed = 1;
            $proceed = 0;
          }
          else {
            $next_s = $this->createBnodeID();
            $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'rest', 'o' => $next_s, 's_type' => 'bnode', 'p_type' => 'iri', 'o_type' => 'bnode', 'o_lang' => '', 'o_dt' => '');
            $s = $next_s;
          }
        }
      } while ($proceed);
      if ($closed) {
        return array($r, $sub_v);
      }
    }
    return array (0, $v);
  }
  
  /* 42 */
  
  function xVarOrTerm($v) {
    if ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) {
      return array($sub_r, $sub_v);
    }
    elseif ((list($sub_r, $sub_v) = $this->xGraphTerm($v)) && $sub_r) {
      return array($sub_r, $sub_v);
    }
    return array(0, $v);
  }
  
  /* 44, 74.., 75.. */
  
  function xVar($v) {
    if ($r = $this->x('(\?|\$)([^\s]+)', $v)) {
      if ((list($sub_r, $sub_v) = $this->xVARNAME($r[2])) && $sub_r) {
        if (!in_array($sub_r, $this->r['vars'])) {
          $this->r['vars'][] = $sub_r;
        }
        return array(array('val' => $sub_r, 'type' => 'var'), $sub_v . $r[3]);
      }
    }
    return array(0, $v);
  }

  /* 45 */
  
  function xGraphTerm($v) {
    foreach (array('IRIref' => 'iri', 'RDFLiteral' => 'literal', 'NumericLiteral' => 'literal', 'BooleanLiteral' => 'literal', 'BlankNode' => 'bnode', 'NIL' => 'iri') as $term => $type) {
      $m = 'x' . $term;
      if ((list($sub_r, $sub_v) = $this->$m($v)) && $sub_r) {
        if (!is_array($sub_r)) {
          $sub_r = array('val' => $sub_r);
        }
        $sub_r['type'] = $this->v1('type', $type, $sub_r);
        return array($sub_r, $sub_v);
      }
    }
    return array(0, $v);
  }

  /* 60 */
  
  function xRDFLiteral($v) {
    if ((list($sub_r, $sub_v) = $this->xString($v)) && $sub_r) {
      $r = $sub_r;
      if ((list($sub_r, $sub_v) = $this->xLANGTAG($sub_v)) && $sub_r) {
        $r['lang'] = $sub_r;
      }
      elseif (!$this->x('\s', $sub_v) && ($sub_r = $this->x('\^\^', $sub_v)) && (list($sub_r, $sub_v) = $this->xIRIref($sub_r[1])) && $sub_r[1]) {
        $r['dt'] = $sub_r;
      }
      return array($r, $sub_v);
    }
    return array(0, $v);
  }

  /* 61.., 62.., 63.., 64.. */  
  
  function xNumericLiteral($v) {
    $sub_r = $this->x('(\-|\+)?', $v);
    $prefix = $sub_r[1];
    $sub_v = $sub_r[2];
    foreach (array('DOUBLE' => 'double', 'DECIMAL' => 'decimal', 'INTEGER' => 'integer') as $type => $xsd) {
      $m = 'x' . $type;
      if ((list($sub_r, $sub_v) = $this->$m($sub_v)) && ($sub_r !== false)) {
        $r = array('val' => $prefix . $sub_r, 'type' => 'literal', 'dt' => $this->xsd . $xsd);
        return array($r, $sub_v);
      }
    }
    return array(0, $v);
  }
  
  /* 65.. */
  
  function xBooleanLiteral($v) {
    if ($r = $this->x('(true|false)', $v)) {
      return array($r[1], $r[2]);
    }
    return array(0, $v);
  }

  /* 66.., 87.., 88.., 89.., 90.., 91.. */
    //if ($r = $this->x('\'\'\'(((\'|\'\')?[^\'\\\]|\\\t|\\\b|\\\n|\\\r|\\\f|\\\"|\\\')*)\'\'\'', $v)) {
  
  function xString($v) {
    if (preg_match('/^\s*([\']{1,3})([^\'].*)?(\\1)(.*)$/s', $v, $m) || preg_match('/^\s*([\"]{1,3})([^\"].*)?(\\1)(.*)$/s', $v, $m)) {
      $delim = $m[1];
      $v = $m[1] . $m[2] . $m[3];
      $rest = $m[4];
    }
    else {
      return array(0, $v);
    }
    if (strlen($v) > 3000) {/* avoid regex choke */
      $pos = strlen($delim);
      while (($pos < strlen($v)) && ($next_pos = strpos($v, $delim, $pos))) {
        $sub_v = substr($v, 0, $next_pos + strlen($delim));
        $sub_rest = substr($v, $next_pos + strlen($delim));
        $pos = $next_pos + strlen($delim);
        if (strlen($sub_v) < 3000) {
          if ((list($sub_r, $sub_v) = $this->xString($sub_v)) && $sub_r) {
            $rest = $sub_v . $sub_rest . $rest;
            return array($sub_r, $rest);
          }
        }
        else {/* simplified, match is good enough */
          $rest = $sub_rest . $rest;
          $types = array("'''" => 'literal_long1', '"""' => 'literal_long2', "'" => 'literal1', '"' => 'literal2');
          return array(array('val' => substr($v, strlen($delim), - strlen($delim)), 'type' => $types[$delim]), $rest);
        }
      }
    }
    else {
      if ($r = $this->x('\'\'\'((\x5c[\']{1,3}[^\']|[\']{1,2}[^\']|[\x5c][^\']|[^\'\x5c]|\\\t|\\\b|\\\n|\\\r|\\\f|\\\u|\\\U|\x5c\")*)\'\'\'', $v)) {
        return array(array('val' => $r[1], 'type' => 'literal_long1'), $r[3] . $rest);
      }
      if ($r = $this->x('\"\"\"((\x5c[\"]{1,3}[^\"]|[\"]{1,2}[^\"]|[\x5c][^\"]|[^\"\x5c]|\\\t|\\\b|\\\n|\\\r|\\\f|\\\u|\\\U|\x5c\')*)\"\"\"', $v)) {
        return array(array('val' => $r[1], 'type' => 'literal_long2'), $r[3] . $rest);
      }
      if ($r = $this->x('\'(([^\x27\x5c\xa\xd]|\\\t|\\\b|\\\n|\\\r|\\\f|\\\u|\\\U|\\\"|\x5c\')*)\'', $v)) {
        if (!preg_match('/\x5c$/', $r[1])) {
          return array(array('val' => $r[1], 'type' => 'literal1'), $r[3] . $rest);
        }
      }
      if ($r = $this->x('\"(([^\x22\x5c\xa\xd]|\\\t|\\\b|\\\n|\\\r|\\\f|\\\u|\\\U|\\\"|\x5c\'|\x5c\x5c)*)\"', $v)) {
        if (!preg_match('/\x5c$/', $r[1])) {
          return array(array('val' => $r[1], 'type' => 'literal2'), $r[3] . $rest);
        }
      }
    }
    return array(0, $v . $rest);
  }
  
  /* 67 */
  
  function xIRIref($v) {
    if ((list($r, $v) = $this->xIRI_REF($v)) && $r) {
      return array($this->calcUri($r, $this->base), $v);
    }
    elseif ((list($r, $v) = $this->xPrefixedName($v)) && $r) {
      return array($r, $v);
    }
    return array(0, $v);
  }
  
  /* 68 */
  
  function xPrefixedName($v) {
    if ((list($r, $v) = $this->xPNAME_LN($v)) && $r) {
      return array($r, $v);
    }
    elseif ((list($r, $sub_v) = $this->xPNAME_NS($v)) && $r) {
      return isset($this->prefixes[$r]) ? array($this->prefixes[$r], $sub_v) : array(0, $v);
    }
    return array(0, $v);
  }
  
  /* 69.., 73.., 93, 94..  */
  
  function xBlankNode($v) {
    if (($r = $this->x('\_\:', $v)) && (list($r, $sub_v) = $this->xPN_LOCAL($r[1])) && $r) {
      return array(array('type' => 'bnode', 'val' => '_:' . $r), $sub_v);
    }
    if ($r = $this->x('\[[\x20\x9\xd\xa]*\]', $v)) {
      return array(array('type' => 'bnode', 'val' => $this->createBnodeID()), $r[1]);
    }
    return array(0, $v);
  }

  /* 70.. */
  
  function xIRI_REF($v) {
    if ($r = $this->x('\<([^\<\>\"\{\}\|\^\'[:space:]]*)\>', $v)) {
      return array($r[1] ? $r[1] : true, $r[2]);
    }
    return array(0, $v);
  }
  
  /* 71 */
  
  function xPNAME_NS($v) {
    list($r, $sub_v) = $this->xPN_PREFIX($v);
    $prefix = $r ? $r : '';
    return ($r = $this->x("\:", $sub_v)) ? array($prefix . ':', $r[1]) : array(0, $v);
  }

  /* 72 */
  
  function xPNAME_LN($v) {
    if ((list($r, $sub_v) = $this->xPNAME_NS($v)) && $r) {
      if (!$this->x('\s', $sub_v) && (list($sub_r, $sub_v) = $this->xPN_LOCAL($sub_v)) && $sub_r) {
        if (!isset($this->prefixes[$r])) {
          return array(0, $v);
        }
        return array($this->prefixes[$r] . $sub_r, $sub_v);
      }
    }
    return array(0, $v);
  }
  
  /* 76 */
  
  function xLANGTAG($v) {
    if (!$this->x('\s', $v) && ($r = $this->x('\@([a-z]+(\-[a-z0-9]+)*)', $v))) {
      return array($r[1], $r[3]);
    }
    return array(0, $v);
  }
  
  /* 77.. */
  
  function xINTEGER($v) {
    if ($r = $this->x('([0-9]+)', $v)) {
      return array($r[1], $r[2]);
    }
    return array(false, $v);
  }

  /* 78.. */

  function xDECIMAL($v) {
    if ($r = $this->x('([0-9]+\.[0-9]*)', $v)) {
      return array($r[1], $r[2]);
    }
    if ($r = $this->x('(\.[0-9]+)', $v)) {
      return array($r[1], $r[2]);
    }
    return array(false, $v);
  }

  /* 79.., 86.. */

  function xDOUBLE($v) {
    if ($r = $this->x('([0-9]+\.[0-9]*E[\+\-]?[0-9]+)', $v)) {
      return array($r[1], $r[2]);
    }
    if ($r = $this->x('(\.[0-9]+E[\+\-]?[0-9]+)', $v)) {
      return array($r[1], $r[2]);
    }
    if ($r = $this->x('([0-9]+E[\+\-]?[0-9]+)', $v)) {
      return array($r[1], $r[2]);
    }
    return array(false, $v);
  }
  
  /* 92 */
  
  function xNIL($v) {
    if ($r = $this->x('\([\x20\x9\xd\xa]*\)', $v)) {
      return array(array('type' => 'iri', 'val' => $this->rdf . 'nil'), $r[1]);
    }
    return array(0, $v);
  }

  /* 95.. */
  
  function xPN_CHARS_BASE($v) {
    if ($r = $this->x("([a-z]+|\\\u[0-9a-f]{1,4})", $v)) {
      return array($r[1], $r[2]);
    }
    return array(0, $v);
  }

  /* 96 */
  
  function xPN_CHARS_U($v) {
    if ((list($r, $sub_v) = $this->xPN_CHARS_BASE($v)) && $r) {
      return array($r, $sub_v);
    }
    elseif ($r = $this->x("(_)", $v)) {
      return array($r[1], $r[2]);
    }
    return array(0, $v);
  }

  /* 97.. */
  
  function xVARNAME($v) {
    $r = '';
    do {
      $proceed = 0;
      if ($sub_r = $this->x('([0-9]+)', $v)) {
        $r .= $sub_r[1];
        $v = $sub_r[2];
        $proceed = 1;
      }
      elseif ((list($sub_r, $sub_v) = $this->xPN_CHARS_U($v)) && $sub_r) {
        $r .= $sub_r;
        $v = $sub_v;
        $proceed = 1;
      }
      elseif ($r && ($sub_r = $this->x('([\xb7\x300-\x36f]+)', $v))) {
        $r .= $sub_r[1];
        $v = $sub_r[2];
        $proceed = 1;
      }
    } while ($proceed);
    return array($r, $v);
  }

  /* 98.. */
  
  function xPN_CHARS($v) {
    if ((list($r, $sub_v) = $this->xPN_CHARS_U($v)) && $r) {
      return array($r, $sub_v);
    }
    elseif ($r = $this->x('([\-0-9\xb7\x300-\x36f])', $v)) {
      return array($r[1], $r[2]);
    }
    return array(false, $v);
  }

  /* 99 */
  
  function xPN_PREFIX($v) {
    if ((list($r, $sub_v) = $this->xPN_CHARS_BASE($v)) && $r) {
      do {
        $proceed = 0;
        list($sub_r, $sub_v) = $this->xPN_CHARS($sub_v);
        if ($sub_r !== false) {
          $r .= $sub_r;
          $proceed = 1;
        }
        elseif ($sub_r = $this->x("\.", $sub_v)) {
          $r .= '.';
          $sub_v = $sub_r[1];
          $proceed = 1;
        }
      } while ($proceed);
      list($sub_r, $sub_v) = $this->xPN_CHARS($sub_v);
      $r .= $sub_r ? $sub_r : '';
    }
    return array($r, $sub_v);
  }
  
  /* 100 */
  
  function xPN_LOCAL($v) {
    $r = '';
    $sub_v = $v;
    do {
      $proceed = 0;
      if ($this->x('\s', $sub_v)) {
        return array($r, $sub_v);
      }
      if ($sub_r = $this->x('([0-9])', $sub_v)) {
        $r .= $sub_r[1];
        $sub_v = $sub_r[2];
        $proceed = 1;
      }
      elseif ((list($sub_r, $sub_v) = $this->xPN_CHARS_U($sub_v)) && $sub_r) {
        $r .= $sub_r;
        $proceed = 1;
      }
      elseif ($r) {
        if (($sub_r = $this->x('(\.)', $sub_v)) && !preg_match('/^\s/s', $sub_r[2])) {
          $r .= $sub_r[1];
          $sub_v = $sub_r[2];
        }
        if ((list($sub_r, $sub_v) = $this->xPN_CHARS($sub_v)) && $sub_r) {
          $r .= $sub_r;
          $proceed = 1;
        }
      }
    } while ($proceed);
    return array($r, $sub_v);
  }
  
  /*  */
  
}
