Quicklisp tip of the day: multiple local-projects folders

Quicklisp provides a folder where it easily finds your local projects:

~/quicklisp/local-projects

It is easy to tell quicklisp to find projects in other folders too. Just add path of additional folder to:

quicklisp-client:*local-project-directories*

Evaluate

(push #p"/path/to/your/lisp/projects/folder/" 
    quicklisp-client:*local-project-directories*)

.

Or put this in your init-file (.sbclrc, .lispworks, …).

CL-Markup

Generation of HTML markup with Common Lisp using cl-markup.

(defpackage :cl-markup-test (use :cl))
 
(in-package :cl-markup-test)
 
;; central feature is MARKUP:
 
(markup (:p 123))
=> "<p>123</p>"
 
;; MARKUP expects list arguments, atoms are beeing rejected
 
(markup "abc")
=> The value "abc" is not of type LIST.
   [Condition of type TYPE-ERROR]
 
;; arguments can be multiple lists
 
(markup (:p 123) (:p 234))
=> "<p>123</p><p>234</p>"
 
;; first element of list is used as tag, not bothering what type it is
;; and wether it smells like HTML
 
(markup ("p" 123))
=> "<p>123</p>"
 
(markup (p 123))
=> "<p>123</p>"
 
(markup ('p 123))
=> "<'p>123</'p>" ; oops
 
(markup (123 p))
=> error: The variable P is unbound.
   [Condition of type UNBOUND-VARIABLE]
 
(markup (123 "p"))
=> "<123>p</123>"  ; !!
 
;; whole HTML pages can be produced with cl-markup macros HTML, HTML5,
;; XHTML and XML.
 
(html5 (:p 42))
=> "<!DOCTYPE html><html><p>42</p></html>"
 
;; lisp expressions can be inserted everywhere except first position of list
 
(markup (:p (concatenate 'string "1" "2" "3")))
=> "<p>123</p>"
 
;; these expressions are considered good citizens when they produce
;; string results, else:
 
(markup (:p (+ 100 20 3)))
=> The value 123 is not of type STRING.
   [Condition of type TYPE-ERROR]
 
;; weird things seem to happen when combining snippets
 
(let ((snip (markup (:p "abc"))))
  (markup (:div snip)))
=> "<div>&lt;p&gt;abc&lt;/p&gt;</div>" ; !!
 
;; This is a feature called auto-escape and it provides correct
;; solutions for these kind of tasks:
 
(markup (:p "1<3"))
=> "<p>1&lt;3</p>"
 
(markup (:p "R&B"))
=> "<p>R&amp;B</p>"
 
;; auto-escaping can be turned off like this
 
(let* ((*auto-escape* nil)
       (snip (markup (:p "abc"))))
  (markup (:div snip)))
=> "<div><p>abc</p></div>"
 
;; another way to shelter strings against greedy auto-escape
;; is wrapping them in a list
 
(let ((snip (markup (:p "abc"))))
  (markup (:div (list snip))))
=> "<div><p>abc</p></div>"
 
;; same result using backquote syntax
 
(let ((snip (markup (:p "abc"))))
  (markup (:div `(,snip))))
=> "<div><p>abc</p></div>"
 
;; and this can also be done with CL-MARKUPs RAW macro.  RAW sounds
;; like a kind of strange name for a list-wrapping feature.  Maybe the
;; name tries to express that this procedure treats the string
;; like the raw markup from which it evolved.
 
(let ((snip (markup (:p "abc"))))
  (markup (:div (raw snip))))
=> "<div><p>abc</p></div>"
 
;; and now for the best of all: it is possible to write cl macros
;; producing HTML snippets.
 
(defmacro snip (name)
  `(markup (:p ,name)))
 
(snip "foo")
=> "<p>foo</p>"
 
;; It is save to handle string results.
 
;; Trying to pass markup asks for trouble
 
(defmacro snip-markup (name)
  `(:p ,name))
 
(markup (snip-markup "foo"))
=> "<snip-markup>foo</snip-markup>" ;; oops
 
;; MARKUP is a macro, it does not evaluate its arguments

Filemaker Custom Function: FM2Word

This custom function generates MS Word documents out of FileMaker data. The format is Word XML 2003 which is a bit outdatet, but still accessible with actual Word versions. I selected this grammar because it is easiest and does not require creation of a zip archive (‘docx’ is technically a zip file).

/*
 *
 *  Generates Microsoft Word 2003 XML Documents from FileMaker.
 *
 *  Export result into a textfile with extension '.xml' and it can be opened in MS Word, OpenOffice, Libre Office, ...
 *
 *
 *  The expected input is a text with tags:
 *
 *  #<p># - start paragraph
 *  #</p># - end paragraph
 *  #</p><p># - end + start paragraph
 *
 *  #<table># - tables
 *  #</table>#
 *  #<table-row>#
 *  #</table-row>#
 *  #<table-cell>#
 *  #</table-cell>#
 *
 *  #<section># - section break, continuous no new page
 *  #<section-2-col># - section break starting two column page layout
 *  #<section-3-col># - section break starting three column page layout
 *
 *  #<b># - bold
 *  #</b>#
 *  #<i># - italics
 *  #</i>#
 *
 *  #<pict>#
 *  #</pict>#
 *
 * Author: Jens Teich, http://jensteich.de
 *
 */

Let([

styles= "<w:styles>" &

"<w:style w:type='paragraph' w:styleId='FileMakersHomebrew'>" &
"<w:name w:val='FileMakersHomebrew'/>" &
"<w:basedOn w:val='Standard'/>" &
"<w:rsid w:val='00AF7A99'/>" &
"<w:pPr><w:spacing w:line='360' w:line-rule='auto'/></w:pPr><w:rPr>" &
"<w:rFonts w:ascii='Garamond' w:h-ansi='Garamond'/>" &
"<wx:font wx:val='Garamond'/>" &
"<w:color w:val='7030A0'/>" &
"<w:sz w:val='24'/>" &
"<w:lang w:val='EN-US'/>" &
"</w:rPr>" &
"</w:style>" &

"</w:styles>" ;

include_style_FileMakersHomebrew = "";

header = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" &
"<?mso-application progid='Word.Document'?>" &
"<w:wordDocument xmlns:w='http://schemas.microsoft.com/office/word/2003/wordml' " &
"xmlns:wx='http://schemas.microsoft.com/office/word/2003/auxHint' " &
"xmlns:v='urn:schemas-microsoft-com:vml' " &"
xmlns:o='urn:schemas-microsoft-com:office:office'>" ;


// footer = "</w:t></w:r></w:p></w:body></w:wordDocument>"
footer = "</w:body></w:wordDocument>"

];

header &
styles &

"<w:body>" & //<w:p><w:r><w:t>" &

Substitute ( input ;
["¶" ; "" ];["'" ; "\"" ];
["#<p>#";"<w:p><w:r><w:t>"];
["#</p>#";"</w:t></w:r></w:p>"];
["#</p><p>#";"</w:t></w:r></w:p><w:p><w:r><w:t>"];
["#<p-Style-FileMaker-Homebrew>#"; "<w:p><w:pPr><w:pStyle w:val='FileMakersHomebrew'/></w:pPr><w:r><w:t>"];
["#<table>#";"<w:tbl>"];
["#</table>#";"</w:tbl>"];
["#<table-row>#";"<w:tr>"];
["#</table-row>#";"</w:tr>"];
["#<table-cell>#";"<w:tc>"];
["#</table-cell>#";"</w:tc>"];
["#<section>#";"<w:p><w:pPr><w:sectPr><w:type w:val='continuous'/></w:sectPr></w:pPr></w:p>"];
["#<section-2-col>#";"<w:p><w:pPr><w:sectPr><w:type w:val='continuous'/><w:cols w:num='2' w:space='708'/></w:sectPr></w:pPr></w:p>"];
["#<section-3-col>#";"<w:p><w:pPr><w:sectPr><w:type w:val='continuous'/><w:cols w:num='3' w:space='708'/></w:sectPr></w:pPr></w:p>"];
["#<b>#";"</w:t></w:r><w:r><w:rPr><w:b/></w:rPr><w:t>"];
["#</b>#";"</w:t></w:r><w:r><w:t>"];
["#<i>#";"</w:t></w:r><w:r><w:rPr><w:i/></w:rPr><w:t>"];
["#</i>#";"</w:t></w:r><w:r><w:t>"];
["#<pict>#";"<w:pict><v:shape><v:imagedata src='"];
["#</pict>#";"'/></v:shape></w:pict>"]) &

footer

)

It it not (yet?) possible to print a FileMaker layout into a Word document, but this cf might be a step in this direction.

Use custom function like this:

FM2Word( "#<p>#Hello World!#</p><p>#
Second Paragraph.#</p><p>#
Third Paragraph!#</p>#" )

And get this (line breaks and indentation manually inserted for better readability):

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<?mso-application progid='Word.Document'?>
<w:wordDocument 
   xmlns:w='http://schemas.microsoft.com/office/word/2003/wordml'
   xmlns:wx='http://schemas.microsoft.com/office/word/2003/auxHint'
   xmlns:v='urn:schemas-microsoft-com:vml'
   xmlns:o='urn:schemas-microsoft-com:office:office'>

  <w:styles>
    <w:style w:type='paragraph' w:styleId='FileMakersHomebrew'>
      <w:name w:val='FileMakersHomebrew'/>
      <w:basedOn w:val='Standard'/>
      <w:rsid w:val='00AF7A99'/>
      <w:pPr><w:spacing w:line='360' w:line-rule='auto'/></w:pPr>
      <w:rPr>
	<w:rFonts w:ascii='Garamond' w:h-ansi='Garamond'/>
	<wx:font wx:val='Garamond'/>
	<w:color w:val='7030A0'/>
	<w:sz w:val='24'/>
	<w:lang w:val='EN-US'/>
      </w:rPr>
    </w:style>
  </w:styles>

  <w:body>
    <w:p><w:r><w:t>Hello World!</w:t></w:r></w:p>
    <w:p><w:r><w:t>Second Paragraph.</w:t></w:r></w:p>
    <w:p><w:r><w:t>Third Paragraph!</w:t></w:r></w:p>
  </w:body>
</w:wordDocument>

The custom function contains style ‘FileMakersHomebrew’ which shows how to define custom (MS Word) styles and make life much easier.

Sample file showing usage: test-word-transfer.fmp12.