# Copyright (C) 2005 Meredith L. Patterson
#
# 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 2
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import libsqlparse, tempfile, bison
from string import *
from xml.dom import *

def postorder(node): # adapted from the ASPN Python Cookbook, #201423
	if node:
		for n in node.childNodes:
			for elem in postorder(n):
				yield elem
		yield node

# Ugliest. Function. Ever.
def nodeEquals(node1, node2):
	if (node1.nodeType == node2.nodeType == Node.TEXT_NODE):
		return (node1.nodeName == node2.nodeName and node1.nodeValue == node2.nodeValue)
	else:
		return (node1.nodeType == node2.nodeType and node1.nodeName == node2.nodeName and node1.nodeValue == node2.nodeValue and node1.getAttribute('option') == node2.getAttribute('option') and node1.getAttribute('target') == node2.getAttribute('target'))

# Second. Ugliest. Function. Ever.
def isEquivalentTree(exemplar, target):
	if exemplar and (exemplar.nodeType == Node.TEXT_NODE):
		if not target:
			return False
		else:  # uh, hold on, can TEXT_NODEs *have* children?
			for (x, y) in zip(exemplar.childNodes, target.childNodes):
				isEquivalentTree(x,y)
			return nodeEquals(exemplar, target)
	if exemplar and (exemplar.getAttribute('irrelevant') != True):
		if not target:
			# the exemplar is larger than the target
			return False
		else:
			foo = True
			for (x, y) in zip(exemplar.childNodes, target.childNodes):
				foo = isEquivalentTree(x, y) # Problem: we need to break the entire recursion the moment anything's
						       # false, and the iteration is preventing that from happening
				if foo == False: break
			return (foo and nodeEquals(exemplar, target))
	elif target and not exemplar:
		# the target is larger than the exemplar
		return False
	else:
		if exemplar and not target:
			return False
		# otherwise, ignore everything underneath this node
		return nodeEquals(exemplar, target)

class Dejector:
	
	def __init__(self, query):
		# Right now there's only the one parsing engine;
		# in time there will be more, and thus a constructor
		# parameter to reflect this.
		#
		# If you get a segfault when trying to initialise an
		# object of this class, check and see whether 
		# libsqlparse-engine.so is someplace that sqlparse.py can
		# find it. The dirty hack I've been using has just been
		# to add . to LD_LIBRARY_PATH.

		if query.find('[') == -1 and query.find(']') == -1:
			self.bracketed = ''
		else:
			self.bracketed = join(split(query[query.index('[')+1:query.index(']')]), '')
		t = tempfile.TemporaryFile()
		t.write(self.stripBrackets(query))
		t.seek(0)
		self.p = libsqlparse.Parser()
		self.exemplarTree = self.p.run(file=t).toxmldoc()
		self.lowestEnclosingNode = self.markLowestEnclosingNode()
		t.close()

	def stripBrackets(self, str):
		if str.find(']') == -1 or str.find('[') == -1: return str
		return join([x for x in str if x != '[' and x != ']'], '')

	def findChildString(self, node, ret = None):
		if (ret == None): ret = []
		if node.hasChildNodes():
			for n in node.childNodes:
				self.findChildString(n, ret)
			if (node.nodeType == Node.TEXT_NODE): 
				ret.append(node.data)
			return join(ret, '')
		else:
			if (node.nodeType == Node.TEXT_NODE): 
				ret.append(node.data)
			return join(ret, '')
	
	def markLowestEnclosingNode(self):
		for x in postorder(self.exemplarTree):
			if (self.findChildString(x).find(self.bracketed) != -1 and x.nodeType == Node.ELEMENT_NODE):
				x.setAttribute('irrelevant', True)
				return x

	def validate(self, target):
		t = tempfile.TemporaryFile()
		t.write(target)
		t.seek(0)
		try:
			targetTree = self.p.run(file=t).toxmldoc()
			t.close()
			return isEquivalentTree(self.exemplarTree.childNodes[0], targetTree.childNodes[0])
		except bison.ParserSyntaxError:
			print "Error: target didn't parse."
			return False