Select Top Level Row That Is a Task or That Has a Descendant Subtask

Hello all!

I am trying to adapt the script Jesse provided in the Outline Paths release topic to the journal script from the Bike guide.

I have modified the outline path to look for child tasks from the previous sibling of the “today” row, and transfer their top level row into the newest today node. However, the only way I can get the top level row of any descendant that contains a subtask is by hard coding the value for @level.

Here’s a sample outline path:

//@id matches "2023_11_08"/task not @done union //@id matches "2023_11_08"/descendant:: not task/task not @done/ancestor:: @level matches 4

I tried using a value expression, but (//@id matches "2023_11_08"/@level) didn’t evaluate to 4.

Did I misinterpret the use of value expressions, or is there another way I could write this path to achieve my goal? I can also attach a sample journal file if necessary.

And if anyone wants to see / use the full script in its current state please expand this triangle
set yearName to do shell script "date +'%Y'"
set yearId to do shell script "date +'%Y'" & "/00/00"
set monthName to do shell script "date +'%B'"
set monthId to do shell script "date +'%Y_%m'" & "/00"
set dayName to do shell script "date +'%a, %B %e, %Y'"
set dayId to do shell script "date +'%Y_%m_%d'"

tell application "Bike"
	tell front document
		set y to my getOrMake(yearId, yearName, root row)
		set m to my getOrMake(monthId, monthName, y)
		set d to my getOrMake(dayId, dayName, m)
			set yesterdayId to id of prev sibling row of d
		on error
			select at make row at back of rows of d
		end try
		set notDonePath to "//@id matches" & "\"" & yesterdayId & "\"" & "/task not @done union //@id matches " & "\"" & yesterdayId & "\"" & "/descendant:: not task/task not @done/ancestor:: @level matches 4"
		set notDoneTasks to query outline path notDonePath
		repeat with notDoneTask in notDoneTasks
			move notDoneTask to d
		end repeat
		select at make row at back of rows of d
	end tell
end tell

to getOrMake(getId, getName, rowContainer)
	using terms from application "Bike"
		tell container document of rowContainer
			if exists row id getId then
				return row id getId
				tell rowContainer
					return make row with properties {id:getId, name:getName, type:heading}
				end tell
			end if
		end tell
	end using terms from
end getOrMake

Just responding to this line for the moment… it is saying:

  1. //@id matches "2023_11_08"
    Means select any descendant of the root row whose @id attribute value regular expression matches “2023_11_08”. I think one potential problem is that you are using matches when you should be using = instead.

  2. /@level
    Means from the previously selected rows look at their child rows and return any that have a @level attribute. The @level attribute is built in, so that will just match all children. (Not there isn’t a way currently to have a path lookup and return attribute values).

Can you provide a an example outline showing the types of things you are trying to query. I’m having a hard time understanding the goal details.

Thanks for the insight on how the attribute values work, and the suggestion to use = instead of matches.

I have attached a sample outline file where I’ve been running the query.

What I am trying to do is grab every row from the yesterday node that contains a task or is itself a task I will move these rows to my current, today node.

The goal is to preserve context of tasks and the indentation so that the following structure is copied to the new node

  • Topic
    • Sub topic
      • [ ] task

instead of just [ ] task getting copied to the new node. Grabbing the Topic node and moving was the best way I found to preserve the structure. (1.7 KB)

Ah, got it. So what makes it tricky is you are looking for unfinished tasks, but then want to jump back and move their ancestors. I would think about it like this:

  1. //@id = "2023_11_08"//task not @done
    Find the tasks from previous day

  2. //@id = "2023_11_08"//task not @done/ancestor-or-self::*
    Now that you have the tasks you need to find the right ancestor to move, start by finding them all.

  3. //@id = "2023_11_08"//task not @done/ancestor-or-self::* intersect //@id = "2023_11_08"/*
    Finally you don’t want all the ancestors, you only want children of the previous day, so intersect with those children.

To be clear… there is only one path here, I’m just extending it with the new requirements. You should only need to do a single query in your script.

I “think” that’s what you want. Let me know if that seems to work or not.


This does the trick! Thanks for helping me use the right tools to get the job done.

1 Like