excelxmlfilelispcommon-lisp

Creating File with Square Brackets in the name in Common Lisp in a MacBook creates problems. How can I do it?


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.


Solution

  • 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]"