From 5de714b9fe6187fb3f0febd9e16fe1a00a3950d0 Mon Sep 17 00:00:00 2001 From: Tim Gebbels Date: Wed, 13 May 2026 23:48:05 +0100 Subject: [PATCH 1/6] Support adding images to XLSX files. --- .zenodo.json | 2 +- CHANGELOG.md | 1 + src/XLSX.jl | 1 + src/images.jl | 463 ++++++++++++++++++++++++++++++++++++++++++++ src/relationship.jl | 3 +- 5 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 src/images.jl diff --git a/.zenodo.json b/.zenodo.json index 8ce9fd22..8af7c331 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -12,7 +12,7 @@ "type": "ProjectMember" } ], - "description": "XLSX.jl is a Julia package to read and write Excel spreadsheet files in the XLSX format.", + "description": "XLSX.jl is a Julia package to read and write Excel spreadsheet files.", "license": "mit", "upload_type": "software", "keywords": ["julia", "excel", "xlsx", "spreadsheet"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ca61d14..d95c8de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - add `dependabot` support ([#130](https://github.com/JuliaData/XLSX.jl/issues/130)) - add ability to read native template (`.xltx`) files ([#293](https://github.com/JuliaData/XLSX.jl/issues/293)) - add `getDefinedNames` to mirror `addDefinedName` +- support adding png, jpg or gif image into a sheet ([#134](https://github.com/JuliaData/XLSX.jl/issues/134)) ## [v0.11.7](https://github.com/JuliaData/XLSX.jl/tree/v0.11.7) - 2026-05-07 - Fix issue [#368](https://github.com/JuliaData/XLSX.jl/issues/368) for both reading and writing diff --git a/src/XLSX.jl b/src/XLSX.jl index c00ac460..58e8a368 100644 --- a/src/XLSX.jl +++ b/src/XLSX.jl @@ -60,6 +60,7 @@ include("cellformat-helpers.jl") # must load before cellformats.jl include("cellformats.jl") include("conditional-format-helpers.jl") # must load before conditional-formats.jl include("conditional-formats.jl") +include("images.jl") include("write.jl") include("fileArray.jl") diff --git a/src/images.jl b/src/images.jl new file mode 100644 index 00000000..8f9c8918 --- /dev/null +++ b/src/images.jl @@ -0,0 +1,463 @@ +""" + addImage(s::Worksheet, ref, image; size=nothing, z_layer=nothing) + + +Insert an image into a worksheet at the given cell reference. +Supports file paths and `IOBuffer` sources, auto‑detects image format, creates drawing parts and relationships as needed, and computes the anchor size using either native pixel dimensions or a user‑supplied `(width_px, height_px)`. + +Returns a structured summary describing where and how the image was placed. + +--- + +# **Arguments** + +- **`s::Worksheet`** + Target worksheet. + +- **`ref`** + Excel cell reference (`"A1"`, `"B2"`, …). + Must be valid; otherwise an error is thrown. + +- **`image`** + Either: + - a file path (`String`) + - an `IOBuffer` containing raw image bytes + + Supported formats (auto‑detected): PNG, JPEG, GIF. + +--- + +# **Keyword Arguments** + +- **`size = nothing`** + If `nothing`, the image’s native pixel size is used. + Otherwise supply `(width_px, height_px)`. Actual size will snap to the nearest actual cell boundaries. + +- **`z_layer = nothing`** + Optional stacking order inside the drawing. + +--- + +# **Return Value** + +A `NamedTuple`: + +```julia +( + sheet = sheet_name::String, + media_name = media_name::String, + from = Start cell (top left) + to = End cell (bottom right), +) +``` + +Where: + +- **`media_name`** is the filename created in `xl/media/` +- **`from`** and **`to`** are zero‑based anchor coordinates +- **`width_px`**, **`height_px`** are the pixel dimensions used for sizing + +--- + +# **Examples** + +Insert from a file: + +```julia +info = XLSX.addImage(sheet, "B2", "photo.jpg") +``` + +Insert from an `IOBuffer`: + +```julia +buf = IOBuffer(read("logo.png")) +info = XLSX.addImage(sheet, "C5", buf) +``` + +Insert with explicit size: + +```julia +info = XLSX.addImage(sheet, "A1", "icon.png"; size=(128, 128)) +``` + +""" +addImage( + s::Worksheet, + ref::AbstractString, + image::Union{AbstractString, IOBuffer}; + size::Union{Nothing, Tuple{<:Integer, <:Integer}} = nothing, + z_layer::Union{Nothing, Integer} = nothing +) = addImage(s, CellRef(ref), image; size, z_layer) +addImage( + s::Worksheet, + row::Integer, col::Integer, + image::Union{AbstractString, IOBuffer}; + size::Union{Nothing, Tuple{<:Integer, <:Integer}} = nothing, + z_layer::Union{Nothing, Integer} = nothing +) = addImage(s, CellRef(row, col), image; size, z_layer) +function addImage( + s::Worksheet, + cellref::CellRef, + image::Union{AbstractString, IOBuffer}; + size::Union{Nothing, Tuple{<:Integer, <:Integer}} = nothing, + z_layer::Union{Nothing, Integer} = nothing +) + xf = s.package + sheet_path = get_relationship_target_by_id("xl", get_workbook(s), s.relationship_id) + sheet_name = s.name + + media_name = add_media!(xf, image) + drawing_path = ensure_drawing!(xf, sheet_path) + img_rid = add_image_rel!(xf, drawing_path, media_name) + + # Pass media_name directly — no need to re-derive it from the rels XML + col, row, col_to, row_to = + add_anchor!(xf, drawing_path, img_rid, media_name, cellref; size, z_layer) + + return ( + sheet = sheet_name, + media_name = media_name, + from = "$(CellRef(col, row))", + to = "$(CellRef(col_to, row_to))", + ) +end + +function add_media!(xf::XLSXFile, image_path::AbstractString)::String + return _add_media_bytes!(xf, read(image_path)) +end +function add_media!(xf::XLSXFile, io::IOBuffer)::String + return _add_media_bytes!(xf, take!(io)) +end +function _add_media_bytes!(xf::XLSXFile, bytes::Vector{UInt8})::String + ext = detect_image_ext(bytes) + existing = count(k -> startswith(k, "xl/media/"), keys(xf.binary_data)) + name = "image$(existing + 1)$(ext)" + xf.binary_data["xl/media/$name"] = bytes + # binary_data has its own write loop in writexlsx — no xf.files entry needed + register_image_content_type!(xf, ext) + return name +end + +function ensure_drawing!(xf::XLSXFile, sheet_path::String)::String + # All keys in xf.data / xf.files use forward slashes — use rsplit, not + # dirname/basename/joinpath, which use the OS separator on Windows. + sheet_dir = rsplit(sheet_path, "/"; limit=2)[1] # "xl/worksheets" + sheet_file = rsplit(sheet_path, "/"; limit=2)[2] # "sheet1.xml" + sheet_rels_path = sheet_dir * "/_rels/" * sheet_file * ".rels" + + if !haskey(xf.data, sheet_rels_path) + xf.data[sheet_rels_path] = empty_rels_doc() + xf.files[sheet_rels_path] = true + end + rels_root = root_element(xf.data[sheet_rels_path]) + + # Return existing drawing path if one is already linked to this sheet + for node in something(XML.children(rels_root), []) + XML.nodetype(node) === XML.Element || continue + XML.tag(node) == "Relationship" || continue + attrs = XML.attributes(node) + attrs === nothing && continue + if get(attrs, "Type", "") == REL_DRAWING + drawing_file = rsplit(get(attrs, "Target", ""), "/"; limit=2)[2] + return "xl/drawings/" * drawing_file + end + end + + # Find a free drawing filename (avoids collisions with charts etc.) + drawing_file = let i = 1 + while haskey(xf.data, "xl/drawings/drawing$i.xml"); i += 1; end + "drawing$i.xml" + end + drawing_path = "xl/drawings/" * drawing_file + + xf.data[drawing_path] = empty_drawing_doc() + xf.files[drawing_path] = true + + rid = new_relationship_id(rels_root) + push!(rels_root, XML.Element("Relationship"; + Id = rid, + Type = REL_DRAWING, + Target = "../drawings/" * drawing_file, + )) + + ensure_drawing_element!(xf.data[sheet_path], rid) + register_drawing_content_type!(xf, drawing_path) + + return drawing_path +end + +function add_image_rel!(xf::XLSXFile, drawing_path::String, media_name::String)::String + drawing_file = rsplit(drawing_path, "/"; limit=2)[2] + rels_path = "xl/drawings/_rels/" * drawing_file * ".rels" + + if !haskey(xf.data, rels_path) + xf.data[rels_path] = empty_rels_doc() + xf.files[rels_path] = true + end + rels_root = root_element(xf.data[rels_path]) + + # Return existing rid if this media is already referenced + for node in something(XML.children(rels_root), []) + XML.nodetype(node) === XML.Element || continue + attrs = XML.attributes(node) + attrs === nothing && continue + get(attrs, "Target", "") == "../media/$media_name" && return get(attrs, "Id", "") + end + + rid = new_relationship_id(rels_root) + push!(rels_root, XML.Element("Relationship"; + Id = rid, + Type = REL_IMAGE, + Target = "../media/$media_name", + )) + return rid +end + +function add_anchor!( + xf::XLSXFile, + drawing_path::String, + img_rid::String, + media_name::String, # passed directly — no rels lookup needed + cellref::CellRef; + size::Union{Nothing,Tuple{<:Integer,<:Integer}} = nothing, + z_layer::Union{Nothing,Integer} = nothing, +) + root_el = root_element(xf.data[drawing_path]) + + col = column_number(cellref) - 1 + row = row_number(cellref) - 1 + + bytes = xf.binary_data["xl/media/" * media_name] + w_px, h_px = size === nothing ? image_dimensions(bytes) : size + + col_to = col + max(1, round(Int, w_px / 64)) + row_to = row + max(1, round(Int, h_px / 20)) + + # Unique id for cNvPr: count existing anchors in this drawing + n_anchors = count(n -> XML.nodetype(n) === XML.Element, + something(XML.children(root_el), [])) + + push!(root_el, build_two_cell_anchor( + col, row, col_to, row_to, img_rid; + shape_id = n_anchors + 2, # cNvPr id must be ≥2 and unique per drawing + z_layer = z_layer, + )) + + return col, row, col_to, row_to +end + + +# --------------------------------------------------------------------------- +# Low-level helpers +# --------------------------------------------------------------------------- + +const REL_DRAWING = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" +const REL_IMAGE = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" +const NS_RELATIONSHIPS = + "http://schemas.openxmlformats.org/package/2006/relationships" +const NS_XDR = + "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" +const NS_A = + "http://schemas.openxmlformats.org/drawingml/2006/main" +const NS_R = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + +const MIME_DRAWING = + "application/vnd.openxmlformats-officedocument.drawing+xml" +const EXT_MIME = Dict( + ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", +) + +# ── XML document factories ─────────────────────────────────────────────────── + +empty_rels_doc() = XML.Document( + XML.Declaration(; version="1.0", encoding="UTF-8"), + XML.Element("Relationships"; xmlns=NS_RELATIONSHIPS), +) + +empty_drawing_doc() = XML.Document( + XML.Declaration(; version="1.0", encoding="UTF-8"), + XML.Element("xdr:wsDr"; + var"xmlns:xdr" = NS_XDR, + var"xmlns:a" = NS_A, + var"xmlns:r" = NS_R, + ), +) + +# ── Root-element accessor ──────────────────────────────────────────────────── + +function root_element(doc::XML.Node)::XML.Node + for node in something(XML.children(doc), []) + XML.nodetype(node) === XML.Element && return node + end + throw(XLSXError("Document has no root element")) +end + +# ── Relationship helpers ───────────────────────────────────────────────────── + +"""Choose the next available `rId` by scanning existing Id attributes.""" +function new_relationship_id(rels_root::XML.Node)::String + ids = Int[] + for node in something(XML.children(rels_root), []) + XML.nodetype(node) === XML.Element || continue + attrs = XML.attributes(node) + attrs === nothing && continue + m = match(r"rId(\d+)", get(attrs, "Id", "")) + m !== nothing && push!(ids, parse(Int, m[1])) + end + return "rId$(isempty(ids) ? 1 : maximum(ids) + 1)" +end + +# ── Content-type registration (split into two focused functions) ───────────── + +function register_drawing_content_type!(xf::XLSXFile, drawing_path::String)::Nothing + ct_root = root_element(xf.data["[Content_Types].xml"]) + abs_path = "/" * drawing_path + for node in something(XML.children(ct_root), []) + XML.nodetype(node) === XML.Element || continue + XML.tag(node) == "Override" || continue + attrs = XML.attributes(node) + attrs !== nothing && + get(attrs, "PartName", "") == abs_path && return nothing + end + push!(ct_root, XML.Element("Override"; + PartName = abs_path, + ContentType = MIME_DRAWING, + )) + return nothing +end + +function register_image_content_type!(xf::XLSXFile, image_ext::String)::Nothing + ct_root = root_element(xf.data["[Content_Types].xml"]) + ext_no_dot = lstrip(image_ext, '.') + mime = get(EXT_MIME, image_ext, "image/$ext_no_dot") + for node in something(XML.children(ct_root), []) + XML.nodetype(node) === XML.Element || continue + XML.tag(node) == "Default" || continue + attrs = XML.attributes(node) + attrs !== nothing && + get(attrs, "Extension", "") == ext_no_dot && return nothing + end + push!(ct_root, XML.Element("Default"; + Extension = ext_no_dot, + ContentType = mime, + )) + return nothing +end + +# ── Sheet XML ──────────────────────────────────────────────────────────────── + +"""Append `` to the sheet root if not already present.""" +function ensure_drawing_element!(sheet_doc::XML.Node, rid::String) + sheet_root = root_element(sheet_doc) + for node in something(XML.children(sheet_root), []) + XML.nodetype(node) === XML.Element || continue + XML.tag(node) == "drawing" && return nothing + end + attrs = XML.attributes(sheet_root) + if attrs === nothing || !haskey(attrs, "xmlns:r") + sheet_root["xmlns:r"] = NS_R + end + el = XML.Element("drawing") + el["r:id"] = rid + push!(sheet_root, el) + return nothing +end + +# ── Anchor XML ─────────────────────────────────────────────────────────────── + +function build_two_cell_anchor( + col::Int, row::Int, + col_to::Int, row_to::Int, + img_rid::String; + shape_id::Int, + z_layer = nothing)::XML.Node + + tel(tag, text) = XML.Element(tag, XML.Text(text)) + + function cell_marker(tag, c, r) + XML.Element(tag, + tel("xdr:col", string(c)), + tel("xdr:colOff", "0"), + tel("xdr:row", string(r)), + tel("xdr:rowOff", "0"), + ) + end + + nvpicpr = XML.Element("xdr:nvPicPr", + XML.Element("xdr:cNvPr"; id=string(shape_id), name="Image $shape_id"), + XML.Element("xdr:cNvPicPr", + XML.Element("a:picLocks"; noChangeAspect="1"), + ), + ) + + blip = XML.Element("a:blip") + blip["r:embed"] = img_rid # colon in attribute name — set after construction + blipfill = XML.Element("xdr:blipFill", + blip, + XML.Element("a:stretch", XML.Element("a:fillRect")), + ) + + sppr = XML.Element("xdr:spPr", + XML.Element("a:xfrm", + XML.Element("a:off"; x="0", y="0"), + XML.Element("a:ext"; cx="0", cy="0"), + ), + XML.Element("a:prstGeom", XML.Element("a:avLst"); prst="rect"), + ) + + return XML.Element("xdr:twoCellAnchor", + cell_marker("xdr:from", col, row), + cell_marker("xdr:to", col_to, row_to), + XML.Element("xdr:pic", nvpicpr, blipfill, sppr), + XML.Element("xdr:clientData"), + ) +end + +# ── Image format detection ─────────────────────────────────────────────────── + +function detect_image_ext(bytes::Vector{UInt8})::String + length(bytes) ≥ 8 && + bytes[1:8] == UInt8[0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A] && return ".png" + length(bytes) ≥ 2 && + bytes[1] == 0xFF && bytes[2] == 0xD8 && return ".jpg" + length(bytes) ≥ 4 && + bytes[1:4] == UInt8[0x47,0x49,0x46,0x38] && return ".gif" + throw(XLSXError("Unsupported or unknown image format")) +end + +function image_dimensions(bytes::Vector{UInt8}) + # PNG: IHDR chunk begins at byte 9; width at 17-20, height at 21-24 (big-endian) + if length(bytes) ≥ 24 && + bytes[1:8] == UInt8[0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A] + w = Int(bytes[17]) << 24 | Int(bytes[18]) << 16 | Int(bytes[19]) << 8 | Int(bytes[20]) + h = Int(bytes[21]) << 24 | Int(bytes[22]) << 16 | Int(bytes[23]) << 8 | Int(bytes[24]) + return (w, h) + end + # GIF: little-endian width at bytes 7-8, height at 9-10 + if length(bytes) ≥ 10 && bytes[1:4] == UInt8[0x47,0x49,0x46,0x38] + return (Int(bytes[7]) | Int(bytes[8]) << 8, + Int(bytes[9]) | Int(bytes[10]) << 8) + end + # JPEG: scan for SOF0/SOF1/SOF2 (0xFFC0–0xFFC3) markers + if length(bytes) ≥ 2 && bytes[1] == 0xFF && bytes[2] == 0xD8 + i = 3 + while i + 8 ≤ length(bytes) + bytes[i] == 0xFF || break + marker = bytes[i+1] + if marker in 0xC0:0xC3 + h = Int(bytes[i+5]) << 8 | Int(bytes[i+6]) + w = Int(bytes[i+7]) << 8 | Int(bytes[i+8]) + return (w, h) + end + i += 2 + (Int(bytes[i+2]) << 8 | Int(bytes[i+3])) + end + throw(XLSXError("Could not find JPEG SOF marker")) + end + throw(XLSXError("Unsupported image format for dimension extraction")) +end \ No newline at end of file diff --git a/src/relationship.jl b/src/relationship.jl index 4cd419b3..11376cf2 100644 --- a/src/relationship.jl +++ b/src/relationship.jl @@ -142,4 +142,5 @@ function is_chartsheet(wb::Workbook, sheetname::AbstractString)::Bool end end return false -end \ No newline at end of file +end + From a2e8553df85398daeef709b359b7cfbda6708352 Mon Sep 17 00:00:00 2001 From: Tim Gebbels Date: Fri, 15 May 2026 12:54:18 +0100 Subject: [PATCH 2/6] Address #134: Support adding images to sheets. --- src/images.jl | 639 ++++++++++++++++++++++---------------- src/relationship.jl | 40 +-- src/write.jl | 51 +++ test/data/track_start.jpg | Bin 0 -> 9894 bytes test/runtests.jl | 96 ++++++ 5 files changed, 522 insertions(+), 304 deletions(-) create mode 100644 test/data/track_start.jpg diff --git a/src/images.jl b/src/images.jl index 8f9c8918..b9ccf89b 100644 --- a/src/images.jl +++ b/src/images.jl @@ -1,65 +1,135 @@ -""" - addImage(s::Worksheet, ref, image; size=nothing, z_layer=nothing) +# =========================================================================== +# Constants +# =========================================================================== +const REL_DRAWING = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" +const REL_IMAGE = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" +const NS_RELATIONSHIPS = + "http://schemas.openxmlformats.org/package/2006/relationships" +const NS_XDR = + "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" +const NS_A = + "http://schemas.openxmlformats.org/drawingml/2006/main" +const NS_R = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships" -Insert an image into a worksheet at the given cell reference. -Supports file paths and `IOBuffer` sources, auto‑detects image format, creates drawing parts and relationships as needed, and computes the anchor size using either native pixel dimensions or a user‑supplied `(width_px, height_px)`. +const MIME_DRAWING = + "application/vnd.openxmlformats-officedocument.drawing+xml" +const EXT_MIME = Dict( + ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", +) -Returns a structured summary describing where and how the image was placed. +const ImageInfo = NamedTuple{ + (:sheet, :media_name, :from, :to), + Tuple{String, String, String, String}, +} ---- +# =========================================================================== +# Traversal helpers (eliminate the repeated nodetype/tag/attributes pattern) +# =========================================================================== -# **Arguments** +element_children(node::XML.Node) = + filter(n -> XML.nodetype(n) === XML.Element, something(XML.children(node), [])) -- **`s::Worksheet`** - Target worksheet. +# Match on local name only (ignores namespace prefix) +elements_with_tag(node::XML.Node, tag::String) = + filter(n -> localname(XML.tag(n)) == tag, element_children(node)) -- **`ref`** - Excel cell reference (`"A1"`, `"B2"`, …). - Must be valid; otherwise an error is thrown. +get_attr(node::XML.Node, key::AbstractString, default::AbstractString = "") = + something(get(XML.attributes(node), key, nothing), default) -- **`image`** - Either: - - a file path (`String`) - - an `IOBuffer` containing raw image bytes +function root_element(doc::XML.Node)::XML.Node + children = something(XML.children(doc), []) + idx = findfirst(n -> XML.nodetype(n) === XML.Element, children) + idx !== nothing ? children[idx] : throw(XLSXError("Document has no root element")) +end - Supported formats (auto‑detected): PNG, JPEG, GIF. +function _text_value(node::XML.Node)::Union{Nothing,String} + for c in something(XML.children(node), []) + XML.nodetype(c) === XML.Text && return XML.value(c) + end + return nothing +end ---- +# Returns the local part of a tag, e.g. "pkg:Relationship" → "Relationship" +localname(tag::AbstractString) = last(split(tag, ':'; limit=2)) -# **Keyword Arguments** +# Prepends prefix if non-empty: prefixed_tag("pkg", "Relationship") → "pkg:Relationship" +prefixed_tag(prefix::AbstractString, name::AbstractString) = + isempty(prefix) ? name : "$prefix:$name" -- **`size = nothing`** - If `nothing`, the image’s native pixel size is used. - Otherwise supply `(width_px, height_px)`. Actual size will snap to the nearest actual cell boundaries. +# =========================================================================== +# Document templates +# =========================================================================== -- **`z_layer = nothing`** - Optional stacking order inside the drawing. +empty_rels_doc() = XML.Document( + XML.Declaration(; version="1.0", encoding="UTF-8"), + XML.Element("Relationships"; xmlns=NS_RELATIONSHIPS), +) ---- +empty_drawing_doc() = XML.Document( + XML.Declaration(; version="1.0", encoding="UTF-8"), + XML.Element("xdr:wsDr"; + var"xmlns:xdr" = NS_XDR, + var"xmlns:a" = NS_A, + var"xmlns:r" = NS_R, + ), +) + +# =========================================================================== +# addImage — public API +# =========================================================================== -# **Return Value** +""" + addImage(s::Worksheet, ref::AbstractString, image::Union{AbstractString, IOBuffer}; size::Union{Nothing, Tuple{<:Integer, <:Integer}}=nothing) + addImage(s::Worksheet, row::Integer, col::Integer, image::Union{AbstractString, IOBuffer}; size::Union{Nothing, Tuple{<:Integer, <:Integer}}=nothing) -A `NamedTuple`: + +Insert an image into a worksheet at the given cell reference. The image "floats" above the +grid and does not affect cell contents or dimensions. In Excel, the image may be resized +and repositioned by the user as normal. +Supports file paths and `IOBuffer` sources. + +If multiple, overlapping images are added, newer images overly older ones. + +# Arguments + +- `s::Worksheet`: the target worksheet. +- `ref::AbstractString`: Either a valid cell reference (e.g. `"A1"`) or a valid cell range (e.g. `"B2:D4"`). +The image will be anchored to the top left of the reference and sized to fit within the reference bounds. +If a cell range is given, the `size` keyword argument is ignored. + +- `image::Union{AbstractString, IOBuffer}`: Specifies the image to be inserted. Either: + - a file path (`String`) + - an `IOBuffer` containing raw image bytes + +Supported formats (auto-detected): PNG, JPEG, GIF. + +# Keyword Arguments + +- `size`: provide the desired size of the image as a tuple of integers: `(width_px, height_px)`. Actual size +will snap to the nearest actual cell boundaries. If `nothing` (default), the image's native pixel size is used. +Ignored if `ref` is a cell range. + +# Return Value + +Returns a structured summary describing where and how the image was placed as a `NamedTuple` of `String` values: ```julia ( - sheet = sheet_name::String, - media_name = media_name::String, - from = Start cell (top left) + sheet = sheet name, + media_name = internal media file name, + from = Start cell (top left), to = End cell (bottom right), ) ``` -Where: - -- **`media_name`** is the filename created in `xl/media/` -- **`from`** and **`to`** are zero‑based anchor coordinates -- **`width_px`**, **`height_px`** are the pixel dimensions used for sizing - ---- - -# **Examples** +# Examples Insert from a file: @@ -81,114 +151,110 @@ info = XLSX.addImage(sheet, "A1", "icon.png"; size=(128, 128)) ``` """ -addImage( - s::Worksheet, - ref::AbstractString, - image::Union{AbstractString, IOBuffer}; - size::Union{Nothing, Tuple{<:Integer, <:Integer}} = nothing, - z_layer::Union{Nothing, Integer} = nothing -) = addImage(s, CellRef(ref), image; size, z_layer) -addImage( - s::Worksheet, - row::Integer, col::Integer, - image::Union{AbstractString, IOBuffer}; - size::Union{Nothing, Tuple{<:Integer, <:Integer}} = nothing, - z_layer::Union{Nothing, Integer} = nothing -) = addImage(s, CellRef(row, col), image; size, z_layer) +addImage(s::Worksheet, row::Integer, col::Integer, image; kw...) = + addImage(s, CellRef(row, col), image; kw...) + +function addImage(s::Worksheet, ref::AbstractString, image; kw...) + if is_valid_cellname(ref) + addImage(s, CellRef(ref), image; kw...) + elseif is_valid_cellrange(ref) + addImage(s, CellRange(ref), image; kw...) + else + throw(ArgumentError("Invalid cell reference: $ref")) + end +end + function addImage( s::Worksheet, - cellref::CellRef, + cellref::Union{CellRef, CellRange}, image::Union{AbstractString, IOBuffer}; - size::Union{Nothing, Tuple{<:Integer, <:Integer}} = nothing, - z_layer::Union{Nothing, Integer} = nothing + size::Union{Nothing, Tuple{<:Integer,<:Integer}} = nothing, ) - xf = s.package + xf = get_xlsxfile(s) sheet_path = get_relationship_target_by_id("xl", get_workbook(s), s.relationship_id) - sheet_name = s.name media_name = add_media!(xf, image) drawing_path = ensure_drawing!(xf, sheet_path) img_rid = add_image_rel!(xf, drawing_path, media_name) - - # Pass media_name directly — no need to re-derive it from the rels XML col, row, col_to, row_to = - add_anchor!(xf, drawing_path, img_rid, media_name, cellref; size, z_layer) + add_anchor!(xf, drawing_path, img_rid, media_name, cellref; size) return ( - sheet = sheet_name, + sheet = s.name, media_name = media_name, - from = "$(CellRef(col, row))", - to = "$(CellRef(col_to, row_to))", + from = string(CellRef(row, col)), + to = string(CellRef(row_to, col_to)), ) end -function add_media!(xf::XLSXFile, image_path::AbstractString)::String - return _add_media_bytes!(xf, read(image_path)) -end -function add_media!(xf::XLSXFile, io::IOBuffer)::String - return _add_media_bytes!(xf, take!(io)) -end +# =========================================================================== +# Media +# =========================================================================== + +add_media!(xf::XLSXFile, path::AbstractString) = _add_media_bytes!(xf, read(path)) +add_media!(xf::XLSXFile, io::IOBuffer) = _add_media_bytes!(xf, take!(io)) + function _add_media_bytes!(xf::XLSXFile, bytes::Vector{UInt8})::String ext = detect_image_ext(bytes) existing = count(k -> startswith(k, "xl/media/"), keys(xf.binary_data)) - name = "image$(existing + 1)$(ext)" + name = "image$(existing + 1)$ext" xf.binary_data["xl/media/$name"] = bytes - # binary_data has its own write loop in writexlsx — no xf.files entry needed - register_image_content_type!(xf, ext) + ext_no_dot = String(lstrip(ext, '.')) + register_content_type!(xf, "[Content_Types].xml"; + tag="Default", key="Extension", val=ext_no_dot, + content_type=get(EXT_MIME, ext, "image/$ext_no_dot")) return name end +# =========================================================================== +# Drawing setup +# =========================================================================== + function ensure_drawing!(xf::XLSXFile, sheet_path::String)::String - # All keys in xf.data / xf.files use forward slashes — use rsplit, not - # dirname/basename/joinpath, which use the OS separator on Windows. - sheet_dir = rsplit(sheet_path, "/"; limit=2)[1] # "xl/worksheets" - sheet_file = rsplit(sheet_path, "/"; limit=2)[2] # "sheet1.xml" - sheet_rels_path = sheet_dir * "/_rels/" * sheet_file * ".rels" - - if !haskey(xf.data, sheet_rels_path) - xf.data[sheet_rels_path] = empty_rels_doc() - xf.files[sheet_rels_path] = true + sheet_dir, sheet_file = rsplit(sheet_path, "/"; limit=2) + rels_path = "$sheet_dir/_rels/$sheet_file.rels" + + if !haskey(xf.data, rels_path) + xf.data[rels_path] = empty_rels_doc() + xf.files[rels_path] = true end - rels_root = root_element(xf.data[sheet_rels_path]) + rels_root = root_element(xf.data[rels_path]) - # Return existing drawing path if one is already linked to this sheet - for node in something(XML.children(rels_root), []) - XML.nodetype(node) === XML.Element || continue - XML.tag(node) == "Relationship" || continue - attrs = XML.attributes(node) - attrs === nothing && continue - if get(attrs, "Type", "") == REL_DRAWING - drawing_file = rsplit(get(attrs, "Target", ""), "/"; limit=2)[2] - return "xl/drawings/" * drawing_file + # Return existing drawing path if already linked + for node in elements_with_tag(rels_root, "Relationship") + if get_attr(node, "Type") == REL_DRAWING + drawing_file = rsplit(get_attr(node, "Target"), "/"; limit=2)[2] + return "xl/drawings/$drawing_file" end end - # Find a free drawing filename (avoids collisions with charts etc.) - drawing_file = let i = 1 - while haskey(xf.data, "xl/drawings/drawing$i.xml"); i += 1; end - "drawing$i.xml" - end - drawing_path = "xl/drawings/" * drawing_file + # Create a new drawing + i = 1 + while haskey(xf.data, "xl/drawings/drawing$i.xml"); i += 1; end + drawing_file = "drawing$i.xml" + drawing_path = "xl/drawings/$drawing_file" xf.data[drawing_path] = empty_drawing_doc() xf.files[drawing_path] = true rid = new_relationship_id(rels_root) - push!(rels_root, XML.Element("Relationship"; + pfx = get_prefix(rels_path, xf) + push!(rels_root, XML.Element(prefixed_tag(pfx, "Relationship"); Id = rid, Type = REL_DRAWING, - Target = "../drawings/" * drawing_file, + Target = "../drawings/$drawing_file", )) - ensure_drawing_element!(xf.data[sheet_path], rid) - register_drawing_content_type!(xf, drawing_path) - + ensure_drawing_element!(xf, xf.data[sheet_path], sheet_path, rid) + register_content_type!(xf, "[Content_Types].xml"; + tag="Override", key="PartName", val="/$drawing_path", + content_type=MIME_DRAWING) return drawing_path end function add_image_rel!(xf::XLSXFile, drawing_path::String, media_name::String)::String drawing_file = rsplit(drawing_path, "/"; limit=2)[2] - rels_path = "xl/drawings/_rels/" * drawing_file * ".rels" + rels_path = "xl/drawings/_rels/$drawing_file.rels" if !haskey(xf.data, rels_path) xf.data[rels_path] = empty_rels_doc() @@ -196,16 +262,14 @@ function add_image_rel!(xf::XLSXFile, drawing_path::String, media_name::String): end rels_root = root_element(xf.data[rels_path]) - # Return existing rid if this media is already referenced - for node in something(XML.children(rels_root), []) - XML.nodetype(node) === XML.Element || continue - attrs = XML.attributes(node) - attrs === nothing && continue - get(attrs, "Target", "") == "../media/$media_name" && return get(attrs, "Id", "") + # Reuse existing rel if the same media is already referenced + for node in elements_with_tag(rels_root, "Relationship") + get_attr(node, "Target") == "../media/$media_name" && return get_attr(node, "Id") end rid = new_relationship_id(rels_root) - push!(rels_root, XML.Element("Relationship"; + pfx = get_prefix(rels_path, xf) + push!(rels_root, XML.Element(prefixed_tag(pfx, "Relationship"); Id = rid, Type = REL_IMAGE, Target = "../media/$media_name", @@ -213,171 +277,87 @@ function add_image_rel!(xf::XLSXFile, drawing_path::String, media_name::String): return rid end +# =========================================================================== +# Anchor +# =========================================================================== + function add_anchor!( xf::XLSXFile, drawing_path::String, img_rid::String, - media_name::String, # passed directly — no rels lookup needed - cellref::CellRef; + media_name::String, + cellref::Union{CellRef, CellRange}; size::Union{Nothing,Tuple{<:Integer,<:Integer}} = nothing, - z_layer::Union{Nothing,Integer} = nothing, ) - root_el = root_element(xf.data[drawing_path]) - - col = column_number(cellref) - 1 - row = row_number(cellref) - 1 - - bytes = xf.binary_data["xl/media/" * media_name] - w_px, h_px = size === nothing ? image_dimensions(bytes) : size - - col_to = col + max(1, round(Int, w_px / 64)) - row_to = row + max(1, round(Int, h_px / 20)) + # Convention: col/row/col_to/row_to are 1-based inclusive throughout. + # build_two_cell_anchor takes 0-based (from inclusive, to exclusive). + # 1-based inclusive → 0-based inclusive: n - 1 + # 1-based inclusive → 0-based exclusive: n (unchanged, since excl = incl + 1 - 1) + + if cellref isa CellRef + col, row = column_number(cellref), row_number(cellref) + bytes = xf.binary_data["xl/media/$media_name"] + w_px, h_px = size !== nothing ? size : image_dimensions(bytes) + col_to = col + max(1, round(Int, w_px / 64)) - 1 + row_to = row + max(1, round(Int, h_px / 20)) - 1 + else + col, row = column_number(cellref.start), row_number(cellref.start) + col_to, row_to = column_number(cellref.stop), row_number(cellref.stop) + end - # Unique id for cNvPr: count existing anchors in this drawing - n_anchors = count(n -> XML.nodetype(n) === XML.Element, - something(XML.children(root_el), [])) + root_el = root_element(xf.data[drawing_path]) + n_anchors = count(_ -> true, element_children(root_el)) push!(root_el, build_two_cell_anchor( - col, row, col_to, row_to, img_rid; - shape_id = n_anchors + 2, # cNvPr id must be ≥2 and unique per drawing - z_layer = z_layer, + col - 1, row - 1, # 0-based inclusive from + col_to, row_to, # 0-based exclusive to + img_rid; + shape_id = n_anchors + 2, )) return col, row, col_to, row_to end +# =========================================================================== +# Relationship / content-type helpers +# =========================================================================== -# --------------------------------------------------------------------------- -# Low-level helpers -# --------------------------------------------------------------------------- - -const REL_DRAWING = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" -const REL_IMAGE = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" -const NS_RELATIONSHIPS = - "http://schemas.openxmlformats.org/package/2006/relationships" -const NS_XDR = - "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" -const NS_A = - "http://schemas.openxmlformats.org/drawingml/2006/main" -const NS_R = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships" - -const MIME_DRAWING = - "application/vnd.openxmlformats-officedocument.drawing+xml" -const EXT_MIME = Dict( - ".png" => "image/png", - ".jpg" => "image/jpeg", - ".jpeg" => "image/jpeg", - ".gif" => "image/gif", -) - -# ── XML document factories ─────────────────────────────────────────────────── - -empty_rels_doc() = XML.Document( - XML.Declaration(; version="1.0", encoding="UTF-8"), - XML.Element("Relationships"; xmlns=NS_RELATIONSHIPS), -) - -empty_drawing_doc() = XML.Document( - XML.Declaration(; version="1.0", encoding="UTF-8"), - XML.Element("xdr:wsDr"; - var"xmlns:xdr" = NS_XDR, - var"xmlns:a" = NS_A, - var"xmlns:r" = NS_R, - ), -) - -# ── Root-element accessor ──────────────────────────────────────────────────── - -function root_element(doc::XML.Node)::XML.Node - for node in something(XML.children(doc), []) - XML.nodetype(node) === XML.Element && return node - end - throw(XLSXError("Document has no root element")) -end - -# ── Relationship helpers ───────────────────────────────────────────────────── - -"""Choose the next available `rId` by scanning existing Id attributes.""" -function new_relationship_id(rels_root::XML.Node)::String - ids = Int[] - for node in something(XML.children(rels_root), []) - XML.nodetype(node) === XML.Element || continue - attrs = XML.attributes(node) - attrs === nothing && continue - m = match(r"rId(\d+)", get(attrs, "Id", "")) - m !== nothing && push!(ids, parse(Int, m[1])) - end - return "rId$(isempty(ids) ? 1 : maximum(ids) + 1)" -end - -# ── Content-type registration (split into two focused functions) ───────────── - -function register_drawing_content_type!(xf::XLSXFile, drawing_path::String)::Nothing - ct_root = root_element(xf.data["[Content_Types].xml"]) - abs_path = "/" * drawing_path - for node in something(XML.children(ct_root), []) - XML.nodetype(node) === XML.Element || continue - XML.tag(node) == "Override" || continue - attrs = XML.attributes(node) - attrs !== nothing && - get(attrs, "PartName", "") == abs_path && return nothing - end - push!(ct_root, XML.Element("Override"; - PartName = abs_path, - ContentType = MIME_DRAWING, - )) - return nothing -end - -function register_image_content_type!(xf::XLSXFile, image_ext::String)::Nothing - ct_root = root_element(xf.data["[Content_Types].xml"]) - ext_no_dot = lstrip(image_ext, '.') - mime = get(EXT_MIME, image_ext, "image/$ext_no_dot") - for node in something(XML.children(ct_root), []) - XML.nodetype(node) === XML.Element || continue - XML.tag(node) == "Default" || continue - attrs = XML.attributes(node) - attrs !== nothing && - get(attrs, "Extension", "") == ext_no_dot && return nothing - end - push!(ct_root, XML.Element("Default"; - Extension = ext_no_dot, - ContentType = mime, - )) +function register_content_type!( + xf::XLSXFile, + path::AbstractString; + tag::AbstractString, key::AbstractString, val::AbstractString, content_type::AbstractString, +)::Nothing + ct_root = root_element(xf.data[path]) + pfx = get_prefix(path, xf) + any(n -> localname(XML.tag(n)) == tag && get_attr(n, key) == val, + element_children(ct_root)) && return nothing + push!(ct_root, XML.Element(prefixed_tag(pfx, tag); Symbol(key) => val, ContentType=content_type)) return nothing end -# ── Sheet XML ──────────────────────────────────────────────────────────────── - -"""Append `` to the sheet root if not already present.""" -function ensure_drawing_element!(sheet_doc::XML.Node, rid::String) +function ensure_drawing_element!(xf::XLSXFile, sheet_doc::XML.Node, sheet_path::String, rid::String) sheet_root = root_element(sheet_doc) - for node in something(XML.children(sheet_root), []) - XML.nodetype(node) === XML.Element || continue - XML.tag(node) == "drawing" && return nothing - end - attrs = XML.attributes(sheet_root) - if attrs === nothing || !haskey(attrs, "xmlns:r") + any(n -> localname(XML.tag(n)) == "drawing", element_children(sheet_root)) && return nothing + if !haskey(something(XML.attributes(sheet_root), Dict()), "xmlns:r") sheet_root["xmlns:r"] = NS_R end - el = XML.Element("drawing") + pfx = get_prefix(sheet_path, xf) + el = XML.Element(prefixed_tag(pfx, "drawing")) el["r:id"] = rid push!(sheet_root, el) return nothing end -# ── Anchor XML ─────────────────────────────────────────────────────────────── +# =========================================================================== +# Low-level XML builder +# =========================================================================== function build_two_cell_anchor( - col::Int, row::Int, - col_to::Int, row_to::Int, - img_rid::String; - shape_id::Int, - z_layer = nothing)::XML.Node - + col::Int, row::Int, # 0-based inclusive + col_to::Int, row_to::Int, # 0-based exclusive + img_rid::String; + shape_id::Int, +)::XML.Node tel(tag, text) = XML.Element(tag, XML.Text(text)) function cell_marker(tag, c, r) @@ -389,37 +369,38 @@ function build_two_cell_anchor( ) end - nvpicpr = XML.Element("xdr:nvPicPr", - XML.Element("xdr:cNvPr"; id=string(shape_id), name="Image $shape_id"), - XML.Element("xdr:cNvPicPr", - XML.Element("a:picLocks"; noChangeAspect="1"), - ), - ) - blip = XML.Element("a:blip") - blip["r:embed"] = img_rid # colon in attribute name — set after construction - blipfill = XML.Element("xdr:blipFill", - blip, - XML.Element("a:stretch", XML.Element("a:fillRect")), - ) - - sppr = XML.Element("xdr:spPr", - XML.Element("a:xfrm", - XML.Element("a:off"; x="0", y="0"), - XML.Element("a:ext"; cx="0", cy="0"), - ), - XML.Element("a:prstGeom", XML.Element("a:avLst"); prst="rect"), - ) + blip["r:embed"] = img_rid return XML.Element("xdr:twoCellAnchor", cell_marker("xdr:from", col, row), cell_marker("xdr:to", col_to, row_to), - XML.Element("xdr:pic", nvpicpr, blipfill, sppr), + XML.Element("xdr:pic", + XML.Element("xdr:nvPicPr", + XML.Element("xdr:cNvPr"; id=string(shape_id), name="Image $shape_id"), + XML.Element("xdr:cNvPicPr", + XML.Element("a:picLocks"; noChangeAspect="1"), + ), + ), + XML.Element("xdr:blipFill", + blip, + XML.Element("a:stretch", XML.Element("a:fillRect")), + ), + XML.Element("xdr:spPr", + XML.Element("a:xfrm", + XML.Element("a:off"; x="0", y="0"), + XML.Element("a:ext"; cx="0", cy="0"), + ), + XML.Element("a:prstGeom", XML.Element("a:avLst"); prst="rect"), + ), + ), XML.Element("xdr:clientData"), ) end -# ── Image format detection ─────────────────────────────────────────────────── +# =========================================================================== +# Image format detection +# =========================================================================== function detect_image_ext(bytes::Vector{UInt8})::String length(bytes) ≥ 8 && @@ -431,20 +412,22 @@ function detect_image_ext(bytes::Vector{UInt8})::String throw(XLSXError("Unsupported or unknown image format")) end -function image_dimensions(bytes::Vector{UInt8}) - # PNG: IHDR chunk begins at byte 9; width at 17-20, height at 21-24 (big-endian) +function image_dimensions(bytes::Vector{UInt8})::Tuple{Int,Int} + # PNG: width/height in bytes 17–20 and 21–24 if length(bytes) ≥ 24 && bytes[1:8] == UInt8[0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A] - w = Int(bytes[17]) << 24 | Int(bytes[18]) << 16 | Int(bytes[19]) << 8 | Int(bytes[20]) - h = Int(bytes[21]) << 24 | Int(bytes[22]) << 16 | Int(bytes[23]) << 8 | Int(bytes[24]) + w = Int(bytes[17]) << 24 | Int(bytes[18]) << 16 | + Int(bytes[19]) << 8 | Int(bytes[20]) + h = Int(bytes[21]) << 24 | Int(bytes[22]) << 16 | + Int(bytes[23]) << 8 | Int(bytes[24]) return (w, h) end - # GIF: little-endian width at bytes 7-8, height at 9-10 + # GIF: little-endian 16-bit at bytes 7–10 if length(bytes) ≥ 10 && bytes[1:4] == UInt8[0x47,0x49,0x46,0x38] return (Int(bytes[7]) | Int(bytes[8]) << 8, Int(bytes[9]) | Int(bytes[10]) << 8) end - # JPEG: scan for SOF0/SOF1/SOF2 (0xFFC0–0xFFC3) markers + # JPEG: scan for SOF marker if length(bytes) ≥ 2 && bytes[1] == 0xFF && bytes[2] == 0xD8 i = 3 while i + 8 ≤ length(bytes) @@ -460,4 +443,108 @@ function image_dimensions(bytes::Vector{UInt8}) throw(XLSXError("Could not find JPEG SOF marker")) end throw(XLSXError("Unsupported image format for dimension extraction")) +end + +# =========================================================================== +# getImages — public API +# =========================================================================== + +function getImages(s::Worksheet)::Vector{ImageInfo} + xf = get_xlsxfile(s) + sheet_path = get_relationship_target_by_id("xl", get_workbook(s), s.relationship_id) + return _images_for_sheet(xf, sheet_path, s.name) +end + +function getImages(xf::XLSXFile)::Vector{ImageInfo} + wb = get_workbook(xf) + return reduce(vcat, [ + _images_for_sheet(xf, + get_relationship_target_by_id("xl", wb, sheet.relationship_id), + sheet.name) + for sheet in wb.sheets + ]; init=ImageInfo[]) +end + +function _images_for_sheet(xf::XLSXFile, sheet_path::String, sheet_name::String)::Vector{ImageInfo} + drawing_path = _drawing_path_for_sheet(xf, sheet_path) + drawing_path === nothing && return ImageInfo[] + return _images_for_drawing(xf, drawing_path, sheet_name) +end + +function _drawing_path_for_sheet(xf::XLSXFile, sheet_path::String)::Union{Nothing,String} + sheet_dir, sheet_file = rsplit(sheet_path, "/"; limit=2) + rels_path = "$sheet_dir/_rels/$sheet_file.rels" + haskey(xf.data, rels_path) || return nothing + + for node in elements_with_tag(root_element(xf.data[rels_path]), "Relationship") + if get_attr(node, "Type") == REL_DRAWING + drawing_file = rsplit(get_attr(node, "Target"), "/"; limit=2)[2] + return "xl/drawings/$drawing_file" + end + end + return nothing +end + +function _images_for_drawing(xf::XLSXFile, drawing_path::String, sheet_name::String)::Vector{ImageInfo} + haskey(xf.data, drawing_path) || return ImageInfo[] + drawing_file = rsplit(drawing_path, "/"; limit=2)[2] + rels_path = "xl/drawings/_rels/$drawing_file.rels" + haskey(xf.data, rels_path) || return ImageInfo[] + + rid_to_media = _rid_to_media(xf.data[rels_path]) + return filter(!isnothing, [ + _parse_anchor(node, rid_to_media, sheet_name) + for node in elements_with_tag(root_element(xf.data[drawing_path]), "twoCellAnchor") + ]) +end + +function _rid_to_media(rels_doc::XML.Node)::Dict{String,String} + Dict( + get_attr(n, "Id") => rsplit(get_attr(n, "Target"), "/"; limit=2)[2] + for n in elements_with_tag(root_element(rels_doc), "Relationship") + if get_attr(n, "Type") == REL_IMAGE && !isempty(get_attr(n, "Id")) + ) +end + +function _parse_anchor( + anchor::XML.Node, + rid_to_media::Dict{String,String}, + sheet_name::String, +)::Union{Nothing,ImageInfo} + from_ref = _parse_cell_marker(anchor, "from"; is_to=false) + to_ref = _parse_cell_marker(anchor, "to"; is_to=true) + rid = _find_blip_rid(anchor) + media_name = rid !== nothing ? get(rid_to_media, rid, nothing) : nothing + (from_ref === nothing || to_ref === nothing || media_name === nothing) && return nothing + return (sheet=sheet_name, media_name=media_name, from=from_ref, to=to_ref) +end + +function _parse_cell_marker(anchor::XML.Node, tag::String; is_to::Bool)::Union{Nothing,String} + marker = nothing + for n in element_children(anchor) + localname(XML.tag(n)) == tag && (marker = n; break) + end + marker === nothing && return nothing + vals = Dict(localname(XML.tag(c)) => _text_value(c) for c in element_children(marker)) + col = get(vals, "col", nothing) + row = get(vals, "row", nothing) + (col === nothing || row === nothing) && return nothing + adj = is_to ? 0 : 1 + return string(CellRef(parse(Int, row) + adj, parse(Int, col) + adj)) +end + +function _find_blip_rid(node::XML.Node)::Union{Nothing,String} + XML.nodetype(node) === XML.Element || return nothing + if localname(XML.tag(node)) == "blip" + attrs = XML.attributes(node) + attrs === nothing && return nothing + return something(get(attrs, "r:embed", nothing), + get(attrs, "{$(NS_R)}embed", nothing), + nothing) + end + for child in something(XML.children(node), []) + rid = _find_blip_rid(child) + rid !== nothing && return rid + end + return nothing end \ No newline at end of file diff --git a/src/relationship.jl b/src/relationship.jl index 11376cf2..d4cb3ea8 100644 --- a/src/relationship.jl +++ b/src/relationship.jl @@ -75,38 +75,22 @@ function get_workbook_relationship_root(xf::XLSXFile)::XML.Node return xroot end +function new_relationship_id(rels_root::XML.Node)::String + ids = [parse(Int, m[1]) + for n in element_children(rels_root) + for m in [match(r"rId(\d+)", get_attr(n, "Id"))] + if m !== nothing] + return "rId$(isempty(ids) ? 1 : maximum(ids) + 1)" +end + # Adds new relationship. Returns new generated rId. function add_relationship!(wb::Workbook, target::String, _type::String)::String - xf = get_xlsxfile(wb) -# !is_writable(xf) && throw(XLSXError("XLSXFile instance is not writable.")) - local rId::String - - let - got_unique_id = false - id = 1 - - while !got_unique_id - got_unique_id = true - rId = string("rId", id) - for r in wb.relationships - if r.Id == rId - got_unique_id = false - id += 1 - break - end - end - end - end - - # adds to relationship vector - new_relationship = Relationship(rId, _type, target) - push!(wb.relationships, new_relationship) - - # adds to XML tree + xf = get_xlsxfile(wb) xroot = get_workbook_relationship_root(xf) - el = XML.Element("Relationship"; Id=rId, Type=_type, Target=target) - push!(xroot, el) + rId = new_relationship_id(xroot) + push!(wb.relationships, Relationship(rId, _type, target)) + push!(xroot, XML.Element("Relationship"; Id=rId, Type=_type, Target=target)) return rId end diff --git a/src/write.jl b/src/write.jl index d67cb781..d35a4804 100644 --- a/src/write.jl +++ b/src/write.jl @@ -1289,6 +1289,57 @@ function copysheet!(ws::Worksheet, name::AbstractString="")::Worksheet # insert the copied sheet into the workbook new_ws = insertsheet!(wb, xdoc, new_cache, ws.sst_count, pfx, name; dim) + # Copy images if the sheet has a drawing + sheet_path = get_relationship_target_by_id("xl", wb, ws.relationship_id) + drawing_path = _drawing_path_for_sheet(xl, sheet_path) + + if drawing_path !== nothing + src_drawing_file = rsplit(drawing_path, "/"; limit=2)[2] + src_drawing_rels = "xl/drawings/_rels/$src_drawing_file.rels" + + # Pick a fresh drawing file name + i = 1 + while haskey(xl.data, "xl/drawings/drawing$i.xml"); i += 1; end + new_drawing_file = "drawing$i.xml" + new_drawing_path = "xl/drawings/$new_drawing_file" + new_drawing_rels = "xl/drawings/_rels/$new_drawing_file.rels" + + # Copy drawing XML and rels verbatim + xl.data[new_drawing_path] = copynode(xl.data[drawing_path]) + xl.files[new_drawing_path] = true + xl.data[new_drawing_rels] = copynode(xl.data[src_drawing_rels]) + xl.files[new_drawing_rels] = true + + # Register content types for drawing and any media it references + register_content_type!(xl, "[Content_Types].xml"; + tag="Override", key="PartName", val="/$new_drawing_path", + content_type=MIME_DRAWING) + for img in _images_for_drawing(xl, drawing_path, ws.name) + ext = detect_image_ext(xl.binary_data["xl/media/$(img.media_name)"]) + ext_no_dot = String(lstrip(ext, '.')) + register_content_type!(xl, "[Content_Types].xml"; + tag="Default", key="Extension", val=ext_no_dot, + content_type=get(EXT_MIME, ext, "image/$ext_no_dot")) + end + + # Link drawing to new sheet using existing helpers + new_sheet_path = get_relationship_target_by_id("xl", wb, new_ws.relationship_id) + new_rels_path = let (d, f) = rsplit(new_sheet_path, "/"; limit=2) + "$d/_rels/$f.rels" + end + if !haskey(xl.data, new_rels_path) + xl.data[new_rels_path] = empty_rels_doc() + xl.files[new_rels_path] = true + end + new_rels_root = root_element(xl.data[new_rels_path]) + rid = new_relationship_id(new_rels_root) + pfx = get_prefix(new_rels_path, xl) + push!(new_rels_root, XML.Element(prefixed_tag(pfx, "Relationship"); + Id=rid, Type=REL_DRAWING, Target="../drawings/$new_drawing_file", + )) + ensure_drawing_element!(xl, xl.data[new_sheet_path], new_sheet_path, rid) + end + # copy defined names from the original worksheet to the new worksheet ws_keys = [x for x in keys(wb.worksheet_names) if first(x) == ws.sheetId] for k in ws_keys diff --git a/test/data/track_start.jpg b/test/data/track_start.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14dec2388311909e6e165cc6ca451458681d845c GIT binary patch literal 9894 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 LOPij#y)6GfxY*N) literal 0 HcmV?d00001 diff --git a/test/runtests.jl b/test/runtests.jl index dc060010..3f5c29c8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6956,6 +6956,7 @@ end XLSX.mergeCells(f[2], "A4:C5") @test XLSX.setConditionalFormat(f[2], "H2:H15", :dataBar) == 0 XLSX.setFormula(f[2], "A16:N16", "=sum(A2:A15)") + XLSX.addImage(f[2], "B17", joinpath(data_directory, "track_start.jpg")) XLSX.copysheet!(f[2], "newSheet") XLSX.writexlsx("mytest.xlsx", f, overwrite=true) @@ -6981,6 +6982,7 @@ end @test XLSX.getFormula(f2[2], "M16") == "=sum(M2:M15)" @test XLSX.hassheet(f, "newSheet") + @test XLSX.getImages(f["newSheet"]) == [(sheet = "newSheet", media_name = "image1.jpg", from = "B17", to = "E27")] for row = 1:16 for col = 1:14 @@ -7533,3 +7535,97 @@ end end end end + + +@testset "Add Images" begin + REL_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + jpeg = joinpath(data_directory, "track_start.jpg") + bytes = read(jpeg) + + # Helper so each testset gets a fresh workbook + fresh() = (xf = XLSX.newxlsx(); (xf, xf["Sheet1"])) + + @testset "cell addressing variants" begin + cases = [ + ((1, 1), "A1", nothing), + (("B2",), "B2", nothing), + (("C3:D5",), "C3", "D5"), + ((CellRef("E7"),), "E7", nothing), + ((XLSX.CellRange("F4:H9"),), "F4", "H9"), + ] + for (args, exp_from, exp_to) in cases + xf, s = fresh() + info = XLSX.addImage(s, args..., jpeg) + @test info.from == exp_from + exp_to === nothing || @test info.to == exp_to + @test haskey(xf.binary_data, "xl/media/" * info.media_name) + end + end + @testset "IOBuffer input" begin + xf, s = fresh() + info = XLSX.addImage(s, 3, 4, IOBuffer(copy(bytes))) + @test startswith(info.media_name, "image") + @test xf.binary_data["xl/media/" * info.media_name] == bytes + @test haskey(xf.data, "xl/drawings/drawing1.xml") + end + + @testset "drawing XML and relationships" begin + xf, s = fresh() + info = XLSX.addImage(s, "B2", jpeg; size=(128, 128)) + + # Relationships + rels_root = xf.data["xl/drawings/_rels/drawing1.xml.rels"][end] + rel_nodes = [n for n in XML.children(rels_root) if XML.tag(n) == "Relationship"] + @test !isempty(rel_nodes) + @test any(get(XML.attributes(n), "Type", "") == REL_IMAGE for n in rel_nodes) + + # Anchor geometry + drawing_root = xf.data["xl/drawings/drawing1.xml"][end] + anchors = [n for n in XML.children(drawing_root) if XML.tag(n) == "xdr:twoCellAnchor"] + @test length(anchors) == 1 + @test XLSX._parse_cell_marker(anchors[1], "from"; is_to=false) == info.from + @test XLSX._parse_cell_marker(anchors[1], "to"; is_to=true) == info.to + end + + @testset "multiple images" begin + xf, s = fresh() + info1 = XLSX.addImage(s, 1, 1, jpeg) + info2 = XLSX.addImage(s, 5, 5, jpeg) + + @test info1.media_name != info2.media_name + @test haskey(xf.binary_data, "xl/media/" * info1.media_name) + @test haskey(xf.binary_data, "xl/media/" * info2.media_name) + + rels_root = xf.data["xl/drawings/_rels/drawing1.xml.rels"][end] + rel_nodes = [n for n in XML.children(rels_root) if XML.tag(n) == "Relationship"] + @test length(rel_nodes) == 2 + @test all(get(XML.attributes(n), "Type", "") == REL_IMAGE for n in rel_nodes) + + drawing_root = xf.data["xl/drawings/drawing1.xml"][end] + anchors = [n for n in XML.children(drawing_root) if XML.tag(n) == "xdr:twoCellAnchor"] + @test length(anchors) == 2 + @test XLSX._parse_cell_marker(anchors[1], "from"; is_to=false) == info1.from + @test XLSX._parse_cell_marker(anchors[2], "from"; is_to=false) == info2.from + end + + @testset "round-trip (file and IOBuffer)" begin + for (label, src) in [("file path", jpeg), ("IOBuffer", IOBuffer(copy(bytes)))] + xf, s = fresh() + XLSX.addImage(s, 1, 1, src) + tmp = tempname() * ".xlsx" + XLSX.writexlsx(tmp, xf) + @test isfile(tmp) && filesize(tmp) > 0 + + xf2 = XLSX.readxlsx(tmp) + imgs = XLSX.getImages(xf2) + @test length(imgs) == 1 + @test imgs[1].sheet == "Sheet1" + @test startswith(imgs[1].media_name, "image") + end + end + + @testset "invalid cell reference" begin + xf, s = fresh() + @test_throws ArgumentError XLSX.addImage(s, "ZZZ9999", jpeg) + end +end \ No newline at end of file From 8319c6a4e3360a97cfe8a9e835c25bea3161f87d Mon Sep 17 00:00:00 2001 From: Tim Gebbels Date: Fri, 15 May 2026 13:26:31 +0100 Subject: [PATCH 3/6] Fix `deletesheet!` to clean up images, too. --- CHANGELOG.md | 2 +- docs/src/api/files.md | 1 + src/XLSX.jl | 1 + src/write.jl | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d95c8de0..4925282c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - add `dependabot` support ([#130](https://github.com/JuliaData/XLSX.jl/issues/130)) - add ability to read native template (`.xltx`) files ([#293](https://github.com/JuliaData/XLSX.jl/issues/293)) - add `getDefinedNames` to mirror `addDefinedName` -- support adding png, jpg or gif image into a sheet ([#134](https://github.com/JuliaData/XLSX.jl/issues/134)) +- support adding png, jpg or gif images into a sheet ([#134](https://github.com/JuliaData/XLSX.jl/issues/134)) ## [v0.11.7](https://github.com/JuliaData/XLSX.jl/tree/v0.11.7) - 2026-05-07 - Fix issue [#368](https://github.com/JuliaData/XLSX.jl/issues/368) for both reading and writing diff --git a/docs/src/api/files.md b/docs/src/api/files.md index 6a2a7094..9c581ffe 100644 --- a/docs/src/api/files.md +++ b/docs/src/api/files.md @@ -24,4 +24,5 @@ XLSX.addsheet! XLSX.renamesheet! XLSX.copysheet! XLSX.deletesheet! +XLSX.addImage ``` diff --git a/src/XLSX.jl b/src/XLSX.jl index 58e8a368..c35b9444 100644 --- a/src/XLSX.jl +++ b/src/XLSX.jl @@ -23,6 +23,7 @@ export writexlsx, savexlsx, Worksheet, sheetnames, sheetcount, hassheet, addsheet!, renamesheet!, copysheet!, deletesheet!, + addImage, # Cells & data CellRef, row_number, column_number, eachtablerow, readdata, getdata, gettable, readtable, readto, diff --git a/src/write.jl b/src/write.jl index d35a4804..978dcb18 100644 --- a/src/write.jl +++ b/src/write.jl @@ -1619,6 +1619,46 @@ function deletesheet!(wb::Workbook, name::AbstractString)::XLSXFile delete!(wb.worksheet_names, oldkey) end + # Drawing and image cleanup + sheet_path = "xl/worksheets/sheet" * rId[4:end] * ".xml" + drawing_path = _drawing_path_for_sheet(xf, sheet_path) + + if drawing_path !== nothing + drawing_file = rsplit(drawing_path, "/"; limit=2)[2] + drawing_rels = "xl/drawings/_rels/$drawing_file.rels" + + # Remove media — but only if no other sheet references it + all_images = getImages(xf) + deleted_images = _images_for_drawing(xf, drawing_path, name) + deleted_names = Set(img.media_name for img in deleted_images) + still_used = Set(img.media_name for img in all_images + if img.sheet != name) + for media_name in deleted_names + if media_name ∉ still_used + delete!(xf.binary_data, "xl/media/$media_name") + end + end + + # Remove drawing XML and rels + for path in (drawing_path, drawing_rels) + delete!(xf.files, path) + delete!(xf.data, path) + end + + # Remove drawing Override from [Content_Types].xml + ctype_root = xmlroot(xf, "[Content_Types].xml")[end] + cont = XML.children(ctype_root) + idx = findfirst(i -> haskey(cont[i], "PartName") && + cont[i]["PartName"] == "/$drawing_path", eachindex(cont)) + idx !== nothing && deleteat!(cont, idx) + + # Remove sheet rels file (contains the drawing relationship) + sheet_dir, sheet_file = rsplit(sheet_path, "/"; limit=2) + sheet_rels = "$sheet_dir/_rels/$sheet_file.rels" + delete!(xf.files, sheet_rels) + delete!(xf.data, sheet_rels) + end + # Files xml_filename = "xl/worksheets/sheet" * rId[4:end] * ".xml" if in(xml_filename, keys(xf.files)) From 861fd7fd9f9c3f34b77db6882979dcbe6cde94c0 Mon Sep 17 00:00:00 2001 From: Tim Gebbels Date: Fri, 15 May 2026 14:11:01 +0100 Subject: [PATCH 4/6] Extend tests for addImage and getImages functions --- test/runtests.jl | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 3f5c29c8..ab86eb72 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7628,4 +7628,53 @@ end xf, s = fresh() @test_throws ArgumentError XLSX.addImage(s, "ZZZ9999", jpeg) end + + @testset "image cleaned up when sheet deleted" begin + xf = XLSX.newxlsx() + s1 = xf["Sheet1"] + XLSX.addImage(s1, 1, 1, jpeg) + info = XLSX.getImages(s1)[1] + + wb = XLSX.get_workbook(xf) + XLSX.addsheet!(wb, "Sheet2") # need a second sheet to allow deletion + XLSX.deletesheet!(wb, "Sheet1") + + # Media removed + @test !haskey(xf.binary_data, "xl/media/" * info.media_name) + + # Drawing XML and rels removed + @test !haskey(xf.data, "xl/drawings/drawing1.xml") + @test !haskey(xf.data, "xl/drawings/_rels/drawing1.xml.rels") + + # No images reported + @test isempty(XLSX.getImages(xf)) + end + + @testset "shared media preserved when only one sheet deleted" begin + xf = XLSX.newxlsx() + s1 = xf["Sheet1"] + wb = XLSX.get_workbook(xf) + + XLSX.addImage(s1, 1, 1, jpeg) + + XLSX.copysheet!(s1, "Sheet2") + s2 = xf["Sheet2"] + + info1 = XLSX.getImages(s1)[1] + info2 = XLSX.getImages(s2)[1] + + # Both sheets reference the same media file + @test info1.media_name == info2.media_name + media_key = "xl/media/" * info1.media_name + + XLSX.deletesheet!(wb, "Sheet1") + + # Media still present — Sheet2 still references it + @test haskey(xf.binary_data, media_key) + + # Sheet2 image still retrievable + imgs = XLSX.getImages(xf) + @test length(imgs) == 1 + @test imgs[1].sheet == "Sheet2" + end end \ No newline at end of file From 9a82784bdadd9525312b83c4d1c6d92dd75ecfa7 Mon Sep 17 00:00:00 2001 From: Tim Gebbels Date: Fri, 15 May 2026 14:33:25 +0100 Subject: [PATCH 5/6] Add png file to tests. --- src/images.jl | 3 --- test/data/track_start.png | Bin 0 -> 95792 bytes test/runtests.jl | 5 +++-- 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 test/data/track_start.png diff --git a/src/images.jl b/src/images.jl index b9ccf89b..66eceb57 100644 --- a/src/images.jl +++ b/src/images.jl @@ -56,9 +56,6 @@ function _text_value(node::XML.Node)::Union{Nothing,String} return nothing end -# Returns the local part of a tag, e.g. "pkg:Relationship" → "Relationship" -localname(tag::AbstractString) = last(split(tag, ':'; limit=2)) - # Prepends prefix if non-empty: prefixed_tag("pkg", "Relationship") → "pkg:Relationship" prefixed_tag(prefix::AbstractString, name::AbstractString) = isempty(prefix) ? name : "$prefix:$name" diff --git a/test/data/track_start.png b/test/data/track_start.png new file mode 100644 index 0000000000000000000000000000000000000000..781d8395a3f033963abb9e6bca392a7d33a7f983 GIT binary patch literal 95792 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

Ke@wjhhf+H6*xiZ#*FcR3d#E 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 literal 0 HcmV?d00001 diff --git a/test/runtests.jl b/test/runtests.jl index ab86eb72..938c880d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7540,6 +7540,7 @@ end @testset "Add Images" begin REL_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" jpeg = joinpath(data_directory, "track_start.jpg") + png = joinpath(data_directory, "track_start.png") bytes = read(jpeg) # Helper so each testset gets a fresh workbook @@ -7555,7 +7556,7 @@ end ] for (args, exp_from, exp_to) in cases xf, s = fresh() - info = XLSX.addImage(s, args..., jpeg) + info = XLSX.addImage(s, args..., png) @test info.from == exp_from exp_to === nothing || @test info.to == exp_to @test haskey(xf.binary_data, "xl/media/" * info.media_name) @@ -7632,7 +7633,7 @@ end @testset "image cleaned up when sheet deleted" begin xf = XLSX.newxlsx() s1 = xf["Sheet1"] - XLSX.addImage(s1, 1, 1, jpeg) + XLSX.addImage(s1, 1, 1, png) info = XLSX.getImages(s1)[1] wb = XLSX.get_workbook(xf) From 4fb78627c589413e71baf2b8fa0bf0a3d1326e04 Mon Sep 17 00:00:00 2001 From: Tim Gebbels Date: Fri, 15 May 2026 15:39:34 +0100 Subject: [PATCH 6/6] Add an example of adding an image to a sheet. --- docs/src/examples.md | 31 ++++++++++++++++++++++++++++++- docs/src/images/Add_image_1.png | Bin 0 -> 7938 bytes docs/src/images/Add_image_2.png | Bin 0 -> 22455 bytes src/images.jl | 5 ++--- src/table.jl | 1 + 5 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 docs/src/images/Add_image_1.png create mode 100644 docs/src/images/Add_image_2.png diff --git a/docs/src/examples.md b/docs/src/examples.md index 17e166ae..f9709692 100644 --- a/docs/src/examples.md +++ b/docs/src/examples.md @@ -326,4 +326,33 @@ XLSX.setBorder(s, "F112,F113"; allsides = ["style" => "thin", "color" => "black" XLSX.setBorder(s, "B112:B114,D112:D115"; outside = ["style" => "thin", "color" => "black"]) XLSX.writexlsx("myNewTemplate.xlsx", f, overwrite=true) -``` \ No newline at end of file +``` + +## Adding a plot image + +Use Julia functionality to create a chart based upon data from a spreadsheet and then add that chart +(as a static image) back into the worksheet. + +![image|320x500](./images/Add_image_1.png) + +```julia +using CairoMakie, XLSX + +f=opentemplate("Example_add_chart.xlsx") +table = XLSX.gettable(f[1]) +x = 1:length(table.data[1]) + +fig = Figure() +ax = Axis(fig[1, 1], xticks=(x, table.data[1])) +barplot!(ax, x, table.data[2]) + +# Write PNG to IOBuffer +io = IOBuffer() +show(io, MIME("image/png"), fig) + +XLSX.addImage(f[1], "D2:H12", io) + +XLSX.writexlsx("Example_add_chart_out.xlsx", f, overwrite=true) +``` + +![image|320x500](./images/Add_image_2.png) \ No newline at end of file diff --git a/docs/src/images/Add_image_1.png b/docs/src/images/Add_image_1.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd390fa059ffcfb03e324aeb2160252bd8ce453 GIT binary patch literal 7938 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/Add_image_2.png b/docs/src/images/Add_image_2.png new file mode 100644 index 0000000000000000000000000000000000000000..141f2bf58130c706eea2bf16eba0e6bb8927f96d GIT binary patch literal 22455 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 U$wW;_0dIBiboFyt=akR{08mp~QUCw| literal 0 HcmV?d00001 diff --git a/src/images.jl b/src/images.jl index 66eceb57..356f4bad 100644 --- a/src/images.jl +++ b/src/images.jl @@ -109,9 +109,8 @@ Supported formats (auto-detected): PNG, JPEG, GIF. # Keyword Arguments -- `size`: provide the desired size of the image as a tuple of integers: `(width_px, height_px)`. Actual size -will snap to the nearest actual cell boundaries. If `nothing` (default), the image's native pixel size is used. -Ignored if `ref` is a cell range. +- `size`: provide the desired size of the image as a tuple of integers: `(width_px, height_px)`. If `nothing` (default), +the image's native pixel size is used. Ignored if `ref` is a cell range. Actual size will snap to the nearest cell boundaries. # Return Value diff --git a/src/table.jl b/src/table.jl index 5bdfc21b..5f1b1167 100644 --- a/src/table.jl +++ b/src/table.jl @@ -916,6 +916,7 @@ XLSXFile("blank.xlsx") containing 1 Worksheet """ function XLSXFile(table) + Tables.istable(table) || throw(XLSXError("Input must be a Tables.jl compatible table.")) isempty(Tables.rows(table)) && throw(XLSXError("Cannot create XLSXFile from an empty table.")) xf = newxlsx() writetable!(xf[1], table)