00001 '''
00002 Created on Jul 2, 2011
00003
00004 @author: mplajner
00005 '''
00006
00007 from xml.dom import minidom
00008 import Variable
00009 import logging.config
00010 import os
00011 from cPickle import load, dump
00012 from hashlib import md5
00013
00014 class XMLFile():
00015 '''Takes care of XML file operations such as reading and writing.'''
00016
00017 def __init__(self):
00018 self.xmlResult = '<?xml version="1.0" encoding="UTF-8"?><env:config xmlns:env="EnvSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="EnvSchema ./EnvSchema.xsd ">\n'
00019 self.declaredVars = []
00020 logConf = os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + '/log.conf')
00021 if not logging.getLogger('envLogger').handlers and os.path.exists(logConf):
00022 logging.config.fileConfig(logConf)
00023 self.logger = logging.getLogger('envLogger')
00024
00025 def variable(self, path, namespace='EnvSchema', name=None):
00026 '''Returns list containing name of variable, action and value
00027
00028 If no name given, returns list of lists of all variables and locals(instead of action 'local' is filled).
00029 '''
00030 if not os.path.isfile(path):
00031 raise IOError('No such file.')
00032 sum = md5()
00033 sum.update(open(path, 'rb').read())
00034 sum = sum.digest()
00035
00036 cpath = path + "c"
00037 try:
00038 f = open(cpath, 'rb')
00039 oldsum, data = load(f)
00040 if oldsum == sum:
00041 return data
00042 except:
00043 pass
00044
00045
00046 self.doc = minidom.parse(path)
00047 if namespace == '':
00048 namespace = None
00049
00050
00051 nodes = self.doc.getElementsByTagNameNS(namespace, "config")[0].childNodes
00052 variables = []
00053 for node in nodes:
00054
00055 if node.nodeType == 1:
00056 varname = str(node.getAttribute('variable'))
00057 if name and varname != name:
00058 continue
00059
00060 action = str(node.localName)
00061 if action == 'declare':
00062 variables.append((action, (varname, str(node.getAttribute('type')), str(node.getAttribute('local')))))
00063 else:
00064 if node.childNodes:
00065 value = str(node.childNodes[0].data)
00066 else:
00067 value = ''
00068 variables.append((action, (varname, value, None)))
00069
00070 try:
00071 f = open(cpath, 'wb')
00072 dump((sum, variables), f, protocol=2)
00073 f.close()
00074 except:
00075 pass
00076 return variables
00077
00078
00079 def resetWriter(self):
00080 '''resets the buffer of writer'''
00081 self.xmlResult = '<?xml version="1.0" encoding="UTF-8"?><env:config xmlns:env="EnvSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="EnvSchema ./EnvSchema.xsd ">\n'
00082 self.declaredVars = []
00083
00084 def writeToFile(self, outputFile = ''):
00085 '''Finishes the XML input and writes XML to file.'''
00086 if(outputFile == ''):
00087 raise IOError("No output file given")
00088 self.xmlResult += '</env:config>'
00089
00090 doc = minidom.parseString(self.xmlResult)
00091 with open(outputFile, "w") as f:
00092 f.write( doc.toxml() )
00093
00094 f.close()
00095 return outputFile
00096
00097 def writeVar(self, varName, action, value, type='list', local='false'):
00098 '''Writes a action to a file. Declare undeclared elements (not local List is default type).'''
00099 if action == 'declare':
00100 self.xmlResult += '<env:declare variable="'+varName+'" type="'+ type.lower() +'" local="'+(str(local)).lower()+'" />\n'
00101 self.declaredVars.append(varName)
00102 return
00103
00104 if varName not in self.declaredVars:
00105 self.xmlResult += '<env:declare variable="'+varName+'" type="'+ type +'" local="'+(str(local)).lower()+'" />\n'
00106 self.declaredVars.append(varName)
00107 self.xmlResult += '<env:'+action+' variable="'+ varName +'">'+value+'</env:'+action+'>\n'
00108
00109
00110 class Report():
00111 '''This class is used to catch errors and warnings from XML file processing to allow better managing and testing.'''
00112
00113
00114 def __init__(self, level = 1, reportOutput = False):
00115 self.errors = []
00116 self.warns = []
00117 self.info = []
00118 self.warnings = []
00119 self.level = level
00120
00121 if not reportOutput:
00122 self.reportOutput = False
00123 else:
00124 self.reportOutput = open(reportOutput, 'w')
00125
00126 logConf = os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + '/log.conf')
00127 if not logging.getLogger('envLogger').handlers and os.path.exists(logConf):
00128 logging.config.fileConfig(logConf)
00129 self.logger = logging.getLogger('envLogger')
00130
00131 def addError(self, message, varName = '', action = '', varValue = '', procedure = ''):
00132 error = [message, varName, action, varValue, procedure]
00133 if self.level < 4:
00134 if not self.reportOutput:
00135 print 'Error: ' + error[0]
00136 else:
00137 self.reportOutput.write('Error: ' + error[0] + '\n')
00138 self.errors.append(error)
00139 self.logger.error(message)
00140
00141 def addWarn(self, message, varName = '', action = '', varValue = '', procedure = ''):
00142 error = [message, varName, action, varValue, procedure]
00143 if self.level < 1:
00144 if not self.reportOutput:
00145 print 'Warn: ' + error[0]
00146 else:
00147 self.reportOutput.write('Warn: ' + error[0] + '\n')
00148 self.warns.append(error)
00149 self.logger.warn(message)
00150
00151 def addWarning(self, message, varName = '', action = '', varValue = '', procedure = ''):
00152 error = [message, varName, action, varValue, procedure]
00153 if self.level < 2:
00154 if not self.reportOutput:
00155 print 'Warning: ' + error[0]
00156 else:
00157 self.reportOutput.write('Warning: ' + error[0] + '\n')
00158 self.warnings.append(error)
00159 self.logger.warning(message)
00160
00161 def addInfo(self, message, varName = '', action = '', varValue = '', procedure = ''):
00162 error = [message, varName, action, varValue, procedure]
00163 if self.level < 3:
00164 if not self.reportOutput:
00165 print 'Info: ' + error[0]
00166 else:
00167 self.reportOutput.write('Info: ' + error[0] + '\n')
00168 self.warnings.append(error)
00169 self.logger.info(message)
00170
00171 def clear(self):
00172 self.errors = []
00173 self.warns = []
00174 self.info = []
00175 self.warnings = []
00176
00177 def closeFile(self):
00178 if self.reportOutput:
00179 self.reportOutput.close()
00180
00181 def numErrors(self):
00182 return len(self.errors)
00183
00184 def numWarnings(self):
00185 return len(self.warns) + len(self.warnings)
00186
00187 def error(self, key):
00188 return self.errors[key]
00189
00190 def warn(self, key):
00191 return self.warns[key]
00192
00193
00194 class XMLOperations():
00195 '''This class is for checking and merging XML files.
00196
00197 Variables are stored in a double dictionary with keys of names and then actions.
00198 '''
00199 def __init__(self, separator=':', reportLevel = 0, reportOutput = False):
00200 self.posActions = ['append','prepend','set','unset', 'remove', 'remove-regexp', 'declare']
00201 self.separator = separator
00202 self.report = Report(reportLevel, reportOutput = reportOutput)
00203
00204 def errors(self):
00205 return self.report.numErrors()
00206
00207 def warnings(self):
00208 return self.report.numWarnings()
00209
00210 def check(self, xmlFile):
00211 '''Runs a check through file
00212
00213 First check is made on wrong action parameter.
00214 All valid actions are checked after and duplicated variables as well.
00215 '''
00216
00217 self.varNames = []
00218 self.realVariables = {}
00219 self.variables = {}
00220
00221
00222 self._loadVariables(xmlFile)
00223
00224
00225 if (self.warnings() > 0 or self.errors() > 0):
00226 self.report.addInfo('Encountered '+ (str)(self.warnings()) +' warnings and ' + (str)(self.errors()) + ' errors.')
00227 return [self.warnings(), self.errors()]
00228 else:
00229 return True
00230
00231 self.report.closeFile()
00232
00233
00234 def merge(self, xmlDoc1, xmlDoc2, outputFile = '', reportCheck = False):
00235 '''Merges two files together. Files are checked first during variables loading process.
00236
00237 Second file is processed first, then the first file and after that they are merged together.
00238 '''
00239 self.output = outputFile
00240 self.file = XMLFile()
00241 self.variables = {}
00242
00243 variables = self.file.variable(xmlDoc1)
00244 self._processVars(variables)
00245 variables = self.file.variable(xmlDoc2)
00246 self._processVars(variables)
00247
00248 if not reportCheck:
00249 self.report.level = 5
00250
00251 self.file.writeToFile(outputFile)
00252
00253 self.report.addInfo('Files merged. Running check on the result.')
00254 self.check(self.output)
00255 self.report.closeFile()
00256
00257 def _processVars(self, variables):
00258 for action, (arg1, arg2, arg3) in variables:
00259 if action == 'declare':
00260 if arg1 in self.variables.keys():
00261 if arg2.lower() != self.variables[arg1][0]:
00262 raise Variable.EnvironmentError(arg1, 'redeclaration')
00263 else:
00264 if arg3.lower() != self.variables[arg1][1]:
00265 raise Variable.EnvironmentError(arg1, 'redeclaration')
00266 else:
00267 self.file.writeVar(arg1, 'declare', '', arg2, arg3)
00268 self.variables[arg1] = [arg2.lower(), arg3.lower()]
00269 else:
00270 self.file.writeVar(arg1, action, arg2)
00271
00272
00273 def _checkVariable(self, varName, action, local, value, nodeNum):
00274 '''Tries to add to variables dict, checks for errors during process'''
00275
00276 if varName not in self.variables:
00277 self.variables[varName] = []
00278 self.variables[varName].append(action)
00279
00280
00281 elif action == 'unset':
00282 if 'unset' in self.variables[varName]:
00283 self.report.addWarn('Multiple "unset" actions found for variable: "'+varName+'".', varName, 'multiple unset','', 'checkVariable')
00284 if not('unset' in self.variables[varName] and len(self.variables[varName]) == 1):
00285 self.report.addError('Node '+str(nodeNum)+': "unset" action found for variable "'+varName+'" after previous command(s). Any previous commands are overridden.', varName, 'unset overwrite')
00286
00287
00288 elif action == 'set':
00289 if len(self.variables[varName]) == 1 and 'unset' in self.variables[varName]:
00290 self.report.addWarn('Node '+str(nodeNum)+': "set" action found for variable "'+varName+'" after unset. Can be merged to one set only.')
00291 else:
00292 self.report.addError('Node '+str(nodeNum)+': "set" action found for variable "'+varName+'" after previous command(s). Any previous commands are overridden.', varName, 'set overwrite')
00293 if 'set' in self.variables[varName]:
00294 self.report.addWarn('Multiple "set" actions found for variable: "'+varName+'".', varName, 'multiple set','', 'checkVariable')
00295
00296 if action not in self.variables[varName]:
00297 self.variables[varName].append(action)
00298
00299 try:
00300 if action == 'remove-regexp':
00301 action = 'remove_regexp'
00302 eval('(self.realVariables[varName]).'+action+'(value)')
00303 except Variable.EnvironmentError as e:
00304 if e.code == 'undefined':
00305 self.report.addWarn('Referenced variable "' +e.val+ '" is not defined.')
00306 elif e.code == 'ref2var':
00307 self.report.addError('Reference to list from the middle of string.')
00308 elif e.code == 'redeclaration':
00309 self.report.addError('Redeclaration of variable "'+e.val+'".')
00310 else:
00311 self.report.addError('Unknown environment error occured.')
00312
00313
00314 def _loadVariables(self, fileName):
00315 '''loads XML file for input variables'''
00316 XMLfile = XMLFile()
00317 variables = XMLfile.variable(fileName)
00318 for i, (action, (arg1, arg2, arg3)) in enumerate(variables):
00319 undeclared = False
00320 if arg1 == '':
00321 raise RuntimeError('Empty variable or local name is not allowed.')
00322
00323 if arg1 not in self.realVariables.keys():
00324 if action != 'declare':
00325 self.report.addInfo('Node '+str(i)+': Variable '+arg1+' is used before declaration. Treated as an unlocal list furthermore.')
00326 undeclared = True
00327 self.realVariables[arg1] = Variable.List(arg1, False, report=self.report)
00328 else:
00329 self.varNames.append(arg1)
00330 if arg2 == 'list':
00331 self.realVariables[arg1] = Variable.List(arg1, arg3, report=self.report)
00332 else:
00333 self.realVariables[arg1] = Variable.Scalar(arg1, arg3, report=self.report)
00334 if not undeclared:
00335 continue
00336
00337 if action not in self.posActions:
00338 self.report.addError('Node '+str(i)+': Action "'+action+'" which is not implemented found. Variable "'+arg1+'".', arg1, action, arg2)
00339 continue
00340
00341 else:
00342 if action == 'declare':
00343 self.report.addError('Node '+str(i)+': Variable '+arg1+' is redeclared.')
00344 else:
00345 self._checkVariable(arg1, action, self.realVariables[arg1].local, str(arg2), i)