The Omni Group
These forums are now read-only. Please visit our new forums to participate in discussion. A new account will be required to post in the new forums. For more info on the switch, see this post. Thank you!

Go Back   The Omni Group Forums > OmniOutliner > OmniOutliner 3 for Mac
FAQ Members List Calendar Search Today's Posts Mark Forums Read

 
Hierarchical List of Cells Matching a Condition Thread Tools Search this Thread Display Modes
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 ancestors 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 children collection of the ancestor (and on downwards), or, perhaps more simply, use the flattened rows collection of the ancestor, using the level 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
--

Last edited by RobTrew; 2011-04-08 at 05:08 AM.. Reason: Added illustrative code
 
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 (http://forums.omnigroup.com/showthread.php?t=16392). 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:
Originally Posted by JLGray View Post
how do you deal with the possibility that within a reasonably complex hierarchy several rows matching the search query will be returned.
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
Quote:
Originally Posted by JLGray View Post
is there a way to tell what type of object you're dealing with in Apple Script?
Yes, objects have a class property.

Code:
if class of oObject is document then ...
 
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
 
Quote:
Originally Posted by JLGray View Post
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...?
You do know the hierarchy. No need to process them again :-)

As before, the rows 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.

--

Last edited by RobTrew; 2011-04-08 at 12:01 PM..
 
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
 
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
 
 


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes


Similar Threads
Thread Thread Starter Forum Replies Last Post
Completion Cells leanda OmniFocus 2 for Mac (Private Test) 1 2013-04-08 10:17 PM
Selecting projects from a hierarchical list Stephen Brown OmniFocus for iPad 5 2012-09-03 09:50 PM
Merging Cells ktozawa OmniGraffle General 2 2011-04-29 01:52 PM
Merge cells in a row sauron93 OmniOutliner 3 for Mac 0 2009-03-05 04:22 PM
Bug or feature?: Non-matching items eronel OmniFocus 1 for Mac 8 2007-11-26 06:38 PM


All times are GMT -8. The time now is 03:30 PM.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2024, vBulletin Solutions, Inc.