	// The RDF tokeniser goes here.
	// This will return the following types:
	// doctype (.current = doctype text)
	// Opentag (.current = tagname)
	// attribute (.current = attname, .value = attvalue)
	// opentagend
	// literal (.current = literaltext)
	// closetag (.current = tagname)
	// The tokeniser eats whitespace automatically,
	// and holds enough state to remember if it is
	// looking for attributes.

	function retSpace() { return " "; }

	function RDFTokeniser(string) {
		// this.remainder = (string.split("\n")).join(" ");
		this.remainder = string.replace(/\015/g, retSpace).replace(/\012/g, retSpace).replace(/\011/g, retSpace);
		this.current = null;
		this.type = null;	// "eof", "opentag", "attribute", "opentagend", "literal", "closetag"
		this.state = "outer";	// "outer" or "inner" (inside opentag)
		this.consume =
			function () {
				if (this.type == "eof") return;
				var r;
				// Eat any leading WS
				while (true) {
					r = this.remainder.match(/^\s+(.*)$/);
					if (r) {
						this.remainder = r[1];
						continue;
					}
					r = this.remainder.match(/^\<\!--(.*)$/);
					if (r) {
						var p = r[1].indexOf("-->");
						this.remainder = r[1].substring(p + 3);
						continue;
					}
					break;
				}

				if (this.remainder == "") {
					this.current = null;
					this.type = "eof";
					return;
				}

				if (this.state == "outer") {
					// outside an opening tag

					r = this.remainder.match(/^\<\?([^\>]*)\?\>(.*)$/);
					if (r) {
						this.remainder = r[2];
						this.current = r[1];
						this.type = "doctype";
						return;
					}

					// Closing tag?
					r = this.remainder.match(/^\<\/\s*([^\s]*)\s*\>(.*)$/);
					if (r) {
						this.remainder = r[2];
						this.current = r[1];
						this.type = "closetag";
						return;
					}

					// Opening tag?
					r = this.remainder.match(/^\<\s*([^\/]\s*[^\s\>]*)\s*(.*)$/);
					if (r) {
						this.remainder = r[2];
						this.current = r[1];
						this.type = "opentag";
						this.state = "inner";
						return;
					}

					// Literal, I guess...
					r = this.remainder.match(/([^\<]*)(\<.*)$/);
					if (r) {
						this.remainder = r[2];
						this.current = trim(r[1]);
						this.type = "literal";
						return;
					}

				} else {	// if (state="outer")
					// Inside an opening tag
					// Empty element? (fake close tag)
					r = this.remainder.match(/^\/\>(.*)$/);
					if (r) {
						this.remainder = "</>" + r[1];
						this.current = "";
						this.type = "opentagend";
						this.state = "outer";
						return;
					}

					// End of opentag?
					r = this.remainder.match(/^\>(.*)$/);
					if (r) {
						this.remainder = r[1];
						this.current = "";
						this.type = "opentagend";
						this.state = "outer";
						return;
					}

					// attribute value
					r = this.remainder.match(/^([^\s=]+)=\"([^\"]*)\"(.*)$/);
					if (r) {
						this.remainder = r[3];
						this.current = r[1];
						this.value = r[2];
						this.type = "attribute";
						return;
					}

					// attribute value using single quotes
					r = this.remainder.match(/^([^\s=]+)=\'([^\']*)\'(.*)$/);
					if (r) {
						this.remainder = r[3];
						this.current = r[1];
						this.value = r[2];
						this.type = "attribute";
						return;
					}


				}	// --if (state="outer")

				this.current = null;
				this.type = "eof";
				return;
			};
		this.consume();	// Load up the first token.
	}

	function trim(string) {
		var offs = string.length;
		while (offs > 0) {
			if (string[offs-1] != " ") break;
			offs --;
		}
		return string.substring(0, offs);
	}

	var RDFParserNS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";

	// The RDF parser
	function parseRDF(tokenstream, model) {
		// Eat up tokens from the stream and
		// make assertions into the model.

		// RDF ::-> doctype <rdf:RDF nsstuff> description* </rdf:RDF>

		if (tokenstream.type == "doctype") tokenstream.consume();

		if (tokenstream.type != "opentag")
			return "rdf:RDF expected";

		// Parse NS declarations (only permit them here)
		var opentag = tokenstream.current;
		tokenstream.consume();

		while (tokenstream.type == "attribute") {
			var r = tokenstream.current.split(":");
			if (r[0] == "xmlns") {
				if (r.length == 1) {
					model.setDefaultNS(tokenstream.value);
				} else if (r.length == 2) {
					model.setNS(r[1], tokenstream.value);
				} else {
					return "too many parts in NS declaration";
				}
			} else {
				debug("rdf:RDF unknown attribute", 99);
				var i;
				for (i in r) {
					debug(i + "th split value is "+r[i], 2);
				}
			}
			// return null;
			tokenstream.consume();
		}

		if (tokenstream.type != "opentagend") return "> expected";
		if (model.expandNS(opentag) != RDFParserNS_RDF+"RDF") return "<rdf:RDF> expected after NS qualification";

		tokenstream.consume();
		var result;
		while ((result = parseResourceDef(tokenstream, model)) && result.uri);
		if (result) return result;

		if (tokenstream.type != "closetag" || model.expandNS(tokenstream.current) != RDFParserNS_RDF+"RDF")
			return "/rdf:RDF expected";

		tokenstream.consume();
		return null;		// Success!
	}

	function parsePropertyList(tokenstream, model, resource) {
		// propertylist{resource} ::-> kleeneClosureOf
		//			<propertyname [rdf:resource=uri] />
		//		||	<propertyname> Literal </propertyname>
		//		||	<propertyname> resourcedef </propertyname>
		while (tokenstream.type == "opentag") {
			var thisProperty = model.expandNS(tokenstream.current);
			tokenstream.consume();
			var resourceVal = null;
			if (tokenstream.type == "attribute") {
				if (tokenstream.current == "resource" || model.expandNS(tokenstream.current)==RDFParserNS_RDF+"resource") {
					resourceVal = model.makeResource(model.expandNS(tokenstream.value));
					tokenstream.consume();
				} /* else if (model.expandNS(tokenstream.current) == RDFParserNS_RDF+"ID") {
					resourceVal = model.makeResource("location:#" + tokenstream.value);
					tokenstream.consume();
				} */
			}

			if (tokenstream.type != "opentagend") return "> expected in parsePropertyList";
			tokenstream.consume();
			if (resourceVal) {
				// First possibility:
				resource.setProperty(thisProperty, resourceVal);
				if (tokenstream.type != "closetag") return "parsePropertyList: rdf:resource=uri must be empty";
				tokenstream.consume();
				continue;
			}
			if (tokenstream.type == "literal") {
				// second possibility
				resource.setProperty(thisProperty, tokenstream.current);
				tokenstream.consume();
				if (tokenstream.type != "closetag" ||
					(model.expandNS(tokenstream.current) != thisProperty
					&& tokenstream.current != ""))
					return "parsePropertyList: expected closing "+thisProperty+" after literal";
				tokenstream.consume();
				continue;
			} else {
				// Third possibility
				resourceVal = parseResourceDef(tokenstream, model);
				if (!resourceVal) return "resourceVal failed from parsePropertyList";
				resource.setProperty(thisProperty, resourceVal);
				if (tokenstream.type != "closetag" ||
					(model.expandNS(tokenstream.current) != thisProperty
					&& tokenstream.current != ""))
					return "parsePropertyList: expected closing "+thisProperty+" after literal";
				tokenstream.consume();
				continue;
			}
		}

		return null;
	}

	function parseResourceDef(tokenstream, model) {
		// resourceDef{returns resource(uri)} ::->
		//			<typename [rdf:about=uri]>
		//				[propertylist{resource(uri)}]
		//			</typename>
		// 		||	<rdf:Description [rdf:about = uri]>
		//				propertylist{resource(uri)}
		//			</rdf:Description>
		// on error: returns an error string (so it won't have a .uri) (note change here!)
		// On completion: returns resource, or null if there wasn't one.

		if (tokenstream.type != "opentag")
			return null;
		var thisType = model.expandNS(tokenstream.current);
		tokenstream.consume();

		var resource;
		if (tokenstream.type == "attribute") {
			if (tokenstream.current == "about" || model.expandNS(tokenstream.current) == RDFParserNS_RDF+"about") {
				// Technically, this next line shouldn't be here, but I add
				// it 'cause it's a frigging mistake not to.
				resource = model.makeResource(model.expandNS(tokenstream.value));
				tokenstream.consume();
			}
		} else {
			resource = model.makeAnonResource();
		}

		if (tokenstream.type != "opentagend")
			return "resourceDef for " + thisType +" expected to be closed";
		tokenstream.consume();

		if (!resource)
			return "resourceDef for " + thisType +" contains bad attributes";

		var result = parsePropertyList(tokenstream, model, resource);
		if (result) return result;

		if (tokenstream.type != "closetag")
			return "</" + (thisType ? thisType : "rdf:Description" ) + "> expected";

		if (tokenstream.current != "" && model.expandNS(tokenstream.current) != thisType)
			return "</" + thisType + "> expected";
		tokenstream.consume();

		if (thisType != RDFParserNS_RDF+"Description")
			resource.setProperty(RDFParserNS_RDF+"type", model.makeResource(thisType));
		return resource;
	}

	// The RDF Model bits and pieces.
	// This lets us assert triples into the model;
	// it also keeps track of NS expansions and supports
	// the expansion of a given URI according to current NSs.
	function Model() {
		this.ns = [];
		this.defaultNS = null;
		this.setDefaultNS = function (ns) { this.defaultNS = ns; };
		this.setNS = function (ns, expansion) { this.ns[ns] = expansion; };
		this.dumpNS = function () {
			var i;
			// if (this.defaultNS) debug ("Default NS: " + this.defaultNS, 1);
			/*
			for (i in this.ns)
				debug("xmlns:" + i + "=\"" + this.ns[i] + "\"", 1);
			*/
		};
		this.expandNS = function (uri) {
			var r = uri.split(":");
			if (r.length == 1) {
				if (this.defaultNS)
					return this.defaultNS + uri;
				else
					return uri;
			} else if (r.length == 2) {
				if (this.ns[r[0]])
					return this.ns[r[0]] + r[1];
				else
					return uri;
			} else {
				return uri;
			}
		};
		this.db = [];
		this.getResource = function (uri) { return this.db[uri]; };
		this.anonymiser = 0;
		this.makeResource = function (uri) {
			var r = this.getResource(uri);
			if (r) return r;
			return this.db[uri] = new Resource(uri);
		};
		this.makeAnonResource = function () {
			var uri = "anon:" + (this.anonymiser ++);
			var r = this.getResource(uri);
			if (r) return r;
			return this.db[uri] = new Resource(uri);
		};
		this.assert = function (s_uri, p_uri, o) {
			// When we get this far, we don't need s to already be made.
			// o, however, should be so if it's a resource (don't need
			// anything similar for literals)
			var s_res = this.makeResource(s_uri);
			s_res.setProperty(p_uri, o);
		};
		this.forEach = function (callMe) {
			for (var i in this.db) callMe(this.db[i]);
		};
	}

	function Resource(uri) {
		this.uri = uri;
		this.property = [];
		this.setProperty = function (prop, value) {
			var propset = this.property[prop];
			if (!propset) {
				this.property[prop] = propset = new Propset();
			}
			propset.addValue(value);
		};
		this.getPropertySet = function (uri) { return this.property[uri]; }
		this.dumpProperties = function () {
			debug(this.uri + ":", 99);
			var p, pp, v;
			for (p in this.property) {
				debug ("  --"+p+"-->", 99);
				pp = this.property[p];
				for (v=0; v<pp.length; v++) {
					debug ("    " + (pp[v].uri ? pp[v].uri : "\""+pp[v]+"\""), 99);
				}
			}
		};
	}

	function Propset() {
		this.length = 0;
		this.addValue = function (value) {
			var i;
			for (i = 0; i<this.length; i++)
				if (this[i] == value) return;
			this[this.length++] = value;
		};
		this.forEach = function (callMe) {
			for (var i = 0; i<this.length; i++) callMe(this[i]);
		};
	}

