#!/usr/bin/env python # Copyright (c) 2005 by Mark T. Holder, Florida State University. (see end of file) '''Classes and functions for reading an instance document of a CIPRES UI XML. Uses xml_to_obj to make the sax code easier.''' from xml_to_obj import * import os, sys, time, re from PIPRes.util.io import logException, cipresGetLogger from PIPRes.wrap.idl import CipresInterfaceEnum, CipresIDL_api1, stringifyEntry import PIPRes.util.ui_tools as ui_tools import copy _LOG = cipresGetLogger('pipres.util.ui_xml') TextOnlyElement.automaticallyConvertToString = True SAXConstructible.purgeParsingVars = True class UIServiceConfigError(ValueError): pass class UITestFromXML(SAXConstructible): def validate(self, focalObj): _LOG.warn('UITestFromXML.validate not implemented') return True def readUICmdParamGroupFromXMLObj(uiStreamObject): '''Reads the uiStreamObject and returns a PIPRes.util.ui_tools.UI_Group object''' uiContent = CipresServiceCommandPanel('service-command-panel',{}) uiContent.datatype = "Paragraph" if uiContent.parseFileObj(uiStreamObject): return uiContent.toUIToolsObj() raise UIServiceConfigError, 'Could not parse UI Stream' class CipresServiceCommandPanel(SAXConstructible): def endSelfElement(self, name): self.isMandatory = True self.parent = None def toUIToolsObj(self): g = ui_tools.UI_Group(self.label) for i in self.cmdParams: setattr(g, i.name, i.toUIToolsObj()) return g class UICmdParamFromXML(SAXConstructible): mthExtensions = ["Python Method", "List"] validDatatypes = ["JPanel", "Excl", "Integer", "Paragraph", "String", "InFile", "Boolean", "Float"] + mthExtensions def endSelfElement(self, name): self.isMandatory = self.isMandatory is not None and self.isMandatory.upper() == 'TRUE' self.parent = self._parent if self.datatype is None: raise ValueError, 'Datatype must be specified for %s cmd-param element' % self.name if self.datatype not in UICmdParamFromXML.validDatatypes: interfaceName = self.datatype.startswith('Interface ') and self.datatype[len('Interface '):] or '' if interfaceName not in CipresInterfaceEnum.names: raise ValueError, 'Datatype %s is not valid.' % self.datatype self.__class__ = InterfaceCmdParam self.interfaceName = interfaceName else: self.__class__ = UICmdParamFromXML.toDerivedClass.get(self.datatype, UICmdParamInterface) self.postCast() self.resetValue() class VarNotFoundError(ValueError): pass class UICmdParamInterface(object): import re codeTemplateInterpolator = re.compile(r'.*\$([a-zA-Z]+)') # look for words after $, I thougha about including (?' if self.defaultValue: self.setValue(self.defaultValue.strip()) def __str__(self): return self.valString def childChangedValue(self, changedChild): pass def __deepcopy__(self, memo): '''If still parsing this is a copy by reference of the parent and deepcopy of everything else. If not, it is a normal deepcopy.''' newObj = self.__class__() par = self.parent del self.parent if '_parent' in self.__dict__: #if we come in here we don xmlParent = self._parent del self._parent xmlParser = self._parser self._parser = None else: xmlParent = None newObj.__dict__ = copy.deepcopy(self.__dict__) newObj.parent = par self.parent = par if xmlParent is not None: self._parent = xmlParent self._parser = xmlParser return newObj def linesForDisplay(self): return ['Current value for %s:\n %s' % (self.briefLabel, self.valString)] def takeText(self, cmd): '''Usually this is just an alias for setValue, some commmand types (e.g. interfaces may need to accept commands).''' return self.setValue(cmd) def isLeaf(self): return len(self.cmdParams) == 0 def isAvaliable(self): _LOG.warn('IsValid always returns True!!') if self.availabilityTestExpression is not None and False: self.availabilityTestExpression.validate(self) return True def postCast(self): pass def setValue(self, valString, throwIfTestFail = True): self.value = valString self.valString = valString def isValid(self): _LOG.warn('IsValid always returns True!!') if self.requirementTestExpression is not None and False: self.requirementTestExpression.validate(self) return True def isReady(self): return not self.isMandatory or self.isValid() def needingConfiguration(self): needConfig = [] if not self.isReady(): needConfig.append(self) needConfig.extend([i for i in self.cmdParams if not i.isReady()]) return needConfig def getChild(self, name): for i in self.cmdParams: if i.name == name: return i if name.isdigit(): iName = int(name) if iName >= 0 and iName < len(self.cmdParams): return self.cmdParams[iName] return None def _storeOldCode(self): if presentIfValid: toConfigure = self.needingConfiguration() or [self] else: toConfigure = not self.isReady() and self.needingConfiguration() or [] try: while len(toConfigure): toConfigure = toConfigure[0].nodePresentUI(registry, environ) if toConfigure is None or toConfigure == []: toConfigure = self.needingConfiguration() except UIServiceConfigError: return False return True def getStatus(self): labStr = self.label and ' (%s) ' % self.label or '' return self.briefLabel + ' = ' + self.valString def setRegistry(self, registry): for c in self.cmdParams: c.setRegistry(registry) def getBriefLabel(self): return self.name def toUIToolsObj(self): p = _createBlankUIToolsObj() if self.isMandatory: p.demand = ui_tools.UI_Param.IS_VALID d = self.getUIDefaultValue() if d is not None: p.default = d p.help = self.toolTipText p.formatter = self.getUIToolsFormatter() for i in self.cmdParams: p.addSubParam(i.toUIToolsObj()) return p status = property(getStatus) briefLabel = property(getBriefLabel) class BooleanCmdParam(UICmdParamInterface): def setValue(self, valString): uv = valString.upper() if uv == '1' or 'YES'.startswith(uv) or 'TRUE'.startswith(uv): self.value = True elif uv == '0' or 'NO'.startswith(uv) or 'FALSE'.startswith(uv): self.value = False else: raise ValueError, '%s is not valid for a Boolean parameter (%s)' % (str(valString), self.name) self.valString = str(self.value) _numberOnly = re.compile(r'^\s*[-0-9.]+\s*$') class ExclCmdParam(UICmdParamInterface): def linesForDisplay(self): self.refreshChoices() hasAllNumbers = bool([1 for i in self.choices if _numberOnly.match(i[0])]) if len(self.choices) > 1: choiceStrings = [' %s' % i[0] for i in self.choices] return ['Possible choices for %s:' % self.briefLabel] + choiceStrings + ['Current value is %s' % self.getValueShortName()] if len(self.choices) == 1: if self.value is not self.choices[0][1]: self.setValue(self.choice[0][0]) return ['Current value is %s (which is the only valid choice for %s)' % (self.getValueShortName(), self.briefLabel)] return ['There are no valid choices for %s.' % self.briefLabel] def postCast(self): self.choices = [] if self.listItem: self.defaultValue = None for i in self.listItem: self.choices.append((i.display, i.value)) if i.isDefault: self.defaultValue = i.display self.resetValue() if self.defaultValue is None: self.defaultValue = self.listItem[0].display self.resetValue() self.linesForDisplay() def setValue(self, valString): self.refreshChoices() if isinstance(valString, str): for i in self.choices: if i[0].startswith(valString): self.value = i[1] self.valString = i[0] return if not valString.isdigit(): raise ValueError, '%s is not valid' % valString n = int(valString) if n > -1 and n < len(self.choices): i = self.choices[n] self.value = i[1] self.valString = i[0] return raise ValueError, '%s is not valid' % str(valString) def refreshChoices(self): pass def getValueShortName(self): if self.value is None: return '' return self.valString def _createBlankUIToolsObj(self): self.refreshChoices() return ui_tools.UI_List(self.label, [ui_tools.UI_Choice(i[0],i[1]) for i in self.choices]) class FloatCmdParam(UICmdParamInterface): def setValue(self, valString): self.value = float(valString) self.valString = valString class GroupCmdParam(UICmdParamInterface): def setValue(self, valString): raise ValueError, 'setValue not supported for GroupCmdParam' def linesForDisplay(self): if len(self.cmdParams) > 0: return ['Setting%s for %s' % (len(self.cmdParams) > 1 and 's' or '', self.briefLabel)] +[' %d %s' % (n, a.status) for n, a in enumerate(self.cmdParams)] return 'Empty group of command params %s.' % self.briefLabel def expandSelfForCode(self): retString = ''.join(map(UICmdParamInterface.expandCommand, self.cmdParams)) return retString class InFileCmdParam(UICmdParamInterface): pass class IntegerCmdParam(UICmdParamInterface): def setValue(self, valString): epsilon = 1e-06 v = int(valString) fv = float(valString) if abs(fv - float(v)) > epsilon: ValueError, 'IntegerCmdParam.setValue expects integer argument' self.value = v self.valString = valString class InterfaceCmdParam(ExclCmdParam): def expandSelfForCode(self): retString = ''.join(map(UICmdParamInterface.expandCommand, self.cmdParams)) return retString def getServantConfigCmd(self): if self.value is not None and self.cmdParams: return self.expandCommand() return None def takeText(self, valString): if valString.upper() == 'CONFIGURE': return self.do_configure() super(InterfaceCmdParam, self).takeText(valString) def do_configure(self): assert(self.registry is not None) subUIXML = self.registry.getUIXML(self.value) if subUIXML is None: raise ValueError, 'No interface description found for %s' % self.valString uiContent = CipresServiceCommandPanel('service-command-panel', {}) if not uiContent.parseFileObj(subUIXML): raise ValueError, 'Error reading the interface description for %s' % self.valString if len(self.cmdParams): raise ValueError, 'No configurable parameters found in the interface description of %s' % self.valString self.cmdParams = uiContent.cmdParams del uiContent.cmdParams if not self.cmdParams: self.unconfigurableChild = True for i in self.cmdParams: i.parent = self i.setRegistry(self.registry) self.configureRan = True return def setValue(self, valString): self.configureRan = False self.unconfigurableChild = False if valString == '': self.value = None self.cmdParams = [] self.valString = '' return ExclCmdParam.setValue(self, valString) self.parent.childChangedValue(self) def linesForDisplay(self): if self.value is None: self.refreshChoices() if len(self.choices) > 1: choiceStrings = [' %d %s' % (n, i[1].entryInfo()) for n,i in enumerate(self.choices)] return ['Possible choices for %s:' % self.briefLabel] + choiceStrings + ['Current value is %s' % self.getValueShortName()] if len(self.choices) == 1: self.setValue(self.choices[0][0]) return ['Current value is %s (which is the only valid choice for %s)' % (self.getValueShortName(), self.briefLabel)] return ['There are no available implementations for %s.' % self.briefLabel] else: if self.cmdParams: return ['Setting%s for %s' % (len(self.cmdParams) > 1 and 's' or '', self.briefLabel)] +[' %d %s' % (n, a.status) for n, a in enumerate(self.cmdParams)] elif self.unconfigurableChild: return ['There are no configurable settings for %s ' % self.briefLabel] else: assert(not self.configureRan) self.do_configure() return self.linesForDisplay() def setRegistry(self, registry): self.configureRan = False self.registry = registry UICmdParamInterface.setRegistry(self, registry) self.linesForDisplay() def postCast(self): self.configureRan = False self.registry = None self.regEntries = [] def refreshChoices(self): if len(self.regEntries) == 0: if self.registry is None: raise ValueError, 'No registry specified' query = CipresIDL_api1.RegistryEntryInfo('', '' , '', '') query.repositoryID = 'IDL:CipresIDL_api1/%s:1.0' % self.interfaceName self.regEntries = self.registry.find(query) if len(self.regEntries) == 0: raise ValueError, 'No implementations of %s found in registry.' % self.interfaceName self.choices = map(lambda i: (i.entryInfo(), i), self.regEntries) def getValueShortName(self): if self.value is None: return '' return self.value.briefEntryInfo() class ListCmdParam(GroupCmdParam): def getServantConfigCmd(self): return map(UICmdParamInterface.expandCommand, self.cmdParams) def linesForDisplay(self): if len(self.cmdParams) > 0: return ['List of object(s) for %s' % self.briefLabel] +[' %d %s' % (n, str(a)) for n, a in enumerate(self.cmdParams)] + ['Enter the number of an object to configure it'] return 'Empty group of command params %s.' % self.briefLabel def postCast(self): self.value = self.cmdParams #self.cmdParams = copy.deepcopy(self.value) self.resetValString() assert(len(self.cmdParams)) def resetValue(self): #self.value = copy.deepcopy(self.cmdParams) self.resetValString() def resetValString(self): self.valString = self.value is None and '[]' or '[%s]' % ', '.join(map(str, self.value)) def getStatus(self): super(ListCmdParam, self) def setValue(self, valString): '''This is a dangerous method to call because all of the contained elements will be copied and then re-set. If they are complex objects this will replicate non-setting dependent attributes. The situation is complex if the number of list elements specified is greater than the current number of elements in the list - the trailing elements will be copies of the original cmdParams (or the first original cmdParams if there are not corresponding entries).''' if valString == '': self.resetValString() # hook return if valString[0] != '[' or valString[-1] != ']': raise ValueError, 'Expecing ListCmdParam to be in [] brackets.''' valList = valString[1:-1].split() assert(valList == []) newValues = [] for n, v in enumerate(valList): '''@TEMP this loop is unreachable (because of the assert) basically allowing setValue for lists is going to be too tricky to deal with soon.''' if n < len(self.value): newCmdParam = self.value[n] else: ind = n < len(self.cmdParams) and n or 0 # use the original contained parameters as a template up to the length encountered, then use the first entry for all subsequent elements newCmdParam = copy.deepcopy(self.cmdParams[ind]) newCmdParam.setValue(v.strip()) newValues.append(newCmdParam) self.valString = valString self.value = newValues def childChangedValue(self, changedChild): self.resetValString() class PythonMethodCmdParam(UICmdParamInterface): def setValue(self, valString): try: evaluated = eval(valString) except: raise ValueError, '%s does not evaluate to a callable object' % valString if evaluated is not None and not callable(evaluated): raise ValueError, '%s is not callable' % valString self.value = evaluated self.valString = valString class StringCmdParam(UICmdParamInterface): pass UICmdParamFromXML.toDerivedClass = { "JPanel" : GroupCmdParam, "Excl" : ExclCmdParam, "Integer" : IntegerCmdParam, "Paragraph" : GroupCmdParam, "String" : StringCmdParam, "InFile" : InFileCmdParam, "Boolean" : BooleanCmdParam, "Float" : FloatCmdParam, "Python Method" : PythonMethodCmdParam, "List" : ListCmdParam, } class ValueObjectFromXML(SAXConstructible): def endSelfElement(self, name): self.isDefault = self.isDefault and self.isDefault.upper() == 'TRUE' #statics ValueObjectFromXML._CTH = ChildrenToHandle( attr = [ 'is-default', ], singleEl = { 'value': TextOnlyElement, 'display': TextOnlyElement }) UITestFromXML._CTH = ChildrenToHandle( attr = [ 'test-name', ], singleEl = { 'left-operand': TextOnlyElement, 'operator': TextOnlyElement, 'right-operand': TextOnlyElement }) UICmdParamFromXML._CTH = ChildrenToHandle( attr = [ 'name', ], singleEl = { 'datatype': TextOnlyElement, 'is-mandatory': TextOnlyElement, 'label': TextOnlyElement, 'default-value': TextOnlyElement, 'tool-tip-text': TextOnlyElement, 'code-template': TextOnlyElement, 'switch-true-value' : TextOnlyElement, 'switch-false-value' : TextOnlyElement, 'requirement-test-expression' : UITestFromXML, 'requirement-test-expression-message' : TextOnlyElement, 'availability-test-expression' : UITestFromXML, 'availability-test-expression-message' : TextOnlyElement, }, multiEl = { 'cmd-params' : UICmdParamFromXML, 'list-item' : ValueObjectFromXML, }) CipresServiceCommandPanel._CTH = ChildrenToHandle( attr = [ 'label', ], multiEl = { 'cmd-params' : UICmdParamFromXML, }) # This file is part of the PIPRes library # The PIPRes library is free software; you can redistribute it # and/or modify it under the terms of the GNU Lesser General # Public License as published by the Free Software Foundation; # either version 2.1 of the License, or (at your option) any later # version. # # This library 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free # Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, # MA 02111-1307, USA