/**----------------------------------------------------------------***
*  Copyright 2007 - Paul Seamons                                     *
*  Distributed under the Perl Artistic License without warranty      *
*  Based upon YAML.pm v0.35 from Perl                                *
***----------------------------------------------------------------**/

// $Revision: 1.18 $

// allow for missing methods in ie 5.0

if (! Array.prototype.unshift)
  Array.prototype.unshift = function (add) {
    for (var i=this.length; i > 0; i--) this[i] = this[i - 1];
    this[0] = add;
  };

if (!Array.prototype.shift)
  Array.prototype.shift = function () {
    var ret = this[0];
    for (var i=0; i<this.length-1; i++) this[i] = this[i + 1];
    this.length -= 1;
    return ret;
  };

if (!Array.prototype.push)
  Array.prototype.push = function (add) {
    this[this.length] = add;
  };

// and now - the rest of the library

function YAML () {
  this.parse            = yaml_parse;
  this.error            = yaml_error;
  this.warn             = yaml_warn;
  this.parse_throwaway  = yaml_parse_throwaway;
  this.parse_node       = yaml_parse_node;
  this.parse_next_line  = yaml_parse_next_line;
  this.parse_qualifiers = yaml_parse_qualifiers;
  this.parse_explicit   = yaml_parse_explicit;
  this.parse_implicit   = yaml_parse_implicit;
  this.parse_map        = yaml_parse_map;
  this.parse_seq        = yaml_parse_seq;
  this.parse_inline     = yaml_parse_inline;
}

function yaml_error (err) {
  err += '\nDocument: '+this.document+'\n';
  err += '\nLine: '    +this.line    +'\n';
  if (! document.hide_yaml_errors) alert(err);
  document.yaml_error_occured = 1;
  return;
}

function yaml_warn (err) {
  if (! document.hide_yaml_errors) alert(err);
  return;
}

function yaml_parse (text) {
  document.yaml_error_occured = undefined;

  // translate line endings down to \012
  text = text.replace(new RegExp('\015\012','g'), '\012');
  text = text.replace(new RegExp('\015','g'), '\012');
  if (text.match('[\\x00-\\x08\\x0B-\\x0D\\x0E-\\x1F]'))
    return this.error("Bad characters found");
  if (text.length && ! text.match('\012$'))
    text += '\012';

  this.line      = 1;
  this.lines     = text.split("\012");
  this.document  = 0;
  this.documents = new Array();

  this.parse_throwaway();
  if (! this.eoy && ! this.lines[0].match('^---(\\s|$)')) {
    this.lines.unshift('--- #YAML:1.0');
    this.line --;
  }

  // loop looking for data structures
  while (! this.eoy) {
    this.anchors = new Array();
    this.offset  = new Array();
    this.options = new Array();
    this.document  ++;
    this.done      = 0;
    this.level     = 0;
    this.offset[0] = -1;
    this.preface   = '';
    this.content   = '';
    this.indent    = -1;

    var m = this.lines[0].match('---\\s*(.*)$')
    if (! m) return this.error("Missing YAML separator\n("+this.lines[0]+")");
    var words = m[1].split("\\s+");
    while (words.length && (m = words[0].match('^#(\\w+):(\\S.*)$'))) {
      words.shift();
      if (this.options[m[1]]) {
        yaml.warn("Parse warn - multiple options " + m[1]);
        continue;
      }
      this.options[m[1]] = m[2];
    }

    if (this.options['YAML'] && this.options['YAML'] != '1.0')
      return this.error('Bad YAML version number - must be 1.0');
    if (this.options['TAB'] && ! this.options['TAB'].match('^(NONE|\\d+)(:HARD)?$'))
      return this.error('Unrecognized TAB policy');

    this.documents.push(this.parse_node());
  }

  return this.documents;
}

function yaml_parse_throwaway () {
  while (this.lines.length && this.lines[0].match('^\\s*(#|$)')) {
    this.lines.shift();
    this.line ++;
  }
  this.eoy = this.done = ! this.lines.length;
}

function yaml_parse_node (no_next) {
  if (! no_next) this.parse_next_line(2); // COLLECTION

  var preface   = this.preface;
  this.preface  = '';
  var node      = '';
  var type      = '';
  var indicator = '';
  var escape    = '';
  var chomp     = '';

  var info     = this.parse_qualifiers(preface);
  var anchor   = info[0];
  var alias    = info[1];
  var explicit = info[2];
  var implicit = info[3];
  var yclass   = info[4];
  preface      = info[5];


  if (alias) {
    if (! this.anchors[alias]) return this.error("Parse error - missing alias: "+alias);
    return this.anchors[alias];
  }

  // see if this is a literal or an unfold block
  this.inline = '';
  if (preface.length) {
    m = preface.match('^([>\\|])([+\\-]?)\\d*\\s*');
    if (m) {
      indicator   = m[1];
      chomp       = m[2];
      preface     = preface.substring(0,m[0].length);
    } else {
      this.inline = preface;
      preface     = '';
    }
  }

  
  if (this.inline.length) {
    node = this.parse_inline(1, implicit, explicit, yclass);
    if (this.inline.length) return this.error("Parse error - must be single line ("+this.inline+')');
  } else {
    this.level ++;
    // block items
    if (indicator) {
      node = '';
      while (! this.done && this.indent == this.offset[this.level]) {
        node += this.content + '\n';
        this.parse_next_line(1); // LEAF
      }
      if (indicator == '>') {
        node = node.replace(new RegExp('[ \\t]*\n[ \\t]*(\\S)','gm'), ' $1');
      }
      if (! chomp || chomp == '-') node = node.replace(new RegExp('\n$',''),'');
      if (implicit) node = this.parse_implicit(node);

    } else {
      if (! this.offset[this.level]) this.offset[this.level] = 0;
      if (this.indent == this.offset[this.level]) {
        if (this.content.match('^-( |$)')) {
          node = this.parse_seq(anchor);
        } else if (this.content.match('(^\\?|:( |$))')) {
          node = this.parse_map(anchor);
        } else if (preface.match('^\\s*$')) {
          node = ''; //this.parse_implicit('');
        } else {
          return this.error('Parse error - bad node +('+this.content+')('+preface+')');
        }
      } else {
        node = '';
      }
    }
    this.level --
  }
  this.offset = this.offset.splice(0, this.level + 1);

  if (explicit) {
    if (yclass) return this.error("Parse error - classes not supported");
    else node = this.parse_explicit(node, explicit);
  }
  if (anchor) this.anchors[anchor] = node;

  return node;
}

function yaml_parse_next_line (type) {
  var m;
  var level  = this.level;
  var offset = this.offset[level];

  if (offset == undefined) return this.error("Parse error - Bad level " + level);

  // done with the current line - get the next
  // remove following commented lines
  this.lines.shift();
  this.line ++;
  this.eoy = this.done = ! this.lines.length;
  if (this.eoy) return;
  this.parse_throwaway();
  if (this.eoy) return;

  // Determine the offset for a new leaf node
  if (this.preface && (m = this.preface.match('[>\\|][+\\-]?(\\d*)\\s*$'))) {
    if (m[1].length && m[1] == '0') return this.error("Parse error zero indent");
    type = 1;
    if (m[1].length) {
      this.offset[level + 1] = offset + m[1];
    } else if ((m = this.lines[0].match('^( *)\\S')) && m[1].length > offset) {
      this.offset[level + 1] = m[1].length;
    } else {
      this.offset[level + 1] = offset + 1;
    }
    level ++;
    offset = this.offset[level];
  }

  // COLLECTION
  if (type == 2 && this.preface.match('^\\s*(!\\S*|&\\S+)*\\s*$')) {
    m = this.lines[0].match('^( *)\\S');
    if (! m) return this.error("Missing leading space on line "+this.lines[0]);
    this.offset[level + 1] = (m[1].length > offset) ? m[1].length : offset + 1;
    offset = this.offset[++ level];

  // LEAF
  } else if (type == 1) {
    // skip blank lines and comment lines
    while (this.lines.length && this.lines[0].match('^\\s*(#|$)')) {
      m = this.lines[0].match('^( *)');
      if (! m) return this.error("Missing leading space on comment " + this.lines[0]);
      if (m[1].length > offset) break;
      this.lines.shift();
      this.line ++;
    }
    this.eoy = this.done = ! this.lines.length;      
  } else {
    this.parse_throwaway();
  }

  if (this.eoy) return;
  if (this.lines[0].match('^---(\\s|$)')) {
    this.done = 1;
    return;
  }

  if (type == 1 && (m = this.lines[0].match('^ {'+offset+'}(.*)$'))) {
    this.indent = offset;
    this.content = m[1];
  } else if (this.lines[0].match('^\\s*$')) {
    this.indent = offset;
    this.content = '';
  } else {
    m = this.lines[0].match('^( *)(\\S.*)$');
    // # yaml.warn("   indent(${\length($1)})  offsets(@{$o->{offset}}) \n");
    var len = (m) ? m[1].length : 0;
    while (this.offset[level] > len) level --;
    if (this.offset[level] != len)
      return this.error("Parse error inconsitent indentation:\n"
                        + '(this.lines[0]: '+this.lines[0]+', len: '+len+', level: '+level+', this.offset[level]: '+this.offset[level]+')\n');
    
    this.indent  = len;
    this.content = m ? m[2] : '';
  }

  if (this.indent - offset > 1)
    return this.error("Parse error - indentation");

  return;
}

function yaml_parse_qualifiers (preface) {
  var info = new Array();
  // 0 = anchor
  // 1 = alias
  // 2 = explicit
  // 3 = implicit
  // 4 = class - not used for now
  // 5 = preface

  var m;
  while (preface.match('^[&\\*!]')) {
    // explicit, implicit
    if (m = preface.match('^\!(\\S*)\\s*')) {
      preface = preface.substring(m[0].length);
      if (m[1].length) info[2] = m[1];
      else info[3] = 1;
    // anchor, alias
    } else if (m = preface.match('^([&\\*])([^ ,:]+)\\s*')) {
      preface = preface.substring(m[0].length);
      if (! m[2].match('^\\w+$')) return this.error("Bad name "+m[2]);
      if (info[0] || info[1]) return this.error("Already found anchor or alias "+m[2]);
      if (m[1] == '&') info[0] = m[2];
      if (m[1] == '*') info[1] = m[2];
    }
  }

  info[5] = preface;
  return info;
}

function yaml_parse_explicit (node, explicit) {
  var m;
  if (m = explicit.match('^(int|float|bool|date|time|datetime|binary)$')) {
    // return this.error("No handler yet for explict " + m[1]);
    // just won't check types for now
    return node;
  } else if (m = explicit.match('^perl/(glob|regexp|code|ref):(\\w(\\w|::)*)?$')) {
    return this.error("No handler yet for perltype " + m[1]);
  } else if (m = explicit.match('^perl/(\\@|\\$)?([a-zA-Z](\\w|::)+)$')) {
    return this.error("No handler yet for perl object " + m[1]);
  } else if (! (m = explicit.match('/'))) {
    return this.error("Load error - no conversion "+explicit);
  } else {
    return this.error("No YAML::Node handler made yet "+explicit);
  }
}

function yaml_parse_implicit (value) {
  value.replace(new RegExp('\\s*$',''),'');
  if (value == '') return '';
  if (value.match('^-?\\d+$')) return 0 + value;
  if (value.match('^[+-]?(\\d*)(\\.\\d*|)?([Ee][+-]?\\d+)?$')) return 1 * value;
  if (value.match('^\\d{4}\-\\d{2}\-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d*[1-9])?(Z|[-+]\\d{2}(:\\d{2})?))?$')
      || value.match('^\\w')) return "" + value;
  if (value == '~') return undefined;
  if (value == '+') return 1;
  if (value == '-') return 0;
  return this.error("Parse Error bad implicit value ("+value+")");
}

function yaml_parse_map (anchor) {
  var m;
  var node = new Array ();
  if (anchor) this.anchors[anchor] = node;
  
  while (! this.done && this.indent == this.offset[this.level]) {
    var key;
    if (this.content.match('^\\?\\s*')) {
      this.preface = this.content;
      key = '' + this.parse_node();
    } else if (m = this.content.match('^=\\s*')) {
      this.content = this.content.substring(m[0].length);
      key = "\x07YAML\x07VALUE\x07";
    } else if (m = this.content.match('^//\\s*')) {
      this.content = this.content.substring(m[0].length);
      key = "\x07YAML\x07COMMENT\x07";
    } else {

      this.inline = this.content;
      key = this.parse_inline();
      this.content = this.inline;
      this.inline = '';
    }

    if (! (m = this.content.match('^:\\s*'))) return this.error("Parse error - bad map element "+this.content);
    this.content = this.content.substring(m[0].length);
   
    this.preface = this.content;

    var value = this.parse_node();

    if (node[key]) this.warn('Warn - duplicate key '+key);
    else node[key] = value;

  }

  return node;
}

function yaml_parse_seq (anchor) {
  var m;
  var node = new Array ();
  if (anchor) this.anchors[anchor] = node;
  while (! this.done && this.indent == this.offset[this.level]) {
    var m;
    if ((m = this.content.match('^- (.*)$')) || (m = this.content.match('^-()$'))) {
      this.preface = m[1];
    } else return this.error("Parse error - bad seq element "+this.content);

    if (m = this.preface.match('^(\\s*)(\\w.*:( |$).*)$')) {
      this.indent = this.offset[this.level] + 2 + m[1].length;
      this.content = m[2];
      this.offset[++ this.level] = this.indent;
      this.preface = '';
      node.push(this.parse_map(''));
      this.level --;
      this.offset[this.offset.length - 1] = this.level;
    } else {
      node.push(this.parse_node());
    }
  }

  return node;
}

function yaml_parse_inline (top, top_implicit, top_explicit, top_class) {
  this.inline = this.inline.replace('^\\s+','').replace(new RegExp('\\s+$',''),'');

  var info     = this.parse_qualifiers(this.inline);
  var anchor   = info[0];
  var alias    = info[1];
  var explicit = info[2];
  var implicit = info[3];
  var yclass   = info[4];
  this.inline  = info[5];
  var node;
  var m;

  // copy the reference
  if (alias) {
    if (! this.anchors[alias]) return this.error("Parse error - missing alias: "+alias);
    node = this.anchors[alias];

  // new key based array
  } else if (m = this.inline.match('^\\{\\s*')) {
    this.inline = this.inline.substring(m[0].length);
    node = new Array ();
    while (! (m = this.inline.match('^\\}'))) {
      var key = this.parse_inline();
      if (! (m = this.inline.match('^:\\s+'))) return this.error("Parse error - bad map element "+this.inline);
      this.inline = this.inline.substring(m[0].length);
      var value = this.parse_inline();
      if (node[key]) this.warn("Warn - duplicate key found: "+key);
      else node[key] = value;
      if (this.inline.match('^\\}')) break;
      if (! (m = this.inline.match('^,\\s*'))) return this.error("Parse error - missing map comma "+this.inline);
      this.inline = this.inline.substring(m[0].length);
    }
    this.inline = this.inline.substring(m[0].length);    

  // new array
  } else if (m = this.inline.match('^\\[\\s*')) {
    this.inline = this.inline.substring(m[0].length);
    node = new Array ();
    while (! (m = this.inline.match('^\\]'))) {
      node.push(this.parse_inline());
      if (m = this.inline.match('^\\]')) break;
      if (! (m = this.inline.match('^,\\s*'))) return this.error("Parse error - missing seq comma "+this.inline);
      this.inline = this.inline.substring(m[0].length);
    }
    this.inline = this.inline.substring(m[0].length);

  // double quoted
  } else if (this.inline.match('^"')) {
    if (m = this.inline.match('^"((?:"|[^"])*)"\\s*(.*)$')) {
      this.inline = m[2];
      m[1] = m[1].replace(new RegExp('\\\\"','g'),'"');
      node = m[1];
    } else {
      return this.error("Bad double quote "+this.inline);
    }
    node = unescape(node); // built in
    if (implicit || top_implicit) node = this.parse_implicit(node);

  // single quoted
  } else if (this.inline.match("^'")) {
    if (m = this.inline.match("^'((?:''|[^'])*)'\\s*(.*)$")) { 
      this.inline = m[2];
      m[1] = m[1].replace(new RegExp("''",'g'),"'");
      node = m[1];
    } else {
      return this.error("Bad single quote "+this.inline);
    }
    node = unescape(node);  // built in
    if (implicit || top_implicit) node = this.parse_implicit(node);

  // simple
  } else {
    if (top) {
      node = this.inline;
      this.inline = '';
    } else {
      if (m = this.inline.match('^([^!@#%^&*,\\[\\]{}\\:]*)')) {
        this.inline = this.inline.substring(m[1].length);
        node = m[1];
      } else {
        return this.error ("Bad simple match "+this.inline);
      }
      if (! explicit && ! top_explicit) node = this.parse_implicit(node);
    }
  }
  if (explicit || top_explicit) {
    if (! explicit) explicit = top_explicit;
    if (yclass) return this.error("Parse error - classes not supported");
    else node = this.parse_explicit(node, explicit);
  }

  if (anchor) this.anchors[anchor] = node;

  return node;
}

document.yaml_load = function (text, anchors) {
  var yaml = new YAML();
  return yaml.parse(text, anchors);
}

document.js_dump = function (obj, name) {
  var t = '';
  if (! name) {
    name = '[obj]';
    t = 'Dump:\n'
  }
  if (typeof(obj) == 'function') return name+'=[FUNCTION]\n'
  if (typeof(obj) != 'object') return name+'='+obj+'\n';
  var hold = new Array();
  for (var i in obj) hold[hold.length] = i;
  hold = hold.sort();
  for (var i = 0; i < hold.length; i++) {
    var n = hold[i];
    t += document.js_dump(obj[n], name +'.'+n);
  }
  return t;
}

// the end
