mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Added basic support for embedded images
This commit is contained in:
parent
10c5781e6d
commit
698b40c54c
59
include/xlnt/drawing/spreadsheet_drawing.hpp
Normal file
59
include/xlnt/drawing/spreadsheet_drawing.hpp
Normal 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
|
||||
|
|
@ -50,7 +50,6 @@ class cell;
|
|||
class cell_style;
|
||||
class color;
|
||||
class const_worksheet_iterator;
|
||||
class drawing;
|
||||
class fill;
|
||||
class font;
|
||||
class format;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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/"}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -232,7 +232,7 @@ private:
|
|||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void read_drawings();
|
||||
void read_drawings(worksheet ws, const path &part);
|
||||
|
||||
// Unknown Parts
|
||||
|
||||
|
|
|
@ -2965,9 +2965,12 @@ 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()
|
||||
|
|
|
@ -123,6 +123,7 @@ private:
|
|||
|
||||
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_drawings(const relationship &rel, worksheet ws);
|
||||
|
||||
// Other Parts
|
||||
|
||||
|
|
121
source/drawing/spreadsheet_drawing.cpp
Normal file
121
source/drawing/spreadsheet_drawing.cpp
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
BIN
tests/data/14_images.xlsx
Normal file
Binary file not shown.
59
tests/drawing/drawing_test_suite.cpp
Normal file
59
tests/drawing/drawing_test_suite.cpp
Normal 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;
|
Loading…
Reference in New Issue
Block a user