// Copyright (c) 2014-2021 Thomas Fussell // Copyright (c) 2010-2015 openpyxl // // 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 #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xlnt { namespace detail { struct stylesheet { class format create_format(bool default_format) { format_impls.push_back(format_impl()); auto &impl = format_impls.back(); impl.parent = this; impl.id = format_impls.size() - 1; impl.references = default_format ? 1 : 0; return xlnt::format(&impl); } class xlnt::format format(std::size_t index) { auto iter = format_impls.begin(); std::advance(iter, static_cast::difference_type>(index)); return xlnt::format(&*iter); } class style create_style(const std::string &name) { auto &impl = style_impls.emplace(name, style_impl()).first->second; impl.parent = this; impl.name = name; impl.border_id = 0; impl.fill_id = 0; impl.font_id = 0; impl.number_format_id = 0; style_names.push_back(name); return xlnt::style(&impl); } class style create_builtin_style(const std::size_t builtin_id) { // From Annex G.2 static const auto names = std::unordered_map { { 0, "Normal" }, { 1, "RowLevel_1" }, { 2, "ColLevel_1" }, { 3, "Comma" }, { 4, "Currency" }, { 5, "Percent" }, { 6, "Comma [0]" }, { 7, "Currency [0]" }, { 8, "Hyperlink" }, { 9, "Followed Hyperlink" }, { 10, "Note" }, { 11, "Warning Text" }, { 15, "Title" }, { 16, "Heading 1" }, { 17, "Heading 2" }, { 18, "Heading 3" }, { 19, "Heading 4" }, { 20, "Input" }, { 21, "Output"}, { 22, "Calculation"}, { 22, "Calculation" }, { 23, "Check Cell" }, { 24, "Linked Cell" }, { 25, "Total" }, { 26, "Good" }, { 27, "Bad" }, { 28, "Neutral" }, { 29, "Accent1" }, { 30, "20% - Accent1" }, { 31, "40% - Accent1" }, { 32, "60% - Accent1" }, { 33, "Accent2" }, { 34, "20% - Accent2" }, { 35, "40% - Accent2" }, { 36, "60% - Accent2" }, { 37, "Accent3" }, { 38, "20% - Accent3" }, { 39, "40% - Accent3" }, { 40, "60% - Accent3" }, { 41, "Accent4" }, { 42, "20% - Accent4" }, { 43, "40% - Accent4" }, { 44, "60% - Accent4" }, { 45, "Accent5" }, { 46, "20% - Accent5" }, { 47, "40% - Accent5" }, { 48, "60% - Accent5" }, { 49, "Accent6" }, { 50, "20% - Accent6" }, { 51, "40% - Accent6" }, { 52, "60% - Accent6" }, { 53, "Explanatory Text" } }; auto new_style = create_style(names.at(builtin_id)); new_style.d_->builtin_id = builtin_id; return new_style; } class style style(const std::string &name) { if (!has_style(name)) throw key_not_found(); return xlnt::style(&style_impls[name]); } bool has_style(const std::string &name) { return style_impls.count(name) > 0; } std::size_t next_custom_number_format_id() const { std::size_t id = 164; for (const auto &nf : number_formats) { if (nf.id() >= id) { id = nf.id() + 1; } } return id; } template std::size_t find_or_add(C &container, const T &item) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" auto iter = std::find(container.begin(), container.end(), item); if (iter != container.end()) { return std::size_t(iter - container.begin()); } iter = container.emplace(container.end(), item); return std::size_t(iter - container.begin()); #pragma GCC diagnostic pop } template std::unordered_map garbage_collect( const std::unordered_map &reference_counts, std::vector &container) { std::unordered_map id_map; std::size_t unreferenced = 0; const auto original_size = container.size(); for (std::size_t i = 0; i < original_size; ++i) { id_map[i] = i - unreferenced; if (reference_counts.count(i) == 0 || reference_counts.at(i) == 0) { container.erase(container.begin() + static_cast::difference_type>(i - unreferenced)); unreferenced++; } } return id_map; } void garbage_collect() { if (!garbage_collection_enabled) return; auto format_iter = format_impls.begin(); while (format_iter != format_impls.end()) { auto &impl = *format_iter; if (impl.references != 0) { ++format_iter; } else { format_iter = format_impls.erase(format_iter); } } std::size_t new_id = 0; std::unordered_map alignment_reference_counts; std::unordered_map border_reference_counts; std::unordered_map fill_reference_counts; std::unordered_map font_reference_counts; std::unordered_map number_format_reference_counts; std::unordered_map protection_reference_counts; fill_reference_counts[0]++; fill_reference_counts[1]++; for (auto &impl : format_impls) { impl.id = new_id++; if (impl.alignment_id.is_set()) { alignment_reference_counts[impl.alignment_id.get()]++; } if (impl.border_id.is_set()) { border_reference_counts[impl.border_id.get()]++; } if (impl.fill_id.is_set()) { fill_reference_counts[impl.fill_id.get()]++; } if (impl.font_id.is_set()) { font_reference_counts[impl.font_id.get()]++; } if (impl.number_format_id.is_set()) { number_format_reference_counts[impl.number_format_id.get()]++; } if (impl.protection_id.is_set()) { protection_reference_counts[impl.protection_id.get()]++; } } for (auto &name_impl_pair : style_impls) { auto &impl = name_impl_pair.second; if (impl.alignment_id.is_set()) { alignment_reference_counts[impl.alignment_id.get()]++; } if (impl.border_id.is_set()) { border_reference_counts[impl.border_id.get()]++; } if (impl.fill_id.is_set()) { fill_reference_counts[impl.fill_id.get()]++; } if (impl.font_id.is_set()) { font_reference_counts[impl.font_id.get()]++; } if (impl.number_format_id.is_set()) { number_format_reference_counts[impl.number_format_id.get()]++; } if (impl.protection_id.is_set()) { protection_reference_counts[impl.protection_id.get()]++; } } auto alignment_id_map = garbage_collect(alignment_reference_counts, alignments); auto border_id_map = garbage_collect(border_reference_counts, borders); auto fill_id_map = garbage_collect(fill_reference_counts, fills); auto font_id_map = garbage_collect(font_reference_counts, fonts); auto protection_id_map = garbage_collect(protection_reference_counts, protections); for (auto &impl : format_impls) { if (impl.alignment_id.is_set()) { impl.alignment_id = alignment_id_map[impl.alignment_id.get()]; } if (impl.border_id.is_set()) { impl.border_id = border_id_map[impl.border_id.get()]; } if (impl.fill_id.is_set()) { impl.fill_id = fill_id_map[impl.fill_id.get()]; } if (impl.font_id.is_set()) { impl.font_id = font_id_map[impl.font_id.get()]; } if (impl.protection_id.is_set()) { impl.protection_id = protection_id_map[impl.protection_id.get()]; } } for (auto &name_impl : style_impls) { auto &impl = name_impl.second; if (impl.alignment_id.is_set()) { impl.alignment_id = alignment_id_map[impl.alignment_id.get()]; } if (impl.border_id.is_set()) { impl.border_id = border_id_map[impl.border_id.get()]; } if (impl.fill_id.is_set()) { impl.fill_id = fill_id_map[impl.fill_id.get()]; } if (impl.font_id.is_set()) { impl.font_id = font_id_map[impl.font_id.get()]; } if (impl.protection_id.is_set()) { impl.protection_id = protection_id_map[impl.protection_id.get()]; } } } format_impl *find_or_create(format_impl &pattern) { pattern.references = 0; std::size_t id = 0; auto iter = format_impls.begin(); while (iter != format_impls.end() && !(*iter == pattern)) { ++id; ++iter; } if (iter == format_impls.end()) { iter = format_impls.emplace(format_impls.end(), pattern); } auto &result = *iter; result.parent = this; result.id = id; result.references++; if (id != pattern.id) { iter = format_impls.begin(); std::advance(iter, static_cast::difference_type>(pattern.id)); iter->references -= iter->references > 0 ? 1 : 0; garbage_collect(); } return &result; } format_impl *find_or_create_with(format_impl *pattern, const std::string &style_name) { format_impl new_format = *pattern; new_format.style = style_name; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } format_impl *find_or_create_with(format_impl *pattern, const alignment &new_alignment, optional applied) { format_impl new_format = *pattern; new_format.alignment_id = find_or_add(alignments, new_alignment); new_format.alignment_applied = applied; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } format_impl *find_or_create_with(format_impl *pattern, const border &new_border, optional applied) { format_impl new_format = *pattern; new_format.border_id = find_or_add(borders, new_border); new_format.border_applied = applied; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } format_impl *find_or_create_with(format_impl *pattern, const fill &new_fill, optional applied) { format_impl new_format = *pattern; new_format.fill_id = find_or_add(fills, new_fill); new_format.fill_applied = applied; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } format_impl *find_or_create_with(format_impl *pattern, const font &new_font, optional applied) { format_impl new_format = *pattern; new_format.font_id = find_or_add(fonts, new_font); new_format.font_applied = applied; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } format_impl *find_or_create_with(format_impl *pattern, const number_format &new_number_format, optional applied) { format_impl new_format = *pattern; if (new_number_format.id() >= 164) { find_or_add(number_formats, new_number_format); } new_format.number_format_id = new_number_format.id(); new_format.number_format_applied = applied; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } format_impl *find_or_create_with(format_impl *pattern, const protection &new_protection, optional applied) { format_impl new_format = *pattern; new_format.protection_id = find_or_add(protections, new_protection); new_format.protection_applied = applied; if (pattern->references == 0) { *pattern = new_format; } return find_or_create(new_format); } std::size_t style_index(const std::string &name) const { return static_cast(std::distance(style_names.begin(), std::find(style_names.begin(), style_names.end(), name))); } void clear() { conditional_format_impls.clear(); format_impls.clear(); style_impls.clear(); style_names.clear(); alignments.clear(); borders.clear(); fills.clear(); fonts.clear(); number_formats.clear(); protections.clear(); colors.clear(); } conditional_format add_conditional_format_rule(worksheet_impl *ws, const range_reference &ref, const condition &when) { conditional_format_impls.push_back(conditional_format_impl()); auto &impl = conditional_format_impls.back(); impl.when = when; impl.parent = this; impl.target_sheet = ws; impl.target_range = ref; impl.differential_format_id = conditional_format_impls.size() - 1; return xlnt::conditional_format(&impl); } workbook *parent; bool operator==(const stylesheet& rhs) const { // no equality on parent as there is only 1 stylesheet per borkbook hence would always be false return garbage_collection_enabled == rhs.garbage_collection_enabled && known_fonts_enabled == rhs.known_fonts_enabled && conditional_format_impls == rhs.conditional_format_impls && format_impls == rhs.format_impls && style_impls == rhs.style_impls && style_names == rhs.style_names && default_slicer_style == rhs.default_slicer_style && alignments == rhs.alignments && borders == rhs.borders && fills == rhs.fills && fonts == rhs.fonts && number_formats == rhs.number_formats && protections == rhs.protections && colors == rhs.colors; } bool garbage_collection_enabled = true; bool known_fonts_enabled = false; std::list conditional_format_impls; std::list format_impls; std::unordered_map style_impls; std::vector style_names; optional default_slicer_style; std::vector alignments; std::vector borders; std::vector fills; std::vector fonts; std::vector number_formats; std::vector protections; std::vector colors; }; } // namespace detail } // namespace xlnt