def add(self, timeObject, timeString, rule = None): ''' timeObject - the id found in the <time> tag timeString - the text of the <time> tag rule - this parameter is purely for recursion. Because the rule has already been created, it can be passed in as a parameter to avoid creating an unnecessary copy. Recursion is only employed if the rule is a constant (no symbols); otherwise, a modified timeObject will invalidate the rule. ''' log('Adding rule %s: ' % str(timeObject) + timeString.encode('utf-8')) # Use 1-based time for rule resolution timeObject.hours = (timeObject.hours - 1) % (24 if self.use24 else 12) + 1 # Create the rule if not rule: rule = [self.createToken(word, timeObject) for word in timeString.split(' ')] # If rule contains no symbols, only constants, then only add rule for its hour isConst = self.isConstant(rule) # Now that the rule is created, we can apply the default duration if # the rule is all constant if isConst and not timeObject.duration: timeObject.duration = Time.fromSeconds(self.defaultDuration) for i in range(len(self.rules)): hour = (i - 1) % (24 if self.use24 else 12) + 1 # make hour 1-based # Constant times are only for a single hour if isConst and hour != timeObject.hours: continue self.rules[i] = self.insert(self.rules[i], rule, timeObject, hour) if isConst: # However, if the duration pushes the rule into the next hour, # clone the rule (with an appropriately shorter duration) and # recursively add to the next hour as well. hourBoundary = (timeObject.hours + 1) * 3600 if timeObject.end() > hourBoundary: # Start at the beginning of the next hour nextTime = Time(timeObject.hours + 1, 0, 0) nextTime.duration = Time.fromSeconds(timeObject.end() - hourBoundary) nextTime.useSeconds = timeObject.useSeconds # For cosmetics self.add(nextTime, timeString, rule) break
def __init__(self, file): log('Using layout: ' + os.path.basename(file)) try: root = ElementTree.parse(file).getroot() except: log('Error parsing layout file!') raise # Let the user see the error try: background = root.find('background') self.height = int(background.attrib['height']) self.width = int(background.attrib['width']) except: log('Error: <background> tag missing/incorrect!') sys.exit() self.matrix = [] entities = [ char.strip().upper() for char in background.text.split(',') ] if (self.height * self.width > len(entities)): log('Error: Too many characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() elif (self.height * self.width < len(entities)): log('Error: Too few characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() for i in range(self.height): self.matrix.append(entities[i * self.width:(i + 1) * self.width]) timesNode = root.find('times') if timesNode == None: log('Error: <times> node not found!') sys.exit() if 'use24' in timesNode.attrib and timesNode.attrib['use24'] == 'true': self.use24 = True else: self.use24 = False self.times = {} for time in timesNode.findall('time'): if 'id' not in time.attrib: log('Warning: found <time> tag with no "id" attribute') continue key = Time(time.attrib['id']) # If set, store the duration with the key if 'duration' in time.attrib: key.duration = Time(time.attrib['duration']) self.times[key] = time.text.lower() if len(self.times) == 0: log('Error: no <time> tags found!') sys.exit() self.strings = {} stringsNode = root.find('strings') if stringsNode == None: log('Error: <strings> node not found!') sys.exit() for string in stringsNode.findall('string'): if 'id' not in string.attrib: log('Warning: found <string> tag with no "id" attribute') continue if string.text == None or string.text.strip() == '': log('Warning: empty <string> tag for id=%s' % string.attrib['id']) continue self.strings[int( string.attrib['id'])] = string.text.strip().lower()
def insert(self, node, rule, time, ruleChainHour): ''' Recursive functions can get really tricky really quickly sticky. Ideally, this docstring would contain some helpful hints. Here's some terminology: "this node" - the parameter called node "this rule" - the parameter called rule, not this node's rule Once upon a time, the code for this function was short and concise. Then the dawning of the "duration" attribute appeared. With this attribute, a constant and a rule of variants can overlap and overwrite other rules. The code is now much more complex, because each hour has to be properly partitioned by rules defined in all hours; and not only this, but this partitioned time has to be represented by linked lists and everything has to occur recursively. Keep in mind: Constants ALWAYS have a duration (if it isn't explicitly declared, then it defaults to the GCD). Rule with no duration are considered to be rules with INFINITE duration. Variant rules CAN have a duration; this acts to give them a lower priority in times past the end of the duration and a higher priority in times during the duration. The return value of this function is a RuleNode which (in the first level deep) becomes the new root node for that hour in the RuleChain. Therefore, it is helpful to think of the return value as a way to modify the parent node's next node. ''' # If true, rule will override node during conflicts precedence = ruleChainHour >= time.hours if node == None: # Base case: insert a new node into the rule chain by creating it # and setting its child to this node. return RuleNode(rule, time, node) # Assume the same hour as the node so comparisons will work tempTime = Time(node.time.hours, time.minutes, time.seconds) tempTime.duration = time.duration # At the highest level, we consider three cases: the time attached to # this rule is either before, equal to, or after the time of this node. if tempTime.toSeconds() < node.time.toSeconds(): # Time occurs before this node. In all cases, the rule is prepended # to the chain. Keep in mind, a time with no duration is basically # a time with infinite duration. Also keep in mind, Constants # ALWAYS have a duration. if not time.duration: return RuleNode(rule, time, node) # Three cases: rules don't overlap, rules overlap partially, rules overlap fully # Case 1: rules don't overlap if tempTime.end() <= node.time.toSeconds(): return RuleNode(rule, time, node) # Case 2: rules overlap partially if tempTime.end() < node.time.end(): if precedence: # Move node into the furture and shorten its duration newBeginning = Time.fromSeconds(tempTime.end()) newDuration = node.time.duration.toSeconds() - (node.time.end() - tempTime.end()) newBeginning.duration = Time.fromSeconds(newDuration) node.time = newBeginning return RuleNode(rule, time, node) else: # Shorten time time.duration = Time.fromSeconds(node.time.toSeconds() - tempTime.toSeconds()) return RuleNode(rule, time, node) # Case 3: node is fully overlapped by rule # time.end() >= node.time.end() if precedence: # Not including this node in the return statement effectively # eliminates it. However, things aren't this simple. We need to # check if the next node is partially/fully consumed via # recursion. return self.insert(node.next, rule, time, ruleChainHour) else: # Split the rule into two nodes that fall on either side of # this node. We create the following chain: # parent node -> node1 -> node -> node2 -> node.next time1 = time.copy() time1.duration = Time.fromSeconds(node.time.toSeconds() - tempTime.toSeconds()) node1 = RuleNode(rule, time1, node) time2 = Time.fromSeconds(node.time.end()) time2.hours = time.hours # Use original hours to maintain precedence time2.duration = Time.fromSeconds(tempTime.end() - node.time.end()) # Use recursion, because time2 might extend past node.next node2 = self.insert(node.next, rule, time2, ruleChainHour) node.next = node2 return node1 # The case where rule occured before node was relatively straightforward. # Now, rule and node occur at the same time, which means that most # likely either rule or node is omitted. if tempTime.toSeconds() == node.time.toSeconds(): if not precedence: # Ignore the rule return node # We've established that the rule has precedence. Now it's just a # matter of finding out how much of node (and its children) to # delete. # Three cases # Case 1: No rule duration if not tempTime.duration: # Replace the node return RuleNode(rule, time, node.next) # Case 2: Rule duration, but no node duration if not node.time.duration: # Peek ahead at the future node if node.next: tempTime2 = Time(node.next.time.hours, time.minutes, time.seconds) tempTime2.duration = time.duration if tempTime2.end() > node.next.time.toSeconds(): # This node is fully engulfed. Replace the node, and # make sure that we replace any other overlapped nodes # (recursively, of course) # parent node -> node1 -> node.next node1 = self.insert(node.next, rule, time, ruleChainHour) return node1 # Make this node start at the end of this rule node.time = Time.fromSeconds(tempTime.end()) return RuleNode(rule, time, node) # Case 3: Rule duration AND node duration. Let the battle begin! if tempTime.end() >= node.time.end(): # Replace the node and any following nodes if necessary node1 = self.insert(node.next, rule, time, ruleChainHour) return node1 else: # Chop off and preserve the dangling part end = node.time.end() node.time = Time.fromSeconds(tempTime.end()) # node.time.hours is already set because tempTime.hours was copied earlier node.time.duration = Time.fromSeconds(end - tempTime.end()) return RuleNode(rule, time, node) # Rule occurs in the future: tempTime.toSeconds() > node.time.toSeconds() # Three cases: rules don't overlap, rules overlap partially, rules overlap fully # Case 1 if node.time.end() <= tempTime.toSeconds(): # If node.rule is a constant, it doesn't need to persist after its duration if not tempTime.duration or self.isConstant(node.rule): # Regular rule or node is constant, recurse deeper node.next = self.insert(node.next, rule, time, ruleChainHour) return node else: # Peek ahead at the future node if node.next: tempTime2 = Time(node.next.time.hours, time.minutes, time.seconds) tempTime2.duration = time.duration if tempTime2.toSeconds() > node.next.time.toSeconds(): # Next node is in the future too, recurse deeper node.next = self.insert(node.next, rule, time, ruleChainHour) return node # tempTime has a duration so node should persist after rule's completion # To do so, we dupe node and let recursion do the rest timeCopy = Time.fromSeconds(tempTime.toSeconds()) if node.time.duration: # Need to modify duration of both old and new time timeCopy.duration = Time.fromSeconds(node.time.end() - tempTime.toSeconds()) node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) nodeCopy = RuleNode(node.rule, timeCopy, node.next) node.next = self.insert(nodeCopy, rule, time, ruleChainHour) return node # Implicit that node.time.duration exists # Case 2 if tempTime.end() > node.time.end(): # Implicit that tempTime.duration exists if precedence: # Shorten node node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) # Link in the new node after this one node.next = RuleNode(rule, time, node.next) return node else: # Shorten rule by giving it a later starting time time2 = Time.fromSeconds(node.time.end()) time2.hours = time.hours # Use original hours to maintain precedence time2.duration = Time.fromSeconds(tempTime.end() - node.time.end()) node.next = RuleNode(rule, time2, node.next) return node # Case 3: node.time has a duration and tempTime occurs in the middle of it if not precedence: if tempTime.duration: # tempTime is fully engulfed by node return node else: # tempTime is a rule, so it continues past node time2 = Time.fromSeconds(node.time.end()) node.next = self.insert(node.next, rule, time2, ruleChainHour) return node if not tempTime.duration: # tempTime is a regular rule, so chop node in half and occupy the # second half with this rule node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) # Link in the new node after this one node.next = RuleNode(rule, time, node.next) return node if tempTime.end() == node.time.end(): # Ends coincide. Things are easy, just chop and link node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) # Link in the new node after this one node.next = RuleNode(rule, time, node.next) return node # Things are messy. Bisect node: # parent node -> node(1) -> rule -> node(2) -> node.next # Go from last to first. Start with second node: time2 = Time.fromSeconds(tempTime.end()) time2.duration = Time.fromSeconds(node.time.end() - tempTime.end()) node2 = RuleNode(node.rule, time2, node.next) newNode = RuleNode(rule, time, node2) time1 = node.time.copy() time1.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) node.time = time1 node.next = newNode return node
def __init__(self, file): log('Using layout: ' + os.path.basename(file)) try: root = ElementTree.parse(file).getroot() except: log('Error parsing layout file!') raise # Let the user see the error try: background = root.find('background') self.height = int(background.attrib['height']) self.width = int(background.attrib['width']) except: log('Error: <background> tag missing/incorrect!') sys.exit() self.matrix = [] entities = [char.strip().upper() for char in background.text.split(',')] if (self.height * self.width > len(entities)): log('Error: Too many characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() elif (self.height * self.width < len(entities)): log('Error: Too few characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() for i in range(self.height): self.matrix.append(entities[i * self.width : (i + 1) * self.width]) timesNode = root.find('times') if timesNode == None: log('Error: <times> node not found!') sys.exit() if 'use24' in timesNode.attrib and timesNode.attrib['use24'] == 'true': self.use24 = True else: self.use24 = False self.times = {} for time in timesNode.findall('time'): if 'id' not in time.attrib: log('Warning: found <time> tag with no "id" attribute') continue key = Time(time.attrib['id']) # If set, store the duration with the key if 'duration' in time.attrib: key.duration = Time(time.attrib['duration']) self.times[key] = time.text.lower() if len(self.times) == 0: log('Error: no <time> tags found!') sys.exit() self.strings = {} stringsNode = root.find('strings') if stringsNode == None: log('Error: <strings> node not found!') sys.exit() for string in stringsNode.findall('string'): if 'id' not in string.attrib: log('Warning: found <string> tag with no "id" attribute') continue if string.text == None or string.text.strip() == '': log('Warning: empty <string> tag for id=%s' % string.attrib['id']) continue self.strings[int(string.attrib['id'])] = string.text.strip().lower()