#!/usr/bin/python
import sys, scribus, shutil, os, os.path as path
from cssutils import css, stylesheets
import xml.parsers.expat

###! Methods for Parsing Scribus XML Documents !###

# Dictionaries to hold style sheets and other information
xml_parstyles = {} # Paragraph Styles
xml_charstyles = {} # Character Styles
xml_colors = {} # Document Colors
xml_document = {} # Document Properties
xml_pages = {} # Individual Page Structures

scribus_units = {scribus.UNIT_INCHES : 'in', scribus.UNIT_MILLIMETERS : 'mm', scribus.UNIT_PICAS : 'pc', scribus.UNIT_POINTS : 'pt'}

fontface_defs = [] # List of font faces that need to be defined
doc_unit = scribus_units[scribus.getUnit()] # Default unit of measurement


def start_element(name, attrs):
	'''Build dictionaries from SLA doc file.'''
	global styles, charstyles, document, pages
	if name == 'DODCUMENT':
		document = attrs
	if name == 'STYLE':
		try: xml_parstyles[attrs[u'NAME']] = attrs
		except: pass
	if name == 'CHARSTYLE':
		try: xml_charstyles[attrs[u'CNAME']] = attrs
		except: pass
	if name == 'PAGE':
		try: xml_pages[attrs[u'NUM']] = attrs
		except: pass
	if name == 'COLORS':
		try: xml_colors[attrs[u'NAME']] = attrs
		except: pass


def parseDoc():
	'''Get attributes from the current Scribus doc.'''
	# Set Up XML Document Parser
	parser = xml.parsers.expat.ParserCreate('UTF-8')
	parser.StartElementHandler = start_element
	try:
		# Save the current document
		docName = scribus.saveDoc()
		# Get the document name
		docName = unicode(scribus.getDocName())
		# Open and parse for nodes of interest
		file = open(docName, 'r')
		parser.ParseFile(file)
		file.close()
	except:
		print "Unable to load document"
		pass


def export_css():
	'''Export paragraph and character styles from a Scribus document for use in other files. Parses the XML file directly for additional information.'''

	# Show export message, prompt user to continue or cancel
	continue_export = scribus.messageBox('CSS Export Wizard', '<h3>Welcome to the CSS export wizard</h3><p>This plugin will help you to export the styles in your Scribus document to CSS. This will allow you to use them in eBook files or web pages.</p><p>For the export to proceed correctly, please ensure the following:</p><ol><li>Style names should be written without spaces or special characters (meaning that they should be written as "h1" rather than "Heading 1" or "heading one")</li><li>id attributes should be written with a hash in front of them (e.g., #)</li><li>class names should be written with a dot in front (e.g., . )</li></ol><p>Problems with CSS names may result in an incomplete export of the styles in your document or cause problems in your eBook or web page.', icon=1, button1=1, button2=2)

	# Prompt for file location and export properties
	if continue_export == 1:
		export_filename = scribus.fileDialog('Save CSS File', defaultname = 'ScribusCSS.css', issave = True, filter = "*.css")
		open_document = scribus.haveDoc()

	# Begin Export
	if (export_filename != '') & (open_document == True):

		# Create a stylesheet object
		css_stylesheet = css.CSSStyleSheet()
		parseDoc()
		
		# Process Character Styles
		for style in xml_charstyles:
			css_def = css.CSSStyleDeclaration()
			selector_list, css_def = processCharacterStyle(style, xml_charstyles[style], css_def)
			style_rule = css.CSSStyleRule(selector_list, css_def)
			css_stylesheet.add(style_rule)
		
		# Process Paragraph Styles
		for style in sorted(xml_parstyles):
			css_def = css.CSSStyleDeclaration()
			selector_list, css_def = processParagraphStyle(style, xml_parstyles[style], css_def)
			style_rule = css.CSSStyleRule(selector_list, css_def)
			css_stylesheet.add(style_rule)

			# Drop Caps
			css_drop = css.CSSStyleDeclaration()
			selector_list, css_drop = dropCap(style, xml_parstyles[style], css_drop)
			if selector_list != '':
				style_rule = css.CSSStyleRule(selector_list, css_drop)
				css_stylesheet.add(style_rule)
		
		# Create @font-face rules
		for font in fontface_defs:
			font_def = css.CSSStyleDeclaration()
			font_def = createFontfaceRule(font, font_def, export_filename)
			font_rule = css.CSSFontFaceRule(style=font_def)
			css_stylesheet.insertRule(font_rule)
		
		# Write the Style Information to Disk
		file = open(export_filename, 'w')
		file.writelines(css_stylesheet.cssText)
		file.close()


def processCharacterStyle(style_name, style_properties, css_def):
	'''Process character style. 1. Scan other character styles to determine if the style is a parent, collect font names for @font-face definitions. 2. Parse character style properties. Returns a list of selector names and list of CSS properties that they apply to.'''

	selector_list = []

	# Scan character styles
	for charstyle in xml_charstyles:
		charstyle_data = xml_charstyles[charstyle]
		
		# Check character style to see if is parent
		try:
			if (charstyle_data[u'CPARENT'] == style_name) & (charstyle != style_name):
				selector_list.append(charstyle)
		except: pass

		# Check @font-face
		try: fontface_defs.index(charstyle_data[u'FONT'])
		except:
			try: fontface_defs.append(charstyle_data[u'FONT'])
			except: pass

	# Scan paragraph styles to determine if the style is a parent
	for parstyle in xml_parstyles:
		parstyle_data = xml_parstyles[parstyle]
		
		# Check character style to see if is parent
		try:
			if (parstyle_data[u'CPARENT'] == style_name) & (parstyle != style_name) & (parstyle != 'Default Paragraph Style'):
				selector_list.append(parstyle)
			elif (parstyle_data[u'CPARENT'] == style_name) & (parstyle == 'Default Paragraph Style'):
				selector_list.append('p')
		except: pass

		# Check @font-face
		try: fontface_defs.index(parstyle_data[u'FONT'])
		except:
			try: fontface_defs.append(parstyle_data[u'FONT'])
			except: pass
	
	css_def = parseFont(style_properties, css_def)

	selector_list = sorted(selector_list)
	return ', '.join(selector_list), css_def

def processParagraphStyle(style_name, style_properties, css_def):
	'''Process paragraph style. 1. Scan other paragraph styles to determine if the style is a parent. 2. Parse paragraph style properties. Returns a list of selector names and list of CSS properties that they apply to.'''

	selector_list = []
	if style_name == 'Default Paragraph Style': selector_list.append('p')
	else: selector_list.append(style_name)

	selector_list = scanParChildren(style_name, selector_list)
	
	css_def = parseFont(style_properties, css_def)
	css_def = parseParagraph(style_properties, css_def)

	selector_list = sorted(selector_list)
	return ', '.join(selector_list), css_def


###! Methods for Translating Scribus XML Properties to CSS !###

def parseFont(style_properties, css_def):
	'''Parse font properties and generate corresponding CSS.'''

	# Font-family
	try: css_def['font-family'] = style_properties[u'FONT'] 
	except: pass
	
	# Font-size
	try: css_def['font-size'] = style_properties[u'FONTSIZE']
	except: pass

	# Font Color
	try: css_color = style_properties[u'FCOLOR']
	except: css_color = ''
	if css_color != '':
		R,G,B = scribus.getColorAsRGB(css_color)
		css_def['color'] = 'rgb(' + str(R) + ', ' + str(G) + ', ' + str(B) + ')'
	
	# Font Features (Small Caps, All Caps, Underline, Subscript, Superscript, Strike Out, Outline, Shadowed Text)
	try:
		css_features = style_properties[u'FEATURES']
		css_features = css_features.split()
	except: css_features = []
	
	# Check for all-caps
	if 'allcaps' in css_features:
		css_def['text-transform'] = 'uppercase'
	
	# Check for small-caps
	if 'smallcaps' in css_features:
		css_def['font-variant'] = 'small-caps'
	
	# Check for underline
	if ('underline' in css_features) | ('underlinewords' in css_features):
		css_def['text-decoration'] = 'underline'
	
	# Check for subscript
	if ('subscript' in css_features):
		css_def['vertical-align'] = 'sub'
	
	# Check for superscript
	if ('superscript' in css_features):
		css_def['vertical-align'] = 'super'
		css_def['font-size'] = '50%'
	
	# Check for strike-through
	if 'strike' in css_features:
		css_def['text-decoration'] = 'line-through'
	
	# Outline not supported in any major browsers
	
	# Check for shadowed text
	if ('shadowed' in css_features):
		txtsh_x = float(style_properties[u'TXTSHX'])/100
		txtsh_y = float(style_properties[u'TXTSHY'])/100*-1
		css_def['text-shadow'] = str(txtsh_x) + 'em ' + str(txtsh_y) + 'em'
	
	return css_def

def parseParagraph(style_properties, css_def):
	'''Parse paragraph properties and return corresponding CSS.'''

	# Paragraph Alignment
	try: 
		paragraph_align = int(style_properties[u'ALIGN'])
		if paragraph_align == 0:
			css_def['text-align'] = 'left'
		elif paragraph_align == 1:
			css_def['text-align'] = 'center'
		elif paragraph_align == 2:
			css_def['text-align'] = 'right'
		elif paragraph_align == 3:
			css_def['text-align'] = 'justify'
	except: pass

	# Line Spacing
	try:
		spacing_mode = int(style_properties[u'LINESPMode'])
		# Fixed Line Spacing
		if spacing_mode == 0:
			css_def['line-height'] = style_properties[u'LINESP'] + doc_unit
	except: pass

	# Indent: Left
	try:
		left_indent = int(style_properties[u'INDENT'])
		css_def['padding-left'] = str(left_indent) + doc_unit
	except: pass

	# Indent: Right
	try:
		right_indent = int(style_properties[u'RMARGIN'])
		css_def['padding-right'] = str(right_indent) + doc_unit
	except: pass
	
	# Indent: First Line
	try:
		first_line = int(style_properties[u'FIRST'])
		css_def['text-indent'] = str(first_line) + doc_unit
	except: pass

	# Paragraph Spacing: Before
	try:
		par_spacing = int(style_properties[u'VOR'])
		css_def['padding-top'] = str(par_spacing) + doc_unit
	except: pass

	# Paragraph Spacing: After
	try:
		par_spacing = int(style_properties[u'NACH'])
		if par_spacing > 0: css_def['padding-bottom'] = str(par_spacing) + doc_unit
	except: pass

	return css_def


def dropCap(style_name, style_properties, css_def):
	'''Parse style properties related to drop caps and initials. Handled separately due to the need to create special selector attributes. Scans other paragraph styles to create a list of other styles that require the attribute. Returns a list of selectors and CSS.'''

	selector_list = []

	# Drop Case (Initial) Options
	try:
		drop_case = int(style_properties[u'DROP'])
		if drop_case == 1:
			if style_name == 'Default Paragraph Style': selector_list.append('p')
			else: selector_list.append(style_name)
			selector_list = scanParChildren(style_name, selector_list)
			css_def['float'] = 'left'
			drop_line = int(style_properties[u'DROPLIN'])
			drop_size = drop_line * 150;
			css_def['font-size'] = str(drop_size) + '%'
			drop_distance = int(style_properties[u'DROPDIST'])
			css_def['margin-right'] = str(drop_distance) + doc_unit
			css_def['margin-top'] = str(0.025*drop_line) + 'em'
		else:
			return '', css_def
	except: pass

	# Add Selector Attributes for first-child, first-letter
	for i in range(len(selector_list)):
		selector = selector_list[i] + ':first-letter'
		selector_list[i] = selector
	
	selector_list = sorted(selector_list)
	return ', '.join(selector_list), css_def


def scanParChildren(style_name, selector_list):
	'''Scan paragraph styles to determine the style has children which are based on it. Returns a list of sorted selectors.'''

	# Scan paragraph styles to find children
	for parstyle in xml_parstyles:
		parstyle_data = xml_parstyles[parstyle]
		
		# Check character style to see if it has children
		try:
			if (parstyle_data[u'PARENT'] == style_name) & (parstyle != 'Default Paragraph Style'):
				selector_list.append(parstyle)
			elif (parstyle_data[u'PARENT'] == style_name) & (parstyle == 'Default Paragraph Style'):
				selector_list.append('p')
		except: pass
	
	return selector_list


def oldParseFont(style_properties, css_def):
	'''Parse the style for font properties. Requires a scribus style property as a list of values and a CSS style definition. Returns the CSS style definition with font specific properties added.'''
	# Retrieve list of fonts and their associated properties
	doc_fonts = scribus.getXFontNames()

	# Iterate through the style properties and compose CSS
	for fontproperty in style_properties:
		if fontproperty == 'FONT':
			font_name = style_properties[fontproperty]
			# Find font in doc_fonts
			font_index = [docfont[0] for docfont in doc_fonts].index(font_name)
			if font_index > -1:
				docfont_properties = doc_fonts[font_index]
				# Specify Font-Properties
				style_def['font-family'] = docfont_properties[0]
	
	return style_def

def createFontfaceRule(font_name, css_def, export_filename):
	'''Create @font-rule for the fonts used in the document. Copy fonts to a folder called "fonts" in the same directory as the stylesheet.'''
	# Retrieve list of fonts and their associated properties
	doc_fonts = scribus.getXFontNames()

	# Find font in list of document fonts
	font_index = [docfont[0] for docfont in doc_fonts].index(font_name)
	if font_index > -1:
		docfont_properties = doc_fonts[font_index]
		css_def['font-family'] = docfont_properties[0]
		
		# Copy Font to 'fonts' resource folder
		font_url = path.abspath(docfont_properties[5])
		dest_dir, css_file = path.split(path.abspath(export_filename))
		dest = path.join(dest_dir, 'fonts')
		if not path.exists(dest):
			os.mkdir(dest)
		shutil.copyfile(font_url, path.join(dest,path.basename(font_url)))

		css_def['src'] = 'url(' + 'fonts/' + str(path.basename(font_url)) + ')'
	return css_def

export_css()