The Omni Group Forums

The Omni Group Forums (http://forums.omnigroup.com/index.php)
-   OmniOutliner 3 for Mac (http://forums.omnigroup.com/forumdisplay.php?f=9)
-   -   Hierarchical List of Cells Matching a Condition (http://forums.omnigroup.com/showthread.php?t=20633)

JLGray 2011-04-07 08:31 PM

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

RobTrew 2011-04-07 11:34 PM

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]

JLGray 2011-04-08 06:39 AM

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

RobTrew 2011-04-08 08:59 AM

[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]

JLGray 2011-04-08 09:27 AM

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]

RobTrew 2011-04-08 10:52 AM

[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]

RobTrew 2011-04-08 11:01 AM

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]

JLGray 2011-04-08 12:21 PM

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.