/**
 *	RDFA in Javascript
 *	Ben Adida - ben@mit.edu
 *  Nathan Yergler - nathan@creativecommons.org
 *
 *	licensed under GPL v2
 */

// EXPECTING __RDFA_BASE
if (typeof(__RDFA_BASE) == 'undefined')
  __RDFA_BASE = 'http://www.w3.org/2006/07/SWD/RDFa/impl/js/20070301/';

// setup the basic
if (typeof(RDFA) == 'undefined') {
    RDFA = new Object();
}

RDFA.reset = function() {
   // reset the triple container
   RDFA.triples = new Object();
   RDFA.bnode_counter = 0;
   RDFA.elements = new Object();
};

RDFA.reset();

//
// dummy callbacks in case they're not defined
//
if (!RDFA.CALLBACK_DONE_LOADING)
    RDFA.CALLBACK_DONE_LOADING = function() {};

if (!RDFA.CALLBACK_DONE_PARSING)
    RDFA.CALLBACK_DONE_PARSING = function() {};

if (!RDFA.CALLBACK_NEW_TRIPLE_WITH_URI_OBJECT)
    RDFA.CALLBACK_NEW_TRIPLE_WITH_URI_OBJECT = function(foo,bar) {};

if (!RDFA.CALLBACK_NEW_TRIPLE_WITH_LITERAL_OBJECT)
    RDFA.CALLBACK_NEW_TRIPLE_WITH_LITERAL_OBJECT = function(foo,bar) {};

if (!RDFA.CALLBACK_NEW_TRIPLE_WITH_SUBJECT)
    RDFA.CALLBACK_NEW_TRIPLE_WITH_SUBJECT = function(foo,bar) {};

// a shallow copy of an array (only the named items)
RDFA.object_copy = function(obj) {
    var the_copy = new Object();

    for (i in obj) {
	    the_copy[i] = obj[i];
    }
    
    return the_copy;
};

// iterate through the object keys that are not part of the default object
RDFA.object_each = function(obj,f) {
   var dummy = new Object();
   for (var k in obj) {
       if (!dummy[k]) {
	       f(k);
       }
   }
};

//
//
//

// XML Namespace abstraction
RDFA.Namespace = function(prefix, uri) {
    this.prefix = prefix;
    this.uri = uri;
};

RDFA.Namespace.prototype.equals = function(other) {
    return (this.uri == other.uri);
};

// RDFA CURIE abstraction
RDFA.CURIE = function(ns,suffix) {
    this.ns = ns;
    this.suffix = suffix;
};

RDFA.CURIE.VALID_END_CHARS = ['/','#'];

RDFA.CURIE.prototype.pretty = function() {
    return (this.ns? this.ns.prefix:'?') + ':' + this.suffix;
};

RDFA.CURIE.prototype.uri = function() {
  if (!this.ns) return '?';
  
  var last_char = this.ns.uri.substring(this.ns.uri.length - 1);
  for (var i=0; i < RDFA.CURIE.VALID_END_CHARS.length; i++) {
    if (RDFA.CURIE.VALID_END_CHARS[i] == last_char)
   	  return this.ns.uri + this.suffix;
  }
  
  return this.ns.uri + "#" + this.suffix;
};

RDFA.CURIE.prototype.equals = function(other) {
    return (this.ns.equals(other.ns) && (this.suffix == other.suffix));
};

RDFA.CURIE.parse = function(str, namespaces) {
    var position = str.indexOf(':');

    // this will work even if prefix == -1
    var prefix = str.substring(0,position);
    var suffix = str.substring(position+1);

    var curie = new RDFA.CURIE(namespaces[prefix],suffix);
    return curie;
};

RDFA.CURIE.isCURIE = function(str) {
  return (str.substring(0,1) == '[' && str.substring(str.length-1,str.length) == ']')
}

RDFA.CURIE.prettyCURIEorURI = function(str) {
    if (RDFA.CURIE.isCURIE(str))
        return str.substring(1,str.length - 1);
    else
        return '<' + str + '>';
};

RDFA.CURIE.prettyCURIEorURIinHTML = function(str) {
    if (RDFA.CURIE.isCURIE(str))
        return str.substring(1,str.length - 1);
    else
        return '&lt;' + str + '&gt;';
};

RDFA.CURIE.getAbsoluteURI = function(str) {
  if (RDFA.CURIE.isCURIE(str))
    return str;
    
  var a = document.createElement('a');
  a.href= str;
  return a.href;
};

// RDF Triple abstraction
RDFA.Triple = function() {
    this.subject = '';
    this.predicate = '';
    this.object = '';
    this.object_literal_p = false;
};

RDFA.Triple.prototype.setLiteral= function(is_literal) {
    this.object_literal_p = is_literal;
};

RDFA.Triple.prototype.pretty = function(full_uri_p) {
    // subject
    var pretty_string =  this.prettySubject() + ' ';
    pretty_string += this.prettyPredicate(full_uri_p) + ' ';  
    pretty_string += this.prettyObject();

    return pretty_string;
};

RDFA.Triple.prototype.prettySubject = function() {
  return RDFA.CURIE.prettyCURIEorURI(this.subject);
};

RDFA.Triple.prototype.prettyPredicate = function(full_uri_p) {
  // predicate
  if (full_uri_p)
    return '<' + this.predicate.uri() + '>';
  else
    return this.predicate.pretty()  
};

RDFA.Triple.prototype.prettyObject = function() {
  if (this.object_literal_p) {
      return '"'+ this.object + '"';
  } else {
      return RDFA.CURIE.prettyCURIEorURI(this.object);
  }  
};

RDFA.Triple.prototype.prettyhtml = function() {
    var pretty_subject = this.subject;

    var pretty_string= RDFA.CURIE.prettyCURIEorURIinHTML(this.subject) + ' <a href="' + this.predicate.uri() + '">' + this.predicate.pretty() + '</a> ';

    if (this.object_literal_p) {
        pretty_string+= '"'+ this.object + '"';
    } else {
        pretty_string+= RDFA.CURIE.prettyCURIEorURIinHTML(this.object);
    }

    return pretty_string;
};


//
// This would be done by editing Node.prototype if all browsers supported it... (-Ben)
//
RDFA.getNodeAttributeValue = function(element, attr) {
    if (element == null)
        return null;

    if (element.getAttribute) {
        if (element.getAttribute(attr))
            return(element.getAttribute(attr));
    }

    if (element.attributes == undefined)
      return null;

	  if (!element.attributes[attr])
		  return null;

	return element.attributes[attr].value;
};

RDFA.setNodeAttributeValue = function(element, attr, value) {
    if (element == null)
        return;
        
    if (element.setAttribute != undefined) {
        element.setAttribute(attr,value);
        return;
    }
    
    if (element.attributes == undefined)
        element.attributes = new Object();

    element.attributes[attr] = new Object();
    element.attributes[attr].value = value;
};

//
// Support for loading other files
//

RDFA.GRDDL = new Object();

RDFA.GRDDL.CALLBACKS = new Array();

RDFA.GRDDL.DONE_LOADING = function(url) {
    RDFA.GRDDL.CALLBACKS[url]();
};

RDFA.GRDDL.load = function(url, callback)
{
    var s = document.createElement("script");
    s.type = 'text/javascript';
    s.src = url;

    // set up the callback
    RDFA.GRDDL.CALLBACKS[url] = callback;

    // add it to the document tree, load it up!
    document.getElementsByTagName('head')[0].appendChild(s);
};

//
// Support of in-place-GRDDL
//

RDFA.GRDDL._profiles = new Array();

RDFA.GRDDL.addProfile = function(js_url) {
    RDFA.GRDDL._profiles[RDFA.GRDDL._profiles.length] = js_url;
};

RDFA.GRDDL.runProfiles = function(callback) {
    // patch for prototype <= 1.4
    if (RDFA.GRDDL._profiles.length == 0) {
      callback();
      return;
    }

    var next_profile = RDFA.GRDDL._profiles.shift();

    if (!next_profile) {
        callback();
        return;
    }

    // load the next profile, and when that is done, run the next profiles
    RDFA.GRDDL.load(next_profile, function() {
        RDFA.GRDDL.runProfiles(callback);
    });
}


//
//
//

RDFA.add_triple = function (subject, predicate, object, literal_p) {
  if (!subject) {
    return null;
  }

    var triple = new RDFA.Triple();
    triple.subject = RDFA.CURIE.getAbsoluteURI(subject);
    triple.predicate = predicate;

    if (!literal_p)
      object= RDFA.CURIE.getAbsoluteURI(object);
      
    triple.object = object;
    triple.setLiteral(literal_p);

    // set up the array for that subject
    if (!RDFA.triples[triple.subject])
        RDFA.triples[triple.subject] = {};

    // we have to index by a string, so let's get the unique string, the URI
    var predicate_uri = triple.predicate.uri();

    if (!RDFA.triples[triple.subject][predicate_uri])
        RDFA.triples[triple.subject][predicate_uri] = new Array();

    // store the triple
    RDFA.triples[triple.subject][predicate_uri].push(triple);

    return triple;
};

RDFA.get_special_subject = function(element) {
    // ABOUT overrides ID
    if (RDFA.getNodeAttributeValue(element,'about'))
	    return RDFA.getNodeAttributeValue(element,'about');

    // there is no ABOUT, but this might be the HEAD
    if (element.name == 'head')
        return ""

    // ID
    if (RDFA.getNodeAttributeValue(element,'id'))
	    return "#" + RDFA.getNodeAttributeValue(element,'id');

    // BNODE, let's set it up if we need to
    if (!element.special_subject) {
	    element.special_subject = '[_:' + element.nodeName + RDFA.bnode_counter + ']';
	    RDFA.bnode_counter++;
    }

    return element.special_subject
};

//
// Process Namespaces
//
RDFA.add_namespaces = function(element, namespaces) {
    if (!namespaces)
	    namespaces = {};

    // we only copy the namespaces array if we really need to
    var copied_yet = 0;

    // go through the attributes
    var attributes = element.attributes;
    
    if (!attributes) {
      return namespaces;      
    }
    
    for (var i=0; i<attributes.length; i++) {
        if (attributes[i].name.substring(0,5) == "xmlns") {
            if (!copied_yet) {
                namespaces = RDFA.object_copy(namespaces);
                copied_yet = 1;
            }

            if (attributes[i].name == "xmlns") {
                namespaces[''] = new RDFA.Namespace('',attributes[i].value);
                continue;
            }

            if (attributes[i].name.substring(5,6) != ':') {
              continue;              
            }

            var prefix = attributes[i].name.substring(6);
            var uri = attributes[i].value;
            
            namespaces[prefix] = new RDFA.Namespace(prefix,uri);
        }
    }

    return namespaces;
};

RDFA.associateElementAndSubject = function(element,subject,namespaces) {
   RDFA.elements[subject] = element;
   
   element._RDFA_SUBJECT = subject;
   element._RDFA_NAMESPACES = RDFA.object_copy(namespaces);
};

// this function takes a given element in the DOM tree and:
//
// - determines RDFa statements about this particular element and adds the triples.
// - recurses down the DOM tree appropriately
//
// the namespaces is an associative array where the default namespace is namespaces['']
//
RDFA.traverse = function (element, inherited_about, explicit_about, namespaces) {
    // are there namespaces declared
    namespaces = RDFA.add_namespaces(element,namespaces);

    // special case the BODY
    if (element.nodeName == 'BODY')
      RDFA.associateElementAndSubject(element, document.location, namespaces);

    // determine the current about
    var current_about = inherited_about;
    var children_about = null;
    var element_to_callback = element;

    // do we explicitly override it?
    var new_explicit_about = null;
    if (RDFA.getNodeAttributeValue(element,'about')) {
        new_explicit_about = RDFA.getNodeAttributeValue(element,'about');
        current_about = new_explicit_about;
	      RDFA.associateElementAndSubject(element, new_explicit_about, namespaces);
    }

    // determine the object
    var el_object = null;
    if (RDFA.getNodeAttributeValue(element,'href'))
      el_object = RDFA.getNodeAttributeValue(element,'href');
    if (RDFA.getNodeAttributeValue(element,'src'))
      el_object = RDFA.getNodeAttributeValue(element,'src');
    
    // LINK
    if (element.nodeName == 'link' || element.nodeName == 'meta') {
      current_about = RDFA.get_special_subject(element.parentNode);
      element_to_callback = element.parentNode;
    }

    // REL attribute
    var rel_attr = RDFA.getNodeAttributeValue(element,'rel');
    if (rel_attr) {
        var rels = rel_attr.split(' ');

        for (var i=0; i<rels.length; i++) {
          if (rels[0].indexOf(':') == -1)
            continue;

          // what if there is no object yet?
          if (!el_object) {
            el_object = RDFA.get_special_subject(element);
            children_about = el_object;
    	      RDFA.associateElementAndSubject(element, children_about, namespaces);
          }

          var triple = RDFA.add_triple(current_about, RDFA.CURIE.parse(rel_attr,namespaces), el_object, false);
          RDFA.CALLBACK_NEW_TRIPLE_WITH_URI_OBJECT(element_to_callback, triple);
        }
    }      

    // REV attribute
    if (RDFA.getNodeAttributeValue(element,'rev')) {
      // what if there is no object yet?
      if (!el_object) {
        el_object = RDFA.get_special_subject(element);
        children_about = el_object;
      }

      var triple = RDFA.add_triple(el_object, RDFA.CURIE.parse(RDFA.getNodeAttributeValue(element,'rev'),namespaces), current_about, false);
      RDFA.CALLBACK_NEW_TRIPLE_WITH_URI_OBJECT(element_to_callback, triple);
    }
    
    // PROPERTY attribute
    if (RDFA.getNodeAttributeValue(element,'property')) {
        var content = RDFA.getNodeAttributeValue(element,'content');
	
        if (!content)
            content = element.textContent;
            
        if (!content)
            content = element.innerHTML;
	
        var triple = RDFA.add_triple(current_about, RDFA.CURIE.parse(RDFA.getNodeAttributeValue(element,'property'),namespaces), content, true);
        RDFA.CALLBACK_NEW_TRIPLE_WITH_LITERAL_OBJECT(element_to_callback, triple);
    }

    // about for the children
    if (children_about) {
      new_explicit_about = children_about;
      current_about = children_about;
    }

    // recurse down the children
    var children = element.childNodes;
    for (var i=0; i < children.length; i++) {
	    RDFA.traverse(children[i], current_about, new_explicit_about, namespaces);
    }
};

RDFA.getTriples = function(subject, predicate) {
    subject = RDFA.CURIE.getAbsoluteURI(subject);
    if (!RDFA.triples[subject])
        return null;

    return RDFA.triples[subject][predicate.uri()];
};

RDFA.parse = function(parse_document) {
    parse_document = parse_document || document;
    
    // by default, the current namespace for CURIEs is the current page
    // we remove the hash if there is one
    var location = document.location;
    var default_ns = new RDFA.Namespace('',location);
    var namespaces = new Object();

    // set up default namespace
    namespaces[''] = default_ns;

    // hGRDDL for XHTML1 special needs
    RDFA.GRDDL.addProfile(__RDFA_BASE + 'xhtml1-hgrddl.js');
    
    // do the profiles, and then traverse
    RDFA.GRDDL.runProfiles(function() {
        RDFA.traverse(parse_document, document.location.href, null, namespaces);

        RDFA.CALLBACK_DONE_PARSING();
    });
};

RDFA.log = function(str) {
    alert(str);
};


RDFA.CALLBACK_DONE_LOADING();

