from xml.sax.saxutils import quoteattr, escape

from rdflib.URIRef import URIRef
from rdflib.Literal import Literal
from rdflib.BNode import BNode
from rdflib import exception

def uniq(sequence):
    """removes duplicate strings from the sequence."""
    set = {}
    map(set.__setitem__, sequence, [])
    return set.keys()

NAMESTART = u'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_'
NAMECHARS = NAMESTART + u'0123456789-.'

def split_predicate(predicate):
    predicate = predicate
    length = len(predicate)
    for i in xrange(1, length):
        if not predicate[-i-1] in NAMECHARS:
            for j in xrange(-1-i, length):
                if predicate[j] in NAMESTART:
                    return (predicate[:j], predicate[j:])
    return ("", predicate)

    
class Serializer(object):
    def __init__(self):
        super(Serializer, self).__init__()
        
    def output(self, stream):
        self.namespaces = {}
        self.namespaces["http://www.w3.org/1999/02/22-rdf-syntax-ns#"] = 'rdf'
        self.namespaces["http://www.w3.org/2000/01/rdf-schema#"] = 'rdfs'
        self.namespaceCount = 0
        self.serialized = {}

        for predicate in uniq(self.predicates()):
            uri = split_predicate(predicate)[0]
            if not self.namespaces.has_key(uri):
                self.namespaceCount += 1
                prefix = "n%s" % self.namespaceCount
                self.namespaces[uri] = prefix

        write = stream.write
        self.write = write

        # TODO: character encoding?
        write( """<?xml version="1.0" encoding="UTF-8"?>\n""" )
        write( "<rdf:RDF\n" )
        for uri in self.namespaces:
            write( "   xmlns:%s=\"%s\"\n" % (self.namespaces[uri], uri))
        write( ">\n" )

        for subject in self.subjects():
            # Only write out those which we can not inline
            if self.exists(None, None, subject):
                if self.exists(subject, None, subject):
                    self.subject(subject, 1)
            else:
                self.subject(subject, 1)
        
        # write out anything that has not yet been reached
        for subject in self.subjects():
            self.subject(subject, 1)
        write( "</rdf:RDF>\n" )

    def subject(self, subject, depth=1):
        if not subject in self.serialized:
            self.serialized[subject] = 1            
            indent = "  " * depth 
            write = self.write
            if isinstance(subject, BNode):
                # TODO: add check to see if we need to add rdf:nodeID
                write( '%s<rdf:Description rdf:nodeID="%s">\n' %
                       (indent, subject))
                for predicate, object in self.predicate_objects(subject):
                    self.predicate(predicate, object, depth+1)
                write( "%s</rdf:Description>\n" % indent)               
            else:
                uri = quoteattr(subject)             
                if self.exists(subject, None, None):
                    write( "%s<rdf:Description rdf:about=%s>\n" %
                           (indent, uri))
                    for predicate, object in self.predicate_objects(subject):
                        self.predicate(predicate, object, depth+1)
                    write( "%s</rdf:Description>\n" % indent)
                else:
                    write( "%s<rdf:Description rdf:about=%s/>\n" %
                           (indent, uri))

    def predicate(self, predicate, object, depth=1):
        indent = "  " * depth
        write = self.write
        namespace, localName = split_predicate(predicate)   
        prefix = self.namespaces[namespace]

        if isinstance(object, Literal):
            write("%s<%s:%s>%s</%s:%s>\n" %
                  (indent, prefix, localName,
                   escape(object), prefix, localName) )
        elif object in self.serialized:
            if isinstance(object, BNode):
                write('%s<%s:%s rdf:nodeID="%s"/>\n' %
                      (indent, prefix, localName, object))
            else:
                write("%s<%s:%s rdf:resource=%s/>\n" %
                      (indent, prefix, localName, quoteattr(object)))
        else:
            write("%s<%s:%s>\n" % (indent, prefix, localName))
            self.subject(object, depth+1)
            write("%s</%s:%s>\n" % (indent, prefix, localName))
