context: using System;
  using System.Collections;
  using System.IO;
  using System.Text;
  using System.Xml;
  
  using SemWeb;
  
  namespace SemWeb {
  	public class RdfXmlWriter : RdfWriter {
  		XmlWriter writer;
  		NamespaceManager ns = new NamespaceManager();
  		
  		XmlDocument doc;
  		bool initialized = false;
  		
  		Hashtable nodeMap = new Hashtable();
  		
  		long anonCounter = 0;
  		Hashtable anonAlloc = new Hashtable();
  		Hashtable nameAlloc = new Hashtable();
  		Hashtable nodeReferences = new Hashtable();
  		ArrayList predicateNodes = new ArrayList();
  		
  		static Entity rdftype = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
  		
  		public RdfXmlWriter(XmlDocument dest) { doc = dest; }
  		
  		public RdfXmlWriter(string file) : this(GetWriter(file)) { }
  
  		public RdfXmlWriter(TextWriter writer) : this(NewWriter(writer)) { }
  		
  		private static XmlWriter NewWriter(TextWriter writer) {
  			XmlTextWriter ret = new XmlTextWriter(writer);
  			ret.Formatting = Formatting.Indented;
  			ret.Indentation = 1;
  			ret.IndentChar = '\t';
  			ret.Namespaces = true;
  			return ret;
  		}
  		
  		public RdfXmlWriter(XmlWriter writer) {
  			this.writer = writer;
  		}
  		
  		private void Start() {
  			if (initialized) return;
  			initialized = true;
  			
  			if (doc == null) doc = new XmlDocument();
  			
  			doc.AppendChild(doc.CreateXmlDeclaration("1.0", null, null));
  			
  			string rdfprefix = ns.GetPrefix(NS.RDF);
  			if (rdfprefix == null) {
  				if (ns.GetNamespace("rdf") == null) {
  					rdfprefix = "rdf";
  					ns.AddNamespace(NS.RDF, "rdf");
  				}
  			}
  			
  			XmlElement root = doc.CreateElement(rdfprefix + ":RDF", NS.RDF);
  			foreach (string prefix in ns.GetPrefixes())
  				root.SetAttribute("xmlns:" + prefix, ns.GetNamespace(prefix));
  			
  			if (BaseUri != null)
  				root.SetAttribute("xml:base", BaseUri);
  
  			doc.AppendChild(root);
  		}
  		
  		public override NamespaceManager Namespaces { get { return ns; } }
  		
  		char[] normalizechars = { '#', '/' };
  		
  		private void Normalize(string uri, out string prefix, out string localname) {
  			if (uri == "")
  				throw new InvalidOperationException("The empty URI cannot be used as an element node.");
  				
  			if (BaseUri == null && uri.StartsWith("#")) {
  				// This isn't quite right, but it prevents dieing
  				// for something not uncommon in N3.  The hash
  				// gets lost.
  				prefix = "";
  				localname = uri.Substring(1);
  				return;
  			}
  		
  			if (ns.Normalize(uri, out prefix, out localname))
  				return;
  				
  			// No namespace prefix was registered, so come up with something.
  			
  			int last = uri.LastIndexOfAny(normalizechars);
  			if (last <= 0)
  				throw new InvalidOperationException("No namespace was registered and no prefix could be automatically generated for <" + uri + ">");
  				
  			int prev = uri.LastIndexOfAny(normalizechars, last-1);
  			if (prev <= 0)
  				throw new InvalidOperationException("No namespace was registered and no prefix could be automatically generated for <" + uri + ">");
  			
  			string n = uri.Substring(0, last+1);
  			localname = uri.Substring(last+1);
  			
  			// TODO: Make sure the local name (here and anywhere in this
  			// class) is a valid XML name.
  			if (Namespaces.GetPrefix(n) != null) {
  				prefix = Namespaces.GetPrefix(n);
  				return;
  			}
  			
  			prefix = uri.Substring(prev+1, last-prev-1);
  
  			if (prefix == "xmlns")
  				prefix = "";
  
  			
  			// Remove all non-xmlable (letter) characters.
  			StringBuilder newprefix = new StringBuilder();
  			foreach (char c in prefix)
  				if (char.IsLetter(c))
  					newprefix.Append(c);
  			prefix = newprefix.ToString();
  			
  			if (prefix.Length == 0) {
  				// There were no letters in the prefix!
  				prefix = "ns";
  			}
  			
  			if (Namespaces.GetNamespace(prefix) == null) {
  				doc.DocumentElement.SetAttribute("xmlns:" + prefix, n);
  				Namespaces.AddNamespace(n, prefix);
  				return;
  			}
  			
  			int ctr = 1;
  			while (true) {
  				if (Namespaces.GetNamespace(prefix + ctr) == null) {
  					prefix += ctr;
  					doc.DocumentElement.SetAttribute("xmlns:" + prefix, n);
  					Namespaces.AddNamespace(n, prefix);
  					return;
  				}
  				ctr++;
  			}
  		}
  		
  		private void SetAttribute(XmlElement element, string nsuri, string prefix, string localname, string val) {
  			XmlAttribute attr = doc.CreateAttribute(prefix, localname, nsuri);
  			attr.Value = val;
  			element.SetAttributeNode(attr);
  		}
  		
  		private XmlElement GetNode(Entity entity, string type, XmlElement context) {
  			string uri = entity.Uri;
  		
  			if (nodeMap.ContainsKey(entity)) {
  				XmlElement ret = (XmlElement)nodeMap[entity];
  				if (type == null) return ret;
  				
  				// Check if we have to add new type information to the existing node.
  				if (ret.NamespaceURI + ret.LocalName == NS.RDF + "Description") {
  					// Replace the untyped node with a typed node, copying in
  					// all of the children of the old node.
  					string prefix, localname;
  					Normalize(type, out prefix, out localname);
  					XmlElement newnode = doc.CreateElement(prefix + ":" + localname, ns.GetNamespace(prefix));
  					
  					foreach (XmlNode childnode in ret) {
  						newnode.AppendChild(childnode.Clone());
  					}
  					
  					ret.ParentNode.ReplaceChild(newnode, ret);
  					nodeMap[entity] = newnode;
  					return newnode;
  				} else {
  					// The node is already typed, so just add a type predicate.
  					XmlElement prednode = CreatePredicate(ret, NS.RDF + "type");
  					SetAttribute(prednode, NS.RDF, ns.GetPrefix(NS.RDF), "resource", type);
  					return ret;
  				}
  			}
  			
  			Start();			
  			
  			XmlElement node;
  			if (type == null) {
  				node = doc.CreateElement(ns.GetPrefix(NS.RDF) + ":Description", NS.RDF);
  			} else {
  				string prefix, localname;
  				Normalize(type, out prefix, out localname);
  				node = doc.CreateElement(prefix + ":" + localname, ns.GetNamespace(prefix));
  			}
  			
  			if (uri != null) {
  				string fragment;
  				if (!Relativize(uri, out fragment))
  					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "about", uri);
  				else if (fragment.Length == 0)
  					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "about", "");
  				else
  					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "ID", fragment.Substring(1)); // chop off hash
  			} else {
  				// The nodeID attribute will be set the first time the node is referenced,
  				// in case it's never referenced so we don't need to put a nodeID on it.
  			}
  			
  			if (context == null)
  				doc.DocumentElement.AppendChild(node);
  			else
  				context.AppendChild(node);
  			
  			nodeMap[entity] = node;
  			return node;
  		}
  		
  		private XmlElement CreatePredicate(XmlElement subject, Entity predicate) {
  			if (predicate.Uri == null)
  				throw new InvalidOperationException("Predicates cannot be blank nodes.");
  			
  			string prefix, localname;
  			Normalize(predicate.Uri, out prefix, out localname);
  			XmlElement pred = doc.CreateElement(prefix + ":" + localname, ns.GetNamespace(prefix));
  			subject.AppendChild(pred);
  			predicateNodes.Add(pred);
  			return pred;
  		}
  		
  		public override void Add(Statement statement) {
  			if (statement.AnyNull) throw new ArgumentNullException();
  		
  			XmlElement subjnode;
  			
  			bool hastype = statement.Predicate == rdftype && statement.Object.Uri != null;
  			subjnode = GetNode(statement.Subject, hastype ? statement.Object.Uri : null, null);
  			if (hastype) return;
  
  			XmlElement prednode = CreatePredicate(subjnode, statement.Predicate);
  			
  			if (!(statement.Object is Literal)) {
  				if (nodeMap.ContainsKey(statement.Object)) {
  					if (statement.Object.Uri != null) {
  						string uri = statement.Object.Uri, fragment;
  						if (Relativize(statement.Object.Uri, out fragment))
  							uri = fragment;
  						SetAttribute(prednode, NS.RDF, ns.GetPrefix(NS.RDF), "resource", uri);
  					} else {
  						SetAttribute(prednode, NS.RDF, ns.GetPrefix(NS.RDF), "nodeID", GetBNodeRef((BNode)statement.Object));
  						
  						// If this is the first reference to the bnode, put its nodeID on it, since we
  						// delayed setting that attribute until we needed it.
  						SetAttribute((XmlElement)nodeMap[statement.Object], NS.RDF, ns.GetPrefix(NS.RDF), "nodeID", GetBNodeRef((BNode)statement.Object));
  					}
  
  					// Track at most one reference to this entity as a statement object
  					if (nodeReferences.ContainsKey(nodeMap[statement.Object]))
  						nodeReferences[nodeMap[statement.Object]] = null;
  					else
  						nodeReferences[nodeMap[statement.Object]] = prednode;
  				} else {
  					GetNode((Entity)statement.Object, null, prednode);
  				}
  			} else {
  				Literal literal = (Literal)statement.Object;
  				if (literal.DataType != null && literal.DataType == "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral") {
  					prednode.InnerXml = literal.Value;
  					SetAttribute(prednode, NS.RDF, ns.GetPrefix(NS.RDF), "parseType", "Literal");
  				} else {
  					prednode.InnerText = literal.Value;
  					if (literal.Language != null)
  						prednode.SetAttribute("xml:lang", literal.Language);
  					if (literal.DataType != null)
  						SetAttribute(prednode, NS.RDF, ns.GetPrefix(NS.RDF), "datatype", literal.DataType);
  				}
  			}
  		}
  		
  		private string GetBNodeRef(BNode node) {
  			if (node.LocalName != null &&
  				(nameAlloc[node.LocalName] == null || (BNode)nameAlloc[node.LocalName] == node)
  				&& !node.LocalName.StartsWith("bnode")) {
  				nameAlloc[node.LocalName] = node; // ensure two different nodes with the same local name don't clash
  				return node.LocalName;
  			} else if (anonAlloc[node] != null) {
  				return (string)anonAlloc[node];
  			} else {
  				string id = "bnode" + (anonCounter++);
  				anonAlloc[node] = id;
  				return id;
  			}
  		}
  		
  		public override void Close() {
  			Start(); // make sure the document node was written
  
  			// For any node that was referenced by exactly one predicate,
  			// move the node into that predicate, provided the subject
  			// isn't itself!
  			foreach (DictionaryEntry e in nodeReferences) {
  				if (e.Value == null) continue; // referenced by more than one predicate
  				XmlElement node = (XmlElement)e.Key;
  				XmlElement predicate = (XmlElement)e.Value;
  				if (node.ParentNode != node.OwnerDocument.DocumentElement) continue; // already referenced somewhere
  				if (predicate.ParentNode == node) continue; // can't insert node as child of itself
  				node.ParentNode.RemoveChild(node);
  				predicate.AppendChild(node);
  				predicate.RemoveAttribute("resource", NS.RDF); // it's on the lower node
  				predicate.RemoveAttribute("nodeID", NS.RDF); // it's on the lower node
  				node.RemoveAttribute("nodeID", NS.RDF); // not needed anymore
  			}
  			
  			// Predicates that have rdf:Description nodes 1) with only literal
  			// properties (with no language/datatype/parsetype) can be
  			// condensed by putting the literals onto the predicate as
  			// attributes, 2) with no literal attributes but resource
  			// objects can be condensed by using parseType=Resource.
  			foreach (XmlElement pred in predicateNodes) {
  				// Is this a property with a resource as object?
  				if (!(pred.FirstChild is XmlElement)) continue; // literal value
  				if (pred.Attributes.Count > 0) continue; // parseType=Literal already
  				
  				// Make sure this resource is not typed
  				XmlElement obj = (XmlElement)pred.FirstChild;
  				if (obj.NamespaceURI + obj.LocalName != NS.RDF + "Description") continue; // untyped
  				
  				// And make sure it has no attributes already but an rdf:about
  				if (obj.Attributes.Count > 1) continue; // at most a rdf:about attribute
  				if (obj.Attributes.Count == 1 && obj.Attributes[0].NamespaceURI+obj.Attributes[0].LocalName != NS.RDF+"about") continue;
  				
  				// See if all its predicates are literal with no attributes.
  				bool allSimpleLits = true;
  				foreach (XmlElement opred in obj.ChildNodes) {
  					if (opred.FirstChild is XmlElement)
  						allSimpleLits = false;
  					if (opred.Attributes.Count > 0)
  						allSimpleLits = false;
  				}
  						
  				if (allSimpleLits) {
  					// Condense by moving all of obj's elements to attributes of the predicate,
  					// and turning a rdf:about into a rdf:resource, and then remove obj completely.
  					if (obj.Attributes.Count == 1)
  						SetAttribute(pred, NS.RDF, ns.GetPrefix(NS.RDF), "resource", obj.Attributes[0].Value);
  					foreach (XmlElement opred in obj.ChildNodes)
  						SetAttribute(pred, opred.NamespaceURI, ns.GetPrefix(opred.NamespaceURI), opred.LocalName, opred.InnerText);
  					pred.RemoveChild(obj);
  					
  					if (pred.ChildNodes.Count == 0) pred.IsEmpty = true;
  
  				} else if (obj.Attributes.Count == 0) { // no rdf:about
  					// Condense this node using parseType=Resource
  					pred.RemoveChild(obj);
  					foreach (XmlElement opred in obj.ChildNodes)
  						pred.AppendChild(opred.Clone());
  					SetAttribute(pred, NS.RDF, ns.GetPrefix(NS.RDF), "parseType", "Resource");
  				}
  			}
  
  			base.Close();
  			
  			if (writer != null) {
  				doc.WriteTo(writer);
  				//writer.Close();
  			}
  		}
  		
  		bool Relativize(string uri, out string fragment) {
  			fragment = null;
  			if (BaseUri == null) return false;
  			if (!uri.StartsWith(BaseUri) || uri.Length < BaseUri.Length) return false;
  
gt: 			string rel = uri.Substring(BaseUri.Length);