I ran into a situation yesterday where I wanted to create over a thousand TextExpander snippets. I won’t go into detail about why, but I thought I’d share my solution. In my case, I was working with a huge CSV file where one field would be the abbreviation and another would be the expansion. The script I’m sharing below only works with CSV files containing two fields (abbreviation, expansion), but it can easily be extrapolated to work with much more complex sources of data.

To use the script, you just need a CSV file. You can edit this in any spreadsheet that can export a CSV version of your file (Numbers, Excel, etc.), or you can just edit the list in a text editor if you know how to properly quote CSV strings containing special characters.

The script generates plain text snippets only, and by default sets the group to “Expand after any character.” You can change some of the settings for the group and snippets by editing the XML file, but take a look at an actual TextExpander group export file for reference1.

If you’re in a spreadsheet application, delete any header rows and give yourself just two columns. Fill the first field in each row with the abbreviation you want to use, and the second with the expansion. Export the sheet as a CSV. Then run the script, passing the CSV file as the first argument and the desired name of the TextExpander group file as the second (must have a .textexpander extension). The filename will be used as the group name, but that’s quite simple to edit after import.

After the script has run, just double-click the resulting .textexpander file in Finder to import it into TextExpander as a new group. All of your snippets from the CSV file should be there.

To run the script, save the raw text from below as textexpander_gen.rb somewhere in your path and make it executable (chmod a+x /path/to/textexpander.rb). Create your CSV file and run the script: textexpander_gen.rb input.csv output.textexpander

Have fun.

textexpander_gen.rbraw
"
#!/usr/bin/ruby
require 'csv'
require 'erb'
require 'cgi'

if ARGV.length == 2
	input_csv = ARGV[0]
	output_te = ARGV[1]
	unless output_te =~ /\.textexpander$/
		puts "Second argument must have a '.textexpander' extension."
		puts "#{File.basename(__FILE__)} input.csv output[.textexpander]"
		exit
	end
else
	puts "Two arguments are required, input filename and output filename"
	puts "#{ARGV[0]} input.csv output[.textexpander]"
	exit
end

template = ERB.new <<-ENDTEMPLATE
		<dict>
			<key>abbreviation</key>
			<string><%= abbr %></string>
			<key>abbreviationMode</key>
			<integer>0</integer>
			<key>creationDate</key>
			<date>2013-01-03T20:17:10Z</date>
			<key>flags</key>
			<integer>0</integer>
			<key>label</key>
			<string></string>
			<key>modificationDate</key>
			<date>2013-01-03T20:19:04Z</date>
			<key>plainText</key>
			<string><%= expansion %></string>
			<key>snippetType</key>
			<integer>0</integer>
			<key>useCount</key>
			<integer>0</integer>
			<key>uuidString</key>
			<string><%= uuid %></string>
		</dict>
ENDTEMPLATE

header =<<-ENDHEADER
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>groupInfo</key>
	<dict>
		<key>expandAfterMode</key>
		<integer>2</integer>
		<key>groupName</key>
		<string>#{File.basename(output_te).gsub(/\.textexpander$/,'')}</string>
	</dict>
	<key>snippetsTE2</key>
	<array>
ENDHEADER

footer =<<-ENDFOOTER
	</array>
</dict>
</plist>
ENDFOOTER

fh = File.new(File.expand_path(output_te),'w+')
input = CSV.read(File.expand_path(input_csv))

fh.puts header

input.each { |item|
	abbr = CGI.escapeHTML(item[0].strip)
	expansion = CGI.escapeHTML(item[1].strip)
	uuid = %x{uuidgen}.strip
	$stderr.puts "Processed #{abbr} -> #{expansion}"
	fh.puts template.result(binding)
}

fh.puts footer
fh.close
  1. Set the parameters you want on a group in TextExpander, right click the group and choose “Save a copy.” Open the resulting file in a text editor to see how the options are expressed in the plist format.