Quick LaTeX tables using TextExpander

When I want to include a numerical table in a LaTeX document I often already have the data collected together in a Numbers spreadsheet with the desired layout. For example, I might want to go from:

to

 

The following TextExpander snippet (gist) lets me simply copy the data from Numbers, including the column headers, to the clipboard, and then insert it into a LaTeX document as a nicely formatted table by typing ;ptable:[1]

#!/usr/local/bin/ruby

class Line

  @@max_length = nil
  @@eol = ' \\\\'

  def initialize( data )
    @contents = data
    @@max_length = [0]*@contents.size if @@max_length.nil? # initialize array if this is the first Line instance
    @contents.each_with_index{ |element, i| @@max_length[i] = [element.length, @@max_length[i]].max } # update column widths
  end

  def to_tex
    @contents.zip( @@max_length ).collect { |entry, length| entry.ljust(length).sub(/ \|$/,'').gsub('&','\\\&') }.join(' & ') + @@eol
  end

  def define_format
     @contents.inject('|'){ |string, entry| /\|/.match(entry) ?  string + 'c|' : string + 'c' } + '|'
  end

end

table_header = <<END
\\begin{table}[htb]
\\begin{center}
\\begin{tabular}{FORMAT} \\hline 
END # FORMAT is replaced by a format string generated by define_format

table_footer = <<END
\\hline
\\end{tabular}
\\caption{\\label{tab:NEW_LABEL}CAPTION}
\\end{center}
\\end{table}
END

contents = "%clipboard".split( /[\n\r]/ ).collect{ |line| Line.new( line.split( /\t/ ) )}

puts table_header.sub( 'FORMAT', contents[0].define_format ) 
puts contents.collect{ |line| line.to_tex }.join("\n")
puts table_footer

Running this snippet with the data shown above produces the following LaTeX code:

\begin{table}[htb]
\begin{center}
\begin{tabular}{|c|c|c|} \hline
A     & B     & C     \\
12.62 & 12.62 & 33.88 \\
3.96  & 4.39  & 4.55  \\
2.00  & 2.30  & 2.56  \\
\hline
\end{tabular}
\caption{\label{tab:NEW_LABEL}CAPTION}
\end{center}
\end{table}

The script assumes centred justification of each column. A limited amount of table formatting can be specified in the Numbers data: any cells in the topmost row can include a pipe character | to indicate that this column should be bounded by a vertical line on the right side, e.g. column 1 | will add a c| format string to the begin{tabular}{FORMAT} code in the LaTeX header via the define_format method.


  1. Inspired by a similar workflow by Dr Drang for copying tables from Numbers to Markdown.  ↩

TeXShop Extended - A light theme for LaTeX

Inspired by Eddie Smith’s post on working with LaTeX in Sublime Text 2, I am experimenting with doing just that. While it is going to take me a while to get to grips with all the power-text-editing-goodness inside Sublime Text 2, I have been poking around inside the syntax highlighting, and the result is TeXShop Extended; as the blurb says, a light theme designed for LaTeX in TextMate / Sublime Text. This is based on my colour scheme in TeXShop, with extensions for additional syntax highlighting for \ref, \label, \cite, \footnote, math environment, and others. The .tmTheme file also supports syntax highlighting for \caption and \mathrm, provided these are listed in the LaTeX.tmLanguage file.

The .tmTheme file is available as a gist. Caveat emptor, etc. I have only tested this theme with RevTeX files, and your mileage may vary with standard LaTeX.

Open a New Terminal Window in the Current Working Directory on a Remote Machine

A short rb-appscript script that opens a new window in Terminal in the current working directory on a remote machine. This is much quicker than launching a new window and having to cd manually when you are deep in your directory hierarchy:


require 'appscript'

def run_command_in_frontmost_window( cmd )
  terminal = Appscript.app('Terminal.app')
  terminal.do_script( cmd, :in => terminal.windows[1] )
end

def open_new_window_with_same_command
  Appscript.app("System Events").keystroke('n', :using => [:command_down, :control_down] )
end    

topmost_window = Appscript.app('Terminal.app').windows[0]
dir_re = /\/(.*\Z)/
title_string = topmost_window.custom_title.get
current_directory = title_string.match( dir_re ).nil? ? nil : title_string.match( dir_re )[1]
open_new_window_with_same_command
run_command_in_frontmost_window( "cd #{current_directory}" ) unless current_directory.nil?

I have this bound to ⌃⌥⌘N using FastScripts to extend the native New Window (⌘N) and New Window with Same Command (⌃⌘N) commands.

Using PDFpen AppleScript to Copy Comments from PDFs

Last week I posted a script for extracting the contents of note annotations from PDFs. An alternative approach uses the comprehensive scripting library of PDFpen.[1] One advantage of this method is that the notes, which inherit from the imprint class, are contained within separate pages, allowing page numbers to be accessed. The AppleScript is as follows:

tell application "PDFpen"
    set finalResult to ""
    set pageNumber to 0
    repeat with aPage in pages of document 1
        set pageResult to ""
        set pageNumber to pageNumber + 1
        repeat with theImprint in imprints of aPage
            if the class of theImprint is equal to note then
                set theResult to rich text of theImprint
                set pageResult to pageResult & "* " & theResult & "

"
            end if
        end repeat -- imprints
        if pageResult is not equal to "" then
            set finalResult to finalResult & "page " & (pageNumber as string) & ":

" & pageResult
        end if
    end repeat -- pages
end tell
set the clipboard to the finalResult

This script can be accessed from inside PDFpen by saving it to /Users/name/Library/Application Support/PDFpen/Scripts, or combined with a Open Finder Items action in Automator to build a Service. Running as a Service will crash the AppleScript above if the PDF has not finished opening. Adding the following code makes the main Applescript wait before trying to process the PDF:

repeat
    if document 1 of application "PDFpen" exists then exit repeat
    delay 0.5
end repeat

  1. I was a little surprised to learn that Preview offers zero AppleScript support out of the box.  ↩

OS X Service for Copying Comments from PDFs

Today I was trying out ReaddleDocs on the iPad for reading PDFs of papers. This app is not as full-featured as GoodReader but it importantly supports syncing of entire folders with Dropbox, and I like the cleaner interface.[1]

While apps like this make it very easy to annotate PDFs with comments, which can then be synced across all your devices through the power of Dropbox, getting these comments back out again once you get back to your Mac can be a pain.

This is a ruby script that uses the pdf-reader gem to find all the comments in a PDF document and copy them to the clipboard:

require 'rubygems'
require 'pdf-reader'

ARGV.each do |file|

  pdf = PDF::Reader.new( file )

  comments = []
  pdf.objects.each_pair do |key, value| 
    if value.class == Hash
      comments << "* #{value[:Contents]}\n\n" if value[:Name] == :Comment
    end
  end

  IO.popen('pbcopy', 'w').puts comments

end

This script can be accessed as a Service with the following setup in Automator:

The only complication here is Automator’s insistence on using the system version of ruby (1.8.7) instead of v. 1.9.3 I have installed elsewhere in my path, which meant that the service initially could not find the pdf-reader gem. This was fixed by reinstalling pdf-reader into the system gem path with /usr/bin/gem install pdf-reader.

I would also like to be able to pull out highlighted text, and to add a page number to each comment in the copied output, but these appear to be advanced topics.[2]

Update: An alternative workflow using AppleScript and PDFpen is described here.


  1. The only feature that I miss from GoodReader is the ability to crop all the pages in a document to remove margins.  ↩

  2. Or left as an exercise for the reader.  ↩

Custom Cite Keys in Papers using TextExpander

I use Papers for cataloguing PDFs, which has made it a lot easier to keep track of numerous resources when researching a field or writing a paper.

Papers will automatically generate cite keys; identifier strings that can be used to reference a publication in a manuscript. For example, in LaTeX publications can be referenced using \cite{CITE_KEY} and the bibliographic information included in a bibliography section at the end of the document, or extracted from a linked bibTeX database. While Papers has the noble goal of generating “universal cite keys” to allow collaborating authors to keep compatible library databases, I came to Papers with a large bibTeX database and my own cite key scheme. This scheme defines cite keys as follows:

  • Single author: [Surname]_[Journal][Year], for example Morgan_JImprobRes2012.

  • Two authors: [First_Surname][Second_Surname]_[Journal][Year] for example MorganAndCoauthor_JImprobRes2012.

  • More than two authors: [First_Surname]EtAl_[Journal][Year] for example MorganEtAl_JImprobRes2012.

This is usually sufficient to uniquely identify any paper in my library. In the case of duplicate entries from particularly prodigious authors I then append letters e.g. MorganEtAl_JImprobRes2012a

I find these cite keys more easily human readable than those generated by Papers, which are of the form Morgan:2012sj, or even the possible auto generated cite keys in BibDesk. When reading a LaTeX file I find it easier to remember which paper I referred to, since I can see the journal title, and have some idea about the number of authors. The second advantage is that these cite keys are easily human generated. If I want to reference a paper that is not yet in my database I can type in an appropriate cite key and carry on writing without having to worry about editing a BibTeX entry until later.

Unfortunately Papers does not support user defined cite keys, and editing the metadata by hand is a one of those tasks that only takes a few seconds but becomes tedious for a large number of papers. As a workaround I now use a TextExpander snippet that builds a cite key from the metadata present in Papers.[1] This runs the following ruby script:

require 'appscript'
require 'osax'

def records # still hoping that Papers2 will become AppleScriptable
  papers = Appscript.app("/Applications/Papers2.app")
  system_events = Appscript.app("System Events.app")
  papers.activate

system_events.processes["Papers2"].menu_bars[1].menu_bar_items['Edit'].menus['Edit'].menu_items['Copy As'].menus['Copy As'].menu_items['BibTeX Record'].click 
  clipboard = OSAX.osax.the_clipboard
  return Paper.new(clipboard.split(",\r")[1..-1])
end

class Paper < Hash

  def initialize( strings )
    strings.each do |string|
      key, value = string.split" = "
      self[ key ] = clean( value )
    end
  end

  def clean( string )
    to_substitute = [ [ /\\\"([a-z])/, "\\1" ], # ö
                      [ /\\\'([a-z])/, "\\1" ], # é 
                      [ /\\c /, "" ],           # ç
                      [ /\\v /, "" ],           # š
                      [ /\\([a-z])/, "\\1"] ]   # others, e.g. \\e remaining after string.delete( to_remove )
    to_remove = "\{\}\r\'"
    string.delete( to_remove ).gsub_from_array( to_substitute )
  end

  def author_list
    authors = self["author"].split(" and ").collect{ |string| Author.new( string ) }

    return author_list = case authors.length
      when 1 then authors[0].cite_name
      when 2 then authors[0].cite_name + "And" + authors[1].cite_name
      else authors[0].cite_name + "EtAl"
    end
  end

  def journal
    return self["journal"].delete(" :.\-")
  end

  def year
    return self["year"]
  end

  def cite_string
    self.author_list + '_' + self.journal + self.year
  end

end

class Author
  def initialize( bibtex_string ) # with format e.g "Morgan, B. J."
    @surname = bibtex_string.split(",")[0]
    @forenames = bibtex_string.split(",")[1].split( )
  end

  def cite_name
    @surname.gsub(" ","")
  end
end

class String
  def gsub_from_array( gsub_array )
    gsub_array.inject(self) { | string, sub | string.gsub( sub[0], sub[1] ) }
  end
end

print records.cite_string

This uses appscript [2] and some GUI scripting to access the Papers command “Copy as BibTeX Record” to extract the metadata for the currently selected paper. The rest of the script is straight Ruby, and prints the appropriate cite key.

The end result is that I can edit the auto-generated cite key for the selected paper and type ;cite to change the entry to my own format.[3] This is one of those little reductions in friction that makes life better.[4]

This TextExpander snippet is available on GitHub.


  1. The ability of Papers to extract metadata from a repository such as Web of Knowledge is a huge timesaver here.  ↩

  2. I know appscript is deprecated but I am resisting rewriting in AppleScript. I am sure this policy will come back to bite me when appscript eventually stops working.  ↩

  3. Now if only the Papers developers would add AppleScript support then this could be rolled into a script where all my “incorrect” cite keys could be fixed in one go.  ↩

  4. Especially since it removes the opportunity for typing mistakes which are much harder to find when they show up later as an “undefined citations” error from LaTeX.  ↩