I am trying to translate some Racket package for reading/writing xlsx files to Common Lisp and encountered a problem.
Excel files are nothing else than a bundle of xml files with strict specifications which are zipped.
Amongst them is a [Content_type].xml
or [Content_Type].xml
file.
Trying to generate and writing a file with square brackets in the name in the MacBook generates the error:
(defparameter *zip-xlsx-temp-directory* "test-directory/")
(defparameter *content-type-file* (merge-pathnames "[Content_Types].xml" *zip-xlsx-temp-directory*))
(with-open-file (stream (ensure-directories-exist *content-type-file-1*)
:direction :output
:if-does-not-exist :create
:if-exists :supersede)
(format stream ""))
bad place for a wild pathname
[Condition of type SB-INT:SIMPLE-FILE-ERROR]
Restarts:
0: [RETRY] Retry SLY interactive evaluation request.
1: [*ABORT] Return to SLY's top level.
2: [ABORT] abort thread (#<THREAD tid=11295 "slynk-worker" RUNNING {70073BBFC3}>)
Backtrace:
0: (SB-KERNEL::%FILE-ERROR #P"test-directory/[Content_Types].xml" "bad place for a wild pathname")
Locals:
ARGUMENTS = NIL
DATUM = "bad place for a wild pathname"
PATHNAME = #P"test-directory/[Content_Types].xml"
1: (ENSURE-DIRECTORIES-EXIST #P"test-directory/[Content_Types].xml" :VERBOSE T :MODE 511)
Locals:
#:.DEFAULTING-TEMP. = T
#:.DEFAULTING-TEMP.#1 = 511
SB-IMPL::CREATED-P = NIL
PATHNAME = #P"/Users/<user-name>/.roswell/local-projects/cl-simple-xlsx/tests/test-directory/[Content_Types].xml"
SB-IMPL::PATHSPEC = #P"test-directory/[Content_Types].xml"
2: ((LAMBDA ()))
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (WITH-OPEN-FILE (STREAM (ENSURE-DIRECTORIES-EXIST *CONTENT-TYPE-FILE-1* :VERBOSE T) :DIRECTION :OUTPUT :IF-DOES-NOT-EXIST :CREATE ...) (FORMAT STREAM "")) #<NULL-LEXENV>)
4: (EVAL (WITH-OPEN-FILE (STREAM (ENSURE-DIRECTORIES-EXIST *CONTENT-TYPE-FILE-1* :VERBOSE T) :DIRECTION :OUTPUT :IF-DOES-NOT-EXIST :CREATE ...) (FORMAT STREAM "")))
5: ((LAMBDA NIL :IN SLYNK:INTERACTIVE-EVAL))
6: (SLYNK::CALL-WITH-RETRY-RESTART "Retry SLY interactive evaluation request." #<FUNCTION (LAMBDA NIL :IN SLYNK:INTERACTIVE-EVAL) {70073E8F3B}>)
7: (SLYNK::CALL-WITH-BUFFER-SYNTAX NIL NIL #<FUNCTION (LAMBDA NIL :IN SLYNK:INTERACTIVE-EVAL) {70073E8F1B}>)
8: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SLYNK:INTERACTIVE-EVAL "(with-open-file (stream (ensure-directories-exist *content-type-file-1* :verbose t) ..)
9: (EVAL (SLYNK:INTERACTIVE-EVAL "(with-open-file (stream (ensure-directories-exist *content-type-file-1* :verbose t) ..)
10: (SLYNK:EVAL-FOR-EMACS (SLYNK:INTERACTIVE-EVAL "(with-open-file (stream (ensure-directories-exist *content-type-file-1* :verbose t) ..)
11: ((LAMBDA NIL :IN SLYNK::SPAWN-WORKER-THREAD))
12: (SLYNK-SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SLYNK:SLYNK-DEBUGGER-HOOK> #<FUNCTION (LAMBDA NIL :IN SLYNK::SPAWN-WORKER-THREAD) {70073E8C6B}>)
13: ((FLET SLYNK-BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/Users/<user-name>/.roswell/lisp/sly/git/slynk/backend/sbcl.lisp") #<FUNCTION SLYNK:SLYNK-DEBUGGER-HOOK> #<FUNCTION (LAMBDA NIL :IN SLYNK::SPAWN-WORKER-T..
14: ((LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER))
15: (SLYNK::CALL-WITH-BINDINGS ((*PACKAGE* . #<PACKAGE "CL-SIMPLE-XLSX/TESTS/MAIN">) (*DEFAULT-PATHNAME-DEFAULTS* . #P"/Users/<user-name>/.roswell/local-projects/cl-simple-xlsx/tests/") (* . #1="+01:00") (**..
16: ((LAMBDA NIL :IN SLYNK::SPAWN-WORKER-THREAD))
17: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
18: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::RUN))
19: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
20: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::RUN))
21: (SB-THREAD::RUN)
This seems to be a Lisp-immanent problem.
Taking my Python (in ipython zshell) and doing:
with open("[Content_Types].xml", "w") as f:
f.write("<H1>Hi</H1>")
works without errors.
I tried (truename path) or escaping the brackets in the name "[Content_Types].xml" or "\[Content_Types\].xml" - if escaping the first one seems to be correct. But it didn't help. Still getting the bad wildcard error.
This is a problem specific to SBCL, which parses namestrings containing [...]
as wild. It is allowed to do this: this is not an SBCL bug (although the documentation could be better around this).
A good workaround is to use a namestring parser which does not do this and which is also portable across implementations. UIOP has one such and is very commonly installed. So
> (wild-pathname-p (parse-namestring "[x].y"))
t
> (wild-pathname-p (uiop:ensure-pathname "[x].y"))
nil
> (pathname-name (parse-namestring "[x].y"))
#<sb-impl::pattern (:character-set . "x")>
> (pathname-name (uiop:ensure-pathname "[x].y"))
"[x]"