├── .circleci
└── config.yml
├── .editorconfig
├── .gitignore
├── .ocp-indent
├── CHANGES.md
├── LICENSE.md
├── Makefile
├── README.md
├── dune-project
├── easy_xlsx.descr
├── easy_xlsx.opam
├── easy_xlsx
├── src
│ ├── dune
│ ├── easy_xlsx.ml
│ └── easy_xlsx.mli
└── test
│ ├── dune
│ ├── files
│ ├── autofilter_import_xml_12.xlsx
│ ├── autofilter_import_xml_12_Advanced1.csv
│ ├── autofilter_import_xml_12_Advanced2.csv
│ ├── autofilter_import_xml_12_Custom.csv
│ ├── autofilter_import_xml_12_Discrete.csv
│ ├── autofilter_import_xml_12_Top10.csv
│ ├── chart_3dsettings_import_xml_12.xlsx
│ ├── chart_3dsettings_import_xml_12_Elevation.csv
│ ├── chart_3dsettings_import_xml_12_Perspective.csv
│ ├── chart_3dsettings_import_xml_12_Rotation.csv
│ ├── chart_3dsettings_import_xml_12_Settings.csv
│ ├── chart_3dsettings_import_xml_12_SourceData.csv
│ ├── chart_axis_import_xml_12.xlsx
│ ├── chart_axis_import_xml_12_Axis Labels.csv
│ ├── chart_axis_import_xml_12_Axis Line.csv
│ ├── chart_axis_import_xml_12_Gridlines.csv
│ ├── chart_axis_import_xml_12_SourceData.csv
│ ├── chart_axis_import_xml_12_Tick Marks.csv
│ ├── chart_bitmaps_import_xml_12.xlsx
│ ├── chart_bitmaps_import_xml_12_General.csv
│ ├── chart_bitmaps_import_xml_12_Series 2D.csv
│ ├── chart_bitmaps_import_xml_12_Series 3D.csv
│ ├── chart_bitmaps_import_xml_12_SourceData.csv
│ ├── convert.py
│ ├── formats.xlsx
│ ├── hyperlink_import_xml_12.xlsx
│ ├── hyperlink_import_xml_12_Sheet'!.csv
│ ├── hyperlink_import_xml_12_Sheet1.csv
│ ├── hyperlink_import_xml_12_Sheet2.csv
│ ├── oleobject_import_xml_12.xlsx
│ ├── oleobject_import_xml_12_OLE.csv
│ ├── oleobject_import_xml_12_SourceData.csv
│ ├── scenarios_import_xml_12.xlsx
│ ├── scenarios_import_xml_12_Name.csv
│ ├── scenarios_import_xml_12_Ranges.csv
│ ├── scenarios_import_xml_12_RefCheck.csv
│ ├── scenarios_import_xml_12_Settings.csv
│ ├── sheetprotection_import_xml_12.xlsx
│ ├── sheetprotection_import_xml_12_Sheet1.csv
│ ├── sheetprotection_import_xml_12_Sheet2.csv
│ ├── sheetprotection_import_xml_12_Sheet3.csv
│ └── sheetprotection_import_xml_12_Sheet4.csv
│ ├── test_easy_xlsx_docs.ml
│ └── test_easy_xlsx_formats.ml
├── open_packaging.descr
├── open_packaging.opam
├── open_packaging
└── src
│ ├── dune
│ ├── open_packaging.ml
│ ├── relationship.ml
│ ├── relationship.mli
│ ├── relationships.ml
│ ├── relationships.mli
│ └── utils.ml
├── pkg
└── pkg.ml
├── spreadsheetml.descr
├── spreadsheetml.opam
└── spreadsheetml
└── src
├── dune
├── shared_string_table.ml
├── shared_string_table.mli
├── spreadsheetml.ml
├── styles.ml
├── styles.mli
├── workbook.ml
├── workbook.mli
└── worksheet.ml
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: ocaml/opam2:alpine-3.8-ocaml-4.04
6 | environment:
7 | TERM: xterm
8 | steps:
9 | - run:
10 | name: Update opam
11 | command: |
12 | opam remote remove --all default
13 | opam remote add default https://opam.ocaml.org
14 | - checkout
15 | - run:
16 | name: Pin packages
17 | command: |
18 | opam pin add -y -n easy_xlsx .
19 | opam pin add -y -n open_packaging .
20 | opam pin add -y -n spreadsheetml .
21 | - run:
22 | name: Install system dependencies
23 | command: opam depext -y easy_xlsx open_packaging spreadsheetml
24 | - run:
25 | name: Install OCaml dependencies
26 | command: opam install --deps-only -y easy_xlsx open_packaging spreadsheetml
27 | - run:
28 | name: Build
29 | command: opam config exec -- make
30 | - run:
31 | name: Install OCaml test dependencies
32 | # Not using opam install -y -t because it tries to run csv's tests
33 | command: opam install -y csv oUnit
34 | - run:
35 | name: Test
36 | command: opam config exec -- make test
37 | - run:
38 | name: Coverage
39 | command: opam config exec -- make coverage
40 | - run:
41 | name: Install ocveralls
42 | command: opam install -y ocveralls
43 | - run:
44 | name: Upload coverage report
45 | command: |
46 | cd _build/default
47 | shopt -s globstar
48 | opam config exec -- ocveralls **/bisect*.out --send --repo_token $COVERALLS_REPO_TOKEN --git
49 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | max_line_length = 80
12 |
13 | # Makefiles only support tab indents
14 | [Makefile]
15 | indent_style = tab
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *#
2 | _build
3 | _coverage
4 | *.install
5 | bisect*.out
6 | .merlin
7 |
--------------------------------------------------------------------------------
/.ocp-indent:
--------------------------------------------------------------------------------
1 | JaneStreet
2 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ### 1.0 (2018-05-31)
2 |
3 | Initial release
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | @dune build @install
3 |
4 | clean:
5 | @dune clean
6 |
7 | coverage: clean
8 | @BISECT_ENABLE=YES dune runtest --force
9 | @bisect-ppx-report -I _build/default/ -html _coverage/ \
10 | `find . -name 'bisect*.out'`
11 |
12 | test:
13 | @dune runtest --force
14 |
15 | # until we have https://github.com/ocaml/opam-publish/issues/38
16 | REPO=../opam-repository
17 | PACKAGES=$(REPO)/packages
18 |
19 | pkg-%:
20 | topkg opam pkg -n $*
21 | mkdir -p $(PACKAGES)/$*
22 | cp -r _build/$*.* $(PACKAGES)/$*/
23 | rm -f $(PACKAGES)/$*/$*.opam
24 | cd $(PACKAGES) && git add $*
25 |
26 | PKGS=$(basename $(wildcard *.opam))
27 | opam-pkg:
28 | $(MAKE) $(PKGS:%=pkg-%)
29 |
30 | .PHONY: all build clean coverage opam-pkg test
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/brendanlong/ocaml-ooxml)
2 | [](https://coveralls.io/github/brendanlong/ocaml-ooxml?branch=master)
3 |
4 | The repo contains three libraries for reading data from Microsoft's document
5 | formats ("Office Open XML").
6 |
7 | - `open_packaging` parses Office Open XML's "Open Packaging Conventions"
8 | (the container format for all Microsoft Office documents)
9 | - `spreadsheetml` parses the XML data in SpreadsheetML (i.e. Excel's XLSX
10 | format)
11 | - `easy_xlsx` reads XLSX documents, applies the formatting in the document,
12 | and returns the result as a list of sheets (with a sheet name and then
13 | a `string list list` of data). The goal of this library is to give the
14 | same results as if you exported the XLSX file to CSV and then read it with
15 | a standard CSV reader.
16 |
17 | `open_packaging` and `spreadsheetml` are relatively safe to use but incomplete
18 | (it should be obvious what they're mising -- if a field doesn't exist, I
19 | haven't got to it yet). Everything that does exist should be parsed properly.
20 |
21 | `easy_xlsx` is in very early stages. It should properly give read XLSX files
22 | and output correct types, but the SpreadsheetML spec doesn't list all of the
23 | built-in format strings, so some types may not be handled correctly. At the
24 | moment, `easy_xlsx` will bail out in any case where it can't understand the
25 | formatting, although I'd be open to patches to make this optional.
26 |
27 | [API documentation](https://brendanlong.github.io/ocaml-ooxml/doc/)
28 |
29 | ## Building
30 |
31 | Install dependencies:
32 |
33 | ```
34 | opam pin add -n easy_xlsx .
35 | opam depext easy_xlsx
36 | opam install --deps-only easy_xlsx
37 | ```
38 |
39 | Then build:
40 |
41 | ```
42 | make
43 | ```
44 |
45 | You can run the tests if you want:
46 |
47 | ```
48 | make test
49 | ```
50 |
51 | The `Makefile` is just a thin wrapper around jbuilder, so you can use
52 | jbuilder commands too if you prefer.
53 |
54 | ## Helping
55 |
56 | If you want to help with this, create an issue with what you'd like to work
57 | on, and mention it if you need me to help with anything (point you at the
58 | relevant spec, give my opinion on approaches, etc.).
59 |
60 | Some things I could use help with:
61 |
62 | - Implement more of the specs. See [ECMA 376](https://www.ecma-international.org/publications/standards/Ecma-376.htm).
63 | The editions only contain changes, so most of what's interesting is in
64 | the 1st edition. Part 2 is the most interesting for the Open Packaging
65 | Conventions and Part 4 has specifics for SpreadsheetML (or the other office
66 | formats if you want to start a library for them).
67 | - Add more tests. Right now there's a set of extremely high level tests
68 | that we get the sound output as OpenOffice's CSV export, but being so high
69 | level means a lot of things are completely untested until we're 100%
70 | finished, which isn't a good situation to be in. It's easy to have a typo
71 | when implementing this spec, so I'd like to aim for 100% test coverage.
72 | - The cell formatting needs a lot of work. It's what I'm likely to work on
73 | next, but if you think you can do it first I'd be happy to let someone
74 | else do it (let me know if you start working on this though, so we don't
75 | duplicate effort).
76 | - [Make CamlZip support a string or bigstring interface](https://github.com/xavierleroy/camlzip/pull/7).
77 | Right now we can only read actual files on the filesystem since that's the
78 | interface CamlZip gives us. I'd like to be able to read data from memory.
79 | - Make a pure-OCaml ZIP library. Then we won't need any system dependencies
80 | and should work with `js_of_ocaml` too.
81 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 1.4)
2 |
--------------------------------------------------------------------------------
/easy_xlsx.descr:
--------------------------------------------------------------------------------
1 | A library to easily read XLSX files into a simpler format
2 |
--------------------------------------------------------------------------------
/easy_xlsx.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | authors: ["Brendan Long "]
3 | maintainer: "self@brendanlong.com"
4 | homepage: "https://github.com/brendanlong/ocaml-ooxml"
5 | dev-repo: "git+https://github.com/brendanlong/ocaml-ooxml.git"
6 | bug-reports: "https://github.com/brendanlong/ocaml-ooxml/issues"
7 | doc: "https://brendanlong.github.io/ocaml-ooxml/doc"
8 |
9 | build: [
10 | ["dune" "build" "-p" name "-j" jobs]
11 | ["dune" "runtest" "-p" name "-j" jobs] {with-test}
12 | ]
13 |
14 | depends: [
15 | "camlzip"
16 | "ocaml" {>= "4.04.2"}
17 | "ppx_jane"
18 | "ptime"
19 | "spreadsheetml"
20 |
21 | "bisect_ppx" {build & >= "1.3.0"}
22 | "dune" {build}
23 |
24 | "csv" {test}
25 | "ounit" {test}
26 | ]
27 |
--------------------------------------------------------------------------------
/easy_xlsx/src/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name easy_xlsx)
3 | (public_name easy_xlsx)
4 | (libraries camlzip ptime spreadsheetml str)
5 | (preprocess (pps ppx_jane bisect_ppx -conditional)))
6 |
--------------------------------------------------------------------------------
/easy_xlsx/src/easy_xlsx.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Base.Printf
3 | open Stdint
4 | open Spreadsheetml
5 |
6 | module Value = struct
7 | type t =
8 | | Date of Ptime.date
9 | | Datetime of Ptime.t
10 | | Number of float
11 | | String of string
12 | | Time of Ptime.time
13 |
14 | let to_string = function
15 | | Date (y, m, d) -> sprintf "%d-%d-%d" y m d
16 | | Datetime t -> Ptime.to_rfc3339 t
17 | | Number f ->
18 | Float.to_string f
19 | |> String.rstrip ~drop:(function '.' -> true | _ -> false)
20 | | String s -> s
21 | | Time ((h, m, s), _tz) -> sprintf "%d:%d:%d" h m s
22 |
23 | let sexp_of_t = function
24 | | Number n -> Float.sexp_of_t n
25 | | String s -> String.sexp_of_t s
26 | | _ as t -> to_string t |> String.sexp_of_t
27 |
28 | let built_in_formats =
29 | (* FIXME: This is only the English format codes. There are 4 Asian format
30 | codes (Chinese Simplified, Chinese Traditional, Japanese, Korean) and it's
31 | not clear to me how we're supposed to pick between them. *)
32 | [ 0, "general"
33 | ; 1, "0"
34 | ; 2, "0.00"
35 | ; 3, "#,##0"
36 | ; 4, "#,##0,00"
37 | ; 9, "0%"
38 | ; 10, "0.00%"
39 | ; 11, "0.00E+00"
40 | ; 12, "# ?/?"
41 | ; 13, "# ??/??"
42 | ; 14, "mm-dd-yy"
43 | ; 15, "d-mmm-yy"
44 | ; 16, "d-mmm"
45 | ; 17, "mmm-yy"
46 | ; 18, "h:mm AM/PM"
47 | ; 19, "h:mm:ss AM/PM"
48 | ; 20, "h:mm"
49 | ; 21, "h:mm:ss"
50 | ; 22, "m/d/yy h:mm"
51 | ; 37, "#,##0 ;(#,##0)"
52 | ; 38, "#,##0 ;[Red](#,##0)"
53 | ; 39, "#,##0.00;(#,##0.00)"
54 | ; 40, "#,##0.00;[Red](#,##0.00)"
55 | ; 45, "mm:ss"
56 | ; 46, "[h]:mm:ss"
57 | ; 47, "mmss.0"
58 | ; 48, "##0.0E+0"
59 | ; 49, "@" ]
60 | |> Map.of_alist_exn (module Int)
61 |
62 | let classify_format str =
63 | (* FIXME: This is really slow. We should really use Menhir for this. *)
64 | let str = String.lowercase str in
65 | let remove_strings = Str.regexp "\\[[^\\[]*\\]\\|\"[^\"]*\"" in
66 | let str = Str.global_replace remove_strings "" str in
67 | if String.(str = "general" || str = "") then
68 | `Number
69 | else
70 | let is_string = String.contains str '@' in
71 | let is_date = String.contains str 'y' || String.contains str 'd'
72 | (* QQ is quarter, NN is day of the week *)
73 | || String.contains str 'q' || String.contains str 'n'
74 | (* WW is week number *)
75 | || String.contains str 'w'
76 | || String.is_substring str ~substring:"mmm" in
77 | let is_time = String.contains str 'h' || String.contains str 's'
78 | || String.is_substring str ~substring:"am/pm"
79 | || String.is_substring str ~substring:"a/p" in
80 | if [ is_string ; is_date || is_time ]
81 | |> List.filter ~f:Fn.id
82 | |> List.length > 1 then
83 | failwithf "Ambiguous format string '%s'" str ()
84 | else if is_string then
85 | `String
86 | else if is_date && is_time then
87 | `Datetime
88 | else if is_date then
89 | `Date
90 | else if is_time then
91 | `Time
92 | else
93 | `Number
94 |
95 | let of_cell ~styles ~formats ~shared_strings
96 | ({ Worksheet.Cell.style_index ; data_type ; _ } as cell) =
97 | let formats = Map.merge built_in_formats formats ~f:(fun ~key ->
98 | function
99 | | `Left v -> Some v
100 | | `Right v -> Some v
101 | | `Both (a, b) ->
102 | if String.(a = b) then Some a
103 | else
104 | failwithf "Got format string with ID %d, \"%s\", but there is a \
105 | built-in format string with the same ID, \"%s\""
106 | key b a ())
107 | in
108 | let str = Worksheet.Cell.to_string ~shared_strings cell in
109 | match data_type with
110 | | Number when String.(str <> "") ->
111 | styles.(Uint32.to_int style_index)
112 | |> Spreadsheetml.Styles.Format.number_format_id
113 | |> Option.map ~f:Uint32.to_int
114 | |> Option.value ~default:0
115 | |> (fun num_fmt_id ->
116 | match Map.find formats num_fmt_id with
117 | | Some format -> format
118 | | None ->
119 | failwithf "Cell referenced numFmtId %d but it's not listed in the \
120 | XLSX file and isn't a known built-in format ID"
121 | num_fmt_id ())
122 | |> classify_format
123 | |> (
124 | let xlsx_epoch = Option.value_exn ~here:[%here]
125 | (Ptime.(of_date (1899, 12, 30))) in
126 | let date_of_float n =
127 | Float.iround_exn ~dir:`Down n
128 | |> (fun days -> Ptime.Span.of_d_ps (days, Int64.zero))
129 | |> Option.value_exn ~here:[%here]
130 | |> Ptime.add_span xlsx_epoch
131 | |> Option.value_exn ~here:[%here]
132 | |> Ptime.to_date
133 | in
134 | let time_of_float n =
135 | Float.((modf n |> Parts.fractional) * 24. * 60. * 60.)
136 | |> Ptime.Span.of_float_s
137 | |> Option.value_exn ~here:[%here]
138 | |> Ptime.of_span
139 | |> Option.value_exn ~here:[%here]
140 | |> Ptime.to_date_time
141 | |> snd
142 | in
143 | let n = Float.of_string str in
144 | function
145 | | `Number -> Number n
146 | | `Date -> Date (date_of_float n)
147 | | `Datetime ->
148 | let date = date_of_float n in
149 | let time = time_of_float n in
150 | Datetime (Ptime.of_date_time (date, time)
151 | |> Option.value_exn ~here:[%here])
152 | | `String -> String str
153 | | `Time ->
154 | Time (time_of_float n))
155 | | _ -> String str
156 |
157 | let is_empty = function
158 | | String "" -> true
159 | | _ -> false
160 | end
161 |
162 | type sheet =
163 | { name : string
164 | ; rows : Value.t list list }
165 | [@@deriving fields, sexp_of]
166 |
167 | type t = sheet list [@@deriving sexp_of]
168 |
169 | let read_file filename =
170 | let zip_entry_to_xml zip name =
171 | Zip.find_entry zip name
172 | |> Zip.read_entry zip
173 | |> Xml.parse_string
174 | in
175 | let zip = Zip.open_in filename in
176 | Exn.protect ~f:(fun () ->
177 | let shared_strings =
178 | zip_entry_to_xml zip "xl/sharedStrings.xml"
179 | |> Shared_string_table.of_xml
180 | |> List.to_array
181 | in
182 | let sheets =
183 | zip_entry_to_xml zip "xl/workbook.xml"
184 | |> Workbook.of_xml
185 | |> Workbook.sheets
186 | in
187 | let stylesheet =
188 | zip_entry_to_xml zip "xl/styles.xml"
189 | |> Styles.of_xml
190 | in
191 | let styles =
192 | Styles.cell_formats stylesheet
193 | |> Array.of_list
194 | in
195 | let formats =
196 | Styles.number_formats stylesheet
197 | |> List.map ~f:(fun { Styles.Number_format.id ; format } ->
198 | Uint32.to_int id, format)
199 | |> Map.of_alist_exn (module Int)
200 | in
201 | let rel_map =
202 | zip_entry_to_xml zip "xl/_rels/workbook.xml.rels"
203 | |> Open_packaging.Relationships.of_xml
204 | |> List.map ~f:(fun { Open_packaging.Relationship.id ; target ; _ } ->
205 | id, target)
206 | |> Map.of_alist_exn (module String)
207 | in
208 | List.map sheets ~f:(fun { Workbook.Sheet.name ; id ; _ } ->
209 | let rows =
210 | let target = Map.find_exn rel_map id in
211 | let path = sprintf "xl/%s" target in
212 | let { Worksheet.columns ; rows } =
213 | Zip.find_entry zip path
214 | |> Zip.read_entry zip
215 | |> Xml.parse_string
216 | |> Worksheet.of_xml
217 | in
218 | let num_cols =
219 | columns
220 | |> List.map ~f:Worksheet.Column.max
221 | |> List.map ~f:Uint32.to_int
222 | |> List.max_elt ~compare:Int.compare
223 | |> Option.value ~default:0
224 | in
225 | let row_map =
226 | rows
227 | |> List.map ~f:(fun { Worksheet.Row.row_index ; cells ; _ } ->
228 | let index =
229 | Option.value_exn ~here:[%here] row_index
230 | |> Uint32.to_int
231 | in
232 | let cell_map =
233 | List.map cells ~f:(fun cell ->
234 | Worksheet.Cell.column cell, cell)
235 | |> Map.of_alist_exn (module Int)
236 | in
237 | let cells =
238 | Map.max_elt cell_map
239 | |> Option.map ~f:(fun (max, _) ->
240 | List.init (max + 1) ~f:(fun i ->
241 | Map.find cell_map i
242 | |> Option.value ~default:Worksheet.Cell.default))
243 | |> Option.value ~default:[]
244 | in
245 | index - 1, cells)
246 | |> Map.of_alist_exn (module Int)
247 | in
248 | let n =
249 | Map.keys row_map
250 | |> List.max_elt ~compare:Int.compare
251 | |> Option.map ~f:((+) 1)
252 | |> Option.value ~default:0
253 | in
254 | List.init n ~f:Fn.id
255 | |> List.map ~f:(fun i ->
256 | let row =
257 | Map.find row_map i
258 | |> Option.value ~default:[]
259 | in
260 | let missing_cols = num_cols - List.length row in
261 | if missing_cols > 0 then
262 | row @ List.init ~f:(Fn.const Worksheet.Cell.default) missing_cols
263 | else
264 | row)
265 | |> List.map ~f:(List.map ~f:(Value.of_cell ~styles ~formats ~shared_strings))
266 | in
267 | { name ; rows }))
268 | ~finally:(fun () -> Zip.close_in zip)
269 |
--------------------------------------------------------------------------------
/easy_xlsx/src/easy_xlsx.mli:
--------------------------------------------------------------------------------
1 | (** An easy read for XLSX files. *)
2 | module Value : sig
3 | type t =
4 | | Date of Ptime.date
5 | | Datetime of Ptime.t
6 | | Number of float
7 | | String of string
8 | | Time of Ptime.time
9 | [@@deriving sexp_of]
10 | (** A cell value. Note that a [Number] could be either an integer or
11 | a float; Excel stores them exactly the same and the only difference
12 | is formatting. *)
13 |
14 | val to_string : t -> string
15 | val is_empty : t -> bool
16 | end
17 |
18 | type sheet =
19 | { name : string
20 | ; rows : Value.t list list }
21 | [@@deriving fields, sexp_of]
22 | (** One sheet from a workbook. Empty rows will be returned, and empty or
23 | missing columns will be returned as [""]. *)
24 |
25 | type t = sheet list [@@deriving sexp_of]
26 |
27 | val read_file : string -> sheet list
28 | (** [read_file file_name] synchronously reads the .xlsx document at
29 | [file_name] and returns all of the sheets in the document. Will throw
30 | an exception if the file doesn't exist, can't be read for any reason,
31 | isn't a valid ZIP file, or isn't a valid XLSX file. *)
32 |
--------------------------------------------------------------------------------
/easy_xlsx/test/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (names test_easy_xlsx_docs)
3 | (modules test_easy_xlsx_docs)
4 | (libraries csv easy_xlsx oUnit)
5 | (preprocess (pps ppx_jane)))
6 |
7 | (executables
8 | (names test_easy_xlsx_formats)
9 | (modules test_easy_xlsx_formats)
10 | (libraries csv easy_xlsx oUnit)
11 | (preprocess (pps ppx_jane)))
12 |
13 | (alias
14 | (package easy_xlsx)
15 | (name runtest)
16 | (deps test_easy_xlsx_docs.exe (source_tree files))
17 | (action (run ./test_easy_xlsx_docs.exe)))
18 |
19 | (alias
20 | (package easy_xlsx)
21 | (name runtest)
22 | (deps test_easy_xlsx_formats.exe (source_tree files))
23 | (action (run ./test_easy_xlsx_formats.exe)))
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/autofilter_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/autofilter_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/autofilter_import_xml_12_Advanced1.csv:
--------------------------------------------------------------------------------
1 | AUTOFILTER TEST DOCUMENT,,,,,
2 | ,,,,,
3 | 4 - ADVANCED FILTERS,,,,,
4 | ,,,,,
5 | 4.1 - INPLACE FILTERING,,,,,
6 | Header A,Header B,Header C,,,"(C, D, E, F, G)"
7 | A,1,A,,,
8 | B,2,a,,,
9 | C,3,abc,,,
10 | D,4,ABC,,,
11 | E,5,abcd,,,
12 | F,6,DEF,,,
13 | G,7,defg,,,
14 | H,8,cdef,,,
15 | I,9,A,,,
16 | J,10,a,,,
17 | ,,,,,
18 | Header B,Header B,Header C,,,
19 | >=3,<8,,,,
20 | ,,=ABC,,,
21 | ,,DEF,,,
22 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/autofilter_import_xml_12_Advanced2.csv:
--------------------------------------------------------------------------------
1 | AUTOFILTER TEST DOCUMENT,,,,,
2 | ,,,,,
3 | 4 - ADVANCED FILTERS,,,,,
4 | ,,,,,
5 | 4.2 - FILTERING TO OUTPUT RANGE,,,,,
6 | Header A,Header B,Header C,,,
7 | A,1,A,,,
8 | B,2,a,,,
9 | C,3,abc,,,
10 | D,4,ABC,,,
11 | E,5,abcd,,,
12 | F,6,DEF,,,
13 | G,7,defg,,,
14 | H,8,cdef,,,
15 | I,9,A,,,
16 | J,10,a,,,
17 | ,,,,,
18 | Header B,Header B,Header C,,,
19 | >=3,<8,,,,
20 | ,,=ABC,,,
21 | ,,DEF,,,
22 | ,,,,,
23 | Header A,Header B,Header C,,,"(C, D, E, F, G)"
24 | C,3,abc,,,
25 | D,4,ABC,,,
26 | E,5,abcd,,,
27 | F,6,DEF,,,
28 | G,7,defg,,,
29 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/autofilter_import_xml_12_Custom.csv:
--------------------------------------------------------------------------------
1 | AUTOFILTER TEST DOCUMENT,,,,,
2 | ,,,,,
3 | 3 - CUSTOM FILTERS,,,,,
4 | ,,,,,
5 | "3.1 - NUMERIC FILTERS, GREATER, LESS-EQUAL, EQUAL (table filter)",,,,,
6 | Header A,Header B,Header C,Header D,,"(C, D, G, H)"
7 | A,1,1,1,,
8 | B,2,2,1,,
9 | C,3,3,1,,
10 | D,4,4,1,,
11 | E,5,5,0,,
12 | F,6,6,0,,
13 | G,7,7,1,,
14 | H,8,8,1,,
15 | I,9,9,1,,
16 | J,10,10,1,,
17 | ,,,,,
18 | "3.2 - TEXT FILTERS, NO WILDCARDS, BEGINS, CONTAINS, EQUAL (table filter)",,,,,
19 | Header A,Header B,Header C,Header D,,"(C, D, G, H)"
20 | A,ABCD,ABDE,ABC,,
21 | B,abcd,abde,abc,,
22 | C,ABCD,BCD,ABC,,
23 | D,abcd,bcd,abc,,
24 | E,ABCD,ABCD,ABCD,,
25 | F,abcd,AbCd,BCDE,,
26 | G,ABCD,aBcD,ABC,,
27 | H,abcd,abcd,abc,,
28 | I,ABD,ABC,ABC,,
29 | J,ab,abc,abc,,
30 | ,,,,,
31 | "3.3 - TEXT FILTERS, WILDCARDS, EQUAL (table filter)",,,,,
32 | Header A,Header B,Header C,Header D,,"(C, D, G, H)"
33 | A,CA,ABC,ACDE,,
34 | B,A,abc,ABCFE,,
35 | C,AC,ADC,ABBCGE,,
36 | D,ABC,adc,ABBBCHE,,
37 | E,ABBC,AEC,ABCE,,
38 | F,ABBBC,aec,abcdde,,
39 | G,abbbc,AFC,abbbche,,
40 | H,abbc,afc,abbcge,,
41 | I,abc,AC,abcfe,,
42 | J,ac,ABBC,acde,,
43 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/autofilter_import_xml_12_Discrete.csv:
--------------------------------------------------------------------------------
1 | AUTOFILTER TEST DOCUMENT,,,,,
2 | ,,,,,
3 | 1 - DISCRETE FILTERS,,,,,
4 | ,,,,,
5 | "1.1 - SHOW TEXT ENTRIES, SHOW BLANKS (sheet filter)",,,,,
6 | Header A,Header B,,,,
7 | A,1,,,,
8 | B,2,,,,
9 | C,3,,,,
10 | D,4,,,,
11 | E,5,,,,
12 | ,6,,,,
13 | A,7,,,,
14 | B,8,,,,
15 | C,9,,,,
16 | D,10,,,,
17 | E,11,,,,
18 | ,,,,,
19 | "1.2 - SHOW NUMERIC ENTRIES, HIDE BLANKS (table filter)",,,,,
20 | Header A,Header B,,,,
21 | A,1,,,,
22 | B,2,,,,
23 | C,3,,,,
24 | D,4,,,,
25 | E,5,,,,
26 | F,,,,,
27 | G,1,,,,
28 | H,2,,,,
29 | I,3,,,,
30 | J,4,,,,
31 | K,5,,,,
32 | ,,,,,
33 | 1.3 - HIDE/SHOW BLANKS ONLY (table filter),,,,,
34 | Header A,Header B,Header C,,,"(B, C, H, I)"
35 | A,A,A,,,
36 | B,B,,,,
37 | C,C,,,,
38 | D,D,D,,,
39 | E,,,,,
40 | F,,F,,,
41 | G,G,G,,,
42 | H,H,,,,
43 | I,I,,,,
44 | J,J,J,,,
45 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/autofilter_import_xml_12_Top10.csv:
--------------------------------------------------------------------------------
1 | AUTOFILTER TEST DOCUMENT,,,,,
2 | ,,,,,
3 | 2 - TOP-10 FILTERS,,,,,
4 | ,,,,,
5 | 2.1 - SHOW TOP 3 ITEMS (sheet filter),,,,,
6 | Header A,Header B,,,,"(H, I, J)"
7 | 1,A,,,,
8 | 2,B,,,,
9 | 3,C,,,,
10 | 4,D,,,,
11 | 5,E,,,,
12 | 6,F,,,,
13 | 7,G,,,,
14 | 8,H,,,,
15 | 9,I,,,,
16 | 10,J,,,,
17 | ,,,,,
18 | 2.2 - SHOW BOTTOM 30 PERCENT (table filter),,,,,
19 | Header A,Header B,,,,"(A, B, C)"
20 | A,1,,,,
21 | B,2,,,,
22 | C,3,,,,
23 | D,4,,,,
24 | E,5,,,,
25 | F,6,,,,
26 | G,7,,,,
27 | H,8,,,,
28 | I,9,,,,
29 | J,10,,,,
30 | ,,,,,
31 | 2.3 - MULTIPLE COMBINED TOP-10 FILTERS (table filter),,,,,
32 | Header A,Header B,Header C,Header D,,"(C, D, E, H)"
33 | A,1,1,6,,
34 | B,2,2,7,,
35 | C,3,3,8,,
36 | D,4,4,9,,
37 | E,5,5,10,,
38 | F,6,6,1,,
39 | G,7,7,2,,
40 | H,8,8,3,,
41 | I,9,9,4,,
42 | J,10,10,5,,
43 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_3dsettings_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/chart_3dsettings_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_3dsettings_import_xml_12_Elevation.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - 3D SETTINGS,,,,,,,,
2 | ,,,,,,,,
3 | 2 - ELEVATION,,,,,,,,
4 | ,,,,,,,,
5 | 2.1 - BAR CHART (-180° ... 180°),,,,,,,,
6 | Elevation = -90°,,,,Elevation = -50°,,,,Elevation = -10°
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | Elevation = 10°,,,,Elevation = 50°,,,,Elevation = 90°
12 | ,,,,,,,,
13 | ,,,,,,,,
14 | ,,,,,,,,
15 | ,,,,,,,,
16 | ,,,,,,,,
17 | 2.2 - PIE CHART (0° ... 90°),,,,,,,,
18 | Elevation = 0°,,,,Elevation = 10°,,,,Elevation = 20°
19 | ,,,,,,,,
20 | ,,,,,,,,
21 | ,,,,,,,,
22 | ,,,,,,,,
23 | Elevation = 40°,,,,Elevation = 60°,,,,Elevation = 90°
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_3dsettings_import_xml_12_Perspective.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - 3D SETTINGS,,,,,,,,
2 | ,,,,,,,,
3 | 3 - PERSPECTIVE,,,,,,,,
4 | ,,,,,,,,
5 | 3.1 - BAR CHART (0% ... 100%),,,,,,,,
6 | Perspective = 0%,,,,Perspective = 20%,,,,Perspective = 40%
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | Perspective = 60%,,,,Perspective = 80%,,,,Perspective = 100%
12 | ,,,,,,,,
13 | ,,,,,,,,
14 | ,,,,,,,,
15 | ,,,,,,,,
16 | ,,,,,,,,
17 | 3.2 - PIE CHART (0% ... 100%),,,,,,,,
18 | Perspective = 0%,,,,Perspective = 20%,,,,Perspective = 40%
19 | ,,,,,,,,
20 | ,,,,,,,,
21 | ,,,,,,,,
22 | ,,,,,,,,
23 | Perspective = 60%,,,,Perspective = 80%,,,,Perspective = 100%
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_3dsettings_import_xml_12_Rotation.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - 3D SETTINGS,,,,,,,,
2 | ,,,,,,,,
3 | 1 - ROTATION,,,,,,,,
4 | ,,,,,,,,
5 | 1.1 - BAR CHART (0° ... 359°),,,,,,,,
6 | Rotation = 0°,,,,Rotation = 60°,,,,Rotation = 120°
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | Rotation = 180°,,,,Rotation = 240°,,,,Rotation = 300°
12 | ,,,,,,,,
13 | ,,,,,,,,
14 | ,,,,,,,,
15 | ,,,,,,,,
16 | ,,,,,,,,
17 | 1.2 - PIE CHART (0° ... 359°),,,,,,,,
18 | Rotation = 0°,,,,Rotation = 60°,,,,Rotation = 120°
19 | ,,,,,,,,
20 | ,,,,,,,,
21 | ,,,,,,,,
22 | ,,,,,,,,
23 | Rotation = 180°,,,,Rotation = 240°,,,,Rotation = 300°
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_3dsettings_import_xml_12_Settings.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - 3D SETTINGS,,,,,,,,
2 | ,,,,,,,,
3 | 4 - OTHER 3D SETTINGS,,,,,,,,
4 | ,,,,,,,,
5 | 4.1 - RIGHT-ANGLED AXES,,,,,,,,
6 | RightAngledAxes = off,,,,RightAngledAxes = on,,,,
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | ,,,,,,,,
12 | 4.2 - BAR CHART SCALING (PERSPECTIVE),,,,,,,,
13 | "AutoScaling = on, RelDepth = 30%",,,,"AutoScaling = on, RelDepth = 100%",,,,"AutoScaling = on, RelDepth = 300%"
14 | ,,,,,,,,
15 | ,,,,,,,,
16 | ,,,,,,,,
17 | ,,,,,,,,
18 | "AutoScaling = off, RelHeight = 30%",,,,"AutoScaling = off, RelHeight = 100%",,,,"AutoScaling = off, RelHeight = 300%"
19 | ,,,,,,,,
20 | ,,,,,,,,
21 | ,,,,,,,,
22 | ,,,,,,,,
23 | ,,,,,,,,
24 | 4.3 - BAR CHART SCALING (RIGHT-ANGLED),,,,,,,,
25 | "AutoScaling = on, RelDepth = 30%",,,,"AutoScaling = on, RelDepth = 100%",,,,"AutoScaling = on, RelDepth = 300%"
26 | ,,,,,,,,
27 | ,,,,,,,,
28 | ,,,,,,,,
29 | ,,,,,,,,
30 | "AutoScaling = off, RelHeight = 30%",,,,"AutoScaling = off, RelHeight = 100%",,,,"AutoScaling = off, RelHeight = 300%"
31 | ,,,,,,,,
32 | ,,,,,,,,
33 | ,,,,,,,,
34 | ,,,,,,,,
35 | ,,,,,,,,
36 | 4.4 - PIE CHART SCALING,,,,,,,,
37 | "AutoScaling = off, RelHeight = 30%",,,,"AutoScaling = off, RelHeight = 100%",,,,"AutoScaling = off, RelHeight = 300%"
38 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_3dsettings_import_xml_12_SourceData.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - 3D SETTINGS,,,
2 | ,,,
3 | 5 - SOURCE DATA,,,
4 | ,Series 1,Series 2,Series 3
5 | Point 1,2,4,2
6 | Point 2,4,3,1
7 | Point 3,6,2,-1
8 | Point 4,8,1,-2
9 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_axis_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/chart_axis_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_axis_import_xml_12_Axis Labels.csv:
--------------------------------------------------------------------------------
1 | CHART AXES TEST DOCUMENT,,,,,,,,,,,,
2 | ,,,,,,,,,,,,
3 | 2 - AXIS LABELS,,,,,,,,,,,,
4 | ,,,,,,,,,,,,
5 | 2.1 - 2-DIMENSIONAL LINE CHARTS,,,,,,,,,,,,
6 | Invisible,,,,Next to axis,,,,High,,,,Low
7 | ,,,,,,,,,,,,
8 | ,,,,,,,,,,,,
9 | ,,,,,,,,,,,,
10 | ,,,,,,,,,,,,
11 | ,,,,,,,,,,,,
12 | 2.2 - 2-DIMENSIONAL RADAR CHARTS,,,,,,,,,,,,
13 | Invisible,,,,Next to axis,,,,,,,,
14 | ,,,,,,,,,,,,
15 | ,,,,,,,,,,,,
16 | ,,,,,,,,,,,,
17 | ,,,,,,,,,,,,
18 | ,,,,,,,,,,,,
19 | 2.3 - 2-DIMENSIONAL SCATTER CHARTS,,,,,,,,,,,,
20 | Invisible,,,,Next to axis,,,,High,,,,Low
21 | ,,,,,,,,,,,,
22 | ,,,,,,,,,,,,
23 | ,,,,,,,,,,,,
24 | ,,,,,,,,,,,,
25 | ,,,,,,,,,,,,
26 | 2.4 - 3-DIMENSIONAL DEEP BAR CHARTS,,,,,,,,,,,,
27 | Invisible,,,,Next to axis,,,,High,,,,Low
28 | ,,,,,,,,,,,,
29 | ,,,,,,,,,,,,
30 | ,,,,,,,,,,,,
31 | ,,,,,,,,,,,,
32 | ,,,,,,,,,,,,
33 | 2.5 - LABEL TEXT FORMATTING,,,,,,,,,,,,
34 | "Font size X = 8, Y = 12",,,,"Weight X = bold, posture Y = italic, color X = red, Y = blue",,,,"Underline X = single, strikeout Y = single",,,,"Rotation X = 20° cw, Y =20° ccw"
35 | ,,,,,,,,,,,,
36 | ,,,,,,,,,,,,
37 | ,,,,,,,,,,,,
38 | ,,,,,,,,,,,,
39 | ,,,,,,,,,,,,
40 | 2.6 - LABEL NUMBER FORMAT,,,,,,,,,,,,
41 | Linked to source,,,,0.000,,,,0.000;[Red]-0.000,,,,0%
42 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_axis_import_xml_12_Axis Line.csv:
--------------------------------------------------------------------------------
1 | CHART AXES TEST DOCUMENT,,,,,,,,
2 | ,,,,,,,,
3 | 1 - AXIS LINE FORMATTING,,,,,,,,
4 | ,,,,,,,,
5 | 1.1 - AXIS VISIBILITY,,,,,,,,
6 | Invisible line,,,,Default line,,,,Deleted axes
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | ,,,,,,,,
12 | 1.2 - AXIS LINE FORMATTING,,,,,,,,
13 | "X = solid thin red, Y = solid thin blue",,,,"X = dashed thick red 50% transp., Y = dotted thick blue",,,,"X = black-red gradient arrow, Y = transp. gradient arrow"
14 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_axis_import_xml_12_Gridlines.csv:
--------------------------------------------------------------------------------
1 | CHART GRIDLINES TEST DOCUMENT,,,,,,,,
2 | ,,,,,,,,
3 | 4 - GRIDLINES,,,,,,,,
4 | ,,,,,,,,
5 | 4.1 - MAJOR GRIDLINES IN 2-DIMENSIONAL LINE CHARTS,,,,,,,,
6 | Invisible,,,,Automatic,,,,Manual formatting
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | ,,,,,,,,
12 | 4.2 - MAJOR GRIDLINES IN 2-DIMENSIONAL RADAR CHARTS,,,,,,,,
13 | Invisible,,,,Automatic,,,,Manual formatting
14 | ,,,,,,,,
15 | ,,,,,,,,
16 | ,,,,,,,,
17 | ,,,,,,,,
18 | ,,,,,,,,
19 | 4.3 - MAJOR GRIDLINES IN 2-DIMENSIONAL SCATTER CHARTS,,,,,,,,
20 | Invisible,,,,Automatic,,,,Manual formatting
21 | ,,,,,,,,
22 | ,,,,,,,,
23 | ,,,,,,,,
24 | ,,,,,,,,
25 | ,,,,,,,,
26 | 4.4 - MAJOR GRIDLINES IN 3-DIMENSIONAL DEEP BAR CHARTS,,,,,,,,
27 | Invisible,,,,Automatic,,,,Manual formatting
28 | ,,,,,,,,
29 | ,,,,,,,,
30 | ,,,,,,,,
31 | ,,,,,,,,
32 | Vertical only,,,,Horizontal only,,,,Deep only
33 | ,,,,,,,,
34 | ,,,,,,,,
35 | ,,,,,,,,
36 | ,,,,,,,,
37 | ,,,,,,,,
38 | 4.5 - MAJOR AND MINOR GRIDLINES,,,,,,,,
39 | Invisible,,,,Automatic,,,,Manual formatting
40 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_axis_import_xml_12_SourceData.csv:
--------------------------------------------------------------------------------
1 | CHART AXES TEST DOCUMENT,,,,
2 | ,,,,
3 | 5 - SOURCE DATA,,,,
4 | ,Series 1,Series 2,Series 3,X values
5 | Point 1,2,4,1,-2
6 | Point 2,6,2,2,-1
7 | Point 3,4,3,-1,1
8 | Point 4,8,1,-2,2
9 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_axis_import_xml_12_Tick Marks.csv:
--------------------------------------------------------------------------------
1 | CHART AXES TEST DOCUMENT,,,,,,,,,,,,
2 | ,,,,,,,,,,,,
3 | 3 - AXIS TICK MARKS,,,,,,,,,,,,
4 | ,,,,,,,,,,,,
5 | 3.1 - MAJOR TICKMARKS,,,,,,,,,,,,
6 | None,,,,Inside,,,,Outside,,,,Crossing
7 | ,,,,,,,,,,,,
8 | ,,,,,,,,,,,,
9 | ,,,,,,,,,,,,
10 | ,,,,,,,,,,,,
11 | ,,,,,,,,,,,,
12 | 3.2 - MINOR TICKMARKS,,,,,,,,,,,,
13 | None,,,,Inside,,,,Outside,,,,Crossing
14 | ,,,,,,,,,,,,
15 | ,,,,,,,,,,,,
16 | ,,,,,,,,,,,,
17 | ,,,,,,,,,,,,
18 | ,,,,,,,,,,,,
19 | 3.3 - MAJOR AND MINOR TICKMARKS,,,,,,,,,,,,
20 | None,,,,Inside,,,,Outside,,,,Crossing
21 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_bitmaps_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/chart_bitmaps_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_bitmaps_import_xml_12_General.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - BITMAP FILLS,,,,,,,,,,,
2 | ,,,,,,,,,,,
3 | 1 - GENERAL CHART ELEMENTS,,,,,,,,,,,Image source: http://en.wikipedia.org/wiki/File:Smiley.svg
4 | ,,,,,,,,,,,
5 | 1.1 - CHART BACKGROUND,,,,,,,,,,,
6 | Stretched,,,,"Stretched, left = 30%, top = 10%, right = bottom = -10%",,,,"Stretched, transparency = 50%",,,
7 | ,,,,,,,,,,,
8 | ,,,,,,,,,,,
9 | ,,,,,,,,,,,
10 | ,,,,,,,,,,,
11 | Tiled,,,,"Tiled, scale = 50%, align = bottom-right, mirror = vertical",,,,"Tiled, offsetX = offsetY = 20pt, transparency = 50%",,,
12 | ,,,,,,,,,,,
13 | ,,,,,,,,,,,
14 | ,,,,,,,,,,,
15 | ,,,,,,,,,,,
16 | ,,,,,,,,,,,
17 | "1.2 - PLOT AREA, LEGEND, CHART TITLE, AXIS TITLES",,,,,,,,,,,
18 | Stretched,,,,Tiled (legend and title scale = 50%),,,,,,,
19 | ,,,,,,,,,,,
20 | ,,,,,,,,,,,
21 | ,,,,,,,,,,,
22 | ,,,,,,,,,,,
23 | ,,,,,,,,,,,
24 | "1.3 - FLOOR, BACK WALL, SIDE WALL",,,,,,,,,,,
25 | Stretched,,,,Stacked,,,,,,,
26 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_bitmaps_import_xml_12_Series 2D.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - BITMAP FILLS,,,,,,,,
2 | ,,,,,,,,
3 | 2 - 2-DIMENSIONAL DATA SERIES,,,,,,,,
4 | ,,,,,,,,
5 | "2.1 - BAR CHARTS (Series1, Series2 Point2)",,,,,,,,
6 | Stretched,,,,Stacked,,,,"Stacked with unit, Series1 unit = 10, Series2 unit = 5"
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | ,,,,,,,,
12 | 2.2 - OTHER CHART TYPES,,,,,,,,
13 | "Area, Series1 = stretched, Series2 = tiled",,,,"Pie, Point1 = stretched, Point2 = tiled",,,,"Filled radar, Series1 = stretched, Series2 = tiled"
14 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_bitmaps_import_xml_12_Series 3D.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - BITMAP FILLS,,,,,,,,
2 | ,,,,,,,,
3 | 3 - 3-DIMENSIONAL DATA SERIES,,,,,,,,
4 | ,,,,,,,,
5 | "2.2 - BAR CHARTS (Series1, Series2 Point2)",,,,,,,,
6 | "Series1, Series2/Point2 = stretched",,,,"Series1, Series2/Point2 = stacked",,,,"Stacked with unit, Series1 unit = 10, Series2 unit = 5"
7 | ,,,,,,,,
8 | ,,,,,,,,
9 | ,,,,,,,,
10 | ,,,,,,,,
11 | "Series1 = front only, Series2 = side+top",,,,"Series1 = side only, Series2 = front+top",,,,"Series1 = top only, Series2 = front+side"
12 | ,,,,,,,,
13 | ,,,,,,,,
14 | ,,,,,,,,
15 | ,,,,,,,,
16 | ,,,,,,,,
17 | 3.2 - OTHER CHART TYPES,,,,,,,,
18 | "Area, Series1 = stretched, Series2 = tiled",,,,"Pie, Point1 = stretched, Point2 = tiled",,,,"Line, Series1 = stretched, Series2 = tiled"
19 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/chart_bitmaps_import_xml_12_SourceData.csv:
--------------------------------------------------------------------------------
1 | CHART TEST DOCUMENT - BITMAP FILLS,,
2 | ,,
3 | 4 - SOURCE DATA,,
4 | ,Series 1,Series 2
5 | Point 1,50.0,30.0
6 | Point 2,40.0,25.0
7 | Point 3,30.0,20.0
8 | Point 4,30.0,20.0
9 | Point 5,40.0,25.0
10 | Point 6,50.0,30.0
11 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/convert.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import csv
3 | import os
4 | import subprocess
5 | import sys
6 |
7 | import xlrd
8 |
9 |
10 | files = sys.argv[1:]
11 | for file in files:
12 | name, _ = file.rsplit(".", maxsplit=1)
13 | workbook = xlrd.open_workbook(file)
14 | for sheet in workbook.sheets():
15 | with open("%s_%s.csv" % (name, sheet.name), "w") as f:
16 | writer = csv.writer(f)
17 | for row in sheet.get_rows():
18 | values = [cell.value for cell in row]
19 | writer.writerow(values)
20 | print("; `Skip, \"%s\", [ %s ]" % (name, "; ".join(["\"%s\"" % sheet.name for sheet in workbook.sheets()])))
21 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/formats.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/formats.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/hyperlink_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/hyperlink_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/hyperlink_import_xml_12_Sheet'!.csv:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/hyperlink_import_xml_12_Sheet1.csv:
--------------------------------------------------------------------------------
1 | CELL FORMAT TEST DOCUMENT,,,
2 | ,,,
3 | 1 - INTERNAL LINKS,,,
4 | ,,,
5 | 1.1 - INTERNAL LINKS TO OWN SHEET (cell A1),,,
6 | URL,Hyperlink cell,,
7 | #A1,#A1,,
8 | #Sheet1!A1,#Sheet1!A1,,
9 | #name1,#name1,,
10 | #Sheet1!name1,#Sheet1!name1,,
11 | #name2,#name2,,
12 | #name3,#name3,,
13 | ,,,
14 | 1.2 - INTERNAL LINKS TO OTHER SHEETS (cell A2),,,
15 | URL,Hyperlink cell,,
16 | #Sheet2!A1,#Sheet2!A1,,
17 | #Sheet2!localname,#Sheet2!localname,,
18 | #'Sheet''!'!A1,#'Sheet''!'!A1,,
19 | #'Sheet''!'!localname,#'Sheet''!'!localname,,
20 | ,,,
21 | 2 - FILE LINKS,,,
22 | ,,,
23 | 2.1 - LOCAL FILE LINKS,,,
24 | URL,Hyperlink cell,,
25 | EXTDATA12.XLSX,EXTDATA12.XLSX,,
26 | EXTDATA12.XLSX#A1,EXTDATA12.XLSX#A1,,
27 | long_unicode_filename_€.xls,long_unicode_filename_€.xls,,
28 | long_unicode_filename_€.xls#A1,long_unicode_filename_€.xls#A1,,
29 | PATH\EXTP12_1.XLSX,PATH\EXTP12_1.XLSX,,
30 | PATH\EXTP12_1.XLSX#A1,PATH\EXTP12_1.XLSX#A1,,
31 | \PATH\EXTP12_2.XLSX,\PATH\EXTP12_2.XLSX,,
32 | \PATH\EXTP12_2.XLSX#A1,\PATH\EXTP12_2.XLSX#A1,,
33 | C:\PATH\EXTP12_3.XLSX,C:\PATH\EXTP12_3.XLSX,,
34 | C:\PATH\EXTP12_3.XLSX#A1,C:\PATH\EXTP12_3.XLSX#A1,,
35 | ,,,
36 | 2.2 - UNC PATHS,,,
37 | URL,Hyperlink cell,,
38 | \\127.0.0.1\PATH\EXTP12_4.XLSX,\\127.0.0.1\PATH\EXTP12_4.XLSX,,
39 | \\127.0.0.1\PATH\EXTP12_4.XLSX#A1,\\127.0.0.1\PATH\EXTP12_4.XLSX#A1,,
40 | ,,,
41 | 3 - URLS,,,
42 | ,,,
43 | 3.1 - HTTP LINKS,,,
44 | URL,Hyperlink cell,,
45 | http://www.example.org/,http://www.example.org/,,
46 | http://127.0.0.1/PATH/EXTP12_5.XLSX,http://127.0.0.1/PATH/EXTP12_5.XLSX,,
47 | http://127.0.0.1/PATH/EXTP12_5.XLSX#A1,http://127.0.0.1/PATH/EXTP12_5.XLSX#A1,,
48 | ,,,
49 | 3.2 - MAIL LINKS,,,
50 | URL,Hyperlink cell,,
51 | mailto:test@example.org,mailto:test@example.org,,
52 | mailto:test@example.org?subject=subject,mailto:test@example.org?subject=subject,,
53 | ,,,
54 | 4 - OTHER FEATURES,,,
55 | ,,,
56 | 4.1 - TOOLTIPS,,,
57 | Tooltip,Hyperlink cell,,
58 | This is a tooltip.,http://www.example.org/,,
59 | This is another tooltip.,http://www.example.org/,,
60 | ,,,
61 | "4.2 - CELL TYPES, TEXT FORMATTING",,,
62 | Cell type,Cell without hyperlink,Cell with hyperlink,With text formatting
63 | String,STRING,STRING,STRING
64 | Rich string,RICH STRING,RICH STRING,RICH STRING
65 | Number,12345,12345,12345
66 | Boolean,1,1,1
67 | Error,#DIV/0!,#DIV/0!,#DIV/0!
68 | String formula,STRING,STRING,STRING
69 | Number formula,12345,12345,12345
70 | Boolean formula,1,1,1
71 | Error formula,#DIV/0!,#DIV/0!,#DIV/0!
72 | String formula with HYPERLINK function,STRING,STRING,STRING
73 | Number formula with HYPERLINK function,12345,12345,12345
74 | Boolean formula with HYPERLINK function,1,1,1
75 | Error formula with HYPERLINK function,#DIV/0!,#DIV/0!,#DIV/0!
76 | Single-cell array formulas,STRING,STRING,STRING
77 | Array formula,STRING,STRING,STRING
78 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/hyperlink_import_xml_12_Sheet2.csv:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/oleobject_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/oleobject_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/oleobject_import_xml_12_OLE.csv:
--------------------------------------------------------------------------------
1 | OLE OBJECT TEST DOCUMENT,,,,,,
2 | ,,,,,,
3 | 1 - LINKED AND EMBEDDED OBJECTS,,,,,,
4 | ,,,,,,
5 | 1.1 - LINKED PICTURES,,,,,,
6 | Internal cell,Internal name,Sheet cell,Sheet name,External cell,External name,Linked cell
7 | ,,,,,,
8 | External DDE,External OLE,,,,,
9 | ,,,,,,
10 | ,,,,,,
11 | 1.2 - EMBEDDED OBJECTS,,,,,,
12 | Picture,... as symbol,... own symbol,Text,Spreadsheet,,
13 | ,,,,,,
14 | ,,,,,,
15 | 1.3 - LINKED OBJECTS,,,,,,
16 | Picture,Text (manual update),,,Spreadsheet,,
17 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/oleobject_import_xml_12_SourceData.csv:
--------------------------------------------------------------------------------
1 | Linked sheet
2 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/scenarios_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/scenarios_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/scenarios_import_xml_12_Name.csv:
--------------------------------------------------------------------------------
1 | SCENARIOS TEST DOCUMENT,
2 | ,
3 | 3 - SCENARIO NAMES,
4 | ,
5 | "3.1 - NAMES ALREADY USED IN SHEET ""Ranges""",
6 | "cells = A8:B9, shown = Scenario3, selected = Scenario2",
7 | ,
8 | Item 3.1,Item 3.2
9 | Item 3.3,Item 3.4
10 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/scenarios_import_xml_12_Ranges.csv:
--------------------------------------------------------------------------------
1 | SCENARIOS TEST DOCUMENT,
2 | ,
3 | 1 - SCENARIO RANGES,
4 | ,
5 | 1.1 - 3 SCENARIOS IN SINGLE RANGE,
6 | "cells = A8:B9, shown = Scenario2, selected = Scenario3",
7 | ,
8 | Item 2.1,Item 2.2
9 | Item 2.3,Item 2.4
10 | ,
11 | 1.2 - 2 SCENARIOS IN SINGLE CELLS,
12 | cells = A14;B15,
13 | ,
14 | Item 5.1,
15 | ,Item 5.2
16 | ,
17 | 1.3 - REFERENCE CHECK,
18 | "formula = ""=RefCheck!$A$7"", value = ""linked cell""",
19 | linked cell,
20 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/scenarios_import_xml_12_RefCheck.csv:
--------------------------------------------------------------------------------
1 | SCENARIOS TEST DOCUMENT
2 |
3 | 4 - LINKED DATA
4 |
5 | 4.1 - REFERENCE TARGET
6 |
7 | linked cell
8 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/scenarios_import_xml_12_Settings.csv:
--------------------------------------------------------------------------------
1 | SCENARIOS TEST DOCUMENT,
2 | ,
3 | 2 - SCENARIO SETTINGS,
4 | ,
5 | "2.1 - PROTECTION, VISIBILITY",
6 | "Scenario10 = not protected/visible, Scenario11 = protected/hidden",
7 | ,
8 | 1,1
9 | ,
10 | 2.2 - COMMENTS,
11 | "Scenario12 = ""Comment for Scenario12""",
12 | ,
13 | 1,1
14 | ,
15 | 2.3 - REFERENCE CHECK,
16 | "formula = ""=RefCheck!$A$7"", value = ""linked cell""",
17 | linked cell,
18 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/sheetprotection_import_xml_12.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendanlong/ocaml-ooxml/dd6b8496d22670cc0fa0b50990414f955267ded9/easy_xlsx/test/files/sheetprotection_import_xml_12.xlsx
--------------------------------------------------------------------------------
/easy_xlsx/test/files/sheetprotection_import_xml_12_Sheet1.csv:
--------------------------------------------------------------------------------
1 | PROTECTION TEST DOCUMENT,
2 | ,
3 | 1 - WORKSHEET PROTECTION,
4 | ,
5 | 1.1 - NO WORKSHEET PROTECTION,
6 | Setting,Value
7 | Password,none
8 | Protect locked cells,off
9 | Edit objects,on
10 | Edit scenarios,off
11 | Format cells,off
12 | Format columns,off
13 | Format rows,off
14 | Insert columns,on
15 | Insert rows,on
16 | Insert hyperlinks,on
17 | Delete columns,on
18 | Delete rows,on
19 | Select locked cells,on
20 | Use sort,on
21 | Use autofilter,on
22 | Pivot tables reports,on
23 | Select unlocked cells,on
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/sheetprotection_import_xml_12_Sheet2.csv:
--------------------------------------------------------------------------------
1 | PROTECTION TEST DOCUMENT,
2 | ,
3 | 1 - WORKSHEET PROTECTION,
4 | ,
5 | 1.2 - WORKSHEET PROTECTION WITHOUT PASSWORD,
6 | Setting,Value
7 | Password,none
8 | Protect locked cells,on
9 | Edit objects,off
10 | Edit scenarios,off
11 | Format cells,off
12 | Format columns,off
13 | Format rows,off
14 | Insert columns,off
15 | Insert rows,off
16 | Insert hyperlinks,off
17 | Delete columns,off
18 | Delete rows,off
19 | Select locked cells,on
20 | Use sort,off
21 | Use autofilter,off
22 | Pivot tables reports,off
23 | Select unlocked cells,on
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/sheetprotection_import_xml_12_Sheet3.csv:
--------------------------------------------------------------------------------
1 | PROTECTION TEST DOCUMENT,
2 | ,
3 | 1 - WORKSHEET PROTECTION,
4 | ,
5 | 1.3 - WORKSHEET PROTECTION WITH PASSWORD,
6 | Setting,Value
7 | Password,"""password"""
8 | Protect locked cells,on
9 | Edit objects,off
10 | Edit scenarios,off
11 | Format cells,off
12 | Format columns,off
13 | Format rows,off
14 | Insert columns,off
15 | Insert rows,off
16 | Insert hyperlinks,off
17 | Delete columns,off
18 | Delete rows,off
19 | Select locked cells,on
20 | Use sort,off
21 | Use autofilter,off
22 | Pivot tables reports,off
23 | Select unlocked cells,on
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/files/sheetprotection_import_xml_12_Sheet4.csv:
--------------------------------------------------------------------------------
1 | PROTECTION TEST DOCUMENT,
2 | ,
3 | 1 - WORKSHEET PROTECTION,
4 | ,
5 | 1.4 - WORKSHEET PROTECTION WITH PASSWORD,
6 | Setting,Value
7 | Password,"""password"""
8 | Protect locked cells,on
9 | Edit objects,on
10 | Edit scenarios,on
11 | Format cells,on
12 | Format columns,on
13 | Format rows,on
14 | Insert columns,on
15 | Insert rows,on
16 | Insert hyperlinks,on
17 | Delete columns,on
18 | Delete rows,on
19 | Select locked cells,off
20 | Use sort,on
21 | Use autofilter,on
22 | Pivot tables reports,on
23 | Select unlocked cells,off
24 |
--------------------------------------------------------------------------------
/easy_xlsx/test/test_easy_xlsx_docs.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open OUnit2
3 | open Printf
4 |
5 | (* Test documents come from OpenOffice's test suite:
6 | https://www.openoffice.org/sc/testdocs/
7 |
8 | I downloaded the "XML" versions of each document, then converted them to
9 | CSV using OpenOffice. The tests then compare our input of the XLSX
10 | document to the CSV. *)
11 | type t =
12 | { name : string
13 | ; rows : string list list }
14 |
15 | let pp_diff fmt (exp, real) =
16 | if String.(exp.name <> real.name) then
17 | Caml.Format.fprintf fmt "Expected sheet %s but got sheet %s" exp.name real.name
18 | else
19 | Caml.Format.fprintf fmt "Sheet: %s\n" exp.name;
20 | let print_opt_row diff_mark row =
21 | Option.iter row ~f:(fun row ->
22 | String.concat ~sep:"," row
23 | |> Caml.Format.fprintf fmt "%c %s\n" diff_mark)
24 | in
25 | let rec diff_lists = function
26 | | [], [] -> ()
27 | | exp, real ->
28 | let exp_row = List.hd exp in
29 | let real_row = List.hd real in
30 | if Option.equal (List.equal ~equal:String.equal) exp_row real_row then
31 | print_opt_row ' ' exp_row
32 | else begin
33 | print_opt_row '-' exp_row;
34 | print_opt_row '+' real_row
35 | end;
36 | diff_lists
37 | (List.tl exp |> Option.value ~default:[],
38 | List.tl real |> Option.value ~default:[])
39 | in
40 | diff_lists (exp.rows, real.rows)
41 |
42 | let sort_sheets =
43 | List.sort ~compare:(fun a b ->
44 | String.compare a.name b.name)
45 |
46 | let make_test (file_name, sheet_names) =
47 | let normalize_sheets sheets =
48 | (* Fix sheet order and remove whitespace at the end of lines or sheets,
49 | since we don't care about either of these things *)
50 | sort_sheets sheets
51 | |> List.map ~f:(fun { name ; rows } ->
52 | let rows =
53 | List.rev_map rows ~f:(fun row ->
54 | List.rev row
55 | |> List.drop_while ~f:String.is_empty
56 | |> List.rev)
57 | |> List.drop_while ~f:List.is_empty
58 | |> List.rev
59 | in
60 | { name ; rows })
61 | in
62 | let sheet_names = List.sort ~compare:String.compare sheet_names in
63 | file_name >::
64 | fun _ ->
65 | let expect =
66 | List.map sheet_names ~f:(fun sheet_name ->
67 | let rows =
68 | sprintf "files/%s_%s.csv" file_name sheet_name
69 | |> Csv.load ~strip:false
70 | |> Csv.to_array
71 | |> Array.map ~f:Array.to_list
72 | |> Array.to_list
73 | in
74 | { name = sheet_name ; rows })
75 | |> normalize_sheets
76 | in
77 | Easy_xlsx.read_file (sprintf "files/%s.xlsx" file_name)
78 | |> List.map ~f:(fun { Easy_xlsx.name ; rows } ->
79 | let rows =
80 | List.map rows ~f:(List.map ~f:Easy_xlsx.Value.to_string) in
81 | { name ; rows })
82 | |> normalize_sheets
83 | |> List.iter2_exn expect ~f:(fun expect actual ->
84 | assert_equal ~pp_diff expect actual)
85 |
86 | let () =
87 | [ "autofilter_import_xml_12", [ "Discrete"; "Top10"; "Custom"; "Advanced1"; "Advanced2" ]
88 | ; "chart_3dsettings_import_xml_12", [ "Rotation"; "Elevation"; "Perspective"; "Settings"; "SourceData" ]
89 | ; "chart_axis_import_xml_12", [ "Axis Line"; "Axis Labels"; "Tick Marks"; "Gridlines"; "SourceData" ]
90 | ; "hyperlink_import_xml_12", [ "Sheet1"; "Sheet2"; "Sheet'!" ]
91 | ; "oleobject_import_xml_12", [ "OLE"; "SourceData" ]
92 | ; "scenarios_import_xml_12", [ "Ranges"; "Settings"; "Name"; "RefCheck" ]
93 | ; "sheetprotection_import_xml_12", [ "Sheet1"; "Sheet2"; "Sheet3"; "Sheet4" ] ]
94 | |> List.map ~f:make_test
95 | |> test_list
96 | |> run_test_tt_main
97 |
--------------------------------------------------------------------------------
/easy_xlsx/test/test_easy_xlsx_formats.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Base.Printf
3 | open OUnit2
4 |
5 | (* Test that we properly classify formats from OpenOffice *)
6 |
7 | let test_easy_xlsx_formats =
8 | let open Easy_xlsx.Value in
9 | let expect_number = Number (-12345.6789) in
10 | let expect_date = Date (1866, 03, 12) in
11 | let expect_datetime =
12 | let date = 1866, 03, 12 in
13 | let time = (7, 42, 23), 0 in
14 | Datetime (Ptime.of_date_time (date, time) |> Option.value_exn) in
15 | let expect_time = Time ((16, 17, 37), 0) in
16 | let expect_string = String "-12345.6789" in
17 | Easy_xlsx.read_file "files/formats.xlsx"
18 | |> List.hd_exn
19 | |> Easy_xlsx.rows
20 | |> List.tl_exn
21 | |> List.concat_mapi ~f:(
22 | fun row ->
23 | let row = row + 2 in
24 | let open Easy_xlsx.Value in
25 | function
26 | | _boolean :: number :: date :: datetime :: string :: time :: _ ->
27 | let test type_ value expected =
28 | if is_empty value then
29 | None
30 | else
31 | (sprintf "%s on row %d" type_ row
32 | >:: fun _ ->
33 | assert_equal ~printer:Easy_xlsx.Value.to_string expected value)
34 | |> Option.some
35 | in
36 | [ test "Number" number expect_number
37 | ; test "Date" date expect_date
38 | ; test "Datetime" datetime expect_datetime
39 | ; test "String" string expect_string
40 | ; test "Time" time expect_time ]
41 | | _ -> assert false)
42 | |> List.filter_opt
43 | |> List.rev
44 |
45 | let () =
46 | test_easy_xlsx_formats
47 | |> test_list
48 | |> run_test_tt_main
49 |
--------------------------------------------------------------------------------
/open_packaging.descr:
--------------------------------------------------------------------------------
1 | A library for parsing Microsoft's Open Packaging Specification (most commonly used for Microsoft
2 | Office's "Office Open XML")
3 |
--------------------------------------------------------------------------------
/open_packaging.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | authors: ["Brendan Long "]
3 | maintainer: "self@brendanlong.com"
4 | homepage: "https://github.com/brendanlong/ocaml-ooxml"
5 | dev-repo: "git+https://github.com/brendanlong/ocaml-ooxml.git"
6 | bug-reports: "https://github.com/brendanlong/ocaml-ooxml/issues"
7 | doc: "https://brendanlong.github.io/ocaml-ooxml/doc"
8 |
9 | build: [
10 | ["dune" "build" "-p" name "-j" jobs]
11 | ["dune" "runtest" "-p" name "-j" jobs] {with-test}
12 | ]
13 |
14 | depends: [
15 | "base" {= "v0.11.1"}
16 | "ocaml" {>= "4.04.2"}
17 | "ppx_jane"
18 | "sexplib"
19 | "stdint"
20 | "xml-light"
21 |
22 | "bisect_ppx" {build & >= "1.3.0"}
23 | "dune" {build}
24 |
25 | "ounit" {test}
26 | ]
27 |
--------------------------------------------------------------------------------
/open_packaging/src/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name open_packaging)
3 | (public_name open_packaging)
4 | (libraries base sexplib stdint xml-light)
5 | (flags (:standard -w -47))
6 | (preprocess (pps ppx_jane bisect_ppx -conditional)))
7 |
--------------------------------------------------------------------------------
/open_packaging/src/open_packaging.ml:
--------------------------------------------------------------------------------
1 | (* See ECMA 376 Part 2 - "Open Packaging Conventions" *)
2 |
3 | module Relationship = Relationship
4 | module Relationships = Relationships
5 | module Utils = Utils
6 |
--------------------------------------------------------------------------------
/open_packaging/src/relationship.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Base.Printf
3 | open Utils
4 |
5 | module Target_mode = struct
6 | type t =
7 | | Internal
8 | | External
9 | [@@deriving sexp_of]
10 |
11 | let of_string = function
12 | | "Internal" -> Internal
13 | | "External" -> External
14 | | str -> failwithf "Expected ST_TargetMode but got '%s'" str ()
15 | end
16 |
17 | (* 8.3.3.2 Relationship Element *)
18 | type t =
19 | { target_mode : Target_mode.t
20 | ; target : string
21 | ; type_ : string
22 | ; id : string }
23 | [@@deriving fields, sexp_of]
24 |
25 | let of_xml = function
26 | | Xml.Element ("Relationship", attrs, _) ->
27 | let target_mode = ref Target_mode.Internal in
28 | let target = ref None in
29 | let type_ = ref None in
30 | let id = ref None in
31 | List.iter attrs ~f:(function
32 | | "TargetMode", v -> target_mode := Target_mode.of_string v
33 | | "Target", v -> target := Some v
34 | | "Type", v -> type_ := Some v
35 | | "Id", v -> id := Some v
36 | | _ -> ());
37 | let require_attribute = require_attribute "Relationship" in
38 | let target = require_attribute "Target" !target in
39 | let type_ = require_attribute "Type" !type_ in
40 | let id = require_attribute "Id" !id in
41 | Some { target_mode = !target_mode ; target ; type_ ; id }
42 | | _ -> None
43 |
--------------------------------------------------------------------------------
/open_packaging/src/relationship.mli:
--------------------------------------------------------------------------------
1 | module Target_mode : sig
2 | type t =
3 | | Internal
4 | | External
5 | [@@deriving sexp_of]
6 |
7 | val of_string : string -> t
8 | end
9 |
10 | type t =
11 | { target_mode : Target_mode.t
12 | ; target : string (** xsd:anyURI *)
13 | ; type_ : string (** xsd:anyURI *)
14 | ; id : string (** xsd:ID *) }
15 | [@@deriving fields, sexp_of]
16 |
17 | val of_xml : Xml.xml -> t option
18 |
--------------------------------------------------------------------------------
/open_packaging/src/relationships.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Utils
3 |
4 | (* 8.3.3.1 Relationships Element *)
5 | type t = Relationship.t list
6 | [@@deriving sexp_of]
7 |
8 | let of_xml =
9 | expect_element "Relationships" (fun _ ->
10 | List.filter_map ~f:Relationship.of_xml)
11 |
--------------------------------------------------------------------------------
/open_packaging/src/relationships.mli:
--------------------------------------------------------------------------------
1 | type t = Relationship.t list
2 | [@@deriving sexp_of]
3 |
4 | val of_xml : Xml.xml -> Relationship.t list
5 |
--------------------------------------------------------------------------------
/open_packaging/src/utils.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Base.Printf
3 | open Sexplib
4 | open Stdint
5 |
6 | let sexp_of_uint8 t =
7 | Uint8.to_string t
8 | |> Sexp.of_string
9 |
10 | let sexp_of_uint32 t =
11 | Uint32.to_string t
12 | |> Sexp.of_string
13 |
14 | let expect_element expect_name f =
15 | let open Xml in
16 | function
17 | | Element (name, attrs, children) when String.(name = expect_name) ->
18 | f attrs children
19 | | Element (name, _, _) ->
20 | failwithf "Expected <%s> element but saw <%s>" expect_name name ()
21 | | PCData str ->
22 | failwithf "Expected <%s> element but saw '%s'" expect_name str ()
23 |
24 | let expect_pcdata children =
25 | List.find_map children ~f:(
26 | let open Xml in
27 | function
28 | | PCData str -> Some str
29 | | Element (name, _, _) -> failwithf "Expected PCData but got <%s>" name ())
30 | |> Option.value ~default:""
31 |
32 | let require_attribute element name =
33 | function
34 | | Some v -> v
35 | | None -> failwithf "<%s> missing required attribute '%s'" element name ()
36 |
37 | let bool_of_xsd_boolean =
38 | (* See http://books.xmlschemata.org/relaxng/ch19-77025.html *)
39 | function
40 | | "true" | "1" -> true
41 | | "false" | "0" -> false
42 | | str -> failwithf "Expected xsd:boolean but got '%s'" str ()
43 |
--------------------------------------------------------------------------------
/pkg/pkg.ml:
--------------------------------------------------------------------------------
1 | #use "topfind"
2 | #require "topkg-jbuilder"
3 |
4 | open Topkg
5 |
6 | let () =
7 | Topkg_jbuilder.describe ~name:"easy_xlsx" ();
8 | Topkg_jbuilder.describe ~name:"open_packaging" ();
9 | Topkg_jbuilder.describe ~name:"spreadsheetml" ();
10 |
--------------------------------------------------------------------------------
/spreadsheetml.descr:
--------------------------------------------------------------------------------
1 | A library to parsing SpreadsheetML (used in Microsoft Excel files)
2 |
--------------------------------------------------------------------------------
/spreadsheetml.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | authors: ["Brendan Long "]
3 | maintainer: "self@brendanlong.com"
4 | homepage: "https://github.com/brendanlong/ocaml-ooxml"
5 | dev-repo: "git+https://github.com/brendanlong/ocaml-ooxml.git"
6 | bug-reports: "https://github.com/brendanlong/ocaml-ooxml/issues"
7 | doc: "https://brendanlong.github.io/ocaml-ooxml/doc"
8 |
9 | build: [
10 | ["dune" "build" "-p" name "-j" jobs]
11 | ["dune" "runtest" "-p" name "-j" jobs] {with-test}
12 | ]
13 |
14 | depends: [
15 | "ocaml" {>= "4.04.2"}
16 | "open_packaging"
17 | "ppx_jane"
18 |
19 | "bisect_ppx" {build & >= "1.3.0"}
20 | "dune" {build}
21 |
22 | "ounit" {test}
23 | ]
24 |
--------------------------------------------------------------------------------
/spreadsheetml/src/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name spreadsheetml)
3 | (public_name spreadsheetml)
4 | (libraries open_packaging)
5 | (flags (:standard -w -47))
6 | (preprocess (pps ppx_jane bisect_ppx -conditional)))
7 |
--------------------------------------------------------------------------------
/spreadsheetml/src/shared_string_table.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Base.Printf
3 | open Stdint
4 | open Open_packaging.Utils
5 |
6 | module Rich_text = struct
7 | (* Note: We represent text and rich text the same internally. Unformatted
8 | text is just rich text with empty formatting options *)
9 |
10 | module Underline = struct
11 | (* http://www.datypic.com/sc/ooxml/t-ssml_ST_UnderlineValues.html *)
12 | type t =
13 | | Single
14 | | Double
15 | | Single_accounting
16 | | Double_accounting
17 | | None
18 | [@@deriving sexp_of]
19 |
20 | let of_string = function
21 | | "single" -> Single
22 | | "double" -> Double
23 | | "singleAccounting" -> Single_accounting
24 | | "doubleAccounting" -> Double_accounting
25 | | "none" -> None
26 | | str -> failwithf "Expected ST_Underline but got '%s'" str ()
27 | end
28 |
29 | module Vertical_align = struct
30 | (* http://www.datypic.com/sc/ooxml/t-ssml_ST_VerticalAlignRun.html *)
31 | type t =
32 | | Baseline
33 | | Superscript
34 | | Subscript
35 | [@@deriving sexp_of]
36 |
37 | let of_string = function
38 | | "baseline" -> Baseline
39 | | "superscript" -> Superscript
40 | | "subscript" -> Subscript
41 | | str -> failwithf "Expected ST_VerticalAlign but got '%s'" str ()
42 | end
43 |
44 | type t =
45 | { text : string
46 | ; bold : bool option
47 | ; charset : int32 option
48 | (* ; color : CT_Color *)
49 | ; condense : bool option
50 | ; extend : bool option
51 | ; font : string option
52 | ; font_family : int32 option
53 | ; font_size : float option (* CT_FontSize *)
54 | (* ; font_scheme : CT_FontScheme *)
55 | ; italic : bool option
56 | ; outline : bool option
57 | ; shadow : bool option
58 | ; strikethrough : bool option
59 | ; underline : Underline.t option
60 | ; vertical_align : Vertical_align.t option }
61 | [@@deriving fields, sexp_of]
62 |
63 | let empty =
64 | { text = ""
65 | ; bold = None
66 | ; charset = None
67 | ; condense = None
68 | ; extend = None
69 | ; font = None
70 | ; font_family = None
71 | ; font_size = None
72 | ; italic = None
73 | ; outline = None
74 | ; shadow = None
75 | ; strikethrough = None
76 | ; underline = None
77 | ; vertical_align = None }
78 |
79 | let of_xml =
80 | let open Xml in
81 | function
82 | | Element ("r", _, children) ->
83 | List.fold children ~init:empty ~f:(fun acc -> function
84 | | Element ("rPr", attrs, _) ->
85 | List.fold attrs ~init:acc ~f:(fun acc -> function
86 | | "b", v ->
87 | { acc with bold = Some (bool_of_xsd_boolean v) }
88 | | "charset", v ->
89 | { acc with charset = Some (Int32.of_string v) }
90 | | "condense", v ->
91 | { acc with condense = Some (bool_of_xsd_boolean v) }
92 | | "extend", v ->
93 | { acc with extend = Some (bool_of_xsd_boolean v) }
94 | | "family", v ->
95 | { acc with font_family = Some (Int32.of_string v) }
96 | | "i", v ->
97 | { acc with italic = Some (bool_of_xsd_boolean v) }
98 | | "outline", v ->
99 | { acc with outline = Some (bool_of_xsd_boolean v) }
100 | | "rFont", v ->
101 | { acc with font = Some v }
102 | | "shadow", v ->
103 | { acc with shadow = Some (bool_of_xsd_boolean v) }
104 | | "stike", v ->
105 | { acc with strikethrough = Some (bool_of_xsd_boolean v) }
106 | | "sz", v ->
107 | { acc with font_size = Some (Float.of_string v) }
108 | | "u", v ->
109 | { acc with underline = Some (Underline.of_string v) }
110 | | "vertAlign", v ->
111 | { acc with vertical_align = Some (Vertical_align.of_string v) }
112 | | _ -> acc)
113 | | Element ("t", _, children) ->
114 | { acc with text = expect_pcdata children }
115 | | _ -> acc)
116 | |> Option.some
117 | | Element ("t", _, children) ->
118 | Some { empty with text = expect_pcdata children }
119 | | _ -> None
120 | end
121 |
122 | module String_item = struct
123 | type t = Rich_text.t list
124 | [@@deriving sexp_of]
125 |
126 | let to_string t =
127 | List.map t ~f:Rich_text.text
128 | |> String.concat ~sep:""
129 |
130 | let of_xml = function
131 | | Xml.Element ("si", _, children) ->
132 | List.filter_map children ~f:Rich_text.of_xml
133 | |> Option.some
134 | | _ -> None
135 | end
136 |
137 | type t = String_item.t list
138 | [@@deriving sexp_of]
139 |
140 | let to_string_array t =
141 | List.map t ~f:String_item.to_string
142 | |> List.to_array
143 |
144 | let of_xml =
145 | expect_element "sst" (fun _ ->
146 | List.filter_map ~f:String_item.of_xml)
147 |
--------------------------------------------------------------------------------
/spreadsheetml/src/shared_string_table.mli:
--------------------------------------------------------------------------------
1 | module Rich_text : sig
2 | module Underline : sig
3 | type t =
4 | | Single
5 | | Double
6 | | Single_accounting
7 | | Double_accounting
8 | | None
9 | [@@deriving sexp_of]
10 |
11 | val of_string : string -> t
12 | end
13 |
14 | module Vertical_align : sig
15 | type t =
16 | | Baseline
17 | | Superscript
18 | | Subscript
19 | [@@deriving sexp_of]
20 |
21 | val of_string : string -> t
22 | end
23 |
24 | type t =
25 | { text : string
26 | ; bold : bool option
27 | ; charset : int32 option
28 | ; condense : bool option
29 | ; extend : bool option
30 | ; font : string option
31 | ; font_family : int32 option
32 | ; font_size : float option
33 | ; italic : bool option
34 | ; outline : bool option
35 | ; shadow : bool option
36 | ; strikethrough : bool option
37 | ; underline : Underline.t option
38 | ; vertical_align : Vertical_align.t option }
39 | [@@deriving fields, sexp_of]
40 |
41 | val empty : t
42 |
43 | val of_xml : Xml.xml -> t option
44 | end
45 |
46 | module String_item : sig
47 | type t = Rich_text.t list
48 | [@@deriving sexp_of]
49 |
50 | val to_string : t -> string
51 |
52 | val of_xml : Xml.xml -> t option
53 | end
54 |
55 | type t = String_item.t list
56 | [@@deriving sexp_of]
57 |
58 | val to_string_array : t -> string array
59 |
60 | val of_xml : Xml.xml -> t
61 |
--------------------------------------------------------------------------------
/spreadsheetml/src/spreadsheetml.ml:
--------------------------------------------------------------------------------
1 | (* See ECMA 376 Part 4, Section 3 - "SpreadsheetML Reference Material" *)
2 |
3 | module Shared_string_table = Shared_string_table
4 | module Styles = Styles
5 | module Workbook = Workbook
6 | module Worksheet = Worksheet
7 |
--------------------------------------------------------------------------------
/spreadsheetml/src/styles.ml:
--------------------------------------------------------------------------------
1 | (* 3.8 Styles *)
2 | open Base
3 | open Stdint
4 | open Open_packaging.Utils
5 |
6 | module Format = struct
7 | (* 3.8.45 xf (Format) *)
8 | (* TODO: Add and children *)
9 | type t =
10 | { apply_alignment : bool option
11 | ; apply_border : bool option
12 | ; apply_fill : bool option
13 | ; apply_font : bool option
14 | ; apply_number_format : bool option
15 | ; apply_protection : bool option
16 | ; border_id : uint32 option
17 | ; fill_id : uint32 option
18 | ; font_id : uint32 option
19 | ; number_format_id : uint32 option
20 | ; pivot_button : bool
21 | ; quote_prefix : bool
22 | ; format_id : uint32 option }
23 | [@@deriving fields, sexp_of]
24 |
25 | let default =
26 | { apply_alignment = None
27 | ; apply_border = None
28 | ; apply_fill = None
29 | ; apply_font = None
30 | ; apply_number_format = None
31 | ; apply_protection = None
32 | ; border_id = None
33 | ; fill_id = None
34 | ; font_id = None
35 | ; number_format_id = None
36 | ; pivot_button = false
37 | ; quote_prefix = false
38 | ; format_id = None }
39 |
40 | let of_xml = function
41 | | Xml.Element ("xf", attrs, _) ->
42 | List.fold attrs ~init:default ~f:(fun acc -> function
43 | | "applyAlignment", v ->
44 | { acc with apply_alignment = Some (bool_of_xsd_boolean v) }
45 | | "applyBorder", v ->
46 | { acc with apply_border = Some (bool_of_xsd_boolean v) }
47 | | "applyFill", v ->
48 | { acc with apply_fill = Some (bool_of_xsd_boolean v) }
49 | | "applyFont", v ->
50 | { acc with apply_font = Some (bool_of_xsd_boolean v) }
51 | | "applyNumberFormat", v ->
52 | { acc with apply_number_format = Some (bool_of_xsd_boolean v) }
53 | | "applyProtection", v ->
54 | { acc with apply_protection = Some (bool_of_xsd_boolean v) }
55 | | "borderId", v ->
56 | { acc with border_id = Some (Uint32.of_string v) }
57 | | "fillId", v ->
58 | { acc with fill_id = Some (Uint32.of_string v) }
59 | | "fontId", v ->
60 | { acc with font_id = Some (Uint32.of_string v) }
61 | | "numFmtId", v ->
62 | { acc with number_format_id = Some (Uint32.of_string v) }
63 | | "pivotButton", v ->
64 | { acc with pivot_button = bool_of_xsd_boolean v }
65 | | "quotePrefix", v ->
66 | { acc with quote_prefix = bool_of_xsd_boolean v }
67 | | "xfId", v ->
68 | { acc with format_id = Some (Uint32.of_string v) }
69 | | _ -> acc)
70 | |> Option.some
71 | | _ -> None
72 | end
73 |
74 | module Number_format = struct
75 | (* 3.8.30 numFmt (Number Format) *)
76 | type t =
77 | { id : uint32
78 | ; format : string }
79 | [@@deriving fields, sexp_of]
80 |
81 | let of_xml = function
82 | | Xml.Element ("numFmt", attrs, _) ->
83 | let id = ref None in
84 | let format = ref None in
85 | List.iter attrs ~f:(function
86 | | "numFmtId", v -> id := Some (Uint32.of_string v)
87 | | "formatCode", v -> format := Some v
88 | | _ -> ());
89 | let id = require_attribute "numFmt" "numFmtId" !id in
90 | let format = require_attribute "numFmt" "formatCode" !format in
91 | Some { id ; format }
92 | | _ -> None
93 | end
94 |
95 | type t =
96 | { cell_formats : Format.t list
97 | ; formatting_records : Format.t list
98 | ; number_formats : Number_format.t list }
99 | [@@deriving fields, sexp_of]
100 |
101 | let empty =
102 | { cell_formats = []
103 | ; formatting_records = []
104 | ; number_formats = [] }
105 |
106 | let of_xml =
107 | expect_element "styleSheet" (fun _ ->
108 | List.fold ~init:empty ~f:(fun acc ->
109 | let open Xml in
110 | function
111 | | Element ("cellStyleXfs", _, children) ->
112 | let formatting_records = List.filter_map children ~f:Format.of_xml in
113 | { acc with formatting_records }
114 | | Element ("cellXfs", _, children) ->
115 | let cell_formats = List.filter_map children ~f:Format.of_xml in
116 | { acc with cell_formats }
117 | | Element ("numFmts", _, children) ->
118 | let number_formats =
119 | List.filter_map children ~f:Number_format.of_xml in
120 | { acc with number_formats }
121 | | _ -> acc))
122 |
--------------------------------------------------------------------------------
/spreadsheetml/src/styles.mli:
--------------------------------------------------------------------------------
1 | open Stdint
2 |
3 | module Format : sig
4 | type t =
5 | { apply_alignment : bool option
6 | ; apply_border : bool option
7 | ; apply_fill : bool option
8 | ; apply_font : bool option
9 | ; apply_number_format : bool option
10 | ; apply_protection : bool option
11 | ; border_id : uint32 option
12 | ; fill_id : uint32 option
13 | ; font_id : uint32 option
14 | ; number_format_id : uint32 option
15 | ; pivot_button : bool
16 | ; quote_prefix : bool
17 | ; format_id : uint32 option }
18 | [@@deriving fields, sexp_of]
19 |
20 | val default : t
21 |
22 | val of_xml : Xml.xml -> t option
23 | end
24 |
25 | module Number_format : sig
26 | type t =
27 | { id : uint32
28 | ; format : string }
29 | [@@deriving fields, sexp_of]
30 |
31 | val of_xml : Xml.xml -> t option
32 | end
33 |
34 | type t =
35 | { cell_formats : Format.t list
36 | ; formatting_records : Format.t list
37 | ; number_formats : Number_format.t list }
38 | [@@deriving fields, sexp_of]
39 |
40 | val empty : t
41 |
42 | val of_xml : Xml.xml -> t
43 |
--------------------------------------------------------------------------------
/spreadsheetml/src/workbook.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Base.Printf
3 | open Stdint
4 | open Open_packaging.Utils
5 |
6 | module Book_view = struct
7 | module Visibility = struct
8 | type t =
9 | | Hidden
10 | | Very_hidden
11 | | Visible
12 | [@@deriving sexp_of]
13 |
14 | let of_string =
15 | function
16 | | "hidden" -> Hidden
17 | | "veryHidden" -> Very_hidden
18 | | "visible" -> Visible
19 | | str -> failwithf "Expected ST_Visibility but got '%s'" str ()
20 | end
21 |
22 | (* 3.2.30 workbookView *)
23 | type t =
24 | { active_tab : uint32
25 | ; autofilter_date_grouping : bool
26 | ; first_sheet : uint32
27 | ; minimized : bool
28 | ; show_horizontal_scroll : bool
29 | ; show_vertical_scroll : bool
30 | ; tab_ratio : uint32
31 | ; visibility : Visibility.t
32 | ; window_height : uint32 option
33 | ; window_width : uint32 option
34 | ; x_window : int32 option
35 | ; y_window : int32 option }
36 | [@@deriving fields, sexp_of]
37 |
38 | let default =
39 | { active_tab = Uint32.zero
40 | ; autofilter_date_grouping = true
41 | ; first_sheet = Uint32.zero
42 | ; minimized = false
43 | ; show_horizontal_scroll = true
44 | ; show_vertical_scroll = true
45 | ; tab_ratio = Uint32.of_int 600
46 | ; visibility = Visibility.Visible
47 | ; window_height = None
48 | ; window_width = None
49 | ; x_window = None
50 | ; y_window = None }
51 |
52 | let of_xml = function
53 | | Xml.Element ("bookView", attrs, _) ->
54 | List.fold attrs ~init:default ~f:(fun t ->
55 | function
56 | | "activeTab", v ->
57 | { t with active_tab = Uint32.of_string v }
58 | | "autoFilterDateGrouping", v ->
59 | { t with autofilter_date_grouping = bool_of_xsd_boolean v }
60 | | "firstSheet", v ->
61 | { t with first_sheet = Uint32.of_string v }
62 | | "minimized", v ->
63 | { t with minimized = bool_of_xsd_boolean v }
64 | | "showHorizontalScroll", v ->
65 | { t with show_horizontal_scroll = bool_of_xsd_boolean v }
66 | | "showVerticalScroll", v ->
67 | { t with show_vertical_scroll = bool_of_xsd_boolean v }
68 | | "tabRatio", v ->
69 | { t with tab_ratio = Uint32.of_string v }
70 | | "visibility", v ->
71 | { t with visibility = Visibility.of_string v }
72 | | "windowHeight", v ->
73 | { t with window_height = Some (Uint32.of_string v) }
74 | | "windowWidth", v ->
75 | { t with window_width = Some (Uint32.of_string v) }
76 | | "xWindow", v ->
77 | { t with x_window = Some (Int32.of_string v) }
78 | | "yWindow", v ->
79 | { t with y_window = Some (Int32.of_string v) }
80 | | _ -> t)
81 | |> Option.some
82 | | _ -> None
83 | end
84 |
85 | module Sheet = struct
86 | (* 3.2.19 sheet (Sheet information) *)
87 |
88 | module State = struct
89 | type t =
90 | | Hidden
91 | | Very_hidden
92 | | Visible
93 | [@@deriving sexp_of]
94 |
95 | let of_string =
96 | function
97 | | "hidden" -> Hidden
98 | | "veryHidden" -> Very_hidden
99 | | "visible" -> Visible
100 | | str -> failwithf "Expected ST_SheetState but got '%s'" str ()
101 | end
102 |
103 | type t =
104 | { id : string (* ST_RelationshipId *)
105 | ; name : string
106 | ; sheet_id : uint32
107 | ; state : State.t }
108 | [@@deriving fields, sexp_of]
109 |
110 | let of_xml = function
111 | | Xml.Element ("sheet", attrs, _) ->
112 | let name = ref None in
113 | let sheet_id = ref None in
114 | let state = ref State.Visible in
115 | let id = ref None in
116 | List.iter attrs ~f:(function
117 | | "name", v -> name := Some v
118 | | "sheetId", v -> sheet_id := Some (Uint32.of_string v)
119 | | "state", v -> state := State.of_string v
120 | | "r:id", v -> id := Some v
121 | | _ -> ());
122 | let name = require_attribute "sheet" "name" !name in
123 | let sheet_id = require_attribute "sheet" "sheetId" !sheet_id in
124 | let id = require_attribute "sheet" "r:id" !id in
125 | Some { name ; sheet_id ; state = !state ; id }
126 | | _ -> None
127 | end
128 |
129 | type t =
130 | { book_views : Book_view.t list
131 | (*
132 | ; calculation_properties : Calculation_property.t list
133 | ; custom_workbook_views : Custom_workbook_view.t list
134 | ; defined_names : Defined_name.t list
135 | ; external_references : External_reference.t list
136 | ( * TODO: extLst - Future Feature Storage Area * )
137 | ; file_recovery_properties : File_recovery_property.t list
138 | ; file_sharing : File_sharing.t list
139 | ; file_version : File_version.t list
140 | ; function_groups : Function_group.t list
141 | ; embedded_object_sizes : Embedded_object_size.t list
142 | ; pivot_caches : Pivot_cache.t list *)
143 | ; sheets : Sheet.t list (* 3.2.20 sheets *)
144 | (*
145 | ; smart_tag_properties : Smart_tag_property.t list
146 | ; smart_tag_types : Smart_tag_type.t list
147 | ; web_publishing_properties : Web_publishing_properties.t list
148 | ; web_publish_objects : Web_publish_object.t list
149 | ; workbook_properties : Workbook_property.t list
150 | ; workbook_protection : Workbook_protection.t list *) }
151 | [@@deriving fields, sexp_of]
152 |
153 | let empty =
154 | { book_views = []
155 | ; sheets = [] }
156 |
157 | let of_xml =
158 | expect_element "workbook" (fun _ ->
159 | List.fold ~init:empty ~f:(fun t ->
160 | let open Xml in
161 | function
162 | | Element ("bookViews", _, children) ->
163 | let book_views = List.filter_map children ~f:Book_view.of_xml in
164 | { t with book_views }
165 | | Element ("sheets", _, children) ->
166 | let sheets = List.filter_map children ~f:Sheet.of_xml in
167 | { t with sheets }
168 | | _ -> t))
169 |
--------------------------------------------------------------------------------
/spreadsheetml/src/workbook.mli:
--------------------------------------------------------------------------------
1 | open Stdint
2 |
3 | module Book_view : sig
4 | module Visibility : sig
5 | type t =
6 | | Hidden
7 | | Very_hidden
8 | | Visible
9 | [@@deriving sexp_of]
10 |
11 | val of_string : string -> t
12 | end
13 |
14 | type t =
15 | { active_tab : uint32
16 | ; autofilter_date_grouping : bool
17 | ; first_sheet : uint32
18 | ; minimized : bool
19 | ; show_horizontal_scroll : bool
20 | ; show_vertical_scroll : bool
21 | ; tab_ratio : uint32
22 | ; visibility : Visibility.t
23 | ; window_height : uint32 option
24 | ; window_width : uint32 option
25 | ; x_window : int32 option
26 | ; y_window : int32 option }
27 | [@@deriving fields, sexp_of]
28 |
29 | val default : t
30 |
31 | val of_xml : Xml.xml -> t option
32 | end
33 |
34 | module Sheet : sig
35 |
36 | module State : sig
37 | type t =
38 | | Hidden
39 | | Very_hidden
40 | | Visible
41 | [@@deriving sexp_of]
42 |
43 | val of_string : string -> t
44 | end
45 |
46 | type t =
47 | { id : string (** ST_RelationshipId *)
48 | ; name : string
49 | ; sheet_id : uint32
50 | ; state : State.t }
51 | [@@deriving fields, sexp_of]
52 |
53 | val of_xml : Xml.xml -> t option
54 | end
55 |
56 | type t =
57 | { book_views : Book_view.t list
58 | ; sheets : Sheet.t list (** 3.2.20 sheets *) }
59 | [@@deriving fields, sexp_of]
60 |
61 | val empty : t
62 |
63 | val of_xml : Xml.xml -> t
64 |
--------------------------------------------------------------------------------
/spreadsheetml/src/worksheet.ml:
--------------------------------------------------------------------------------
1 | (* 3.3 Worksheets *)
2 | open Base
3 | open Base.Printf
4 | open Stdint
5 | open Open_packaging.Utils
6 |
7 | module Cell = struct
8 | module Type = struct
9 | (* 3.18.12 ST_CellType (Cell Type) *)
10 | type t =
11 | | Boolean
12 | | Error
13 | | Inline_string
14 | | Number
15 | | Shared_string
16 | | Formula_string
17 | [@@deriving sexp_of]
18 |
19 | let of_string = function
20 | | "b" -> Boolean
21 | | "e" -> Error
22 | | "inlineStr" -> Inline_string
23 | | "n" -> Number
24 | | "s" -> Shared_string
25 | | "str" -> Formula_string
26 | | str -> failwithf "Expected ST_CellType but got '%s'" str ()
27 | end
28 |
29 | module Value = struct
30 | (* 3.3.1.93 v (Cell Value) *)
31 | type t =
32 | | Inline of string
33 | | Rich_inline of Shared_string_table.String_item.t
34 | | Shared of uint32
35 | [@@deriving sexp_of]
36 |
37 | let to_string ~shared_strings = function
38 | | Inline v -> v
39 | | Rich_inline v ->
40 | Shared_string_table.String_item.to_string v
41 | | Shared n ->
42 | let n = Uint32.to_int n in
43 | shared_strings.(n)
44 | |> Shared_string_table.String_item.to_string
45 |
46 | let of_xml ~data_type el =
47 | let open Xml in
48 | match data_type, el with
49 | | _, Element ("is", _, children) ->
50 | let value =
51 | List.filter_map children ~f:Shared_string_table.Rich_text.of_xml in
52 | Some (Rich_inline value)
53 | | Type.Shared_string, Element ("v", _, children) ->
54 | Some (Shared (Uint32.of_string (expect_pcdata children)))
55 | | _, Element ("v", _, children) ->
56 | Some (Inline (expect_pcdata children))
57 | | _ -> None
58 | end
59 |
60 | (* 3.3.1.3 c (Cell) *)
61 | type t =
62 | { reference : string option
63 | ; formula : string option
64 | ; value : Value.t option
65 | ; cell_metadata_index : uint32
66 | ; show_phonetic : bool
67 | ; style_index : uint32
68 | ; data_type : Type.t
69 | ; value_metadata_index : uint32 }
70 | [@@deriving fields, sexp_of]
71 |
72 | let column { reference ; _ } =
73 | (Option.value_exn reference ~here:[%here]
74 | |> String.to_list
75 | |> List.take_while ~f:Char.is_alpha
76 | |> List.map ~f:Char.uppercase
77 | |> List.map ~f:(fun c -> Char.to_int c - Char.to_int 'A' + 1)
78 | |> List.fold ~init:0 ~f:(fun acc n -> acc * 26 + n))
79 | - 1
80 |
81 | let to_string ~shared_strings { value ; _ } =
82 | Option.map value ~f:(Value.to_string ~shared_strings)
83 | |> Option.value ~default:""
84 |
85 | let default =
86 | { reference = None
87 | ; formula = None
88 | ; value = None
89 | ; cell_metadata_index = Uint32.zero
90 | ; show_phonetic = false
91 | ; style_index = Uint32.zero
92 | ; data_type = Type.Number
93 | ; value_metadata_index = Uint32.zero }
94 |
95 | let of_xml =
96 | let open Xml in
97 | function
98 | | Element ("c", attrs, children) ->
99 | let with_attrs =
100 | List.fold attrs ~init:default ~f:(fun acc -> function
101 | | "r", v ->
102 | { acc with reference = Some v }
103 | | "s", v ->
104 | { acc with style_index = Uint32.of_string v }
105 | | "t", v ->
106 | { acc with data_type = Type.of_string v }
107 | | "cm", v ->
108 | { acc with cell_metadata_index = Uint32.of_string v }
109 | | "vm", v ->
110 | { acc with value_metadata_index = Uint32.of_string v }
111 | | "ph", v ->
112 | { acc with show_phonetic = bool_of_xsd_boolean v }
113 | | _ -> acc)
114 | in
115 | List.fold children ~init:with_attrs ~f:(fun acc -> function
116 | | Element ("f", _, children) ->
117 | { acc with formula = Some (expect_pcdata children) }
118 | | el ->
119 | let value = Value.of_xml ~data_type:with_attrs.data_type el in
120 | { acc with value = Option.first_some value acc.value })
121 | |> Option.some
122 | | _ -> None
123 | end
124 |
125 | module Row = struct
126 | (* 3.3.1.71 row (Row) *)
127 | type t =
128 | { cells : Cell.t list
129 | ; collapsed : bool
130 | ; custom_format : bool
131 | ; custom_height : bool
132 | ; hidden : bool
133 | ; height : float option
134 | ; outline_level : uint8
135 | ; show_phonetic : bool
136 | ; row_index : uint32 option
137 | ; style_index : uint32
138 | ; thick_bottom_border : bool
139 | ; thick_top_border : bool }
140 | [@@deriving fields, sexp_of]
141 |
142 | let default =
143 | { cells = []
144 | ; collapsed = false
145 | ; custom_format = false
146 | ; custom_height = false
147 | ; hidden = false
148 | ; height = None
149 | ; outline_level = Uint8.zero
150 | ; show_phonetic = false
151 | ; row_index = None
152 | ; style_index = Uint32.zero
153 | ; thick_bottom_border = false
154 | ; thick_top_border = false }
155 |
156 | let of_xml = function
157 | | Xml.Element ("row", attrs, children) ->
158 | let cells = List.filter_map children ~f:Cell.of_xml in
159 | List.fold attrs ~init:{ default with cells } ~f:(fun acc -> function
160 | | "collapsed", v ->
161 | { acc with collapsed = bool_of_xsd_boolean v }
162 | | "customFormat", v ->
163 | { acc with custom_format = bool_of_xsd_boolean v }
164 | | "customHeight", v ->
165 | { acc with custom_height = bool_of_xsd_boolean v }
166 | | "hidden", v ->
167 | { acc with hidden = bool_of_xsd_boolean v }
168 | | "ht", v ->
169 | { acc with height = Some (Float.of_string v) }
170 | | "outlineLevel", v ->
171 | { acc with outline_level = Uint8.of_string v }
172 | | "ph", v ->
173 | { acc with show_phonetic = bool_of_xsd_boolean v }
174 | | "r", v ->
175 | { acc with row_index = Some (Uint32.of_string v) }
176 | | "s", v ->
177 | { acc with style_index = Uint32.of_string v }
178 | | "thickBot", v ->
179 | { acc with thick_bottom_border = bool_of_xsd_boolean v }
180 | | "thickTop", v ->
181 | { acc with thick_top_border = bool_of_xsd_boolean v }
182 | | _ -> acc)
183 | |> Option.some
184 | | _ -> None
185 | end
186 |
187 | module Column = struct
188 | (* 3.3.1.12 col (Column Width & Formatting) *)
189 | type t =
190 | { best_fit : bool
191 | ; collapsed : bool
192 | ; custom_width : bool
193 | ; hidden : bool
194 | ; max : uint32
195 | ; min : uint32
196 | ; outline_level : uint8
197 | ; show_phonetic : bool
198 | ; default_style : uint32
199 | ; width : float option }
200 | [@@deriving fields, sexp_of]
201 |
202 | let of_xml = function
203 | | Xml.Element ("col", attrs, _) ->
204 | let best_fit = ref false in
205 | let collapsed = ref false in
206 | let custom_width = ref false in
207 | let hidden = ref false in
208 | let max = ref None in
209 | let min = ref None in
210 | let outline_level = ref Uint8.zero in
211 | let show_phonetic = ref false in
212 | let default_style = ref Uint32.zero in
213 | let width = ref None in
214 | List.iter attrs ~f:(function
215 | | "bestFit", v ->
216 | best_fit := bool_of_xsd_boolean v
217 | | "collapsed", v ->
218 | collapsed := bool_of_xsd_boolean v
219 | | "customWidth", v ->
220 | custom_width := bool_of_xsd_boolean v
221 | | "hidden", v ->
222 | hidden := bool_of_xsd_boolean v
223 | | "max", v ->
224 | max := Some (Uint32.of_string v)
225 | | "min", v ->
226 | min := Some (Uint32.of_string v)
227 | | "outlineLevel", v ->
228 | outline_level := Uint8.of_string v
229 | | "phonetic", v->
230 | show_phonetic := bool_of_xsd_boolean v
231 | | "style", v ->
232 | default_style := Uint32.of_string v
233 | | "width", v ->
234 | width := Some (Float.of_string v)
235 | | _ -> ());
236 | let max = require_attribute "col" "max" !max in
237 | let min = require_attribute "col" "min" !min in
238 | Some { best_fit = !best_fit
239 | ; collapsed = !collapsed
240 | ; custom_width = !custom_width
241 | ; hidden = !hidden
242 | ; max
243 | ; min
244 | ; outline_level = !outline_level
245 | ; show_phonetic = !show_phonetic
246 | ; default_style = !default_style
247 | ; width = !width }
248 | | _ -> None
249 | end
250 |
251 | type t =
252 | { columns : Column.t list
253 | ; rows : Row.t list }
254 | [@@deriving fields, sexp_of]
255 |
256 | let default =
257 | { columns = []
258 | ; rows = [] }
259 |
260 | let of_xml =
261 | expect_element "worksheet" (fun _ ->
262 | let open Xml in
263 | List.fold ~init:default ~f:(fun acc -> function
264 | | Element ("cols", _, children) ->
265 | { acc with columns = List.filter_map children ~f:Column.of_xml }
266 | | Element ("sheetData", _, children) ->
267 | { acc with rows = List.filter_map children ~f:Row.of_xml }
268 | | _ -> acc))
269 |
--------------------------------------------------------------------------------