#!/usr/local/bin/pythonw
# coding: iso-8859-1
# Copyright © 2009 by Amos Newcombe
# 
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.

'''Extend xmllib to xhtml.
'''

from pprint import pprint
import os
from xmllib import Document, Element, SSDeclaration, Namespace, Copyright

class XhtmlNamespace(Namespace):
	
	def __init__(self, **d):
		Namespace.__init__(self, **d)
		self['uri'] = 'http://www.w3.org/1999/xhtml'

class XhtmlDocument(Document):
	
	def __init__(self, **d):
# 		print '%s(XhtmlDocument)(**%s)' % (self.__class__.__name__, self.legibleD(d))
		try: d['lsStylesheet'] = d.get('lsStylesheet', []) + [d['sStylesheet']]
		except KeyError: pass
		d['lsStylesheet'] = [
			os.path.join(d.get('pathHome', ''), s) 
			for s in d.get('lsStylesheet', [])
		]
# 		print d['lsStylesheet']
		if isinstance(self.dDoctypes.get(d.get('sDoctype')), dict):
			d['dDoctype'] = self.dDoctypes[d['sDoctype']]
# 			print 'dDoctype set from table'
		try: d['lHead'] = self.Head(d['dHead']) + d.get('lHead', [])
		except KeyError: pass
# 		print 'lHead =', d['lHead']
# 		print 'dHead =', d.get('dHead')
		Document.__init__(self, 
			lxmlMisc = d.get('lxmlMisc', []) + [
				SSDeclaration(sHref=s)
				for s in d.get('lsStylesheet', [])
			], 
			xmlElem = XhtmlElement(sTag = 'html', 
				nsApplied = Namespace(uri='http://www.w3.org/1999/xhtml'),
				ltpAttr = [
					('xml:lang', 'en'),
					(    'lang', 'en'),
				], 
				lContents=[
					XhtmlElement(sTag='head', lContents = d.get('lHead', [])), 
					XhtmlElement(sTag='body', lContents = d.get('lBody', []),
						ltpAttr = d.get('ltpBodyAttr', []),
					),
				], 
			),
			**d
		)
		self['xmlElem']['lContents'][0].AddContents(*[ # To the <head> element, add
			SSElement(href=s)                          # stylesheets
			for s in d.get('lsStylesheet', [])         # as supplied.
		])
	
	def Head(self, d):
		lxml = []
		for dT in [
			{'sTag': 'title', 'lContents': [d['title']]},
			{'sTag': 'meta' , 'ltpAttr'  : [
				('name'   , 'keywords'),
				('content', ','.join(d['lsKeywords'])),
			]},
			{'sTag': 'meta' , 'ltpAttr'  : [
				('name'   , 'description'),
				('content', d['description']),
			]},
			{'sTag': 'meta' , 'ltpAttr'  : [
				('name'   , 'copyright'),
				('content', Copyright(**d['dCopyright'])),
			]},
		]:
			try: lxml.append(XhtmlElement(**dT))
			except KeyError: pass
		return lxml
		
	def getHead(self):
		return self['xmlElem']['lContents'][0]['lContents']
	
	dDoctypes = {
		'xhtml': {        # http://www.w3.org/TR/xhtml1/#strict
			'sElem'    : 'html',
			'uriSystem': 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd',
			'idPublic' : '-//W3C//DTD XHTML 1.0 Strict//EN',
		},
		'xhtmlmathsvg': { # http://www.w3.org/TR/XHTMLplusMathMLplusSVG/
			'sElem'    : 'html',
			'uriSystem': 'http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd',
			'idPublic' : '-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN',
		},
	}

class XhtmlElement(Element):
	
	# ¤ Creation
	
	# These elements have a content model of EMPTY.
	lsEmptyContent = [
		'base', 'meta', 'link',                             # head elements
		'hr', 'br', 'param', 'img', 'area', 'input', 'col', # body elements
	]
	
	# ¤ Description
	
	def XmlCodeNonempty(self):
		'''Group self.lContents into sublists so that only non-inline elements start a new sublist.
		'''
# 		print 'XhtmlElement<%s>.XmlCodeNonempty()' % self['sTag']
		self.llContents = sublist(self.lContents, IsInline) # llContents
		return Element.XmlCodeNonempty(self)
	
	def XmlContent(self):
# 		print 'XhtmlElement<%s>.XmlContent()' % self['sTag']
		llOut = []
		for l in self.llContents:
			lOut = ['']
			for o in l:
				lLines = self.XmlLine(o)
				lOut[-1] += lLines[0]
				lOut.extend(lLines[1:])
			llOut.append(lOut)
		return llOut
	
	def XmlCodeEmpty(self):
		'''Render an empty element with minimized syntax only if it has an empty content model in the DOCTYPE.
		
		This is for compatibility with legacy browsers. See http://www.w3.org/TR/xhtml1/#C_3.
		'''
		if self['sTag'] in self.lsEmptyContent:
			return [self.EmptyTag()]
		else:
			return [self.OpenTag() + self.CloseTag()]
	
	# ¤ Description: XML tags
	
	def EmptyTag(self):
		'''Render empty element with minimized syntax with a space before "/>".
		
		This is for compatibility with legacy browsers. See http://www.w3.org/TR/xhtml1/#C_2.
		'''
		return '<'  + self.TagAttr() + ' />'

def sublist(l, f):
# 	print 'sublist([%d items], %s)' % (len(l), f.__name__)
	ll = []
	isTruePrev = False
	for o in l:
		isTrue = f(o)
		if not (ll and isTrue and isTruePrev):
			ll.append([])
		ll[-1].append(o)
		isTruePrev = isTrue
	return ll

# These elements are rendered is something other than in-line.

lsNotInline = [
	'title', 'meta', 'link', # head elements
	'html', 'body', 'div', 'p', 'center', 'hr', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ol', 'ul', 'dl', 'dt', 'dd', 'dir', 'menu', 'form', 'fieldset', 'frame', 'frameset', 'noframes', 'iframe', 'article', 'section', 'header', 'footer', 'nav', 'aside' # block elements
	'li', # list-item
	'head', 'script', 'keyword' # none
	'table', 'tr', 'thead', 'tbody', 'tfoot', 'col', 'colgroup', 'td', 'th', 'caption', # table elements
	'svg', # svg
]

def IsInline(xml):
# 	print 'IsInline(%s)' % xml
	# A string is always in-line for source code purposes.
	if isinstance(xml, str): return True
	# An element is in-line if its CSS display is not set to the default inline.
	if isinstance(xml, Element): return (xml['sTag'] not in lsNotInline)
	return False

class SSElement(XhtmlElement):
	
	def __init__(self, **d):
		XhtmlElement.__init__(self, sTag = 'link', ltpAttr = [
			('rel', 'stylesheet'),
			('href', d['href']),
		])

class Javascript(XhtmlElement):
	
	def __init__(self, **d):
		try: d['sScript'] = '\n'.join(d['lsScript'])
		except KeyError: pass
		XhtmlElement.__init__(self, sTag = 'script', ltpAttr = [
			('type', 'text/javascript')
		], lContents = [
			'\n//<![CDATA[\n' + d.get('sScript', '') + '\n//]]>\n'
		])

class Timestamp(Javascript):
	
	def __init__(self, **d):
		Javascript.__init__(self, sScript = 
			'document.body.appendChild(document.createTextNode((new Date()).toString()));'
		)

if __name__ == '__main__':
	
	sMsg = 'Hello, World!'
	
	sStylesheet = '''
h1 {
	color      : #000088;
	font-weight: 500;
	font-family: "Party LET", cursive;
	font-size  : 3em;
}
'''
	
	open('HelloWorld.html', 'w').write(str(XhtmlDocument(
		sStylesheet = '#InternalStyle',
		dHead = {'title': sMsg},
		lHead = [
			Element(sTag = 'style', lContents = [sStylesheet], ltpAttr = [
				('type', 'text/css'),
				('id'  , 'InternalStyle'),
			]),
		],
		lBody = [
			Element(sTag = 'h1'   , lContents = [sMsg]),
		],
	)))

