#!/usr/bin/python

import sys
import xml.dom.minidom
import getopt
import re
import os

#define help output
def usage():
	print '\nUsage: sugar-iconify [options] input.svg\n'
	print 'Options:\n'
        print '\t -s hex\t\tHex value to replace with stroke entity'
	print '\t -f hex\t\tHex value to replace with fill entity'
	print '\t -g\t\tAutomatically accept guesses for stroke and fill entities'
	print '\t -i\t\tModify input file in place, overwriting it; overridden by -m'
	print '\t -m\t\tMultiple export; export top level groups as separate icons' 
	print '\t -p pattern\tOnly export icons whose name contains pattern'
	print '\t -o directory\tThe preferred output directory'
	print '\t -e\t\tDo not insert entities for strokes and fills'
	print '\t -h\t\tDisplay this help message'
        print '\t -v\t\tverbose'

#check for valid arguments
try:
	opts,arg = getopt.getopt(sys.argv[1:], "s:f:gimp:o:ehv")
except:
	usage()
	sys.exit(2)
		
if len(arg) < 1:
	usage()
	sys.exit(2)

verbose = False
stroke_color = '#666666'
fill_color = '#ffffff'
entities_passed = 0
confirm_guess = True
stroke_entity = "stroke_color"
fill_entity = "fill_color"
use_entities = True
output_path = ''
pattern = ''
multiple = False
overwrite_input = False

#interpret arguments
for o, a in opts:
	
        if o == '-s':
		stroke_color = '#' + a.lstrip('#').lower()
		entities_passed += 1
	elif o == '-f':
		fill_color = '#' + a.lstrip('#').lower()
		entities_passed += 1
	elif o == '-g':
		confirm_guess = False
	elif o == '-i':
		overwrite_input = True
	elif o == '-o':
		output_path = a.rstrip('/') + '/'
	elif o == '-e':
		use_entities = False
	elif o == '-v':
		verbose = True
	elif o == '-p':
		pattern = a
	elif o == '-h':
		usage()
		sys.exit(2)
	elif o == '-m':
		multiple = True

svgfilename = arg[0]

#load the SVG as text
try:
	svgfile = open(svgfilename, 'r')
except:
	sys.exit('Error: Could not locate ' + svgfilename)

try:
	svgtext = svgfile.read()
	svgfile.close()
except:
	svgfile.close()
	sys.exit('Error: Could not read ' + svgfilename)

#determine the creator of the SVG (we only care about Inkscape and Illustrator)

creator = 'unknown'

if re.search('illustrator', svgtext, re.I):
	creator = 'illustrator'
elif re.search('inkscape', svgtext, re.I):
	creator = 'inkscape'

if verbose:
	print 'The creator of this svg is ' + creator + '.'

#hack the entities into the readonly DTD
if use_entities:
	entities  = '\t<!ENTITY ' + stroke_entity + ' "' + stroke_color + '">\n'
	entities += '\t<!ENTITY ' + fill_entity   + ' "' + fill_color   + '">\n'
	entities += '\t<!ENTITY stroke_opacity "1">\n'
	entities += '\t<!ENTITY fill_opacity "1">\n'

	#for simplicity, we simply replace the entire entity declaration block; this obviously would remove
	#any other custom entities declared within the SVG, but we assume that's an extreme edge case
	
	svgtext, n = re.subn(r'(<!DOCTYPE[^>\[]*)(\[[^\]]*\])*\>', r'\1 \n[\n' + entities + ']>\n', svgtext)

	#add a doctype if none already exists, adding the appropriate entities as well
	if n == 0:
		svgtext,n = re.subn("<svg", "<!DOCTYPE svg  PUBLIC '-//W3C//DTD SVG 1.1//EN'  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [\n" + entities + "]>\n<svg", svgtext)
		if n == 0:
			sys.exit('Error: Could not insert entities into DTD')

#convert entities to references
stroke_entity = '&' + stroke_entity + ';'
fill_entity = '&' + fill_entity + ';'

#create the SVG DOM

try:
	svgxml = xml.dom.minidom.parseString(svgtext);
except Exception, e:
	sys.exit('Error: Could not parse ' + svgfilename + str(e))

#extract top level nodes
i = 0
svgindex = 0;
docindex = 0;
for element in svgxml.childNodes:
	if element.nodeType == 10:
		docindex = i;
	elif element.localName == 'svg':
		svgindex = i;
		break;
	i += 1;

doctype = svgxml.childNodes[docindex]
svg = svgxml.childNodes[svgindex]
icons = svg.childNodes;

#define utility functions; these provide support for stroke/fill attribute nodes, as well as 
#for stroke/fill definitions embedded within the style attribute

def getStroke(node):	
	s = node.getAttribute('stroke')
	if s:
		return s.lower()
	else:
		#print 'style is: ' + node.getAttribute('style')
		if re.search(r'stroke:', node.getAttribute('style')):
			s = re.sub(r'.*stroke:\s*(#*[^;]*).*', r'\1', node.getAttribute('style'))
			#print 'returning stroke value: ' + s.lower()
			return s.lower()
		else:
			return 'none'

#the setter functions also insert entity values for opacity on anything that is colored

def setStroke(node, value):
	#print 'setting stroke to: ' + value
	s = node.getAttribute('stroke')
	if s:
		node.setAttribute('stroke', value)
	else:
		s = re.sub(r'stroke:\s*#*[^;]*', 'stroke:' + value,  node.getAttribute('style'))
		node.setAttribute('style', s)

	s = node.getAttribute('style')

	if s:
		s, n = re.subn(r'stroke-opacity:\s*[^;]*;*', r'stroke-opacity:&stroke_opacity;;', s)
		
		if n == 0:
			node.setAttribute('style',  s + ';stroke-opacity:&stroke_opacity;;')
		else:
			node.setAttribute('style', s)
	else:
		node.setAttribute('style', 'stroke-opacity:&stroke_opacity;;')
		

def getFill(node):
	f = node.getAttribute('fill')
	if f:
		return f.lower()
	else:
		if re.search(r'fill:', node.getAttribute('style')):
			f = re.sub(r'.*fill:\s*(#*[^;]*).*', r'\1', node.getAttribute('style'))
			#print 'returning fill value: ' + f.lower()
			return f.lower()
		else:
			return 'none'
	
def setFill(node, value):
	f = node.getAttribute('fill')
	if f:
		node.setAttribute('fill', value)
	else:
		s = re.sub(r'fill:\s*#*[^;]*', 'fill:' + value,  node.getAttribute('style'))
		node.setAttribute('style', s)

	s = node.getAttribute('style')
	
	if s:
		s, n = re.subn(r'fill-opacity:\s*[^;]*;*', r'fill-opacity:&fill_opacity;;', s)

		if n == 0:
			node.setAttribute('style', s + ';fill-opacity:&fill_opacity;;')
		else:
			node.setAttribute('style', s)

	else:
		node.setAttribute('style', 'fill-opacity:&fill_opacity;;')
		

def replaceEntities(node, indent=''):
	
	entities_replaced = 0

	if node.localName:
		str = indent + node.localName
	
	if node.nodeType == 1: #only element nodes have attrs
		
		#replace entities for matches
		if getStroke(node) == stroke_color:
			setStroke(node, stroke_entity)
			entities_replaced += 1
		
		if getStroke(node) == fill_color:
			setStroke(node, fill_entity)
			entities_replaced += 1
		
		if getFill(node) == fill_color:
			setFill(node, fill_entity)
			entities_replaced += 1
			
		if getFill(node) == stroke_color:
			setFill(node, stroke_entity)
			entities_replaced += 1
		
		str = str + " (" + getStroke(node) + ", " + getFill(node) + ")"
		if verbose:
			print str
			
	#recurse on DOM
	for n in node.childNodes:
		entities_replaced += replaceEntities(n, indent + "   ")

	#return true when replacements are made
	return entities_replaced > 0	


#these functions attempt to guess the hex values for the stroke and fill entities

def getColorPairs(node, pairs=[]):

	if node.nodeType == 1:
		pair = (getStroke(node), getFill(node))
		if pair != ('none', 'none'):
			pairs.append(pair)

	#recurse on DOM
	for n in node.childNodes:
		getColorPairs(n, pairs)

	return pairs

def guessEntities(node):

	guesses = getColorPairs(node)
	#print guesses

	stroke_guess = 'none'
	fill_guess = 'none'

	for guess in guesses:
		if stroke_guess == 'none':
			stroke_guess = guess[0]
		if fill_guess == 'none':
			fill_guess = guess[1]		
		if guess[0] == fill_guess:
			fill_guess = stroke_guess
			stroke_guess = guess[0]
			if guess[1] != 'none':
				fill_guess = guess[1]
	
	return (stroke_guess, fill_guess)


#guess the entity values, if they aren't passed in

if use_entities and entities_passed < 2:
	stroke_color, fill_color = guessEntities(svg)

	if confirm_guess or verbose:
		print 'entity definitions:'
		print '     stroke_entity = ' + stroke_color
		print '     fill_entity = ' + fill_color
		print '     stroke_opacity = 1'
		print '     fill_opacity = 1'

	if confirm_guess:
		response = raw_input("\nAre these entities correct? [y/n] ")
		if response.lower() != 'y':
			print 'Please run this script again, passing the proper colors with the -s and -f flags.'
			sys.exit(1)


if multiple:
	#export each icon as a separate file by top level group

	n_icons_exported = 0
	for icon in icons:
	
		try:
			#skip whitespace and unnamed icons
			if icon.localName == 'g' and icon.attributes:
			
				icon_name = ''
				try:
					if creator == 'inkscape' and icon.attributes.getNamedItem('inkscape:label'):
						icon_name = icon.attributes.getNamedItem('inkscape:label').nodeValue
					else:
						icon_name = icon.attributes.getNamedItem('id').nodeValue
				except:
					pass		

				#skip the template layers
				if not icon_name.startswith('_'):
		
					#skip non-matches
					if pattern == '' or re.search(pattern, icon_name):

						if verbose:
							print 'Exporting ' + icon_name + '.svg...'
						icon_xml = xml.dom.minidom.Document();
			
						#construct the SVG
						icon_xml.appendChild(doctype)
						icon_xml.appendChild(svg.cloneNode(0))
			
						icon_xml.childNodes[1].appendChild(icon) 
						icon_xml.childNodes[1].childNodes[0].setAttribute('display', 'block')
			
						if use_entities:
							replaceEntities(icon_xml.childNodes[1])
			
						#write the file
						try:
							f = open(output_path + icon_name + '.svg', 'w')
						except:
							sys.exit('Error: Could not locate directory ' + output_path)
			
						try:
							#had to hack here to remove the automatic encoding of '&' by toxml() in entity refs
							#I'm sure there is a way to prevent need for this if I knew the XML DOM better
							f.write(re.sub('&amp;', '&', icon_xml.toxml()))
							f.close()
						except:
							sys.exit('Error: Could not write file ' + icon_name + '.svg')
						
						n_icons_exported += 1
		except:
			#catch any errors we may have missed, so the rest of the icons can export normally
			if(icon_name):
				print 'Error: Could not export' + icon_name + '.svg'

	if verbose:
		if n_icons_exported == 1:
			print 'Successfully exported 1 icon' 
		else:
			print 'Successfully exported %d icons' % n_icons_exported

else:

	if not overwrite_input:
		outfilename = re.sub(r'(.*\.)([^.]+)', r'\1sugar.\2', svgfilename)
		if verbose:
			print 'Exporting ' + outfilename + ' ...'
	else:
		outfilename = svgfilename
		if verbose:
			print 'Overwriting ' + outfilename + ' ...'

	#remove the template layers
	for node in svg.childNodes:
		
		#only check named nodes
		if node.localName == 'g' and node.attributes:		
			try:
				if creator == 'inkscape' and node.attributes.getNamedItem('inkscape:label'):
					node_name = node.attributes.getNamedItem('inkscape:label').nodeValue
				else:
					node_name = node.attributes.getNamedItem('id').nodeValue

				if node_name.startswith('_'):
					node.parentNode.removeChild(node)
			except:
				pass

	if use_entities:
		if not replaceEntities(svgxml):
			print 'Warning: no entity replacements were made'

	#save the changes to the input file
	try:
		f = open(output_path + outfilename, 'w')
	except:
		sys.exit('Error: Could not save to ' + output_path + outfilename)

	try:
		f.write(re.sub('&amp;', '&', svgxml.toxml()))
		f.close()
	except:
		sys.exit('Error: Could not write file ' + output_path + outfilename)
