2015-10-31 18:56:54 +08:00
|
|
|
#include <algorithm>
|
|
|
|
|
2015-10-30 01:46:56 +08:00
|
|
|
#include <xlnt/s11n/workbook_serializer.hpp>
|
|
|
|
#include <xlnt/common/datetime.hpp>
|
2015-10-30 07:37:07 +08:00
|
|
|
#include <xlnt/common/exceptions.hpp>
|
2015-10-14 12:03:48 +08:00
|
|
|
#include <xlnt/common/relationship.hpp>
|
2015-10-30 01:46:56 +08:00
|
|
|
#include <xlnt/s11n/xml_document.hpp>
|
|
|
|
#include <xlnt/s11n/xml_node.hpp>
|
2015-10-14 12:03:48 +08:00
|
|
|
#include <xlnt/workbook/document_properties.hpp>
|
2015-10-30 01:46:56 +08:00
|
|
|
#include <xlnt/workbook/manifest.hpp>
|
2015-10-30 07:37:07 +08:00
|
|
|
#include <xlnt/workbook/named_range.hpp>
|
2015-10-14 12:03:48 +08:00
|
|
|
#include <xlnt/workbook/workbook.hpp>
|
2015-10-30 07:37:07 +08:00
|
|
|
#include <xlnt/worksheet/range_reference.hpp>
|
|
|
|
#include <xlnt/worksheet/worksheet.hpp>
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-26 12:44:55 +08:00
|
|
|
#include "detail/constants.hpp"
|
2015-10-14 12:03:48 +08:00
|
|
|
|
|
|
|
namespace {
|
2015-10-15 06:05:13 +08:00
|
|
|
|
2015-10-30 01:46:56 +08:00
|
|
|
xlnt::datetime w3cdtf_to_datetime(const std::string &string)
|
|
|
|
{
|
|
|
|
xlnt::datetime result(1900, 1, 1);
|
|
|
|
auto separator_index = string.find('-');
|
|
|
|
result.year = std::stoi(string.substr(0, separator_index));
|
|
|
|
result.month = std::stoi(string.substr(separator_index + 1, string.find('-', separator_index + 1)));
|
|
|
|
separator_index = string.find('-', separator_index + 1);
|
|
|
|
result.day = std::stoi(string.substr(separator_index + 1, string.find('T', separator_index + 1)));
|
|
|
|
separator_index = string.find('T', separator_index + 1);
|
|
|
|
result.hour = std::stoi(string.substr(separator_index + 1, string.find(':', separator_index + 1)));
|
|
|
|
separator_index = string.find(':', separator_index + 1);
|
|
|
|
result.minute = std::stoi(string.substr(separator_index + 1, string.find(':', separator_index + 1)));
|
|
|
|
separator_index = string.find(':', separator_index + 1);
|
|
|
|
result.second = std::stoi(string.substr(separator_index + 1, string.find('Z', separator_index + 1)));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-10-15 06:05:13 +08:00
|
|
|
std::string fill(const std::string &string, std::size_t length = 2)
|
|
|
|
{
|
|
|
|
if(string.size() >= length)
|
|
|
|
{
|
|
|
|
return string;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string(length - string.size(), '0') + string;
|
|
|
|
}
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-15 06:05:13 +08:00
|
|
|
std::string datetime_to_w3cdtf(const xlnt::datetime &dt)
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
2015-10-15 06:05:13 +08:00
|
|
|
return std::to_string(dt.year) + "-" + fill(std::to_string(dt.month)) + "-" + fill(std::to_string(dt.day)) + "T" + fill(std::to_string(dt.hour)) + ":" + fill(std::to_string(dt.minute)) + ":" + fill(std::to_string(dt.second)) + "Z";
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace xlnt {
|
2015-10-31 06:54:04 +08:00
|
|
|
|
|
|
|
workbook_serializer::workbook_serializer(workbook &wb) : workbook_(wb)
|
|
|
|
{
|
|
|
|
}
|
2015-10-30 01:46:56 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
std::vector<workbook_serializer::string_pair> workbook_serializer::read_sheets()
|
2015-10-30 01:46:56 +08:00
|
|
|
{
|
2015-10-31 06:54:04 +08:00
|
|
|
/*
|
2015-10-30 01:46:56 +08:00
|
|
|
std::string ns;
|
|
|
|
|
|
|
|
for(auto child : doc.children())
|
|
|
|
{
|
|
|
|
std::string name = child.name();
|
|
|
|
|
|
|
|
if(name.find(':') != std::string::npos)
|
|
|
|
{
|
|
|
|
auto colon_index = name.find(':');
|
|
|
|
ns = name.substr(0, colon_index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto with_ns = [&](const std::string &base) { return ns.empty() ? base : ns + ":" + base; };
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
auto root_node = doc.get_child(with_ns("workbook"));
|
|
|
|
auto sheets_node = root_node.get_child(with_ns("sheets"));
|
2015-10-31 06:54:04 +08:00
|
|
|
*/
|
|
|
|
std::vector<string_pair> sheets;
|
|
|
|
/*
|
2015-10-30 01:46:56 +08:00
|
|
|
// store temp because pugixml iteration uses the internal char array multiple times
|
|
|
|
auto sheet_element_name = with_ns("sheet");
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
for(auto sheet_node : sheets_node.children(sheet_element_name))
|
2015-10-30 01:46:56 +08:00
|
|
|
{
|
|
|
|
std::string id = sheet_node.attribute("r:id").as_string();
|
|
|
|
std::string name = sheet_node.attribute("name").as_string();
|
|
|
|
sheets.push_back(std::make_pair(id, name));
|
|
|
|
}
|
2015-10-31 06:54:04 +08:00
|
|
|
*/
|
2015-10-30 01:46:56 +08:00
|
|
|
return sheets;
|
|
|
|
}
|
|
|
|
|
|
|
|
void workbook_serializer::read_properties_core(const xml_document &xml)
|
|
|
|
{
|
2015-10-31 06:54:04 +08:00
|
|
|
auto &props = workbook_.get_properties();
|
|
|
|
auto root_node = xml.get_child("dc:coreProperties");
|
2015-10-30 01:46:56 +08:00
|
|
|
|
|
|
|
props.excel_base_date = calendar::windows_1900;
|
|
|
|
|
|
|
|
if(root_node.has_child("dc:creator"))
|
|
|
|
{
|
|
|
|
props.creator = root_node.get_child("dc:creator").get_text();
|
|
|
|
}
|
|
|
|
if(root_node.has_child("cp:lastModifiedBy"))
|
|
|
|
{
|
|
|
|
props.last_modified_by = root_node.get_child("cp:lastModifiedBy").get_text();
|
|
|
|
}
|
|
|
|
if(root_node.has_child("dcterms:created"))
|
|
|
|
{
|
|
|
|
std::string created_string = root_node.get_child("dcterms:created").get_text();
|
|
|
|
props.created = w3cdtf_to_datetime(created_string);
|
|
|
|
}
|
|
|
|
if(root_node.has_child("dcterms:modified"))
|
|
|
|
{
|
|
|
|
std::string modified_string = root_node.get_child("dcterms:modified").get_text();
|
|
|
|
props.modified = w3cdtf_to_datetime(modified_string);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Return a list of worksheets.
|
|
|
|
/// content types has a list of paths but no titles
|
|
|
|
/// workbook has a list of titles and relIds but no paths
|
|
|
|
/// workbook_rels has a list of relIds and paths but no titles
|
|
|
|
/// </summary>
|
2015-10-31 06:54:04 +08:00
|
|
|
std::vector<workbook_serializer::string_pair> workbook_serializer::detect_worksheets()
|
2015-10-30 01:46:56 +08:00
|
|
|
{
|
|
|
|
static const std::string ValidWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
|
|
|
|
|
|
std::vector<std::string> valid_sheets;
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
for(const auto &content_type : workbook_.get_manifest().get_override_types())
|
2015-10-30 01:46:56 +08:00
|
|
|
{
|
2015-10-30 07:37:07 +08:00
|
|
|
if(content_type.get_content_type() == ValidWorksheet)
|
2015-10-30 01:46:56 +08:00
|
|
|
{
|
2015-10-30 07:37:07 +08:00
|
|
|
valid_sheets.push_back(content_type.get_part_name());
|
2015-10-30 01:46:56 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto &workbook_relationships = workbook_.get_relationships();
|
2015-10-30 01:46:56 +08:00
|
|
|
std::vector<std::pair<std::string, std::string>> result;
|
|
|
|
|
2015-10-30 11:16:31 +08:00
|
|
|
for(const auto &ws : read_sheets())
|
2015-10-30 01:46:56 +08:00
|
|
|
{
|
|
|
|
auto rel = *std::find_if(workbook_relationships.begin(), workbook_relationships.end(), [&](const relationship &r) { return r.get_id() == ws.first; });
|
|
|
|
auto target = rel.get_target_uri();
|
|
|
|
|
|
|
|
if(std::find(valid_sheets.begin(), valid_sheets.end(), "/" + target) != valid_sheets.end())
|
|
|
|
{
|
|
|
|
result.push_back({target, ws.second});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
xml_document workbook_serializer::write_properties_core() const
|
2015-10-15 06:05:13 +08:00
|
|
|
{
|
2015-10-31 06:54:04 +08:00
|
|
|
auto &props = workbook_.get_properties();
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
xml_document xml;
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto root_node = xml.add_child("cp:coreProperties");
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
xml.add_namespace("cp", "http://schemas.openxmlformats.org/package/2006/metadata/core-properties");
|
|
|
|
xml.add_namespace("dc", "http://purl.org/dc/elements/1.1/");
|
|
|
|
xml.add_namespace("dcmitype", "http://purl.org/dc/dcmitype/");
|
|
|
|
xml.add_namespace("dcterms", "http://purl.org/dc/terms/");
|
|
|
|
xml.add_namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
|
|
|
|
|
|
|
|
root_node.add_child("dc:creator").set_text(props.creator);
|
|
|
|
root_node.add_child("cp:lastModifiedBy").set_text(props.last_modified_by);
|
|
|
|
root_node.add_child("dcterms:created").set_text(datetime_to_w3cdtf(props.created));
|
|
|
|
root_node.get_child("dcterms:created").add_attribute("xsi:type", "dcterms:W3CDTF");
|
|
|
|
root_node.add_child("dcterms:modified").set_text(datetime_to_w3cdtf(props.modified));
|
|
|
|
root_node.get_child("dcterms:modified").add_attribute("xsi:type", "dcterms:W3CDTF");
|
|
|
|
root_node.add_child("dc:title").set_text(props.title);
|
|
|
|
root_node.add_child("dc:description");
|
|
|
|
root_node.add_child("dc:subject");
|
|
|
|
root_node.add_child("cp:keywords");
|
|
|
|
root_node.add_child("cp:category");
|
|
|
|
|
|
|
|
return xml;
|
2015-10-15 06:05:13 +08:00
|
|
|
}
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
xml_document workbook_serializer::write_properties_app() const
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
2015-10-30 07:37:07 +08:00
|
|
|
xml_document xml;
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto root_node = xml.add_child("Properties");
|
2015-10-30 07:37:07 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
xml.add_namespace("", "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties");
|
|
|
|
xml.add_namespace("vt", "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes");
|
2015-10-30 07:37:07 +08:00
|
|
|
|
|
|
|
root_node.add_child("Application").set_text("Microsoft Excel");
|
|
|
|
root_node.add_child("DocSecurity").set_text("0");
|
|
|
|
root_node.add_child("ScaleCrop").set_text("false");
|
|
|
|
root_node.add_child("Company");
|
|
|
|
root_node.add_child("LinksUpToDate").set_text("false");
|
|
|
|
root_node.add_child("SharedDoc").set_text("false");
|
|
|
|
root_node.add_child("HyperlinksChanged").set_text("false");
|
|
|
|
root_node.add_child("AppVersion").set_text("12.0000");
|
|
|
|
|
|
|
|
auto heading_pairs_node = root_node.add_child("HeadingPairs");
|
|
|
|
auto heading_pairs_vector_node = heading_pairs_node.add_child("vt:vector");
|
|
|
|
heading_pairs_vector_node.add_attribute("baseType", "variant");
|
|
|
|
heading_pairs_vector_node.add_attribute("size", "2");
|
|
|
|
heading_pairs_vector_node.add_child("vt:variant").add_child("vt:lpstr").set_text("Worksheets");
|
2015-10-31 06:54:04 +08:00
|
|
|
heading_pairs_vector_node.add_child("vt:variant").add_child("vt:i4").set_text(std::to_string(workbook_.get_sheet_names().size()));
|
2015-10-30 07:37:07 +08:00
|
|
|
|
|
|
|
auto titles_of_parts_node = root_node.add_child("TitlesOfParts");
|
|
|
|
auto titles_of_parts_vector_node = titles_of_parts_node.add_child("vt:vector");
|
|
|
|
titles_of_parts_vector_node.add_attribute("baseType", "lpstr");
|
2015-10-31 06:54:04 +08:00
|
|
|
titles_of_parts_vector_node.add_attribute("size", std::to_string(workbook_.get_sheet_names().size()));
|
2015-10-30 07:37:07 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
for(auto ws : workbook_)
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
2015-10-30 07:37:07 +08:00
|
|
|
titles_of_parts_vector_node.add_child("vt:lpstr").set_text(ws.get_title());
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
return xml;
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
xml_document workbook_serializer::write_workbook() const
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
|
|
|
std::size_t num_visible = 0;
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
for(auto ws : workbook_)
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
|
|
|
if(ws.get_page_setup().get_sheet_state() == xlnt::page_setup::sheet_state::visible)
|
|
|
|
{
|
|
|
|
num_visible++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(num_visible == 0)
|
|
|
|
{
|
2015-10-30 07:37:07 +08:00
|
|
|
throw xlnt::value_error();
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
xml_document xml;
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto root_node = xml.add_child("workbook");
|
2015-10-30 07:37:07 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
xml.add_namespace("", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
|
|
|
|
xml.add_namespace("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
|
2015-10-30 07:37:07 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto file_version_node = root_node.add_child("fileVersion");
|
2015-10-30 07:37:07 +08:00
|
|
|
file_version_node.add_attribute("appName", "xl");
|
|
|
|
file_version_node.add_attribute("lastEdited", "4");
|
|
|
|
file_version_node.add_attribute("lowestEdited", "4");
|
|
|
|
file_version_node.add_attribute("rupBuild", "4505");
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto workbook_pr_node = root_node.add_child("workbookPr");
|
2015-10-30 07:37:07 +08:00
|
|
|
workbook_pr_node.add_attribute("codeName", "ThisWorkbook");
|
|
|
|
workbook_pr_node.add_attribute("defaultThemeVersion", "124226");
|
2015-10-31 06:54:04 +08:00
|
|
|
workbook_pr_node.add_attribute("date1904", workbook_.get_properties().excel_base_date == calendar::mac_1904 ? "1" : "0");
|
2015-10-30 07:37:07 +08:00
|
|
|
|
|
|
|
auto book_views_node = root_node.add_child("bookViews");
|
|
|
|
auto workbook_view_node = book_views_node.add_child("workbookView");
|
|
|
|
workbook_view_node.add_attribute("activeTab", "0");
|
|
|
|
workbook_view_node.add_attribute("autoFilterDateGrouping", "1");
|
|
|
|
workbook_view_node.add_attribute("firstSheet", "0");
|
|
|
|
workbook_view_node.add_attribute("minimized", "0");
|
|
|
|
workbook_view_node.add_attribute("showHorizontalScroll", "1");
|
|
|
|
workbook_view_node.add_attribute("showSheetTabs", "1");
|
|
|
|
workbook_view_node.add_attribute("showVerticalScroll", "1");
|
|
|
|
workbook_view_node.add_attribute("tabRatio", "600");
|
|
|
|
workbook_view_node.add_attribute("visibility", "visible");
|
|
|
|
|
|
|
|
auto sheets_node = root_node.add_child("sheets");
|
|
|
|
auto defined_names_node = root_node.add_child("definedNames");
|
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
for(const auto &relationship : workbook_.get_relationships())
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
|
|
|
if(relationship.get_type() == relationship::type::worksheet)
|
|
|
|
{
|
2015-10-31 06:54:04 +08:00
|
|
|
//TODO: this is ugly
|
2015-10-14 12:03:48 +08:00
|
|
|
std::string sheet_index_string = relationship.get_target_uri();
|
|
|
|
sheet_index_string = sheet_index_string.substr(0, sheet_index_string.find('.'));
|
|
|
|
sheet_index_string = sheet_index_string.substr(sheet_index_string.find_last_of('/'));
|
|
|
|
auto iter = sheet_index_string.end();
|
|
|
|
iter--;
|
|
|
|
while (isdigit(*iter)) iter--;
|
|
|
|
auto first_digit = iter - sheet_index_string.begin();
|
2015-10-15 06:05:13 +08:00
|
|
|
sheet_index_string = sheet_index_string.substr(static_cast<std::string::size_type>(first_digit + 1));
|
|
|
|
std::size_t sheet_index = static_cast<std::size_t>(std::stoll(sheet_index_string) - 1);
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
auto ws = workbook_.get_sheet_by_index(sheet_index);
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
auto sheet_node = sheets_node.add_child("sheet");
|
|
|
|
sheet_node.add_attribute("name", ws.get_title());
|
|
|
|
sheet_node.add_attribute("sheetId", std::to_string(sheet_index + 1));
|
2015-10-31 06:54:04 +08:00
|
|
|
sheet_node.add_attribute("r:id", relationship.get_id());
|
2015-10-14 12:03:48 +08:00
|
|
|
|
|
|
|
if(ws.has_auto_filter())
|
|
|
|
{
|
2015-10-31 06:54:04 +08:00
|
|
|
auto defined_name_node = defined_names_node.add_child("definedName");
|
2015-10-30 07:37:07 +08:00
|
|
|
defined_name_node.add_attribute("name", "_xlnm._FilterDatabase");
|
|
|
|
defined_name_node.add_attribute("hidden", "1");
|
|
|
|
defined_name_node.add_attribute("localSheetId", "0");
|
2015-10-14 12:03:48 +08:00
|
|
|
std::string name = "'" + ws.get_title() + "'!" + range_reference::make_absolute(ws.get_auto_filter()).to_string();
|
2015-10-30 07:37:07 +08:00
|
|
|
defined_name_node.set_text(name);
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
auto calc_pr_node = root_node.add_child("calcPr");
|
|
|
|
calc_pr_node.add_attribute("calcId", "124519");
|
|
|
|
calc_pr_node.add_attribute("calcMode", "auto");
|
|
|
|
calc_pr_node.add_attribute("fullCalcOnLoad", "1");
|
2015-10-14 12:03:48 +08:00
|
|
|
|
2015-10-30 07:37:07 +08:00
|
|
|
return xml;
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
2015-10-15 06:05:13 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
xml_node workbook_serializer::write_named_ranges() const
|
2015-10-14 12:03:48 +08:00
|
|
|
{
|
2015-10-31 06:54:04 +08:00
|
|
|
xlnt::xml_node named_ranges_node;
|
|
|
|
|
|
|
|
for(auto &named_range : workbook_.get_named_ranges())
|
2015-10-15 06:05:13 +08:00
|
|
|
{
|
2015-10-30 07:37:07 +08:00
|
|
|
named_ranges_node.add_child(named_range.get_name());
|
2015-10-15 06:05:13 +08:00
|
|
|
}
|
2015-10-30 07:37:07 +08:00
|
|
|
|
2015-10-31 06:54:04 +08:00
|
|
|
return named_ranges_node;
|
2015-10-14 12:03:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace xlnt
|