content: property pTitle : "Test FT path expressions"
  property pVer : ".017"
  property pAuthor : "Rob Trew"
  
  property pstrPath : "/Node paths/@line matches '^##'"
  property plstNodes : {}
  
  property pblnExcludeRoot : false -- Defaults to excluding the virtual root from any query results
  
  property pblnSelect : false -- Select chosen line, rather than focusing on it
  
  property pblnClipMatches : true
  
  -- SUMMARY OF SOME SYNTAX ELEMENTS - USED TO ADD PATH SYNTAX ELEMENTS FROM MENUS
  property plstMain : {"EXAMPLES", "Simple path", "Axis", "Attribute", "Node type", "Comparison operator", "Set operation"}
  
  property plstExamples : {{"/@type=heading", "Top-level headings"}, {"//@type=heading", "All headings"}, ¬
  	{"/*/@type!=empty", "2nd level, excluding blank lines"}, {"///problem", "Any line containing 'problem', with its ancestors"}, {"//* except //*/ancestor::*", "Leaf nodes - 'anything except nodes with descendants'"}}
  
  property plstSimplePaths : {{"/*", "All children (of top level, by default)"}, {"/*/*", "All grandchildren"}, ¬
  	{"/*/*/*", "All great-grandchildren"}, {"//*", "All descendants"}, {"///*", "All descendants + their ancestral paths"}, {"/*///*", "Whole document"}}
  
  -- Ver .015 Corrects description of following-sibling:: and preceding-sibling::
  
  property plstAxes : {¬
  	{"ancestor::", "All ancestors matching predicate"}, ¬
  	{"ancestor-or-self::", "Nodes and their ancestors matching predicate"}, ¬
  	{"descendant::", "All descendants matching predicate. Same as '//' operator"}, ¬
  	{"descendant-or-self::", "Nodes and their descendants matching predicate"}, ¬
  	{"following::", "All nodes following each of the previously matched nodes"}, ¬
  	{"following-sibling::", "Sibling nodes following each of the previously matched nodes"}, ¬
  	{"preceding::", "All nodes preceding each of the previously matched nodes"}, ¬
  	{"preceding-sibling::", "Sibling nodes preceding each of the previously matched nodes"}, ¬
  	{"child::", "Searches child node, same as '/' operator"}, ¬
  	{"parent::", "Searches parent node, same as '..' operator"}, ¬
  	{"self::", "Matches predicate on the same node"}, ¬
  	{"filter-descendants::", "Filter all descendants & return with ancestors, same as '///' operator"} ¬
  		}
  
  property plstTypes : {{"heading", "#"}, {"body", "plain"}, {"ordered", "1. 2. 3."}, {"unordered", "- or *"}, {"blockquote", ">"}, ¬
  	{"codeblock", "(4 spaces)"}, {"linkdef", "[id]:"}, {"property", "key : value"}, {"term", "(above :)"}, {"definition", ":"}, {"horizontalrule", {"***"}}, {"empty", "blank"}}
  
  property plstAttribs : {{"@type", ""}, {"@line", "includes the Markdown prefix"}, {"@mode", ""}, {"@modeContext", ""}, {"@id", ""}, {"@property", ""}, {"@<tagname>", "user-defined"}}
  
  -- Enclose regexes and special characters in single quotes
  property plstRelns : {{" contains ", ""}, {" matches ''", "Javascript regex in single quotes"}, {" beginswith ", ""}, {" endswith ", ""}, {"=", ""}, {"!=", ""}, {"<", ""}, {">", ""}, {"<=", ""}, {">=", ""}}
  
  property plstSetOps : {{" union ", ""}, {" intersect ", ""}, {" except ", ""}}
  
  property plstSub : {plstExamples, plstSimplePaths, plstAxes, plstAttribs, plstTypes, plstRelns, plstSetOps}
  
  property pCancel : "Cancel"
  property pEditHelp : "Add element from path syntax menus"
  property pManual : "Return to manual editing"
  property pOK : "OK"
  
  on run
  	tell application "FoldingText"
  		
  		-- EXIT IF THERE IS NO FRONT DOCUMENT
  		set lstDocs to documents
  		if length of lstDocs < 1 then return
  		set oDoc to item 1 of lstDocs
  		
  		-- OTHERWISE LOOP UNTIL A VALID PATH EXPRESSION IS ENTERED, OR THE USER EXITS
  		set blnEsc to false
  		set strPath to pstrPath
  		repeat while not blnEsc
  			
  			-- PROMPT FOR A FOLDINGTEXT PATH EXPRESSION
  			activate
  			
  			set strBtn to ""
  			repeat while strBtn ≠ pOK
  				try
  					tell (display dialog "Path expression:" default answer strPath & linefeed & linefeed buttons {pCancel, pEditHelp, pOK} cancel button ¬
  						pCancel default button "OK" with title pTitle & tab & "  Ver " & pVer)
  						set {strBtn, strPath} to {button returned, my RTrim(text returned)}
  					end tell
  				on error
  					return
  				end try
  				
  				if strBtn = pEditHelp then
  					set strPath to my EditHelp(plstMain, strPath, pManual)
  				else
  					set strBtn to pOK
  				end if
  			end repeat
  			set pstrPath to strPath
  			
  			-- DISPLAY ANY MATCHES
  			set plstNodes to {}
  			tell oDoc
  				set strSafePath to strPath
  				if pblnExcludeRoot then set strSafePath to "(" & strSafePath & ") except ///@id=0"
  				
  				try
  					set plstNodes to (read nodes it at path strSafePath)
  				end try
  				
  				set lngNodes to length of plstNodes
  				set lngDigits to (length of (lngNodes as string))
  				
  				set lstChoice to {}
  				repeat with i from 1 to lngNodes
  					set end of lstChoice to my PadNum(i, lngDigits) & tab & |text| of item i of plstNodes
  				end repeat
  				
  				set lngChoice to length of lstChoice
  				if lngChoice < 1 then
  					activate
  					display dialog "No matches for " & strPath buttons {"OK"} default button "OK" with title pTitle & "  ver. " & pVer
  				end if
  			end tell
  			
  			-- FOCUS THE DOCUMENT  AND ALLOW THE USER TO CHOOSE ONE OF THE MATCHING LINES
  			if lngChoice > 0 then
  				if pblnClipMatches then -- to clipboard in Markdown code format (4 space prefix for each line)
  					set {dlm, my text item delimiters} to {my text item delimiters, linefeed & "    "}
  					set the clipboard to "    " & (lstChoice as string)
  					set my text item delimiters to dlm
  				end if
  				
  				
  				tell oDoc
  					-- FOCUS ON THE RESULTS
  					--update node path it with text strPath
  					
  					-- AND OFFER TO SELECT
  					activate
  					set varChoice to choose from list lstChoice with title pTitle & tab & pVer with prompt ¬
  						(lngChoice as string) & " matches in  " & name of it & " :" & return & return & strPath default items item 1 of lstChoice ¬
  						OK button name "Focus on matching line	(⌘[ to Focus Out) " cancel button name pCancel with empty selection allowed without multiple selections allowed
  					
  					if varChoice ≠ false then
  						
  						set varChoice to item 1 of varChoice
  						
  						-- GET THE DETAILS OF THE SELECTED NODE
  						set {dlm, my text item delimiters} to {my text item delimiters, tab}
  						set {lngNum, _} to text items of varChoice
  						set my text item delimiters to dlm
  						
  						set {lngTextIndex, strLine, strID} to {|textIndex|, |line|, |id|} of item lngNum of plstNodes
  						
  						-- MAKE SURE THAT THE BRANCH CONTAINING THE SELECTED LINE IS NOT COLLAPSED
  						set lstBranch to read nodes it at path "//@id=" & strID & "/ancestor::@type!=root"
  						update expanded nodes it with changes {|addNodes|:lstBranch}
  						
  						-- SELECT THE CHOSEN LINE
  						-- first allow a moment for the expansion to complete ...
  						--do shell script "sleep 0.2"
  						if pblnSelect then
  							update selection with changes {textRange:{location:lngTextIndex, |length|:(length of strLine)}}
  						else
  							update node path it with text "//@id=" & strID -- & "/descendant-or-self::*"
  						end if
  						blnEsc = true
  					end if
  				end tell
  			end if
  		end repeat
  	end tell
  end run
  
  on BuildChoice(lstMenu)
  	set lst to {}
  	set lngMenu to length of lstMenu
  	set lngMax to 0
  	-- FIRST PASS
  	repeat with oItem in lstMenu
  		set {strTerm, _} to oItem
  		set lngChars to length of strTerm
  		if lngChars > lngMax then set lngMax to lngChars
  	end repeat
  	set lngTarget to lngMax + 8
  	repeat with oItem in lstMenu
  		set {strTerm, strComment} to oItem
  		set lngChars to length of strTerm
  		set end of lst to strTerm & NChars(((lngTarget - lngChars) div 4) + 1, tab) & strComment
  	end repeat
  	return lst
  end BuildChoice
  
  on NChars(lngN, strChar)
  	set str to strChar
  	repeat with i from 1 to (lngN - 1)
  		set str to str & strChar
  	end repeat
  	return str
  end NChars
  
  on EditHelp(lstMenu, strPath, strCancel)
  	-- Append strings from menus until exit
  	set strNewPath to strPath
  	set strBtn to ""
  	if lstMenu ≠ plstMain then
  		set lstChoice to BuildChoice(lstMenu)
  		{}
  	else
  		set lstChoice to plstMain
  	end if
  	tell application "FoldingText"
  		activate
  		repeat while strBtn ≠ pOK
  			
  			set varChoice to choose from list lstChoice with title pTitle & tab & pVer with prompt strNewPath default items item 1 of lstChoice ¬
  				OK button name "OK" cancel button name strCancel with empty selection allowed without multiple selections allowed
  			if varChoice ≠ false then
  				set varChoice to item 1 of varChoice
  			else
  				return strNewPath
  			end if
  			set {dlm, my text item delimiters} to {my text item delimiters, tab}
  			set varChoice to first text item of varChoice
  			set my text item delimiters to dlm
  			
  			if plstMain contains varChoice then
  				repeat with i from 1 to length of plstMain
  					set strChoice to item i of plstMain
  					if varChoice = strChoice then
  						set lstSubMenu to item i of plstSub
  						exit repeat
  					end if
  				end repeat
  				set strNewPath to my EditHelp(lstSubMenu, strNewPath, pCancel)
  			else
  				set strBtn to pOK
  				-- replace * with axis
  				if varChoice ends with "::" and strNewPath ends with "*" then
  					return (text 1 thru -2 of strNewPath) & varChoice
  				else if strNewPath ends with "::" then
  					return strNewPath & "*" & varChoice
  				else
  					return strNewPath & varChoice
  				end if
  				
  			end if
  		end repeat
  	end tell
  	return strNewPath
  end EditHelp
  
  -- LEFT PAD NUMBERS WITH ZEROS TO GET A FIXED LENGTH
  on PadNum(lngNum, lngDigits)
  	set strNum to lngNum as string
  	set lngGap to (lngDigits - (length of strNum))
  	repeat while lngGap > 0
  		set strNum to "0" & strNum
  		set lngGap to lngGap - 1
  	end repeat
  	strNum
  end PadNum
  
  -- REMOVE ANY INSTANCE OF NODE 0 WITH THIS
  -- (FOCUSING ON THE VIRTUAL ROOT NODE CAN TRIP AN ERROR)
  on PruneList(varItem, lst)
  	set lstPruned to {}
  	repeat with oItem in lst
  		set oItem to contents of oItem
  		if varItem ≠ oItem then set end of lstPruned to oItem
  	end repeat
  	return lstPruned
  end PruneList
  
  on RTrim(strText)
  	set lngChars to length of strText
  	if lngChars is 0 then return ""
  	set lstWhite to {space, tab, return, linefeed, ASCII character 0}
  	
  	repeat with iChar from length of strText to 1 by -1
  		if character iChar of strText is not in lstWhite then exit repeat
  	end repeat
  	set strText to text 1 thru iChar of strText
  	return strText
  end RTrim
  
  