Commit e6e38fe7 authored by Michael's avatar Michael

Syncing from Monotone at 20131029074443

parent 411509a5
CL-EMB has fixes by current maintainer, Michael Raskin <38a938c2@rambler.ru>
This fixes are Copyright (c) 2009 by Moscow Center of Continious
Mathematical Education.
CL-EMB is written and Copyright (c) 2004, 2005, 2006 by Stefan Scholl.
Parts of the source are taken from LSP, written by
John Wiseman and copyright 2001, 2002 I/NET Inc.
See lsp-LICENSE.txt
CL-EMB is licensed under the terms of the Lisp Lesser GNU
Public License (http://opensource.franz.com/preamble.html), known as
the LLGPL. The LLGPL consists of a preamble (see above URL) and the
LGPL. Where these conflict, the preamble takes precedence.
CL-EMB is referenced in the preamble as the "LIBRARY."
======== --------------------
CL-EMB Embedded Common Lisp
======== --------------------
A mixture of features from eRuby and HTML::Template. You could name
it "Yet Another LSP" (LispServer Pages) but it's a bit more than that
and not limited to a certain server or text format.
LICENSE
=======
LLGPL
INSTALL
=======
CL-EMB can be installed with ASDF-INSTALL.
See http://weitz.de/asdf-install/ for further information.
USAGE
=====
[generic function]
EXECUTE-EMB name &key env generator-maker => string
NAME can be a registered (with REGISTER-EMB) emb code or a pathname
(type PATHNAME) of a file containing the code. Returns a string.
Keyword parameter ENV to pass objects to the code. ENV must be a
plist. ENV can be accessed within your emb code.
The GENERATOR-MAKER is a function which gets called with a key and value
from the given ENV and should return a generator function like described on
http://www.cs.northwestern.edu/academics/courses/325/readings/graham/generators.html
[generic function]
REGISTER-EMB name code => emb-function
Internally registeres given CODE with NAME to be called with
EXECUTE-EMB. CODE can be a string or a pathname (type PATHNAME) of
a file containing the code.
[function]
PPRINT-EMB-FUNCTION name
DEBUG function. Pretty prints function form, if *DEBUG* was T
when the function was registered.
[function]
CLEAR-EMB name
Remove named emb code.
[function]
CLEAR-EMB-ALL
Remove all registered emb code.
[function]
CLEAR-EMB-ALL-FILES
Remove all registered file emb code (registered/executed by a pathname).
[special variable]
*EMB-START-MARKER* (default "<%")
Start of scriptlet or expression. Remember that a following #\=
indicates an expression.
[special variable]
*EMB-END-MARKER* (default "%>")
End of scriptlet or expression.
[special variable]
*ESCAPE-TYPE*
Default value for escaping @var output is :RAW
Can be changed to :XML, :HTML, :URI, :URL, :URL-ENCODE,
:LATEX
[special variable]
*FUNCTION-PACKAGE*
Package the emb function body gets interned to.
Default: (find-package :cl-emb-intern)
[special variable]
*DEBUG*
Debugging mode if T. Default: NIL
[special variable]
*LOCKING-FUNCTION*
Function to call to lock access to an internal hash table.
Must accept a function designator which must be called with
the lock hold.
IMPORTANT: The locking function must return the value of
the function it calls!
Example:
(defvar *emb-lock* (kmrcl::make-lock "emb-lock")
"Lock for CL-EMB.")
(defun emb-lock-function (func)
"Lock function for CL-EMB."
(kmrcl::with-lock-held (*emb-lock*)
(funcall func)))
(setf emb:*locking-function* 'emb-lock-function)
Files get cached and reread when they change.
The emb code consists of normal text (HTML, XML, or any other text
format) and special tags you know from eRuby or JSP (JavaServer Pages)
which can hold Common Lisp or CL-EMB's template tags, perhaps
comparable to JSP's taglib.
<% ... %> is a scriptlet tag, and wraps
Common Lisp code.
<%= ... %> is an expression tag. Its content gets evaluated and fed as
a parameter to (FORMAT T "~A" ...).
<%# ... #%> is a comment. Everything within will be removed/ignored.
Can't be nested!
Examples:
---------
CL-USER> (asdf:oos 'asdf:load-op :cl-emb)
CL-USER> (cl-emb:register-emb "test1"
"10 stars: <% (dotimes (i 10) %>*<% ) %>")
#<CL-EMB::EMB-FUNCTION {9B74259}>
CL-USER> (cl-emb:execute-emb "test1")
"10 stars: **********"
CL-USER> (cl-emb:register-emb "test2" "2 + 2 = <%= (+ 2 2) %>")
#<CL-EMB::EMB-FUNCTION {9BCACE1}>
CL-USER> (cl-emb:execute-emb "test2")
"2 + 2 = 4"
CL-USER> (let ((emb:*emb-start-marker* "<?emb")
(emb:*emb-end-marker* "?>"))
(emb:register-emb "marker-test"
"42 + 42 = <?emb= (+ 42 42) ?>"))
#<CL-EMB::EMB-FUNCTION {97BEFD9}>
CL-USER> (emb:execute-emb "marker-test")
"42 + 42 = 84"
Template Tags
-------------
You can use special template tags instead of Common Lisp code between
<% and %>. This will be translated to Common Lisp and serves as a
simple shortcut for you.
And more important: It's easier to use for non-programmers. A designer
can work on HTML code and insert these simple template tags.
Template tags start with @.
Currently supported:
@if, @else, @endif, @unless, @endunless, @var, @repeat, @endrepeat,
@loop, @endloop, @include, @call, @with, @endwith, @set, @genloop,
@endgenloop, @insert
@if and @unless check if the given parameter is set in the
supplied environment (parameter ENV of EXECUTE-EMB). The
environment is a plist with keyword + value pairs. Must be terminated
with @endif or @endunless.
@var emits the corresponding value from the environment. Uses the
escape type defined in *ESCAPE-TYPE* (Default :raw, no escaping) or
with -escape modifier. E. g. <% @var foo -escape xml %>
or without modifier <% @var foo %>
Supported escaping: raw, xml (aka html), uri (aka url or url-encode),
latex
@insert inserts a given (text) file. Parameter from the environment.
E. g. <% @insert textfile %>
@repeat repeats everything between it and @endrepeat the given times.
Parameter can be a number or a name. The name will be used to lookup
the corresponding value from the environment.
@loop loops over a named list in the environment. Environment
gets set to current plist inside this list. Must be terminated with
@endloop.
@include includes a given file. Relative to current template.
@call calls a given emb-function, which was registered with REGISTER-EMB
@with is similar to @loop as it sets the current environment to the named
plist. @loop needs a list of plists and @with just a plist associated to
the given name. Block ends in @endwith.
@set is used to set special variables like *ESCAPE-TYPE* from within a
emb code. This way a default for a file can be specified in the file
itself. The variables are changed for the current and called/included code.
Changes to the variables in called/included code don't effect the caller/
includer. E. g. <% @set escape=uri %>
Currently supported: escape (raw, xml, html, url, uri, url-encode, latex)
@genloop starts a special kind of loop: a generator loop. It must be
terminated by @endgenloop and operates on a generator returned by the
given GENERATOR-MAKER (see EXECUTE-EMB). The GENERATOR-MAKER gets called
with two parameters: the key (which is the argument to @genloop) and the
corresponding value in the plist. Each time in the loop the generator is
called first with the parameter :TEST to see if there's data left.
The generator must return a plist on :NEXT, which will be the current
ENV (like @with or within a normal @loop).
The parameters which access the environment can just be the name of a
keyword symbol in the plist. foo -> :FOO in (:FOO "bar")
Or you can provide a path within a nested plist structure by dividing the
parts of the path with a slash. foo/bar -> Value of :BAR inside the
plist at :FOO. (:FOO (:BAR "yeah")) -> "yeah"
Starting the parameter with a slash lets it traverse the nested plists
from the top. That way you can access top values inside loops.
Writing "<% @var foo/bar/quux %>" can be translated to
(GETF (GETF (GETF ENV :FOO) :BAR) :QUUX)
Examples:
---------
CL-USER> (cl-emb:register-emb "test1"
"Foo: <% @if foo %>Yes!<% @else %>No!<% @endif %>")
#<CL-EMB::EMB-FUNCTION {9C0F2D1}>
CL-USER> (cl-emb:execute-emb "test1" :env '(:foo t))
"Foo: Yes!"
CL-USER> (cl-emb:execute-emb "test1")
"Foo: No!"
CL-USER> (cl-emb:execute-emb "test1" :env '(:foo nil))
"Foo: No!"
CL-USER> (cl-emb:register-emb "test2"
"What is set? -> <% @call test1 %>")
#<CL-EMB::EMB-FUNCTION {9C526E9}>
CL-USER> (cl-emb:execute-emb "test2" :env '(:foo t))
"What is set? -> Foo: Yes!"
CL-USER> (cl-emb:register-emb "test3"
"10 stars: <% @repeat 10 %>*<% @endrepeat %>")
#<CL-EMB::EMB-FUNCTION {9C9F1D1}>
CL-USER> (cl-emb:execute-emb "test3")
"10 stars: **********"
CL-USER> (cl-emb:register-emb "test4"
"<% @loop numbers %>[<% @var de %>,<% @var en %>]<% @endloop %>")
#<CL-EMB::EMB-FUNCTION {9174DF1}>
CL-USER> (cl-emb:execute-emb "test4"
:env '(:numbers ((:de "EINS" :en "ONE")
(:de "ZWEI" :en "TWO"))))
"[EINS,ONE][ZWEI,TWO]"
CL-USER> (emb:register-emb "test5"
"<a href=\"http://somewhere.test/test.cgi?<% @var foo -escape uri %>\"><% @var foo %></a>")
#<CL-EMB::EMB-FUNCTION {9FBF5F1}>
CL-USER> (let ((emb:*escape-type* :html))
(emb:execute-emb "test5" :env '(:foo "10 > 7")))
"<a href=\"http://somewhere.test/test.cgi?10+%3E+7\">10 &gt; 7</a>"
CL-USER> (emb:register-emb "test6" "1. <% @with one %>BAZ: <% @var baz %><% @endwith%>
2. <% @with two %>BAZ: <% @var baz %><% @endwith%>")
#<CL-EMB::EMB-FUNCTION {9916EB1}>
CL-USER> (emb:execute-emb "test6" :env '(:one (:baz "first")
:two (:baz "second")))
"1. BAZ: first
2. BAZ: second"
CL-USER> (emb:register-emb "test7" " - <% @var foo -escape uri %> - ")
#<CL-EMB::EMB-FUNCTION {96F1239}>
CL-USER> (emb:pprint-emb-function "test7")
(LAMBDA (&OPTIONAL CL-EMB-INTERN::ENV)
(WITH-OUTPUT-TO-STRING (*STANDARD-OUTPUT*)
(PROGN
(WRITE-STRING " - ")
(FORMAT T "~A" (CL-EMB::ECHO (GETF CL-EMB-INTERN::ENV :FOO) :ESCAPE :URI))
(WRITE-STRING " - "))))
; No value
CL-USER> (emb:register-emb "test8" "<% @set escape=xml %>--<% @var hey %>--")
#<CL-EMB::EMB-FUNCTION {962B839}>
CL-USER> (emb:register-emb "test9" "--<% @var hey %>--<% @call test8 %>--<% @var hey %>--")
#<CL-EMB::EMB-FUNCTION {96931A9}>
CL-USER> (emb:execute-emb "test9" :env '(:hey "5>2"))
"--5>2----5&gt;2----5>2--"
CL-USER> (emb:register-emb "test10" "Square root from 1 to <% @var numbers %>: <% @genloop numbers %>sqrt(<% @var number %>) = <% @var sqrt %> <% @endgenloop %>")
#<CL-EMB::EMB-FUNCTION {581EC765}>
CL-USER> (defun make-sqrt-1-to-n-gen (key n)
(declare (ignore key))
(let ((i 1))
#'(lambda (cmd)
(ecase cmd
(:test (> i n))
(:get `(:number ,i :sqrt ,(sqrt i)))
(:next (prog1 `(:number ,i :sqrt ,(sqrt i))
(unless (> i n)
(incf i))))))))
MAKE-SQRT-1-TO-N-GEN
CL-USER> (emb:execute-emb "test10" :env '(:numbers 10) :generator-maker 'make-sqrt-1-to-n-gen)
"Square root from 1 to 10: sqrt(1) = 1.0 sqrt(2) = 1.4142135 sqrt(3) = 1.7320508 sqrt(4) = 2.0 sqrt(5) = 2.236068 sqrt(6) = 2.4494898 sqrt(7) = 2.6457512 sqrt(8) = 2.828427 sqrt(9) = 3.0 sqrt(10) = 3.1622777 "
CL-USER> (emb:register-emb "test11" "<% @loop bands %>Band: <% @var band %> (Genre: <% @var /genre %>)<br><% @endloop %>")
#<CL-EMB::EMB-FUNCTION {58ADB12D}>
CL-USER> (emb:execute-emb "test11" :env '(:genre "Rock" :bands ((:band "Queen") (:band "The Rolling Stones") (:band "ZZ Top"))))
"Band: Queen (Genre: Rock)<br>Band: The Rolling Stones (Genre: Rock)<br>Band: ZZ Top (Genre: Rock)<br>"
CL-USER> (emb:register-emb "test12" "<% @repeat /foo/bar/count %>*<% @endrepeat %>")
#<CL-EMB::EMB-FUNCTION {58B7583D}>
CL-USER> (emb:execute-emb "test12" :env '(:foo (:bar (:count 42))))
"******************************************"
CL-USER> (emb:register-emb "test13" "The file:<pre><% @insert textfile %></pre>")
#<CL-EMB::EMB-FUNCTION {5894326D}>
CL-USER> (emb:execute-emb "test13" :env '(:textfile "/etc/gentoo-release"))
"The file:<pre>Gentoo Base System version 1.6.14
</pre>"
CREDITS
=======
Uses code from John Wiseman. See http://lemonodor.com/archives/000128.html
and lsp-LICENSE.txt
Thanks to Edi Weitz for letting me use his code for ESCAPE-FOR-XML.
AUTHOR
======
Stefan Scholl <stesch@no-spoon.de>
CURRENT MAINTAINER
==================
Michael Raskin <38a938c2@rambler.ru>
- Documentation
- More examples
- Tests
- Writing own escape functions?
- Better error handling
- Examples for generator loop in the examples.html
- Export GETF-EMB
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; This software is Copyright (c) Stefan Scholl, 2004.
;;; Stefan Scholl grants you the rights to distribute
;;; and use this software as governed by the terms
;;; of the Lisp Lesser GNU Public License
;;; (http://opensource.franz.com/preamble.html),
;;; known as the LLGPL.
(in-package #:cl-user)
(defpackage #:cl-emb.system
(:use #:cl
#:asdf))
(in-package #:cl-emb.system)
(defsystem #:cl-emb
:version "0.4.3"
:author "Stefan Scholl <stesch@no-spoon.de>"
:licence "Lesser Lisp General Public License"
:depends-on (#:cl-ppcre)
:components ((:file "packages")
(:file "emb" :depends-on ("packages"))))
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; This file contains some fixes by Michael Raskin
;;; They are Copyright (c) Moscow Center of Continious Mathematical
;;; Education, 2009
;;; This software is Copyright (c) Stefan Scholl, 2004.
;;; Stefan Scholl grants you the rights to distribute
;;; and use this software as governed by the terms
;;; of the Lisp Lesser GNU Public License
;;; (http://opensource.franz.com/preamble.html),
;;; known as the LLGPL.
;;; Parts of the source are taken from LSP, written by
;;; John Wiseman and copyright 2001, 2002 I/NET Inc.
;;; (http://www.inetmi.com/)
;;; See lsp-LICENSE.txt
(in-package :cl-emb)
(defpackage :cl-emb-intern (:use :cl))
(defvar *function-package* (find-package :cl-emb-intern)
"Package the emb function body gets interned to.")
(defvar *debug* nil
"Debugging for CL-EMB.")
(defvar *locking-function* nil
"Function to call to lock access to an internal hash table. Must accept
a function designator which must be called with the lock hold.")
(defmacro with-lock (&body body)
"Locking all accesses to *functions*"
`(cond (*locking-function*
(funcall *locking-function* #'(lambda () ,@body)))
(t ,@body)))
(defgeneric execute-emb (name &key env generator-maker)
(:documentation "Execute named emb code. Returns a string. Keyword parameter ENV
to pass objects to the code. ENV must be a plist."))
(defmethod execute-emb ((name string) &key env generator-maker)
(funcall (get-emb-function name) :env env :generator-maker generator-maker :name name))
(defmethod execute-emb ((name pathname) &key env generator-maker)
(let ((fun (or (get-emb-function name)
(emb-function-function (register-emb name name)))))
(funcall fun :env env :generator-maker generator-maker :name name)))
(defvar *functions* (make-hash-table :test #'equal)
"Table mapping names to emb-function instances.")
(defclass emb-function ()
((path :initarg :path
:accessor emb-function-path)
(time :initarg :time
:accessor emb-function-time)
(function :initarg :function
:accessor emb-function-function)
(form :initarg :form
:initform nil
:accessor emb-function-form)))
(defun make-emb-function (path time function &optional form)
"Constructor for class EMB-FUNCTION."
(make-instance 'emb-function
:path path
:time time
:function function
:form form))
(defun pprint-emb-function (name)
"DEBUG function. Pretty prints function form, if *DEBUG* was t
when the function was registered."
(with-lock
(pprint (emb-function-form (gethash name *functions*)))))
(defun clear-emb-all ()
"Remove all registered emb code."
(with-lock
(clrhash *functions*)))
(defun clear-emb (name)
"Remove named emb code."
(with-lock
(remhash name *functions*)))
(defun clear-emb-all-files ()
"Remove all registered file emb code (registered/executed by a pathname)."
(with-lock
(maphash (lambda (key value) (declare (ignore value))
(when (typep key 'pathname) (remhash key *functions*)))
*functions*)))
(defun get-emb-function (name)
"Returns the named function implementing a registered emb code.
Rebuilds it when text template was a file which has been modified."
(with-lock
(let* ((emb-function (gethash name *functions*))
(path (when emb-function (emb-function-path emb-function))))
(cond ((and (not (typep name 'pathname)) (null emb-function))
(error "Function ~S not found." name))
((null emb-function)
(return-from get-emb-function))
((and path
(> (file-write-date path) (emb-function-time emb-function)))
;; Update when file is newer
(multiple-value-bind (function form)
(construct-emb-function (contents-of-file path))
(setf (emb-function-time emb-function) (file-write-date path)
(emb-function-function emb-function) function
(emb-function-form emb-function) form))))
(emb-function-function emb-function))))
(defgeneric register-emb (name code)
(:documentation "Register given CODE as NAME."))
(defmethod register-emb (name (code pathname))
(multiple-value-bind (function form)
(construct-emb-function (contents-of-file code))
(with-lock
(setf (gethash name *functions*)
(make-emb-function code
(file-write-date code)
function
form)))))
(defmethod register-emb (name (code string))
(multiple-value-bind (function form)
(construct-emb-function code)
(with-lock
(setf (gethash name *functions*)
(make-emb-function nil
(get-universal-time)
function
form)))))
(defvar *emb-start-marker* "<%"
"Start of scriptlet or expression. Remember that a following #\=
indicates an expression.")
(defvar *emb-end-marker* "%>"
"End of scriptlet or expression.")
(defparameter *set-special-list*
'(("escape" . "cl-emb:*escape-type*")))
(defparameter *set-parameter-list*
'(("xml" . ":xml")
("html" . ":html")
("url" . ":url")
("uri" . ":uri")
("url-encode" . ":url-encode")
("raw" . ":raw")
("latex" . ":latex")
))
;; TODO: Refactor! Looks a bit clumsy.
(defun set-specials (match registers)
"Parse parameter(s) of @set and set special variables
like e. g. *ESCAPE-TYPE*."
;; <% @set escape=xml schnuffel=poe %>
(declare (ignore match))
(let ((setf-pairs
(let ((setf-list nil))
(dolist (pair (cl-ppcre:split "\\s+" (first registers))
(when (first setf-list)
(format nil "~{ ~A~}" (reverse setf-list))))
(destructuring-bind (left right)
(cl-ppcre:split "=" pair)
(let ((place (rest (assoc left *set-special-list* :test #'equalp)))
(value (rest (assoc right *set-parameter-list* :test #'equalp))))
(when (and place value)
(push (concatenate 'string place " " value) setf-list))))))))
(if setf-pairs
(format nil "(setf ~A)" setf-pairs)
"")))
(defparameter *template-tag-expand*
`(("\\s+@if\\s+(\\S+)\\s*" . " (cond ((cl-emb::getf-emb \"\\1\") ")
("\\s+@ifnotempty\\s+(\\S+)\\s*" . " (cond ((> (length (cl-emb::getf-emb \"\\1\")) 0) ")
("\\s+@else\\s*" . " ) (t ")
("\\s+@endif\\s*" . " )) ")
("\\s+@unless\\s+(\\S+)\\s*" . " (cond ((not (cl-emb::getf-emb \"\\1\")) ")
("\\s+@endunless\\s*" . " )) ")
("=?\\s+@var\\s+(\\S+)\\s+-(\\S+)\\s+(\\S+)\\s*"
. "= (cl-emb::echo (cl-emb::getf-emb \"\\1\") :\\2 :\\3) ")
("=?\\s+@var\\s+(\\S+)\\s*" . "= (cl-emb::echo (cl-emb::getf-emb \"\\1\")) ")
("\\s+@repeat\\s+(\\d+)\\s*" . " (dotimes (i \\1) ")
("\\s+@repeat\\s+(\\S+)\\s*" . " (dotimes (i (or (cl-emb::getf-emb \"\\1\") 0)) ")
("\\s+@endrepeat\\s*" . " ) ")
("\\s+@loop\\s+(\\S+)\\s*" . " (dolist (env (cl-emb::getf-emb \"\\1\")) ")
("\\s+@endloop\\s*" . " ) ")
("\\s+@genloop\\s+(\\S+)\\s*" . " (let ((env)
(%gen (funcall generator-maker :\\1
(cl-emb::getf-emb \"\\1\"))))
(loop
(when (funcall %gen :test) (return))
(setq env (funcall %gen :next))
(progn ")
("\\s+@endgenloop\\s*" . " ))) ")
("\\s+@with\\s+(\\S+)\\s*" . " (let ((env (cl-emb::getf-emb \"\\1\"))) ")
("\\s+@endwith\\s*" . " ) ")
("\\s+@include\\s+(\\S+)\\s*" . "= (let ((cl-emb:*escape-type* cl-emb:*escape-type*))
(cl-emb:execute-emb (merge-pathnames \"\\1\" template-path-default) :env env :generator-maker generator-maker)) ")
("\\s+@includevar\\s+(\\S+)\\s*" . "= (let ((cl-emb:*escape-type* cl-emb:*escape-type*))
(cl-emb:execute-emb (merge-pathnames (cl-emb::getf-emb \"\\1\") template-path-default) :env env :generator-maker generator-maker)) ")
("\\s+@call\\s+(\\S+)\\s*" . "= (let ((cl-emb:*escape-type* cl-emb:*escape-type*))
(cl-emb:execute-emb \"\\1\" :env env :generator-maker generator-maker)) ")
("\\s+@insert\\s+(\\S+)\\s*" . "= (cl-emb::contents-of-file (merge-pathnames (cl-emb::getf-emb \"\\1\") template-path-default)) ")
("\\s+@set\\s+(.*?)\\s*" . ,(function set-specials))
)
"List of conses. FIRST is regex, REST replacement (STRING or FUNCTION).
Functions get called with two parameters: match and list of registers.")
;; Code from Edi Weitz's TBNL <http://weitz.de/tbnl/>
(defun escape-for-xml (string)
(with-output-to-string (out)
(with-input-from-string (in string)
(loop for char = (read-char in nil nil)
while char
do (case char
((#\<) (write-string "&lt;" out))
((#\>) (write-string "&gt;" out))
((#\") (write-string "&quot;" out))
((#\') (write-string "&#39;" out))
((#\&) (write-string "&amp;" out))
(otherwise (write-char char out)))))))
(defun escape-by-table (string replacements)
(with-output-to-string (out)
(with-input-from-string (in string)
(loop for char = (read-char in nil nil)
while char
do (let ((new (find char replacements
:test 'equal
:key 'car)))
(if new
(write-string (cdr new) out)
(write-char char out))
)))))
(defvar *latex-replacements*)
(setf *latex-replacements*
(mapcar
(lambda (x) `(,(character (car x)) . ,(cdr x)))
`(
("#" . "\\#")
("$" . "\\$")
("%" . "\\%")
("&" . "\\&")
("_" . "\\_")
("{" . "\\{")
("}" . "\\}")
("<" . "{$<$}")
(">" . "{$>$}")
("\\" . "{$\\backslash{}$}")
("|" . "{$\\vert{}$}")
("~" . "{\\,$\\tilde{}$\\,}")
("^" . "{\\,$\\hat{}$\\,}")
(,(string #\Return) . "\\\\")
(,(string #\NewLine) . "\\\\")
("\"" . "{''}")
("­" . "")