"""xmltramp: Make XML documents easily accessible."""

__version__ = "2.0b"
__author__ = "Aaron Swartz"
__credits__ = "Many thanks to pjz, bitsko, and DanC."
__copyright__ = "(C) 2003 Aaron Swartz. GNU GPL 2."

if not hasattr(__builtins__, 'True'): True, False = 1, 0
def isstr(f): return isinstance(f, type('')) or isinstance(f, type(u''))
def islst(f): return isinstance(f, type(())) or isinstance(f, type([]))

class Element:
	def __init__(self, name, attrs=None, children=None, prefixes=None):
		if islst(name) and name[0] == None: name = name[1]
		if attrs:
			na = {}
			for k in attrs.keys():
				if islst(k) and k[0] == None: na[k[1]] = attrs[k]
				else: na[k] = attrs[k]
			attrs = na
		
		self._name = name
		self._attrs = attrs or {}
		self._dir = children or []
		
		prefixes = prefixes or {}
		self._prefixes = dict(zip(prefixes.values(), prefixes.keys()))
		
		if prefixes: self._dNS = prefixes.get(None, None)
		else: self._dNS = None
	
	def __repr__(self, recursive=0, multiline=0, inprefixes=None):		 
		def qname(name, inprefixes): 
			if islst(name):
				if inprefixes[name[0]] is not None:
					return inprefixes[name[0]]+':'+name[1]
				else:
					return name[1]
			else:
				return name
		
		def arep(a, inprefixes):
			out = ''
			for k in a.keys():
				out += ' ' + qname(k, inprefixes)+ '="' + a[k] + '"'

			for p in self._prefixes.keys():
				if not p in inprefixes.keys():
					out += ' xmlns'
					if self._prefixes[p]: out += ':'+self._prefixes[p]
					out += '="'+p+'"'
					inprefixes[p] = self._prefixes[p]
			return out
		
		inprefixes = inprefixes or {}
		
		# need to call first to set inprefixes:
		attributes = arep(self._attrs, inprefixes) 
		out = '<' + qname(self._name, inprefixes) 
		if recursive: out += attributes 
		out += '>'

		if recursive:
			content = 0
			for x in self._dir: 
				if isinstance(x, Element): content = 1
				
			pad = '\n' + ('\t' * recursive)
			for x in self._dir:
				if multiline and content: out +=  pad 
				if isstr(x): out += x
				else:
					out += x.__repr__(recursive+1, multiline, inprefixes.copy())
			if multiline and content: out += '\n' + ('\t' * (recursive-1))
		else:
			out += '...'
		
		out += '</'+qname(self._name, inprefixes)+'>'
			
		return out
	
	def __str__(self):
		text = ''
		for x in self._dir:
			text += str(x)
		return ' '.join(text.split())
	
	def __getattr__(self, n):
		if n[0] == '_': raise KeyError, "Use foo['"+n+"'] to access the child element."
		if self._dNS: n = (self._dNS, n)
		for x in self._dir:
			if isinstance(x, Element) and x._name == n: return x
		raise KeyError

	def __setattr__(self, n, v):
		if n[0] == '_':
			self.__dict__[n] = v
		else:
			self[n] = v
		
	def __hasattr__(self, n):
		for x in self._dir:
			if isinstance(x, Element) and x._name == n: return True
		return False

	def __getitem__(self, n):
		if isinstance(n, type(0)): # d[1] == d._dir[1]
			return self._dir[n]
		elif isinstance(n, slice(0).__class__):
			# paranoid slice (i.e. copy)
			if n.start == 0: return self._dir[:]
			
			# d['foo':] == all <foo>s
			if self._dNS and not islst(n): n = (self._dNS, n)
			out = []
			for x in self._dir:
				if isinstance(x, Element) and x._name == n.start: out.append(x) 
			return out
		else: # d['foo'] == first <foo>
			if self._dNS and not islst(n): n = (self._dNS, n)
			for x in self._dir:
				if isinstance(x, Element) and x._name == n: return x
			raise KeyError
	
	def __setitem__(self, n, v): 
		if isinstance(n, type(0)): # d[1] = "foo" or d[1] = Element("foo")
			self._dir[n] = v
		elif isinstance(n, slice(0).__class__):
			if self._dNS and not islst(n): n = (self._dNS, n)
			done = False
			for ei in range(len(self._dir)):
				e = self._dir[ei]
				if isinstance(e, Element) and e._name == n.start:
					self._dir[ei]._dir = [v]
					done = True
			if not done:
				raise KeyError
		else: # d["foo"] = "bar"
			if self._dNS and not islst(n): n = (self._dNS, n)
			done = False
			for ei in range(len(self._dir)):
				e = self._dir[ei]
				if isinstance(e, Element) and e._name == n:
					self._dir[ei]._dir = [v]
					done = True
					break
			if not done:
				nv = Element(n)
				nv._dir.append(v)
				self._dir.append(nv)
	
	def __call__(self, _get=None, **_set): 
		if _get is not None:
			return self._attrs[_get]
		if _set is not {}:
			for k in _set: self._attrs[k] = _set[k]
		return self._attrs

class Namespace:
	def __init__(self, uri): self.__uri = uri
	def __getattr__(self, n): return (self.__uri, n)
	def __getitem__(self, n): return (self.__uri, n)

from xml.sax.handler import EntityResolver, DTDHandler, ContentHandler, ErrorHandler

class Seeder(EntityResolver, DTDHandler, ContentHandler, ErrorHandler):
	def __init__(self):
		self.stack = []
		self.ch = ''
		self.prefixes = {}
		ContentHandler.__init__(self)
		
	def startPrefixMapping(self, prefix, uri): self.prefixes[prefix] = uri
	def endPrefixMapping(self, prefix): del self.prefixes[prefix]
	
	def startElementNS(self, name, qname, attrs):
		ch = self.ch.lstrip(); self.ch = ''	
		if ch: self.stack[-1]._dir.append(ch)
		
		self.stack.append(Element(name, attrs, prefixes=self.prefixes.copy()))
	
	def characters(self, ch):
		self.ch += ch.encode('utf8')
	
	def endElementNS(self, name, qname):
		ch = self.ch.rstrip(); self.ch = ''
		if ch: self.stack[-1]._dir.append(ch)
	
		element = self.stack.pop()
		if self.stack:
			self.stack[-1]._dir.append(element)
		else:
			self.result = element
			if element._dir and isstr(element[0]): 
				element[0] = element[0].lstrip()

from xml.sax import make_parser
from xml.sax.handler import feature_namespaces

def seed(fileobj):
	seeder = Seeder()
	parser = make_parser()
	parser.setFeature(feature_namespaces, 1)
	parser.setContentHandler(seeder)
	parser.parse(fileobj)
	return seeder.result

def parse(text):
	from StringIO import StringIO
	return seed(StringIO(text))

def load(url): 
	import urllib
	return seed(urllib.urlopen(url))

if __name__ == "__main__":
	# @@ need more repr tests
	parse('<doc>a<baz>f<b>o</b>ob<b>a</b>r</baz>a</doc>').__repr__(1,1) == \
	  '<doc>\n\ta<baz>\n\t\tf<b>o</b>ob<b>a</b>r\n\t</baz>a\n</doc>'
	
	assert str(parse("<doc />")) == ""
	assert str(parse("<doc>I <b>love</b> you.</doc>")) == "I love you."
	assert parse("<doc>\nmom\nwow\n</doc>")[0] == "mom\nwow"
	assert str(parse('<bing>  <bang> <bong>center</bong> </bang>  </bing>')) == "center"
	assert str(parse('<doc>π</doc>')) == "π"
	
	d = Element('foo', attrs={'foo':'bar'}, children=['hit with a', Element('bar'), Element('bar')])
	
	try: 
		d._doesnotexist
		raise "ExpectedError", "but found success. Damn."
	except KeyError: pass
	assert d.bar._name == 'bar'
	try:
		d.doesnotexist
		raise "ExpectedError", "but found success. Damn."
	except KeyError: pass
	
	assert hasattr(d, 'bar') == True
	
	assert d('foo') == 'bar'
	d(silly='yes')
	assert d('silly') == 'yes'
	
	assert d[0] == 'hit with a'
	d[0] = 'ice cream'
	assert d[0] == 'ice cream'
	assert len(d[:]) == len(d._dir)
	assert len(d['bar':]) == 2
	assert d['bar']._name == 'bar'
	
	doc = Namespace("http://example.org/bar")
	bbc = Namespace("http://example.org/bbc")
	dc = Namespace("http://purl.org/dc/elements/1.1/")
	d = parse("""<doc xmlns="http://example.org/bar" 
	                  xmlns:dc="http://purl.org/dc/elements/1.1/"
	                  xmlns:bbc="http://example.org/bbc">
		<author>John Polk</author>
		<dc:creator>John Polk</dc:creator>
		<bbc:show bbc:station="4">Buffy</bbc:show>
	</doc>""")
	assert str(d.author) == str(d['author']) == "John Polk"
	assert d.author._name == doc.author
	assert str(d[dc.creator]) == "John Polk"
	assert d[dc.creator]._name == dc.creator
	assert d[bbc.show](bbc.station) == "4"

	assert repr(d) == "<doc>...</doc>"
	assert d.__repr__(1) == '<doc xmlns:bbc="http://example.org/bbc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://example.org/bar"><author>John Polk</author><dc:creator>John Polk</dc:creator><bbc:show bbc:station="4">Buffy</bbc:show></doc>'
	assert d.__repr__(1,1) == '<doc xmlns:bbc="http://example.org/bbc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://example.org/bar">\n\t<author>John Polk</author>\n\t<dc:creator>John Polk</dc:creator>\n\t<bbc:show bbc:station="4">Buffy</bbc:show>\n</doc>'	
