Clojure 编程/示例/与 Excel 交互
通过 COM 与其他程序交互在 Java 中使用 JACOB 库可以很容易地实现。但是,处理与应用程序交互的所有底层接口非常繁琐。特别是由于该接口是为 C 设计的,而不是为 Java 设计的,更不用说 Clojure 了。因此,我们将尝试利用 Clojure 的强大功能来改善这种情况。
让我们从一个简单的例子开始。我们启动 Excel,使实例可见。然后我们再次关闭整个系统。
(import '(com.jacob.com Dispatch
ComThread))
(def xl (new Dispatch "Excel.Application"))
(Dispatch/put xl "Visible" true)
(Dispatch/call xl "Quit")
(ComThread/Release)
接口的丑陋之处应该立即显现出来。我们必须通过 Java 调用 C 接口。所以让我们封装 “Visible” 调用。
(defn excel-application-visible
([xl]
; No argument? Return the current value.
(Dispatch/get xl "Visible"))
([xl v]
; Argument given? Set it as new value.
(Dispatch/put xl "Visible" v)))
(excel-application-visible xl true)
这要好多了。我们已经将底层接口隐藏在一个简单的 Clojure 函数调用后面。现在我们必须将所有属性和函数调用包装到这样的函数中。但是等等!到目前为止,这没什么特别。只是一个函数调用。Clojure 可以在这里为我们节省一些工作。
定义几个函数来快速封装接口,显示了很多重复。以之前的例子为例:我们传入 Excel 实例,可能还有一个值,并相应地调用底层接口。但这是相同的任务。所以我们可以将此功能放到一个驱动函数中,并使用部分求值来创建包装函数。
(defn property-driver
([prop xl]
(Dispatch/get xl prop))
([prop xl v]
(Dispatch/put xl prop v)))
(def excel-application-visible (partial property-driver "Visible"))
(excel-application-visible xl true)
所以这里发生了什么?我们定义了一个新的函数,该函数也由属性名称参数化。然后我们使用内置的 partial
来创建一个专门的函数,该函数专门用于作用于 “Visible” 属性。所以 partial
有一个函数作为返回值。
现在我们有一组包装器来隐藏底层接口。但是第一个例子中的最后一条指令怎么办:(ComThread/Release)
?好吧,这是为了清理 COM 端,必须在每个使用 COM 的线程中调用。当遇到异常时,这会变得特别繁琐。为了摆脱底层处理,我们使用 Clojure 强大功能的另一个部分:宏!
(defn with-excel*
[thunk]
(let [worker (fn []
(let [xl (excel-application-new)]
(try
(thunk xl)
(finally
(excel-quit xl)
(ComThread/Release)))))
t (new Thread worker)]
(. t start)
(. t join)))
(defmacro with-excel
[varname & body]
`(with-excel* (fn [~varname] ~@body)))
首先我们再次定义一个驱动函数,它完成繁重的工作。它接受一个函数,在新建的线程中调用。函数传递给 Excel 实例。所有内容都包装在 try
中以确保即使函数抛出异常也能正确清理。
当然,我们的目的不是每次想要调用这个包装器时都手动定义一个新函数。因此我们定义了一个宏,它为我们做了这件事。它接受一个符号,该符号使 Excel 实例可用于主体。主体被包装在一个匿名函数中,该函数将主体关闭在其绑定上,并将所有内容打包到我们的包装器中。
使用这些修改重写我们的初始示例,我们得到了这段简洁的代码。
(with-excel xl
(excel-application-visible xl true))
当然,这个例子并不实用,但它清楚地显示了与之前纯 Java 代码相比的改进。此外,力量在于我们看不到的部分:即使我们在 with-excel
中抛出异常,一切也会被清理干净。
进一步的实验留给用户。遗漏的内容包括:例如,一个构建包装器的宏,或者一个处理 COM 接口 Variant
类型的函数。所以去实验,享受 乐趣!
存在 docjure - Clojure 库,它使用 Apache POI 允许读写 MS Office 文件(电子表格)。
示例
(use 'dk.ative.docjure.spreadsheet)
; Load a spreadsheet and read the first two columns from the price list sheet:
(->> (load-workbook "spreadsheet.xlsx")
(select-sheet "Price List")
(select-columns {:A :name, :B :price}))
> [{:name "Foo Widget", :price 100}, {:name "Bar Widget", :price 200}]