mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
542 lines
16 KiB
C++
542 lines
16 KiB
C++
// Copyright (c) 2014-2021 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, ARISING 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 <detail/header_footer/header_footer_code.hpp>
|
|
//#include <detail/numeric_utils.hpp>
|
|
|
|
namespace xlnt {
|
|
namespace detail {
|
|
|
|
std::array<xlnt::optional<xlnt::rich_text>, 3> decode_header_footer(const std::string &hf_string, const number_serialiser &serialiser)
|
|
{
|
|
std::array<xlnt::optional<xlnt::rich_text>, 3> result;
|
|
|
|
if (hf_string.empty())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
enum class hf_code
|
|
{
|
|
left_section, // &L
|
|
center_section, // &C
|
|
right_section, // &R
|
|
current_page_number, // &P
|
|
total_page_number, // &N
|
|
font_size, // &#
|
|
text_font_color, // &KRRGGBB or &KTTSNN
|
|
text_strikethrough, // &S
|
|
text_superscript, // &X
|
|
text_subscript, // &Y
|
|
date, // &D
|
|
time, // &T
|
|
picture_as_background, // &G
|
|
text_single_underline, // &U
|
|
text_double_underline, // &E
|
|
workbook_file_path, // &Z
|
|
workbook_file_name, // &F
|
|
sheet_tab_name, // &A
|
|
add_to_page_number, // &+
|
|
subtract_from_page_number, // &-
|
|
text_font_name, // &"font name,font type"
|
|
bold_font_style, // &B
|
|
italic_font_style, // &I
|
|
outline_style, // &O
|
|
shadow_style, // &H
|
|
text // everything else
|
|
};
|
|
|
|
struct hf_token
|
|
{
|
|
hf_code code = hf_code::text;
|
|
std::string value;
|
|
};
|
|
|
|
std::vector<hf_token> tokens;
|
|
std::size_t position = 0;
|
|
|
|
while (position < hf_string.size())
|
|
{
|
|
hf_token token;
|
|
|
|
auto next_ampersand = hf_string.find('&', position + 1);
|
|
token.value = hf_string.substr(position, next_ampersand - position);
|
|
auto next_position = next_ampersand;
|
|
|
|
if (hf_string[position] == '&')
|
|
{
|
|
token.value.clear();
|
|
next_position = position + 2;
|
|
auto first_code_char = hf_string[position + 1];
|
|
|
|
if (first_code_char == '"')
|
|
{
|
|
auto end_quote_index = hf_string.find('"', position + 2);
|
|
next_position = end_quote_index + 1;
|
|
|
|
token.value = hf_string.substr(position + 2, end_quote_index - position - 2); // remove quotes
|
|
token.code = hf_code::text_font_name;
|
|
}
|
|
else if (first_code_char == '&')
|
|
{
|
|
token.value = "&&"; // escaped ampersand
|
|
}
|
|
else if (first_code_char == 'L')
|
|
{
|
|
token.code = hf_code::left_section;
|
|
}
|
|
else if (first_code_char == 'C')
|
|
{
|
|
token.code = hf_code::center_section;
|
|
}
|
|
else if (first_code_char == 'R')
|
|
{
|
|
token.code = hf_code::right_section;
|
|
}
|
|
else if (first_code_char == 'P')
|
|
{
|
|
token.code = hf_code::current_page_number;
|
|
}
|
|
else if (first_code_char == 'N')
|
|
{
|
|
token.code = hf_code::total_page_number;
|
|
}
|
|
else if (std::string("0123456789").find(hf_string[position + 1]) != std::string::npos)
|
|
{
|
|
token.code = hf_code::font_size;
|
|
next_position = hf_string.find_first_not_of("0123456789", position + 1);
|
|
token.value = hf_string.substr(position + 1, next_position - position - 1);
|
|
}
|
|
else if (first_code_char == 'K')
|
|
{
|
|
if (hf_string[position + 4] == '+' || hf_string[position + 4] == '-')
|
|
{
|
|
token.value = hf_string.substr(position + 2, 5);
|
|
next_position = position + 7;
|
|
}
|
|
else
|
|
{
|
|
token.value = hf_string.substr(position + 2, 6);
|
|
next_position = position + 8;
|
|
}
|
|
|
|
token.code = hf_code::text_font_color;
|
|
}
|
|
else if (first_code_char == 'S')
|
|
{
|
|
token.code = hf_code::text_strikethrough;
|
|
}
|
|
else if (first_code_char == 'X')
|
|
{
|
|
token.code = hf_code::text_superscript;
|
|
}
|
|
else if (first_code_char == 'Y')
|
|
{
|
|
token.code = hf_code::text_subscript;
|
|
}
|
|
else if (first_code_char == 'D')
|
|
{
|
|
token.code = hf_code::date;
|
|
}
|
|
else if (first_code_char == 'T')
|
|
{
|
|
token.code = hf_code::time;
|
|
}
|
|
else if (first_code_char == 'G')
|
|
{
|
|
token.code = hf_code::picture_as_background;
|
|
}
|
|
else if (first_code_char == 'U')
|
|
{
|
|
token.code = hf_code::text_single_underline;
|
|
}
|
|
else if (first_code_char == 'E')
|
|
{
|
|
token.code = hf_code::text_double_underline;
|
|
}
|
|
else if (first_code_char == 'Z')
|
|
{
|
|
token.code = hf_code::workbook_file_path;
|
|
}
|
|
else if (first_code_char == 'F')
|
|
{
|
|
token.code = hf_code::workbook_file_name;
|
|
}
|
|
else if (first_code_char == 'A')
|
|
{
|
|
token.code = hf_code::sheet_tab_name;
|
|
}
|
|
else if (first_code_char == '+')
|
|
{
|
|
token.code = hf_code::add_to_page_number;
|
|
}
|
|
else if (first_code_char == '-')
|
|
{
|
|
token.code = hf_code::subtract_from_page_number;
|
|
}
|
|
else if (first_code_char == 'B')
|
|
{
|
|
token.code = hf_code::bold_font_style;
|
|
}
|
|
else if (first_code_char == 'I')
|
|
{
|
|
token.code = hf_code::italic_font_style;
|
|
}
|
|
else if (first_code_char == 'O')
|
|
{
|
|
token.code = hf_code::outline_style;
|
|
}
|
|
else if (first_code_char == 'H')
|
|
{
|
|
token.code = hf_code::shadow_style;
|
|
}
|
|
}
|
|
|
|
position = next_position;
|
|
tokens.push_back(token);
|
|
}
|
|
|
|
const auto parse_section = [&tokens, &result, &serialiser](hf_code code) {
|
|
std::vector<hf_code> end_codes{hf_code::left_section, hf_code::center_section, hf_code::right_section};
|
|
end_codes.erase(std::find(end_codes.begin(), end_codes.end(), code));
|
|
|
|
std::size_t start_index = 0;
|
|
|
|
while (start_index < tokens.size() && tokens[start_index].code != code)
|
|
{
|
|
++start_index;
|
|
}
|
|
|
|
if (start_index == tokens.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
++start_index; // skip the section code
|
|
std::size_t end_index = start_index;
|
|
|
|
while (end_index < tokens.size()
|
|
&& std::find(end_codes.begin(), end_codes.end(), tokens[end_index].code) == end_codes.end())
|
|
{
|
|
++end_index;
|
|
}
|
|
|
|
xlnt::rich_text current_text;
|
|
xlnt::rich_text_run current_run;
|
|
|
|
// todo: all this nice parsing and the codes are just being turned back into text representations
|
|
// It would be nice to create an interface for the library to read and write these codes
|
|
|
|
for (auto i = start_index; i < end_index; ++i)
|
|
{
|
|
const auto ¤t_token = tokens[i];
|
|
|
|
if (current_token.code == hf_code::text)
|
|
{
|
|
current_run.first = current_run.first + current_token.value;
|
|
continue;
|
|
}
|
|
|
|
if (!current_run.first.empty())
|
|
{
|
|
current_text.add_run(current_run);
|
|
current_run = xlnt::rich_text_run();
|
|
}
|
|
|
|
switch (current_token.code)
|
|
{
|
|
case hf_code::text: {
|
|
break; // already handled above
|
|
}
|
|
|
|
case hf_code::left_section: {
|
|
break; // used below
|
|
}
|
|
|
|
case hf_code::center_section: {
|
|
break; // used below
|
|
}
|
|
|
|
case hf_code::right_section: {
|
|
break; // used below
|
|
}
|
|
|
|
case hf_code::current_page_number: {
|
|
current_run.first = current_run.first + "&P";
|
|
break;
|
|
}
|
|
|
|
case hf_code::total_page_number: {
|
|
current_run.first = current_run.first + "&N";
|
|
break;
|
|
}
|
|
|
|
case hf_code::font_size: {
|
|
if (!current_run.second.is_set())
|
|
{
|
|
current_run.second = xlnt::font();
|
|
}
|
|
|
|
current_run.second.get().size(serialiser.deserialise(current_token.value));
|
|
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_font_color: {
|
|
if (current_token.value.size() == 6)
|
|
{
|
|
if (!current_run.second.is_set())
|
|
{
|
|
current_run.second = xlnt::font();
|
|
}
|
|
|
|
current_run.second.get().color(xlnt::rgb_color(current_token.value));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_strikethrough: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_superscript: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_subscript: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::date: {
|
|
current_run.first = current_run.first + "&D";
|
|
break;
|
|
}
|
|
|
|
case hf_code::time: {
|
|
current_run.first = current_run.first + "&T";
|
|
break;
|
|
}
|
|
|
|
case hf_code::picture_as_background: {
|
|
current_run.first = current_run.first + "&G";
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_single_underline: {
|
|
if (!current_run.second.is_set())
|
|
{
|
|
current_run.second = xlnt::font();
|
|
}
|
|
current_run.second.get().underline(font::underline_style::single);
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_double_underline: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::workbook_file_path: {
|
|
current_run.first = current_run.first + "&Z";
|
|
break;
|
|
}
|
|
|
|
case hf_code::workbook_file_name: {
|
|
current_run.first = current_run.first + "&F";
|
|
break;
|
|
}
|
|
|
|
case hf_code::sheet_tab_name: {
|
|
current_run.first = current_run.first + "&A";
|
|
break;
|
|
}
|
|
|
|
case hf_code::add_to_page_number: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::subtract_from_page_number: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::text_font_name: {
|
|
auto comma_index = current_token.value.find(',');
|
|
auto font_name = current_token.value.substr(0, comma_index);
|
|
|
|
if (!current_run.second.is_set())
|
|
{
|
|
current_run.second = xlnt::font();
|
|
}
|
|
|
|
if (font_name != "-")
|
|
{
|
|
current_run.second.get().name(font_name);
|
|
}
|
|
|
|
if (comma_index != std::string::npos)
|
|
{
|
|
auto font_type = current_token.value.substr(comma_index + 1);
|
|
|
|
if (font_type == "Bold")
|
|
{
|
|
current_run.second.get().bold(true);
|
|
}
|
|
else if (font_type == "Italic")
|
|
{
|
|
// TODO
|
|
}
|
|
else if (font_type == "BoldItalic")
|
|
{
|
|
current_run.second.get().bold(true);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case hf_code::bold_font_style: {
|
|
if (!current_run.second.is_set())
|
|
{
|
|
current_run.second = xlnt::font();
|
|
}
|
|
|
|
current_run.second.get().bold(true);
|
|
|
|
break;
|
|
}
|
|
|
|
case hf_code::italic_font_style: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::outline_style: {
|
|
break;
|
|
}
|
|
|
|
case hf_code::shadow_style: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!current_run.first.empty())
|
|
{
|
|
current_text.add_run(current_run);
|
|
}
|
|
|
|
auto location_index =
|
|
static_cast<std::size_t>(code == hf_code::left_section ? 0 : code == hf_code::center_section ? 1 : 2);
|
|
|
|
if (!current_text.plain_text().empty())
|
|
{
|
|
result[location_index] = current_text;
|
|
}
|
|
};
|
|
|
|
parse_section(hf_code::left_section);
|
|
parse_section(hf_code::center_section);
|
|
parse_section(hf_code::right_section);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string encode_header_footer(const rich_text &t, header_footer::location where, const number_serialiser &serialiser)
|
|
{
|
|
const auto location_code_map =
|
|
std::unordered_map<header_footer::location,
|
|
std::string, scoped_enum_hash<header_footer::location>>{
|
|
{header_footer::location::left, "&L"},
|
|
{header_footer::location::center, "&C"},
|
|
{header_footer::location::right, "&R"},
|
|
};
|
|
|
|
auto encoded = location_code_map.at(where);
|
|
|
|
for (const auto &run : t.runs())
|
|
{
|
|
if (run.first.empty()) continue;
|
|
|
|
if (run.second.is_set())
|
|
{
|
|
if (run.second.get().has_name())
|
|
{
|
|
encoded.push_back('&');
|
|
encoded.push_back('"');
|
|
encoded.append(run.second.get().name());
|
|
encoded.push_back(',');
|
|
|
|
if (run.second.get().bold())
|
|
{
|
|
encoded.append("Bold");
|
|
}
|
|
else
|
|
{
|
|
encoded.append("Regular");
|
|
}
|
|
// todo: BoldItalic?
|
|
|
|
encoded.push_back('"');
|
|
}
|
|
else if (run.second.get().bold())
|
|
{
|
|
encoded.append("&B");
|
|
}
|
|
|
|
if (run.second.get().has_size())
|
|
{
|
|
encoded.push_back('&');
|
|
encoded.append(serialiser.serialise(run.second.get().size()));
|
|
}
|
|
if (run.second.get().underlined())
|
|
{
|
|
switch (run.second.get().underline())
|
|
{
|
|
case font::underline_style::single:
|
|
case font::underline_style::single_accounting:
|
|
encoded.append("&U");
|
|
break;
|
|
case font::underline_style::double_:
|
|
case font::underline_style::double_accounting:
|
|
encoded.append("&E");
|
|
break;
|
|
case font::underline_style::none:
|
|
break;
|
|
}
|
|
}
|
|
if (run.second.get().has_color())
|
|
{
|
|
encoded.push_back('&');
|
|
encoded.push_back('K');
|
|
encoded.append(run.second.get().color().rgb().hex_string().substr(2));
|
|
}
|
|
}
|
|
|
|
encoded.append(run.first);
|
|
}
|
|
|
|
return encoded;
|
|
};
|
|
|
|
} // namespace detail
|
|
} // namespace xlnt
|