initial implementation of conditional formatting plus a nifty little sample of converting images to workbooks

pull/146/head
Thomas Fussell 2017-04-03 19:24:36 -04:00
parent c3d9b85530
commit 9766daacd5
15 changed files with 8042 additions and 184 deletions

View File

@ -0,0 +1,165 @@
// Copyright (c) 2014-2017 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, 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 <cstdint>
#include <string>
#include <xlnt/xlnt_config.hpp>
#include <xlnt/utils/optional.hpp>
namespace xlnt {
class border;
class fill;
class font;
namespace detail {
struct conditional_format_impl;
struct stylesheet;
class xlsx_consumer;
class xlsx_producer;
} // namespace detail
class XLNT_API condition
{
public:
static condition text_starts_with(const std::string &start);
static condition text_ends_with(const std::string &end);
static condition text_contains(const std::string &start);
static condition text_does_not_contain(const std::string &start);
private:
friend class detail::xlsx_producer;
enum class type
{
contains_text
} type_;
enum class condition_operator
{
starts_with,
ends_with,
contains,
does_not_contain
} operator_;
std::string text_comparand_;
};
/// <summary>
/// Describes a conditional format that will be applied to all cells in the
/// associated range that satisfy the condition. This can only be constructed
/// using methods on worksheet or range.
/// </summary>
class XLNT_API conditional_format
{
public:
/// <summary>
/// Delete zero-argument constructor
/// </summary>
conditional_format() = delete;
/// <summary>
/// Default copy constructor. Constructs a format using the same PIMPL as other.
/// </summary>
conditional_format(const conditional_format &other) = default;
// Formatting (xf) components
/// <summary>
///
/// </summary>
bool has_border() const;
/// <summary>
///
/// </summary>
class border border() const;
/// <summary>
///
/// </summary>
conditional_format border(const xlnt::border &new_border);
/// <summary>
///
/// </summary>
bool has_fill() const;
/// <summary>
///
/// </summary>
class fill fill() const;
/// <summary>
///
/// </summary>
conditional_format fill(const xlnt::fill &new_fill);
/// <summary>
///
/// </summary>
bool has_font() const;
/// <summary>
///
/// </summary>
class font font() const;
/// <summary>
///
/// </summary>
conditional_format font(const xlnt::font &new_font);
/// <summary>
/// Returns true if this format is equivalent to other.
/// </summary>
bool operator==(const conditional_format &other) const;
/// <summary>
/// Returns true if this format is not equivalent to other.
/// </summary>
bool operator!=(const conditional_format &other) const;
private:
friend struct detail::stylesheet;
friend class detail::xlsx_consumer;
/// <summary>
///
/// </summary>
conditional_format(detail::conditional_format_impl *d);
/// <summary>
///
/// </summary>
detail::conditional_format_impl *d_;
};
} // namespace xlnt

View File

@ -30,6 +30,13 @@
#include <vector>
#include <xlnt/xlnt_config.hpp>
#include <xlnt/styles/alignment.hpp>
#include <xlnt/styles/border.hpp>
#include <xlnt/styles/conditional_format.hpp>
#include <xlnt/styles/fill.hpp>
#include <xlnt/styles/font.hpp>
#include <xlnt/styles/number_format.hpp>
#include <xlnt/styles/protection.hpp>
#include <xlnt/worksheet/cell_vector.hpp>
#include <xlnt/worksheet/major_order.hpp>
#include <xlnt/worksheet/range_iterator.hpp>
@ -179,6 +186,11 @@ public:
/// </summary>
range style(const std::string &style_name);
/// <summary>
///
/// </summary>
conditional_format conditional_format(const condition &when);
/// <summary>
///
/// </summary>

View File

@ -44,6 +44,8 @@ class cell_reference;
class cell_vector;
class column_properties;
class comment;
class condition;
class conditional_format;
class const_range_iterator;
class footer;
class header;
@ -666,6 +668,11 @@ public:
/// </summary>
void page_break_at_column(column_t column);
/// <summary>
/// Creates a conditional format for the given range with the given condition.
/// </summary>
conditional_format conditional_format(const range_reference &ref, const condition &when);
private:
friend class cell;
friend class const_range_iterator;

View File

@ -17,7 +17,10 @@ foreach(SAMPLE_SOURCE IN ITEMS ${SAMPLE_SOURCES})
set(SAMPLE_EXECUTABLE sample-${SAMPLE_NAME})
add_executable(${SAMPLE_EXECUTABLE} ${SAMPLE_SOURCE})
target_link_libraries(${SAMPLE_EXECUTABLE} PRIVATE xlnt PRIVATE ${ZLIB_LIBRARIES})
if(NOT STATIC)
add_custom_command(TARGET ${SAMPLE_EXECUTABLE} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:xlnt>
$<TARGET_FILE_DIR:${SAMPLE_EXECUTABLE}>)
endif()
endforeach()
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data
DESTINATION ${CMAKE_BINARY_DIR}/bin)

213
samples/img2xlsx.cpp Normal file
View File

@ -0,0 +1,213 @@
#include <array>
#include <iostream>
#include <unordered_set>
#include <vector>
#include <xlnt/xlnt.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// This sample demonstrates the use of some complex formatting methods to create
// a workbook in which each cell has a fill based on the pixels of an image
// thereby appearing as a mosaic of the given image. Two methods for achieving
// this effect are demonstrated: cell-level format and conditional formatting.
// Clean up the code with some alias declarations
using byte = std::uint8_t;
using pixel = std::array<byte, 3>;
using pixmap = std::vector<std::vector<pixel>>;
// Specify whether the CF or cell format method should be used
static const bool use_conditional_formatting = false;
// Returns a 2D vector of pixels from a given filename. Accepts all file types
// supported by stb_image (JPEG, PNG, TGA, BMP, PSD, GIF, HDR, PIC, PNM).
pixmap load_image(const std::string &filename)
{
int width = 0;
int height = 0;
int bpp = 0;
// Must be freed after use with stbi_image_free
auto image_data = stbi_load(filename.c_str(), &width, &height, &bpp, 0);
if (image_data == nullptr)
{
throw std::runtime_error("bad image or file not found: " + filename);
}
pixmap result;
for (auto row = 0; row < height; ++row)
{
std::vector<pixel> row_pixels;
for (auto column = 0; column < width; ++column)
{
auto r = image_data[row * width * bpp + column * bpp];
auto g = image_data[row * width * bpp + column * bpp + 1];
auto b = image_data[row * width * bpp + column * bpp + 2];
auto current_pixel = pixel({ r, g, b });
row_pixels.push_back(current_pixel);
}
result.push_back(row_pixels);
}
stbi_image_free(image_data);
return result;
}
// Builds and returns a workbook in which each cell has a value
// equal to the color of the pixel in the given image. A conditional format
// is created for every color to set the background fill of each cell to the
// color of its value. This is very slow for large images but is intended
// to illustrate a realistic use of conditional formatting.
xlnt::workbook build_workbook_cf(const pixmap &image)
{
// Create a default workbook with a single worksheet
xlnt::workbook wb;
// Get the active sheet (the only sheet in this case)
auto ws = wb.active_sheet();
// The reference to the cell which is being operated upon
auto current_cell = xlnt::cell_reference("A1");
// The range of cells which will be modified. This is required for conditional formats
auto range = ws.range(xlnt::range_reference(1, 1, image[0].size(), image.size()));
// Track the previously created conditonal formats so they are only created once
std::unordered_set<std::string> defined_colors;
// Iterate over each row in the source image
for (const auto &image_row : image)
{
// Iterate over each pixel in the image row
for (const auto &image_pixel : image_row)
{
// Build an xlnt compatible RGB color from the pixel byte array
const auto color = xlnt::rgb_color(image_pixel[0], image_pixel[1], image_pixel[2]);
// Only create the conditional format if it doesn't yet exist
if (defined_colors.count(color.hex_string()) == 0)
{
// The condition under which the conditional format applies to a cell
// In this case, the condition is satisfied when the text of the cell
// contains the hex string representing the pixel color.
const auto condition = xlnt::condition::text_contains(color.hex_string());
// Create a new conditional format with the above condition on the image pixel range
auto format = range.conditional_format(condition);
// Define the fill for the conditional format
format.fill(xlnt::pattern_fill().background(color));
// Record the created of this CF
defined_colors.insert(color.hex_string());
}
// Dereference the cell at the current position and set its value to the pixel color
ws.cell(current_cell).value(color.hex_string());
// Increment the column
current_cell.column_index(current_cell.column_index() + 1);
}
// Reached the end of the row--move to the first column of the next row
current_cell.row(current_cell.row() + 1);
current_cell.column_index("A");
// Show some progress, it can take a while...
std::cout << current_cell.row() << " " << defined_colors.size() << std::endl;
}
// Return the resulting workbook
return wb;
}
// Builds and returns a workbook in which each cell has a value
// equal to the color of the pixel in the given image. To accomplish this,
// a named style is created for every color in the image and is applied
// to each cell corresponding to pixels of that color.
xlnt::workbook build_workbook_normal(const pixmap &image)
{
// Create a default workbook with a single worksheet
xlnt::workbook wb;
// Get the active sheet (the only sheet in this case)
auto ws = wb.active_sheet();
// The reference to the cell which is being operated upon
auto current_cell = xlnt::cell_reference("A1");
// Iterate over each row in the source image
for (const auto &image_row : image)
{
// Iterate over each pixel in the image row
for (const auto &image_pixel : image_row)
{
// Build an xlnt compatible RGB color from the pixel byte array
const auto color = xlnt::rgb_color(image_pixel[0], image_pixel[1], image_pixel[2]);
// Only create the style if it doesn't yet exist in the workbook
if (!wb.has_style(color.hex_string()))
{
// A style is constructed on a workbook by providing a unique name
auto color_style = wb.create_style(color.hex_string());
// Set the fill to a solid fill of the pixel color
color_style.fill(xlnt::fill::solid(color));
}
// Dereference the cell at the current position and use the previously created color style
ws.cell(current_cell).style(color.hex_string());
// Increment the column
current_cell.column_index(current_cell.column_index() + 1);
}
// Reached the end of the row--move to the first column of the next row
current_cell.row(current_cell.row() + 1);
current_cell.column_index("A");
// Show some progress, it can take a while...
std::cout << current_cell.row() << std::endl;
}
// Return the resulting workbook
return wb;
}
// Builds and returns a workbook in which each cell has a value
// equal to the color of the pixel in the given image using the
// conditional formatting or cell-level formatting depending on
// the value of use_conditional_formatting defined at the top of
// this file.
xlnt::workbook build_workbook(const pixmap &image)
{
if (use_conditional_formatting)
{
return build_workbook_cf(image);
}
else
{
return build_workbook_normal(image);
}
}
// Entry point
int main(int argc, char *argv[])
{
// Ensure that there is a correct number of arguments
if (argc < 3)
{
std::cout << "usage: img2xlsx <input> <output>" << std::endl;
return 0;
}
// The first argument is the name of the input image
const auto input = std::string(argv[1]);
// Load the input image. An exception will be thrown if it doesn't exist.
const auto image = load_image(input);
// Build the workbook from the image
auto workbook = build_workbook(image);
// The second argument is the name of the file to save the workbook as.
const auto output = std::string(argv[2]);
// Save the workbook
workbook.save(output);
return 0;
}

7177
samples/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
#pragma once
#include <cstddef>
#include <xlnt/styles/conditional_format.hpp>
#include <xlnt/utils/optional.hpp>
#include <xlnt/worksheet/range_reference.hpp>
namespace xlnt {
class border;
class fill;
class font;
namespace detail {
struct stylesheet;
struct worksheet_impl;
struct conditional_format_impl
{
stylesheet *parent;
range_reference target_range;
worksheet_impl *target_sheet;
std::size_t priority;
std::size_t differential_format_id;
condition when;
optional<std::size_t> border_id;
optional<std::size_t> fill_id;
optional<std::size_t> font_id;
};
} // namespace detail
} // namespace xlnt

View File

@ -27,15 +27,17 @@
#include <string>
#include <vector>
#include <detail/conditional_format_impl.hpp>
#include <detail/format_impl.hpp>
#include <detail/style_impl.hpp>
#include <xlnt/cell/cell.hpp>
#include <xlnt/styles/conditional_format.hpp>
#include <xlnt/styles/format.hpp>
#include <xlnt/styles/style.hpp>
#include <xlnt/utils/exceptions.hpp>
#include <xlnt/worksheet/worksheet.hpp>
#include <xlnt/workbook/workbook.hpp>
#include <xlnt/workbook/worksheet_iterator.hpp>
#include <xlnt/worksheet/worksheet.hpp>
namespace xlnt {
namespace detail {
@ -510,10 +512,25 @@ struct stylesheet
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 garbage_collection_enabled = true;
bool garbage_collection_enabled = false;
std::list<conditional_format_impl> conditional_format_impls;
std::list<format_impl> format_impls;
std::unordered_map<std::string, style_impl> style_impls;
std::vector<std::string> style_names;

View File

@ -909,9 +909,201 @@ void xlsx_producer::write_shared_workbook_user_data(const relationship & /*rel*/
write_end_element(constants::ns("spreadsheetml"), "users");
}
void xlsx_producer::write_font(const font &f)
{
static const auto &xmlns = constants::ns("spreadsheetml");
write_start_element(xmlns, "font");
if (f.bold())
{
write_start_element(xmlns, "b");
write_attribute("val", write_bool(true));
write_end_element(xmlns, "b");
}
if (f.italic())
{
write_start_element(xmlns, "i");
write_attribute("val", write_bool(true));
write_end_element(xmlns, "i");
}
if (f.underlined())
{
write_start_element(xmlns, "u");
write_attribute("val", f.underline());
write_end_element(xmlns, "u");
}
if (f.strikethrough())
{
write_start_element(xmlns, "strike");
write_attribute("val", write_bool(true));
write_end_element(xmlns, "strike");
}
write_start_element(xmlns, "sz");
write_attribute("val", f.size());
write_end_element(xmlns, "sz");
if (f.has_color())
{
write_start_element(xmlns, "color");
write_color(f.color());
write_end_element(xmlns, "color");
}
write_start_element(xmlns, "name");
write_attribute("val", f.name());
write_end_element(xmlns, "name");
if (f.has_family())
{
write_start_element(xmlns, "family");
write_attribute("val", f.family());
write_end_element(xmlns, "family");
}
if (f.has_scheme())
{
write_start_element(xmlns, "scheme");
write_attribute("val", f.scheme());
write_end_element(xmlns, "scheme");
}
write_end_element(xmlns, "font");
}
void xlsx_producer::write_fill(const fill &f)
{
static const auto &xmlns = constants::ns("spreadsheetml");
write_start_element(xmlns, "fill");
if (f.type() == xlnt::fill_type::pattern)
{
const auto &pattern = f.pattern_fill();
write_start_element(xmlns, "patternFill");
if (pattern.type() != pattern_fill_type::none)
{
write_attribute("patternType", pattern.type());
}
if (pattern.foreground().is_set())
{
write_start_element(xmlns, "fgColor");
write_color(pattern.foreground().get());
write_end_element(xmlns, "fgColor");
}
if (pattern.background().is_set())
{
write_start_element(xmlns, "bgColor");
write_color(pattern.background().get());
write_end_element(xmlns, "bgColor");
}
write_end_element(xmlns, "patternFill");
}
else if (f.type() == xlnt::fill_type::gradient)
{
const auto &gradient = f.gradient_fill();
write_start_element(xmlns, "gradientFill");
write_attribute("gradientType", gradient.type());
if (gradient.degree() != 0.)
{
write_attribute("degree", gradient.degree());
}
if (gradient.left() != 0.)
{
write_attribute("left", gradient.left());
}
if (gradient.right() != 0.)
{
write_attribute("right", gradient.right());
}
if (gradient.top() != 0.)
{
write_attribute("top", gradient.top());
}
if (gradient.bottom() != 0.)
{
write_attribute("bottom", gradient.bottom());
}
for (const auto &stop : gradient.stops())
{
write_start_element(xmlns, "stop");
write_attribute("position", stop.first);
write_start_element(xmlns, "color");
write_color(stop.second);
write_end_element(xmlns, "color");
write_end_element(xmlns, "stop");
}
write_end_element(xmlns, "gradientFill");
}
write_end_element(xmlns, "fill");
}
void xlsx_producer::write_border(const border &current_border)
{
static const auto &xmlns = constants::ns("spreadsheetml");
write_start_element(xmlns, "border");
if (current_border.diagonal().is_set())
{
auto up = current_border.diagonal().get() == diagonal_direction::both
|| current_border.diagonal().get() == diagonal_direction::up;
write_attribute("diagonalUp", write_bool(up));
auto down = current_border.diagonal().get() == diagonal_direction::both
|| current_border.diagonal().get() == diagonal_direction::down;
write_attribute("diagonalDown", write_bool(down));
}
for (const auto &side : xlnt::border::all_sides())
{
if (current_border.side(side).is_set())
{
const auto current_side = current_border.side(side).get();
auto side_name = to_string(side);
write_start_element(xmlns, side_name);
if (current_side.style().is_set())
{
write_attribute("style", current_side.style().get());
}
if (current_side.color().is_set())
{
write_start_element(xmlns, "color");
write_color(current_side.color().get());
write_end_element(xmlns, "color");
}
write_end_element(xmlns, side_name);
}
}
write_end_element(xmlns, "border");
}
void xlsx_producer::write_styles(const relationship & /*rel*/)
{
static const auto &xmlns = constants::ns("spreadsheetml");
static const auto &xmlns = constants::ns("spreadsheetml");
write_start_element(xmlns, "styleSheet");
write_namespace(xmlns, "");
@ -954,69 +1146,10 @@ void xlsx_producer::write_styles(const relationship & /*rel*/)
write_start_element(xmlns, "fonts");
write_attribute("count", fonts.size());
for (const auto &current_font : fonts)
{
write_start_element(xmlns, "font");
if (current_font.bold())
{
write_start_element(xmlns, "b");
write_attribute("val", write_bool(true));
write_end_element(xmlns, "b");
}
if (current_font.italic())
{
write_start_element(xmlns, "i");
write_attribute("val", write_bool(true));
write_end_element(xmlns, "i");
}
if (current_font.underlined())
{
write_start_element(xmlns, "u");
write_attribute("val", current_font.underline());
write_end_element(xmlns, "u");
}
if (current_font.strikethrough())
{
write_start_element(xmlns, "strike");
write_attribute("val", write_bool(true));
write_end_element(xmlns, "strike");
}
write_start_element(xmlns, "sz");
write_attribute("val", current_font.size());
write_end_element(xmlns, "sz");
if (current_font.has_color())
{
write_start_element(xmlns, "color");
write_color(current_font.color());
write_end_element(xmlns, "color");
}
write_start_element(xmlns, "name");
write_attribute("val", current_font.name());
write_end_element(xmlns, "name");
if (current_font.has_family())
{
write_start_element(xmlns, "family");
write_attribute("val", current_font.family());
write_end_element(xmlns, "family");
}
if (current_font.has_scheme())
{
write_start_element(xmlns, "scheme");
write_attribute("val", current_font.scheme());
write_end_element(xmlns, "scheme");
}
write_end_element(xmlns, "font");
}
for (const auto &current_font : fonts)
{
write_font(current_font);
}
write_end_element(xmlns, "fonts");
}
@ -1030,79 +1163,9 @@ void xlsx_producer::write_styles(const relationship & /*rel*/)
write_start_element(xmlns, "fills");
write_attribute("count", fills.size());
for (auto &fill_ : fills)
for (auto &current_fill : fills)
{
write_start_element(xmlns, "fill");
if (fill_.type() == xlnt::fill_type::pattern)
{
const auto &pattern = fill_.pattern_fill();
write_start_element(xmlns, "patternFill");
write_attribute("patternType", pattern.type());
if (pattern.foreground().is_set())
{
write_start_element(xmlns, "fgColor");
write_color(pattern.foreground().get());
write_end_element(xmlns, "fgColor");
}
if (pattern.background().is_set())
{
write_start_element(xmlns, "bgColor");
write_color(pattern.background().get());
write_end_element(xmlns, "bgColor");
}
write_end_element(xmlns, "patternFill");
}
else if (fill_.type() == xlnt::fill_type::gradient)
{
const auto &gradient = fill_.gradient_fill();
write_start_element(xmlns, "gradientFill");
write_attribute("gradientType", gradient.type());
if (gradient.degree() != 0.)
{
write_attribute("degree", gradient.degree());
}
if (gradient.left() != 0.)
{
write_attribute("left", gradient.left());
}
if (gradient.right() != 0.)
{
write_attribute("right", gradient.right());
}
if (gradient.top() != 0.)
{
write_attribute("top", gradient.top());
}
if (gradient.bottom() != 0.)
{
write_attribute("bottom", gradient.bottom());
}
for (const auto &stop : gradient.stops())
{
write_start_element(xmlns, "stop");
write_attribute("position", stop.first);
write_start_element(xmlns, "color");
write_color(stop.second);
write_end_element(xmlns, "color");
write_end_element(xmlns, "stop");
}
write_end_element(xmlns, "gradientFill");
}
write_end_element(xmlns, "fill");
write_fill(current_fill);
}
write_end_element(xmlns, "fills");
@ -1119,45 +1182,7 @@ void xlsx_producer::write_styles(const relationship & /*rel*/)
for (const auto &current_border : borders)
{
write_start_element(xmlns, "border");
if (current_border.diagonal().is_set())
{
auto up = current_border.diagonal().get() == diagonal_direction::both
|| current_border.diagonal().get() == diagonal_direction::up;
write_attribute("diagonalUp", write_bool(up));
auto down = current_border.diagonal().get() == diagonal_direction::both
|| current_border.diagonal().get() == diagonal_direction::down;
write_attribute("diagonalDown", write_bool(down));
}
for (const auto &side : xlnt::border::all_sides())
{
if (current_border.side(side).is_set())
{
const auto current_side = current_border.side(side).get();
auto side_name = to_string(side);
write_start_element(xmlns, side_name);
if (current_side.style().is_set())
{
write_attribute("style", current_side.style().get());
}
if (current_side.color().is_set())
{
write_start_element(xmlns, "color");
write_color(current_side.color().get());
write_end_element(xmlns, "color");
}
write_end_element(xmlns, side_name);
}
}
write_end_element(xmlns, "border");
write_border(current_border);
}
write_end_element(xmlns, "borders");
@ -1433,8 +1458,35 @@ void xlsx_producer::write_styles(const relationship & /*rel*/)
write_end_element(xmlns, "cellStyles");
}
// Conditional Formats
write_start_element(xmlns, "dxfs");
write_attribute("count", "0");
write_attribute("count", stylesheet.conditional_format_impls.size());
for (auto &rule : stylesheet.conditional_format_impls)
{
write_start_element(xmlns, "dxf");
if (rule.border_id.is_set())
{
const auto &current_border = stylesheet.borders.at(rule.border_id.get());
write_border(current_border);
}
if (rule.fill_id.is_set())
{
const auto &current_fill = stylesheet.fills.at(rule.fill_id.get());
write_fill(current_fill);
}
if (rule.font_id.is_set())
{
const auto &current_font = stylesheet.fonts.at(rule.font_id.get());
write_font(current_font);
}
write_end_element(xmlns, "dxf");
}
write_end_element(xmlns, "dxfs");
write_start_element(xmlns, "tableStyles");
@ -2312,6 +2364,49 @@ void xlsx_producer::write_worksheet(const relationship &rel)
write_end_element(xmlns, "mergeCells");
}
// scope for conditional formatting production logic
{
const auto &stylesheet = source_.impl().stylesheet_.get();
const auto &cf_impls = stylesheet.conditional_format_impls;
std::unordered_map<std::string, std::vector<const conditional_format_impl *>> range_map;
for (auto &cf : cf_impls)
{
if (cf.target_sheet != ws.d_) continue;
if (range_map.find(cf.target_range.to_string()) == range_map.end())
{
range_map[cf.target_range.to_string()] = {};
}
range_map[cf.target_range.to_string()].push_back(&cf);
}
for (const auto &range_rules_pair : range_map)
{
write_start_element(xmlns, "conditionalFormatting");
write_attribute("sqref", range_rules_pair.first);
std::size_t i = 1;
for (auto rule : range_rules_pair.second)
{
write_start_element(xmlns, "cfRule");
write_attribute("type", "containsText");
write_attribute("operator", "containsText");
write_attribute("dxfId", rule->differential_format_id);
write_attribute("priority", i++);
write_attribute("text", rule->when.text_comparand_);
//TODO: what does this formula mean and why is it necessary?
write_element(xmlns, "formula", "NOT(ISERROR(SEARCH(\"" + rule->when.text_comparand_ + "\",A1)))");
write_end_element(xmlns, "cfRule");
}
write_end_element(xmlns, "conditionalFormatting");
}
}
if (!hyperlink_rels.empty())
{
write_start_element(xmlns, "hyperlinks");

View File

@ -122,7 +122,9 @@ private:
void write_relationships(const std::vector<xlnt::relationship> &relationships, const path &part);
void write_color(const xlnt::color &color);
void write_dxfs();
void write_border(const xlnt::border &b);
void write_fill(const xlnt::fill &f);
void write_font(const xlnt::font &f);
void write_table_styles();
void write_colors(const std::vector<xlnt::color> &colors);

View File

@ -0,0 +1,117 @@
// Copyright (c) 2014-2017 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, 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/styles/border.hpp>
#include <xlnt/styles/fill.hpp>
#include <xlnt/styles/font.hpp>
#include <xlnt/styles/conditional_format.hpp>
#include <detail/conditional_format_impl.hpp>
#include <detail/stylesheet.hpp>
namespace xlnt {
condition condition::text_starts_with(const std::string &text)
{
condition c;
c.type_ = type::contains_text;
c.operator_ = condition_operator::starts_with;
c.text_comparand_ = text;
return c;
}
condition condition::text_ends_with(const std::string &text)
{
condition c;
c.type_ = type::contains_text;
c.operator_ = condition_operator::ends_with;
c.text_comparand_ = text;
return c;
}
condition condition::text_contains(const std::string &text)
{
condition c;
c.type_ = type::contains_text;
c.operator_ = condition_operator::contains;
c.text_comparand_ = text;
return c;
}
condition condition::text_does_not_contain(const std::string &text)
{
condition c;
c.type_ = type::contains_text;
c.operator_ = condition_operator::does_not_contain;
c.text_comparand_ = text;
return c;
}
conditional_format::conditional_format(detail::conditional_format_impl *d) : d_(d)
{
}
bool conditional_format::operator==(const conditional_format &other) const
{
return d_ == other.d_;
}
bool conditional_format::operator!=(const conditional_format &other) const
{
return !(*this == other);
}
xlnt::border conditional_format::border() const
{
return d_->parent->borders.at(d_->border_id.get());
}
conditional_format conditional_format::border(const xlnt::border &new_border)
{
d_->border_id = d_->parent->find_or_add(d_->parent->borders, new_border);
return *this;
}
xlnt::fill conditional_format::fill() const
{
return d_->parent->fills.at(d_->fill_id.get());
}
conditional_format conditional_format::fill(const xlnt::fill &new_fill)
{
d_->fill_id = d_->parent->find_or_add(d_->parent->fills, new_fill);
return *this;
}
xlnt::font conditional_format::font() const
{
return d_->parent->fonts.at(d_->font_id.get());
}
conditional_format conditional_format::font(const xlnt::font &new_font)
{
d_->font_id = d_->parent->find_or_add(d_->parent->fonts, new_font);
return *this;
}
} // namespace xlnt

View File

@ -252,8 +252,10 @@ bool gradient_fill::operator!=(const gradient_fill &other) const
fill fill::solid(const color &fill_color)
{
return fill(
xlnt::pattern_fill().type(xlnt::pattern_fill_type::solid).foreground(fill_color).background(indexed_color(64)));
return fill(xlnt::pattern_fill()
.type(xlnt::pattern_fill_type::solid)
.foreground(fill_color)
.background(indexed_color(64)));
}
fill::fill()

View File

@ -29,6 +29,7 @@
#include <xlnt/styles/number_format.hpp>
#include <xlnt/styles/protection.hpp>
#include <xlnt/styles/style.hpp>
#include <detail/style_impl.hpp>
#include <detail/stylesheet.hpp>
namespace {

View File

@ -141,6 +141,11 @@ range range::style(const std::string &style_name)
return style(ws_.workbook().style(style_name));
}
conditional_format range::conditional_format(const condition &when)
{
return ws_.conditional_format(ref_, when);
}
void range::apply(std::function<void(class cell)> f)
{
for (auto row : *this)

View File

@ -986,4 +986,9 @@ void worksheet::parent(xlnt::workbook &wb)
d_->parent_ = &wb;
}
conditional_format worksheet::conditional_format(const range_reference &ref, const condition &when)
{
return workbook().d_->stylesheet_.get().add_conditional_format_rule(d_, ref, when);
}
} // namespace xlnt