Coerce GUI scripting information into string?

The conflict with Script Debugger 4.5 is the word specifier as a variable – it’s a dictionary word in SD4.5. Changing it to “spec” does the trick for both compilation AND operation. Works a treat, Nigel.

This is a very impressive script and will help me greatly!

I did have an execution error and needed to add several ‘& tab’ pairs to the ‘set tabs’ line.

Bill

Hi. Welcome to MacScripter.

Thanks for the feedback. I’ve edited the script above in the light of your and Adam’s comments. Twelve tabs weren’t enough?! :o

Hello! :slight_smile:

I got some Internal Table overflow errors, so I tinkered the Brilliant script to just give one chunk of output at a time, either window or menu.

I hope you forgive me! :smiley:


property ScriptTitle : "UI Properties"
-- By squaline (alias partron22?), emendelson, and Nigel Garvey.
-- http://macscripter.net/viewtopic.php?id=37674
on main()
	try
		tell application "System Events"
			set appNames to name of application processes whose visible is true
			set curApp to name of first application process whose frontmost is true
		end tell
		tell application curApp
			
			set appChoice to (choose from list appNames with prompt "Which running application?" with title "Choose an application")
			
			if (appChoice is false) then error number -128
			try
				set outputType to button returned of (display dialog "Choose output" with title ScriptTitle buttons {"Cancel", "Menu", "Window"} default button 2 cancel button 1 with icon 1)
			on error
				error number -128
			end try
			
		end tell
		set appName to item 1 of appChoice
		
		tell application "System Events"
			tell application process appName
				set frontmost to true
				set {windowExists, menuExists} to {front window exists, menu bar 1 exists}
				
				set {winstuff, menustuff} to {"(No window open)", "(No menu!)"}
				if outputType is "Window" then
					if (windowExists) then
						set winstuff to my listToText(entire contents of front window)
					else
						error number 3000
					end if
				else
					if (menuExists) then
						set menustuff to my listToText(entire contents of menu bar 1)
					else
						error number 3001
					end if
				end if
			end tell
		end tell
	on error e number n
		tell application "System Events" to set frontmost of application process curApp to true
		tell application curApp to activate
		if n = 3000 then
			tell application curApp to display alert "No windows to gather data from!"
			
		else if n = 3001 then
			tell application curApp to display alert "No menu to gather data from!"
		end if
		error number -128
	end try
	
	tell application "TextEdit"
		activate
		make new document at the front
		if outputType is "Window" then
			set the text of the front document to winstuff
		else
			set the text of the front document to menustuff
		end if
		set WrapToWindow to text 2 thru -1 of (localized string "&Wrap to Window")
	end tell
	
	tell application "System Events"
		tell application process "TextEdit"
			tell menu item WrapToWindow of menu 1 of menu bar item 5 of menu bar 1
				if ((it exists) and (it is enabled)) then perform action "AXPress"
			end tell
		end tell
	end tell
	tell application curApp to activate
	do shell script "open -b \"com.apple.textedit\""
	
end main

on listToText(entireContents) -- (Handler specialised for lists of System Events references.)
	try
		|| of entireContents -- Deliberate error.
	on error stuff -- Get the error message
	end try
	
	-- Parse the message.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {"{", "}"} -- Snow Leopard or later.
	set stuff to text from text item 2 to text item -2 of stuff
	
	-- If the list text isn't in decompiled form, create a script containing the list in its source code, store it in the Temporary Items folder, and run "osadecompile" on it.
	if (stuff does not contain "application process \"") then
		try
			set scpt to (run script "script
	tell app \"System Events\"
	{" & stuff & "}
	end
	end")
		on error errMsg
			set AppleScript's text item delimiters to astid
			tell application (path to frontmost application as text) to display dialog errMsg buttons {"OK"} default button 1 with icon caution
			return errMsg
		end try
		set tmpPath to (path to temporary items as text) & "Entire contents.scpt"
		store script scpt in file tmpPath replacing yes
		set stuff to (do shell script "osadecompile " & quoted form of POSIX path of tmpPath)
		set stuff to text from text item 2 to text item -2 of stuff
	end if
	
	-- Break up the text, using "\"System Events\", " as a delimiter.
	set AppleScript's text item delimiters to "\"System Events\", "
	set stuff to stuff's text items
	-- Insert a textual "reference" to the root object at the beginning of the resulting list.
	set AppleScript's text item delimiters to " of "
	set beginning of stuff to (text from text item 2 to -1 of item 1 of stuff) & "\"System Events\""
	-- Reduce the remaining "reference" fragments to object specifiers, tabbed according to the number of elements in the references.
	set tabs to tab & tab & tab & tab & tab & tab & tab & tab
	set tabs to tabs & tabs
	set tabs to tabs & tabs -- 32 tabs should be enough!
	repeat with i from 2 to (count stuff)
		set thisLine to item i of stuff
		set lineBits to thisLine's text items
		-- Make sure any " of "s in element names aren't mistaken for those of the reference!
		set elementCount to 0
		set nameContinuation to false
		repeat with j from 1 to (count lineBits)
			set thisBit to item j of lineBits
			if ((not ((nameContinuation) or (thisBit contains "\""))) or ((thisBit ends with "\"") and (thisBit does not end with "\\\"")) or (thisBit ends with "\\\\\"")) then
				-- thisBit is either a complete nameless-element specifier or it ends the right way to be either a complete named one or the completion of a name.
				set nameContinuation to false
				set elementCount to elementCount + 1
				if (elementCount is 1) then set spec to text 1 thru text item j of thisLine
			else
				-- The next "bit" will be the continuation of a name containing " of ".
				set nameContinuation to true
			end if
		end repeat
		set item i of stuff to (text 1 thru (elementCount - 3) of tabs) & spec
	end repeat
	-- Coerce back to a single text, inserting line feeds between the items.
	set AppleScript's text item delimiters to linefeed
	set stuff to stuff as text
	set AppleScript's text item delimiters to astid
	
	return stuff
end listToText

main()

I’ve made a few improvements to the script in post #17. Details at the bottom of the post.

In the latest version of Lion (10.7.5) and Script Debugger 5.0.4 the line (which is fine in AppleScript Editor):


if (elementCount is 1) then set specifier to text 1 thru text item j of thisLine

fails because specifier is a key word in SD’s dictionary. Changing specifier to _specifier everywhere solves that problem for SD users.

Further of course, users of this script must have a copy of Nigel’s Insertion Sort script or the line loading it will fail. Here’s my copy:

(* Insertion sort
Algorithm: unknown author.
AppleScript implementation: Arthur J. Knapp and Nigel Garvey, 2003.

Parameters: (list, range index 1, range index 2)
*)

on insertionSort(theList, l, r)
	script o
		property lst : theList
	end script
	
	-- Process the input parmeters.
	set listLen to (count theList)
	if (listLen > 1) then
		-- Negative and/or transposed range indices.
		if (l < 0) then set l to listLen + l + 1
		if (r < 0) then set r to listLen + r + 1
		if (l > r) then set {l, r} to {r, l}
		
		-- Do the sort.
		set u to item l of o's lst -- The highest value sorted so far!
		repeat with j from (l + 1) to r
			-- Get the next unsorted value.
			set v to item j of o's lst
			if (v < u) then
				-- If it's less than highest sorted value, initialise the insertion location to the beginning of the range. This will usually be changed below when a more suitable slot's found.
				set here to l
				-- Work back through the already sorted items, moving up those with greater values than this one, until a lesser or equal value or the beginning of the range is reached.
				set item j of o's lst to u
				repeat with i from (j - 2) to l by -1
					tell item i of o's lst
						if (it > v) then
							-- Greater value. Move it up one position.
							set item (i + 1) of o's lst to it
						else
							-- Lesser or equal value. Set the vacated slot after it as the insertion location.
							set here to i + 1
							exit repeat
						end if
					end tell
				end repeat
				-- Insert the value for insertion at the appropriate location.
				set item here of o's lst to v
			else
				-- The value's greater than or equal to the highest sorted so far. It's now the highest itself.
				set u to v
			end if
		end repeat
	end if
	
	return -- nothing.
end insertionSort

property sort : insertionSort

(* Demo:
set l to {}
repeat 1000 times
	set end of my l to (random number 1000)
end repeat

sort(l, 1, -1)
l
*)

Thanks, Adam. I’d regard the ‘specifier’ thing as a fault in Script Debugger. Its own keywords shouldn’t get in the way of the scripts it’s running or editing. But I’ve changed ‘specifier’ to ‘_specifier’ in the posted code.

The sort is just a cosmetic nicety from my own copy of the script and I’ve now cut it from the posted version.

What’s interesting about “specifier” is that it works in OS X 10.8, but not in 10.7 or 10.6. What happens is that the word specifier is changed to the word reference by the compiler. For some reason the AppleScript Editor deals with that, but Script Debugger does not. The author of SD is looking into it.

I assume you meant to include a “not” in there, but what you’re asking for is probably impossible given the way AS works. Here’s a counter example:

set language to 5

That compiles fine in Script Debugger 5, with language as a variable, but it won’t compile in ASE because language is one of its keywords. I don’t think there’s anyway around that sort of thing, other than having a minimal dictionary and using multi-word terms as much as possible. (Earlier versions of Script Debugger used to offer the option of turning the dictionary off to avoid the problem, but I don’t know that that is practical with Cocoa scripting.)

The problem with specifier stems in part from the decision at some stage to change the terminology used in AS dictionaries. Where once, for example, the move command would specify a direct parameter of type reference, it now specifies type specifier. But the specifier type isn’t actually defined anywhere, so it’s a type that’s a bit in no-man’s land.

Script Debugger has actually defined the specifier type in its dictionary, and I suspect that’s a sensible thing to do. Making it a synonym for reference was required to avoid the term reference compiling as specifier because they both point to the same thing (Cocoa’s NSScriptObjectSpecifier).

Probably more than anyone wanted to know…

Oops. Yes. I was concentrating so hard on trying to get “its” and “it’s” right, I missed that. Now corrected.

Thanks for the background. I hadn’t noticed the ‘language’ thing before.

In sd-talk today, Matt Neuburg points out that on p. 331 of his “AppleScript: The Definitive Guide” (which I have) in the chapter on Dictionaries and wildcards, he points out:

Just for interest, I’ve had a go a “sed” alternative to the vanilla AppleScript formatting code in my script. It’s not appreciably faster and is noted separately in Edit 4 at the bottom of post #17.

I added memory - when this script is saved and runs as an applet, it remembers and re-selects from the list (if that is still running) the app you previously chose.

property ScriptTitle : "UI Properties"

property appName : ""

-- By squaline (alias partron22?), emendelson, and Nigel Garvey.
-- http://macscripter.net/viewtopic.php?id=37674
on main()
	try
		tell application "System Events"
			set appNames to name of application processes whose visible is true
			set curApp to name of first application process whose frontmost is true
		end tell
		tell application curApp
			if length of appName is not 0 and appNames contains appName then
				set appChoice to (choose from list appNames with prompt "Which running application?" with title "Choose an application" default items {appName})
			else
				set appChoice to (choose from list appNames with prompt "Which running application?" with title "Choose an application")
			end if
			
			if (appChoice is false) then error number -128
			try
				set outputType to button returned of (display dialog "Choose output" with title ScriptTitle buttons {"Cancel", "Menu", "Window"} default button 2 cancel button 1 with icon 1)
			on error
				error number -128
			end try
			
		end tell
		set appName to item 1 of appChoice
		
		tell application "System Events"
			tell application process appName
				set frontmost to true
				set {windowExists, menuExists} to {front window exists, menu bar 1 exists}
				
				set {winstuff, menustuff} to {"(No window open)", "(No menu!)"}
				if outputType is "Window" then
					if (windowExists) then
						set winstuff to my listToText(entire contents of front window)
					else
						error number 3000
					end if
				else
					if (menuExists) then
						set menustuff to my listToText(entire contents of menu bar 1)
					else
						error number 3001
					end if
				end if
			end tell
		end tell
	on error e number n
		tell application "System Events" to set frontmost of application process curApp to true
		tell application curApp to activate
		if n = 3000 then
			tell application curApp to display alert "No windows to gather data from!"
			
		else if n = 3001 then
			tell application curApp to display alert "No menu to gather data from!"
		end if
		error number -128
	end try
	
	tell application "TextEdit"
		activate
		make new document at the front
		if outputType is "Window" then
			set the text of the front document to winstuff
		else
			set the text of the front document to menustuff
		end if
		set WrapToWindow to text 2 thru -1 of (localized string "&Wrap to Window")
	end tell
	
	tell application "System Events"
		tell application process "TextEdit"
			tell menu item WrapToWindow of menu 1 of menu bar item 5 of menu bar 1
				if ((it exists) and (it is enabled)) then perform action "AXPress"
			end tell
		end tell
	end tell
	tell application curApp to activate
	do shell script "open -b \"com.apple.textedit\""
	
end main

on listToText(entireContents) -- (Handler specialised for lists of System Events references.)
	try
		|| of entireContents -- Deliberate error.
	on error stuff -- Get the error message
	end try
	
	-- Parse the message.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {"{", "}"} -- Snow Leopard or later.
	set stuff to text from text item 2 to text item -2 of stuff
	
	-- If the list text isn't in decompiled form, create a script containing the list in its source code, store it in the Temporary Items folder, and run "osadecompile" on it.
	if (stuff does not contain "application process \"") then
		try
			set scpt to (run script "script
	tell app \"System Events\"
	{" & stuff & "}
	end
	end")
		on error errMsg
			set AppleScript's text item delimiters to astid
			tell application (path to frontmost application as text) to display dialog errMsg buttons {"OK"} default button 1 with icon caution
			return errMsg
		end try
		set tmpPath to (path to temporary items as text) & "Entire contents.scpt"
		store script scpt in file tmpPath replacing yes
		set stuff to (do shell script "osadecompile " & quoted form of POSIX path of tmpPath)
		set stuff to text from text item 2 to text item -2 of stuff
	end if
	
	-- Break up the text, using "\"System Events\", " as a delimiter.
	set AppleScript's text item delimiters to "\"System Events\", "
	set stuff to stuff's text items
	-- Insert a textual "reference" to the root object at the beginning of the resulting list.
	set AppleScript's text item delimiters to " of "
	set beginning of stuff to (text from text item 2 to -1 of item 1 of stuff) & "\"System Events\""
	-- Reduce the remaining "reference" fragments to object specifiers, tabbed according to the number of elements in the references.
	set tabs to tab & tab & tab & tab & tab & tab & tab & tab
	set tabs to tabs & tabs
	set tabs to tabs & tabs -- 32 tabs should be enough!
	repeat with i from 2 to (count stuff)
		set thisLine to item i of stuff
		set lineBits to thisLine's text items
		-- Make sure any " of "s in element names aren't mistaken for those of the reference!
		set elementCount to 0
		set nameContinuation to false
		repeat with j from 1 to (count lineBits)
			set thisBit to item j of lineBits
			if ((not ((nameContinuation) or (thisBit contains "\""))) or ((thisBit ends with "\"") and (thisBit does not end with "\\\"")) or (thisBit ends with "\\\\\"")) then
				-- thisBit is either a complete nameless-element specifier or it ends the right way to be either a complete named one or the completion of a name.
				set nameContinuation to false
				set elementCount to elementCount + 1
				if (elementCount is 1) then set spec to text 1 thru text item j of thisLine
			else
				-- The next "bit" will be the continuation of a name containing " of ".
				set nameContinuation to true
			end if
		end repeat
		set item i of stuff to (text 1 thru (elementCount - 3) of tabs) & spec
	end repeat
	-- Coerce back to a single text, inserting line feeds between the items.
	set AppleScript's text item delimiters to linefeed
	set stuff to stuff as text
	set AppleScript's text item delimiters to astid
	
	return stuff
end listToText

main()

Cool, I’m surprised at how robust this script has proven. Ten plus years, and only minor tweaking needed in a GUI script? That’s practically heresy.

Small correction: I’m Squalene over at Mac OS X Hints, not ‘Squaline’.

Nigel — this UI parsing script is fantastic.

Now…imagine this. Instead of text output, send your parsed list into OmniOutliner. Then you generate a very neat outline which is parseable using Applescript (or Javascript if you prefer).

So in OmniOutliner you might have:
A. Menu Bar 1
1. Menu “File”
a) Menu Item “Quit”
(ignore obvious errors in how UI scripting lists these things…it’s just an example)

Then you click on what you want your UI script to run, selecting it, and run a script which does this:

tell application "OmniOutliner"
	tell its document "UI Listing for TextEdit Menubar"
		set mySel to selected rows
		set myAncestors to (every ancestor of item 1 of mySel)
	end tell
	set thetext to "CLICK " & name of item 1 of mySel
	repeat with x in myAncestors
		set thetext to thetext & "OF " & name of x
	end repeat
	thetext
end tell

If you select ‘a) Menu Item “Quit”’ and run the script, it returns something like: click menu item “Quit” of Menu “File” of Menubar 1
Then you just paste that into your script.

Hi Vince.

Thanks for the feedback. That sounds like a fun exercise for an OmniOutliner user. :slight_smile:

It may be possible to implement a similar idea in BBEdit — by having the elementListing() handler return both the tabbed text and another with unedited lines, opening these in different windows. It would then be a matter of selecting text in the tabbed-text window and having a script return the equivalent paragraph(s) from the other. But I haven’t looked into this deeply.

Indeed.

If you have Omnioutliner, and you have previously downloaded Omni’s “Sample Applescripts”, there is a script in there which takes tabbed outlines and converts them to Omnioutliner. But it is rather slow — doing the menubar of Text Edit took over 4 minutes to convert. I think part of the slowness of that script is that it counts tabs by examining every character in every line (suspect the line “repeat with aChar in every character of aLine” is part of the problem?) . That should be something that could be sped up, maybe by just counting tabs and not everything else…. Once it is in Omnioutliner, the script to generate your Applescript GUI scripting line is pretty simple — similar to what I posted above and with a few modifications in the text output. For example, “select” vs “click” depending on the UI element you are activating.

Alas, I do have OmniOutliner — great program, but I consider the $50 I spent on Bill Cheeseman’s “UI Browser” to be money well spent! Just the “show UI element” feature is worth the price of admission - you just click on the element you are interested in and the program generates the AS GUI script you need. No guessing about the elements listed in an outline. https://pfiddlesoft.com/uibrowser/index.html

Attached below is the Omnioutliner import script from their sample scripts.

(* Clipboard to OmniOutline v0.3 2005/05/03
 *
 * Creates a new OmniOutliner Pro outline from text stored in the clipbard
 *  - Each line in the clipboard text becomes a new row in the outline
 *  - Leading tabs in the text are used to control the level/indent of the rows in the outline
 *
 * TODO: Determine how/if to handle broken indenting in the clipboard text
 * TODO: Tune performance - parts of this script can't be fast
 *
 * Copyright (c) 2005 J. A. Greant (zak@greant.com / http://zak.greant.com)
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
*)

tell application "OmniOutliner"
	set newDoc to make new document at beginning of documents
	
	repeat with aLine in my FetchClipboardAsList()
		my CreateRow(newDoc, my CountAndTrimTabs(aLine))
	end repeat
end tell

on FetchClipboardAsList() -- return clipboard contents as list
	set myList to {}
	repeat with currentLine in every paragraph of (the clipboard as string) -- break into list by carriage returns/newlines
		copy currentLine to end of myList
	end repeat
	return myList
end FetchClipboardAsList

on CountAndTrimTabs(aLine) -- count and remove leading tabs from a string
	set tabCount to 0
	repeat with aChar in every character of aLine
		set aChar to aChar as string -- aChar is a single item list til cast to string
		if aChar is equal to tab then
			set tabCount to tabCount + 1 -- count leading tab
		else if tabCount > 0 then
			set aLine to (get text (tabCount + 1) thru (length of aLine) of aLine) -- trim leading tabs
			exit repeat
		end if
	end repeat
	return {tabCount:tabCount, content:aLine}
end CountAndTrimTabs

on FetchLastRow(doc) -- return the last row in an OmniOutliner document. If no rows, return the document itself.
	tell application "OmniOutliner" -- enter right context to work with OmniOutliner Pro objects
		if rows of doc is equal to {} then
			return doc
		end if
		return last row of doc
	end tell
end FetchLastRow

on CreateRow(doc, aLine) -- create a new row from a line with indent information
	tell application "OmniOutliner" -- enter right context to work with OmniOutliner Pro objects
		set parentRow to my FetchLastRow(doc)
		
		if parentRow is doc then
			set parentLevel to 0 -- the document has now rows, and thus, no indent
		else
			set parentLevel to level of parentRow
		end if
		
		if tabCount of aLine > parentLevel then -- create a child of the last row
			make new row with properties {topic:(content of aLine)} at end of children of parentRow
		else -- create a peer or superior of the last row
			repeat (parentLevel - (tabCount of aLine)) times -- if needed, work our way back to the right parent
				set parentRow to parent of parentRow
			end repeat
			make new row with properties {topic:(content of aLine)} at end of parentRow
		end if
	end tell
end CreateRow

That handler could be sped up. Similarly, the FetchClipboardAsList() handler only needs to be:

on FetchClipboardAsList()
	return paragraphs of (the clipboard)
end FetchClipboardAsList

But they’re just AppleScript text work, so the difference shouldn’t be very great. What does seem laborious, just looking at the script (and, as you’ll have guessed, I don’t have OmniOutliner myself), is the fact that the ‘rows’ are added to the document one at a time, with repeats inside the application each time to establish the parent of each one. I assume this is to create the indent structure in the absence of the stripped tabs and that OmniOutliner itself has no native way to turn a tab-indented text into the required ‘row’ structure.

Instead of searching back through the parents within the application every time, I’d be inclined to try maintaining an AppleScript list as a look-up stack. Does this work?

(* Clipboard to OmniOutline
*
* Creates a new OmniOutliner Pro outline from text stored in the clipbard
* - Each line in the clipboard text becomes a new row in the outline
* - Leading tabs in the text are used to control the level/indent of the rows in the outline
*
* Original (c) 2005 J. A. Greant (zak@greant.com / http://zak.greant.com)
* This version guessed but not tested 2019 by Nigel Garvey.
*)

-- Properties to keep track of actual and potential parent objects.
-- OmniOutliner itself will be level 1 — ie. item 1 of the stack.
-- The index of each parent will thus be one more than the number of leading tabs in its immediate childen.
property stackCount : 0
property levelStack: {}

-- Create a new OmniOutliner document and initialise the above properties.
tell application "OmniOutliner" to set levelStack to {make new document at beginning of documents}
set stackCount to 1
	
-- Create the rows from the paragraphs of the clipboard text.
repeat with aLine in (get paragraphs of (the clipboard))
	CreateRow(aLine)
end repeat

on CountAndTrimTabs(aLine) -- count and remove leading tabs from a string
	set tabCount to 0
	repeat while (aLine begins with tab)
		set tabCount to tabCount + 1
		try -- In case the line only contains tabs.
			set aLine to text 2 thru -1 of aLine
		on error
			set aLine to ""
		end try
	end repeat

	return {tabCount:tabCount, content:aLine}
end CountAndTrimTabs

on CreateRow(aLine) -- create a new row from a line with indent information
	-- Count and trim this line's leading tabs.
	set {tabCount:tabCount, content:aLine} to CountAndTrimTabs(aLine)
	-- Determine the stack indices of the parent object and of the row to be created.
	set parentLevel to tabCount + 1
	set rowLevel to tabCount + 2
	
	-- Create the row as a child of the appropriate parent.
	tell application "OmniOutliner"
		set parentObject to item parentLevel of levelStack
		set thisRow to (make new row with properties {topic:aLine} at end of children of parentObject
	end tell
	
	-- Log the row in the appropriate stack slot, extending the stack if necessary.
	if (rowLevel > stackCount) then
		set end of levelStack to thisRow
		set stackCount to stackCount + 1
	else
		set item rowLevel of levelStack to thisRow
	end if
end CreateRow

Edit: Unused property removed.

A few years later and the script still works (specifically, the post 33 variant). After noticing the thread referenced elsewhere, I read through it and wanted to add something regarding omnioutliner.

Omnioutliner has an import command and it is tab-aware (which is only to be hoped for given that one the exports is to tabbed text).

With the textedit menus, it took just over a second to import them. I delete the Apple menu and the Services sub-menu as they don’t have anything to do with the app. The full import for me is 400 lines and it becomes 218 after excluding those two menus.

set inFile to (path to desktop as text) & "tex_menus.txt" as text
tell application "OmniOutliner"
	activate
	tell document 1
		delete children -- delete everything
		make new row -- need at least one row
		
		import file inFile to before first child
		-- expandAll child 1
		delete last child -- previously created row
		delete row "menu bar item \"Apple\""
		delete row "menu item \"Services\""
	end tell
end tell

NB A larger menu system, like that of firefox (3400 rows, due to bookmarks), takes 5 seconds on my old mac. I comment out the expandAll as it’s the slowest part of the script and may not be desired. Also, I do the make/delete row dance here but it’s easy enough to insert the imported branch anywhere within a typical document.

Of course, if all of your tabbed text is on the clipboard, you can also just paste it into OO and it will appear as an outline as long as you have an entire row selected when you paste the text. It can be done in a script like this:

tell application "OmniOutliner" to pbpaste at before first child

There isn’t any need to rely upon UI scripting for such tasks.