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