Hierarchical List of Cells Matching a Condition
Hi all,
I am trying to use OmniOutliner to manage a product backlog for a project plan. I've created an outline with a hierarchical list of topics, owners, sprint due, and some other columns. I am trying to write an AppleScript (well, really a Python script using appscript to interface with the AppleScript API) that allows me to print a filtered version of the outline to a text file. It appears there is a way to easily filter for a list of rows directly using something like this Python code: te.documents[2].rows.cells[its.value.contains("Bob") or its.value.contains("Steve") or its.ancestors()].value() [[u'Bob, Steve'], [], [], [u'Bob, Rich, Steve, others'], [u'Bob, Rich, Steve, others']] But what I really want is to get a list of rows in a parent-child relationship so I can print the any portion of the hierarchy where the search terms appear anywhere at a given level or below. So: 1. Top topic 1.1 Foo | Andy 1.2 Bar | Joe 2. Second topic 2.1 foobar | Bob 2.1.1 abc 2.2 blah | Robin If I search for "Bob", I want to see: 2. Second topic 2.1 foobar | Bob 2.1.1 abc Is something like that possible using AppleScript? Or am I limited to just getting a list of matching rows with no hierarchy? I have searched through these forums, AppleScript documentation, Google, and Safari Books Online. I'm new to this type of scripting (but not programming in general), and may be missing something obvious. I did find a few scripts in this forum that filter a doc based on a search string, but they don't seem to keep the hierarchy intact. Any help would be greatly appreciated. The solution I'm converging on now is to make a tree in Python that I manage myself, prune the tree based on a search, and finally print the pruned version of the tree. Thanks, JL |
Once you have found a row with a cell which matches your search, the [I]ancestors[/I] property of that row will give you the root of the relevant sub-tree.
You can then either make an ordered traversal of the subtree by recursing through the [I]children[/I] collection of the ancestor (and on downwards), or, perhaps more simply, use the flattened [I]rows[/I] collection of the ancestor, using the [I]level[/I] property of each row to derive the indentation for printing. [CODE]SubTreeMatch("bob") on SubTreeMatch(strSearch) tell application id "OOut" tell front document set lstRows to rows where topic contains strSearch if (count of lstRows) < 1 then return "" set strTree to "" repeat with oMatch in lstRows tell oMatch if level > 1 then set oAncestor to last item of ancestors else set oAncestor to it end if end tell tell oAncestor set strTree to strTree & topic & return set {lstLevel, lstTopic} to {level, topic} of rows repeat with i from 1 to length of lstLevel set strTree to strTree & my LevelTabs(item i of lstLevel) & item i of lstTopic & return end repeat end tell set strTree to strTree & return end repeat end tell end tell return strTree end SubTreeMatch on LevelTabs(n) if n < 2 then return "" set str to "" repeat with i from 2 to n set str to str & tab end repeat str end LevelTabs [/CODE] [COLOR="White"]--[/COLOR] |
Hi Rob,
Thanks for the quick response. I'd considered following the ancestors and children to print the hierarchy, but I think there is a problem with that approach. Since I'm not very familiar with Apple Script I may be missing this in your code, but how do you deal with the possibility that within a reasonably complex hierarchy several rows matching the search query will be returned. If you print each one, you'd get multiple duplications of hierarchy. For example, let's make my previous outline a bit more complex: 1. Top topic 1.1 Foo | Andy 1.2 Bar | Joe 2. Second topic <-- Common ancestor of both of the tasks for Bob 2.1 foobar | Bob <-- First task for Bob 2.1.1 abc 2.2 blah | Robin 2.3 blah blah 2.3.1 blah blah blah 2.3.2 blah blah 123 | Bob <-- Another task to be done by Bob What I want to print is: 2. Second topic 2.1 foobar | Bob 2.1.1 abc 2.3 blah blah <-- Notice I skipped 2.2 altogether 2.3.1 blah blah blah 2.3.2 blah blah 123 | Bob Is this case covered by the code you've provided? I tried running one of your posted filtering scripts yesterday but it does not appear to handles the situation correctly either ([url]http://forums.omnigroup.com/showthread.php?t=16392[/url]). The new document just contains a flat list of rows. I can't think of a clean way to handle this other than just managing the tree of objects myself in Python (which I have working, but it's slow and queries are not as powerful as if I'd let OmniOutliner process them directly). This all reminds me of another question... is there a way to tell what type of object you're dealing with in Apple Script? I'm building a tree but the very first node is actually a document. So I manually set the top-level item to not be a "row", but it seems there should be a way to query objects directly to identify the type. One idea we had (since we're programming in Python) is something like: def calc_is_row(o): return o.__repr__().find('row') != -1 Basically, just look at the string representation of the object-type and if it contains the word "row", it is a row. Thanks, JL |
[QUOTE=JLGray;95621]how do you deal with the possibility that within a reasonably complex hierarchy several rows matching the search query will be returned.[/QUOTE]
You get a union of the full list of ancestors - that is, purge any duplicate ancestors before printing the trees. [CODE]SubTreeMatch("bob") on SubTreeMatch(strSearch) tell application id "OOut" tell front document set lstRows to rows where topic contains strSearch set lstAncestors to my GetAncestors(lstRows) end tell end tell -- Process unique list of ancestors -- ... end SubTreeMatch -- Purge any duplicate ancestors on GetAncestors(lstRows) set lstUnion to {} tell application id "OOut" repeat with oRow in lstRows tell oRow if level > 1 then set strID to id of (last ancestor) else set strID to id of it end if end tell if not (lstUnion contains strID) then set end of lstUnion to strID end repeat if lstUnion ≠ {} then tell front document repeat with i from 1 to length of lstUnion set item i of lstUnion to row id (item i of lstUnion) end repeat end tell lstUnion else {} end if end tell end GetAncestors[/CODE] [QUOTE=JLGray;95621]is there a way to tell what type of object you're dealing with in Apple Script?[/QUOTE] Yes, objects have a [I]class[/I] property. [CODE]if class of oObject is document then ...[/CODE] |
I will go off and think about this some more. My first concern is that this doesn't really help, because now I have a flat list of ancestors that is unique, but I don't know the hierarchy of the ancestors, so I'll have to process them all again to figure out the hierarchy. Might as well to have just built up my own tree...?
[CODE] class TreeObj: """Hold a pointer to a row object and its children. Create a tree mirroring OmniOutliner structs""" def __init__(self, o, is_row=0): self.obj = o self.children = [] self.hidden = 0 self.is_row = is_row def init_tree(self): """Recursively initialize a tree of python objects initialized from OmniOutliner doc""" for c_apple in self.obj.children(): c = TreeObj(c_apple, 1) c.init_tree() self.children.append(c) def find_row(self, pattern): retval = 0 if (self.is_row and not self.hidden): for cell in self.obj.cells(): if (re.search(pattern, str(cell.value()))): #print "found pattern " + pattern return 1 for child in self.children: if (1 == child.find_row(pattern)): retval = 1 if (retval == 0): self.hidden = 1 return retval # ... etc [/CODE] |
[QUOTE=JLGray;95628]I don't know the hierarchy of the ancestors, so I'll have to process them all again to figure out the hierarchy. Might as well to have just built up my own tree...? [/QUOTE]
You do know the hierarchy. No need to process them again :-) As before, the [I]rows[/I] property of each ancestor is an ordered top-down left-right traversal, ready for printing. Each ancestor is a unique top level node. All you need to do is supply the indentations from the level of each row, using the function which I supplied in the first posting. [COLOR="White"]--[/COLOR] |
Signing off now for the weekend, but here is a sketch:
[CODE]SubTreeMatch("bob") on SubTreeMatch(strSearch) tell application id "OOut" tell front document set lstRows to rows where topic contains strSearch set lstAncestors to my GetAncestors(lstRows) end tell -- Process unique list of ancestors set strTree to "" repeat with oAncestor in lstAncestors tell oAncestor set strTree to strTree & topic & return set {lstLevel, lstTopic} to {level, topic} of rows repeat with i from 1 to length of lstLevel set strTree to strTree & my LevelTabs(item i of lstLevel) & item i of lstTopic & return end repeat end tell set strTree to strTree & return end repeat end tell return strTree end SubTreeMatch on LevelTabs(n) if n < 2 then return "" set str to "" repeat with i from 2 to n set str to str & tab end repeat str end LevelTabs -- Purge any duplicate ancestors on GetAncestors(lstRows) set lstUnion to {} tell application id "OOut" repeat with oRow in lstRows tell oRow if level > 1 then set strID to id of (last ancestor) else set strID to id of it end if end tell if not (lstUnion contains strID) then set end of lstUnion to strID end repeat if lstUnion ≠ {} then tell front document repeat with i from 1 to length of lstUnion set item i of lstUnion to row id (item i of lstUnion) end repeat end tell lstUnion else {} end if end tell end GetAncestors[/CODE] |
Thanks, Rob. All the info you're providing is sinking in... just not quite as fast as I'd like. Thanks for your patience!
JL |
All times are GMT -8. The time now is 04:08 AM. |
Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2024, vBulletin Solutions, Inc.