Added basic support for embedded images

This commit is contained in:
Kostas Dizas 2018-10-05 14:30:54 +01:00
parent 10c5781e6d
commit 698b40c54c
15 changed files with 357 additions and 16 deletions

View File

@ -0,0 +1,59 @@
// Copyright (c) 2018
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#pragma once
#include <string>
#include <vector>
#include <xlnt/xlnt_config.hpp>
namespace xml {
class parser;
class serializer;
}
namespace xlnt {
class worksheet;
namespace drawing {
/// <summary>
/// The spreadsheet_drawing class encapsulates the information
/// captured from objects within the spreadsheetDrawing schema.
/// </summary>
class XLNT_API spreadsheet_drawing
{
public:
spreadsheet_drawing(xml::parser &parser);
void serialize(xml::serializer &serializer);
std::vector<std::string> get_embed_ids();
private:
std::string serialized_value_;
std::vector<std::string> embed_ids_;
};
} // namespace drawing
} // namespace xlnt

View File

@ -50,7 +50,6 @@ class cell;
class cell_style;
class color;
class const_worksheet_iterator;
class drawing;
class fill;
class font;
class format;

View File

@ -745,6 +745,12 @@ public:
/// </summary>
void format_properties(const sheet_format_properties &properties);
/// <summary>
/// Returns true if this worksheet has a page setup.
/// </summary>
bool has_drawing() const;
private:
friend class cell;
friend class const_range_iterator;

View File

@ -152,6 +152,9 @@ const std::unordered_map<std::string, std::string> &constants::namespaces()
{"xml", "http://www.w3.org/XML/1998/namespace"},
{"xsi", "http://www.w3.org/2001/XMLSchema-instance"},
{"a", "http://schemas.openxmlformats.org/drawingml/2006/main"},
{"xdr", "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"},
{"loext", "http://schemas.libreoffice.org/"}
};

View File

@ -27,6 +27,7 @@
#include <unordered_map>
#include <vector>
#include <xlnt/drawing/spreadsheet_drawing.hpp>
#include <xlnt/packaging/ext_list.hpp>
#include <xlnt/workbook/named_range.hpp>
#include <xlnt/worksheet/column_properties.hpp>
@ -157,6 +158,9 @@ struct worksheet_impl
optional<sheet_pr> sheet_properties_;
optional<ext_list> extension_list_;
std::string drawing_rel_id_;
optional<drawing::spreadsheet_drawing> drawing_;
};
} // namespace detail

View File

@ -29,6 +29,7 @@
#include <xlnt/cell/cell.hpp>
#include <xlnt/cell/comment.hpp>
#include <xlnt/cell/hyperlink.hpp>
#include <xlnt/drawing/spreadsheet_drawing.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/utils/optional.hpp>
#include <xlnt/utils/path.hpp>
@ -1188,7 +1189,11 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
}
else if (current_worksheet_element == qn("spreadsheetml", "drawing")) // CT_Drawing 0-1
{
skip_remaining_content(current_worksheet_element);
if (parser().attribute_present(qn("r", "id")))
{
auto drawing_rel_id = parser().attribute(qn("r", "id"));
ws.d_->drawing_rel_id_ = drawing_rel_id;
}
}
else if (current_worksheet_element == qn("spreadsheetml", "legacyDrawing"))
{
@ -1236,6 +1241,20 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
}
}
if (manifest.has_relationship(sheet_path, xlnt::relationship_type::drawings))
{
auto drawings_part = manifest.canonicalize({workbook_rel, sheet_rel,
manifest.relationship(sheet_path, xlnt::relationship_type::drawings)});
auto receive = xml::parser::receive_default;
auto drawings_part_streambuf = archive_->open(drawings_part);
std::istream drawings_part_stream(drawings_part_streambuf.get());
xml::parser parser(drawings_part_stream, drawings_part.string(), receive);
parser_ = &parser;
read_drawings(ws, drawings_part);
}
return ws;
}
@ -2695,8 +2714,26 @@ void xlsx_consumer::read_comments(worksheet ws)
expect_end_element(qn("spreadsheetml", "comments"));
}
void xlsx_consumer::read_drawings()
void xlsx_consumer::read_drawings(worksheet ws, const path &part)
{
auto images = manifest().relationships(part, relationship_type::image);
auto sd = drawing::spreadsheet_drawing(parser());
for (const auto image_rel_id : sd.get_embed_ids())
{
auto image_rel = std::find_if(images.begin(), images.end(),
[&](const relationship &r) { return r.id() == image_rel_id; });
if (image_rel != images.end())
{
const auto url = image_rel->target().path().resolve(part.parent());
read_image(url);
}
}
ws.d_->drawing_ = sd;
}
// Unknown Parts

View File

@ -232,7 +232,7 @@ private:
/// <summary>
///
/// </summary>
void read_drawings();
void read_drawings(worksheet ws, const path &part);
// Unknown Parts

View File

@ -2965,10 +2965,13 @@ void xlsx_producer::write_worksheet(const relationship &rel)
write_start_element(xmlns, "legacyDrawing");
write_attribute(xml::qname(xmlns_r, "id"), child_rel.id());
write_end_element(xmlns, "legacyDrawing");
// todo: there's only one of these per sheet, right?
break;
}
else if (child_rel.type() == xlnt::relationship_type::drawings)
{
write_start_element(xmlns, "drawing");
write_attribute(xml::qname(xmlns_r, "id"), child_rel.id());
write_end_element(xmlns, "drawing");
}
}
}
@ -3016,6 +3019,10 @@ void xlsx_producer::write_worksheet(const relationship &rel)
{
write_vml_drawings(child_rel, ws, cells_with_comments);
}
else if (child_rel.type() == relationship_type::drawings)
{
write_drawings(child_rel, ws);
}
}
}
}
@ -3271,6 +3278,38 @@ void xlsx_producer::write_vml_drawings(const relationship &rel, worksheet ws, co
write_end_element("xml");
}
void xlsx_producer::write_drawings(const relationship &drawing_rel, worksheet ws)
{
const auto workbook_rel = source_.manifest().relationship(path("/"), relationship_type::office_document);
const auto worksheet_rel = ws.referring_relationship();
const auto drawing_part = source_.manifest().canonicalize({workbook_rel, worksheet_rel, drawing_rel});
const auto drawing_rels = source_.manifest().relationships(drawing_part);
if (ws.d_->drawing_.is_set())
{
ws.d_->drawing_.get().serialize(*current_part_serializer_);
}
if (!drawing_rels.empty())
{
write_relationships(drawing_rels, drawing_part);
for (auto rel : drawing_rels)
{
if (rel.type() == relationship_type::image)
{
const auto image_path = source_.manifest().canonicalize({workbook_rel, worksheet_rel, rel});
if (image_path.string().find("cid:") != std::string::npos)
{
// skip cid attachments
continue;
}
write_image(image_path);
}
}
}
}
// Other Parts
void xlsx_producer::write_custom_property()

View File

@ -122,7 +122,8 @@ private:
// Sheet Relationship Target Parts
void write_comments(const relationship &rel, worksheet ws, const std::vector<cell_reference> &cells);
void write_vml_drawings(const relationship &rel, worksheet ws, const std::vector<cell_reference> &cells);
void write_vml_drawings(const relationship &rel, worksheet ws, const std::vector<cell_reference> &cells);
void write_drawings(const relationship &rel, worksheet ws);
// Other Parts
@ -137,8 +138,8 @@ private:
/// Both are valid, but we can use this method to write either depending on the producer
/// we're trying to match.
/// </summary>
std::string write_bool(bool boolean) const;
std::string write_bool(bool boolean) const;
void write_relationships(const std::vector<xlnt::relationship> &relationships, const path &part);
void write_color(const xlnt::color &color);
void write_border(const xlnt::border &b);
@ -188,12 +189,12 @@ private:
current_part_serializer_->characters(characters);
}
/// <summary>
/// A reference to the workbook which is the object of read/write operations.
/// </summary>
const workbook &source_;
std::unique_ptr<ozstream> archive_;
/// <summary>
/// A reference to the workbook which is the object of read/write operations.
/// </summary>
const workbook &source_;
std::unique_ptr<ozstream> archive_;
std::unique_ptr<xml::serializer> current_part_serializer_;
std::unique_ptr<std::streambuf> current_part_streambuf_;
std::ostream current_part_stream_;

View File

@ -0,0 +1,121 @@
// Copyright (c) 2018
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#include <xlnt/drawing/spreadsheet_drawing.hpp>
#include <detail/constants.hpp>
#include <detail/external/include_libstudxml.hpp>
namespace {
// copy elements to the serializer provided and extract the embed ids
// from blip elements
std::vector<std::string> copy_and_extract(xml::parser& p, xml::serializer &s)
{
std::vector<std::string> embed_ids;
int nest_level = 0;
while (nest_level > 0 || (p.peek() != xml::parser::event_type::end_element && p.peek() != xml::parser::event_type::eof))
{
switch (p.next())
{
case xml::parser::start_element:
{
++nest_level;
auto attribs = p.attribute_map();
auto current_ns = p.namespace_();
s.start_element(p.qname());
s.namespace_decl(current_ns, p.prefix());
if (p.qname().name() == "blip")
{
embed_ids.push_back(attribs.at(xml::qname(xlnt::constants::ns("r"), "embed")).value);
}
p.peek();
auto new_ns = p.namespace_();
if (new_ns != current_ns)
{
auto pref = p.prefix();
s.namespace_decl(new_ns, pref);
}
for (auto &ele : attribs)
{
s.attribute(ele.first, ele.second.value);
}
break;
}
case xml::parser::end_element:
{
--nest_level;
s.end_element();
break;
}
case xml::parser::start_namespace_decl:
{
s.namespace_decl(p.namespace_(), p.prefix());
break;
}
case xml::parser::end_namespace_decl:
{ // nothing required here
break;
}
case xml::parser::characters:
{
s.characters(p.value());
break;
}
case xml::parser::eof:
return embed_ids;
case xml::parser::start_attribute:
case xml::parser::end_attribute:
default:
break;
}
}
return embed_ids;
}
}
namespace xlnt {
namespace drawing {
spreadsheet_drawing::spreadsheet_drawing(xml::parser &parser)
{
std::ostringstream serialization_stream;
xml::serializer s(serialization_stream, "", 0);
embed_ids_ = copy_and_extract(parser, s);
serialized_value_ = serialization_stream.str();
}
// void spreadsheet_drawing::serialize(xml::serializer &serializer, const std::string& ns)
void spreadsheet_drawing::serialize(xml::serializer &serializer)
{
std::istringstream ser(serialized_value_);
xml::parser p(ser, "", xml::parser::receive_default);
copy_and_extract(p, serializer);
}
std::vector<std::string> spreadsheet_drawing::get_embed_ids()
{
return embed_ids_;
}
} // namespace drawing
} // namespace xlnt

View File

@ -252,6 +252,12 @@ path path::resolve(const path &base_path) const
for (const auto &part : split())
{
if (part == "..")
{
copy = copy.parent();
continue;
}
copy = copy.append(part);
}

View File

@ -1121,4 +1121,9 @@ void worksheet::format_properties(const sheet_format_properties &properties)
d_->format_properties_ = properties;
}
bool worksheet::has_drawing() const
{
return d_->drawing_.is_set();
}
} // namespace xlnt

View File

@ -15,6 +15,7 @@ if(STATIC_CRT)
endif()
file(GLOB CELL_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/cell/*.cpp)
file(GLOB DRAWING_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/drawing/*.cpp)
file(GLOB PACKAGING_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/packaging/*.cpp)
file(GLOB STYLES_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/styles/*.cpp)
file(GLOB UTILS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.cpp)
@ -23,6 +24,7 @@ file(GLOB WORKSHEET_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/worksheet/*.cpp)
set(TESTS
${CELL_TESTS}
${DRAWING_TESTS}
${PACKAGING_TESTS}
${STYLES_TESTS}
${UTILS_TESTS}

BIN
tests/data/14_images.xlsx Normal file

Binary file not shown.

View File

@ -0,0 +1,59 @@
// Copyright (c) 2014-2018 Thomas Fussell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#include <iostream>
#include <helpers/test_suite.hpp>
#include <xlnt/worksheet/worksheet.hpp>
#include <xlnt/workbook/workbook.hpp>
#include <xlnt/utils/path.hpp>
class drawing_test_suite : public test_suite
{
public:
drawing_test_suite()
{
register_test(test_load_save);
}
void test_load_save()
{
xlnt::workbook wb1;
wb1.load(path_helper::test_file("2_minimal.xlsx"));
auto ws1 = wb1.active_sheet();
xlnt_assert_equals(ws1.has_drawing(), false);
xlnt::workbook wb2;
wb2.load(path_helper::test_file("14_images.xlsx"));
auto ws2 = wb2.active_sheet();
xlnt_assert_equals(ws2.has_drawing(), true);
wb2.save("temp_with_images.xlsx");
xlnt::workbook wb3;
wb3.load("temp_with_images.xlsx");
auto ws3 = wb3.active_sheet();
xlnt_assert_equals(ws3.has_drawing(), true);
}
};
static drawing_test_suite x;