/************************************************************************* ** PageRanges.cpp ** ** ** ** This file is part of dvisvgm -- a fast DVI to SVG converter ** ** Copyright (C) 2005-2025 Martin Gieseking ** ** ** ** This program is free software; you can redistribute it and/or ** ** modify it under the terms of the GNU General Public License as ** ** published by the Free Software Foundation; either version 3 of ** ** the License, or (at your option) any later version. ** ** ** ** This program is distributed in the hope that it will be useful, but ** ** WITHOUT ANY WARRANTY; without even the implied warranty of ** ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** ** GNU General Public License for more details. ** ** ** ** You should have received a copy of the GNU General Public License ** ** along with this program; if not, see . ** *************************************************************************/ #include #include "algorithm.hpp" #include "Calculator.hpp" #include "InputBuffer.hpp" #include "InputReader.hpp" #include "MessageException.hpp" #include "PageRanges.hpp" using namespace std; using FilterFunc = bool (*)(int); static bool is_even (int n) {return n % 2 == 0;} static bool is_odd (int n) {return n % 2 == 1;} /** Replaces expressions in a given string by the corresponding values and returns the result. * Supported expressions: * %P: maximum page number * %(expr): arithmetic expression * @param[in] str string to expand * @param[in] max_page maximum page number * @return the expanded string */ static string expand_variables (string str, int max_page) { string result; while (!str.empty()) { auto pos = str.find('%'); if (pos == string::npos) { result += str; str.clear(); } else { result += str.substr(0, pos); str = str.substr(pos); pos = 1; switch (str[pos]) { case 'P': result += to_string(max_page); break; case '(': { auto endpos = str.find(')', pos); if (endpos == string::npos) throw MessageException("missing ')' in page argument"); if (endpos-pos > 1) { try { Calculator calculator; calculator.setVariable("P", max_page); result += to_string(static_cast(calculator.eval(str.substr(pos, endpos-pos+1)))); } catch (CalculatorException &e) { throw MessageException("error in page argument (" + string(e.what()) + ")"); } pos = endpos; } break; } default: throw MessageException("invalid expression '%" + str.substr(pos,1) + "' in page argument"); } str = str.substr(pos+1); } } return result; } /** Analyzes a string describing a range sequence. * Syntax: ([0-9]+(-[0-9]*)?)|(-[0-9]+)(,([0-9]+(-[0-9]*)?)|(-[0-9]+))* * @param[in] str string to parse * @param[in] max_page greatest allowed value * @return true on success; false denotes a syntax error */ bool PageRanges::parse (string str, int max_page) { if (max_page > 0) str = expand_variables(str, max_page); StringInputBuffer ib(str); BufferInputReader ir(ib); while (ir && ir.peek() != ':') { int first=1; int last=max_page; ir.skipSpace(); if (!isdigit(ir.peek()) && ir.peek() != '-') return false; if (isdigit(ir.peek())) first = last = ir.getInt(); ir.skipSpace(); if (ir.peek() == '-') { while (ir.peek() == '-') ir.get(); ir.skipSpace(); last = isdigit(ir.peek()) ? ir.getInt() : max_page; } ir.skipSpace(); if (ir.peek() == ',') { ir.get(); if (ir.eof()) return false; } else if (!ir.eof() && ir.peek() != ':') return false; if (first > last) swap(first, last); first = max(1, first); last = max(first, last); if (first <= max_page || max_page == 0) { if (max_page > 0) { first = min(first, max_page); last = min(last, max_page); } addRange(first, last); } } // apply filter if present if (ir.peek() == ':') { ir.get(); string filterName = ir.getWord(); FilterFunc filterFunc; if (filterName == "even") filterFunc = &is_even; else if (filterName == "odd") filterFunc = &is_odd; else return false; *this = filter(filterFunc); } return true; } /** Returns a new PageRanges object that contains only the values * for which the given filter function returns true. */ PageRanges PageRanges::filter (FilterFunc filterFunc) const { PageRanges newRanges; if (filterFunc == nullptr) newRanges = *this; else { for (const auto &range : *this) { for (int i=range.first; i <= range.second; i++) if (filterFunc(i)) newRanges.addRange(i, i); } } return newRanges; } /** Returns the number of pages. */ size_t PageRanges::numberOfPages () const { return algo::accumulate(*this, 0, [](int sum, const Range &range) { return sum + range.second - range.first + 1; }); }