Commit fa3664357b6fd23b6d74c6835bbf6c8e911892aa

Authored by Jay Berkenbilt
1 parent 313ba081

Move numrange code from qpdf.cc to QUtil.cc

Also move tests to libtests.
include/qpdf/QUtil.hh
... ... @@ -26,6 +26,7 @@
26 26 #include <qpdf/Types.h>
27 27 #include <string>
28 28 #include <list>
  29 +#include <vector>
29 30 #include <stdexcept>
30 31 #include <stdio.h>
31 32 #include <time.h>
... ... @@ -220,6 +221,11 @@ namespace QUtil
220 221  
221 222 QPDF_DLL
222 223 bool is_number(char const*);
  224 +
  225 + // This method parses the numeric range syntax used by the qpdf
  226 + // command-line tool. May throw std::runtime_error.
  227 + QPDF_DLL
  228 + std::vector<int> parse_numrange(char const* range, int max);
223 229 };
224 230  
225 231 #endif // QUTIL_HH
... ...
libqpdf/QUtil.cc
... ... @@ -718,3 +718,177 @@ QUtil::strcasecmp(char const *s1, char const *s2)
718 718 return ::strcasecmp(s1, s2);
719 719 #endif
720 720 }
  721 +
  722 +static int maybe_from_end(int num, bool from_end, int max)
  723 +{
  724 + if (from_end)
  725 + {
  726 + if (num > max)
  727 + {
  728 + num = 0;
  729 + }
  730 + else
  731 + {
  732 + num = max + 1 - num;
  733 + }
  734 + }
  735 + return num;
  736 +}
  737 +
  738 +std::vector<int>
  739 +QUtil::parse_numrange(char const* range, int max)
  740 +{
  741 + std::vector<int> result;
  742 + char const* p = range;
  743 + try
  744 + {
  745 + std::vector<int> work;
  746 + static int const comma = -1;
  747 + static int const dash = -2;
  748 +
  749 + enum { st_top,
  750 + st_in_number,
  751 + st_after_number } state = st_top;
  752 + bool last_separator_was_dash = false;
  753 + int cur_number = 0;
  754 + bool from_end = false;
  755 + while (*p)
  756 + {
  757 + char ch = *p;
  758 + if (isdigit(ch))
  759 + {
  760 + if (! ((state == st_top) || (state == st_in_number)))
  761 + {
  762 + throw std::runtime_error("digit not expected");
  763 + }
  764 + state = st_in_number;
  765 + cur_number *= 10;
  766 + cur_number += (ch - '0');
  767 + }
  768 + else if (ch == 'z')
  769 + {
  770 + // z represents max
  771 + if (! (state == st_top))
  772 + {
  773 + throw std::runtime_error("z not expected");
  774 + }
  775 + state = st_after_number;
  776 + cur_number = max;
  777 + }
  778 + else if (ch == 'r')
  779 + {
  780 + if (! (state == st_top))
  781 + {
  782 + throw std::runtime_error("r not expected");
  783 + }
  784 + state = st_in_number;
  785 + from_end = true;
  786 + }
  787 + else if ((ch == ',') || (ch == '-'))
  788 + {
  789 + if (! ((state == st_in_number) || (state == st_after_number)))
  790 + {
  791 + throw std::runtime_error("unexpected separator");
  792 + }
  793 + cur_number = maybe_from_end(cur_number, from_end, max);
  794 + work.push_back(cur_number);
  795 + cur_number = 0;
  796 + from_end = false;
  797 + if (ch == ',')
  798 + {
  799 + state = st_top;
  800 + last_separator_was_dash = false;
  801 + work.push_back(comma);
  802 + }
  803 + else if (ch == '-')
  804 + {
  805 + if (last_separator_was_dash)
  806 + {
  807 + throw std::runtime_error("unexpected dash");
  808 + }
  809 + state = st_top;
  810 + last_separator_was_dash = true;
  811 + work.push_back(dash);
  812 + }
  813 + }
  814 + else
  815 + {
  816 + throw std::runtime_error("unexpected character");
  817 + }
  818 + ++p;
  819 + }
  820 + if ((state == st_in_number) || (state == st_after_number))
  821 + {
  822 + cur_number = maybe_from_end(cur_number, from_end, max);
  823 + work.push_back(cur_number);
  824 + }
  825 + else
  826 + {
  827 + throw std::runtime_error("number expected");
  828 + }
  829 +
  830 + p = 0;
  831 + for (size_t i = 0; i < work.size(); i += 2)
  832 + {
  833 + int num = work.at(i);
  834 + // max == 0 means we don't know the max and are just
  835 + // testing for valid syntax.
  836 + if ((max > 0) && ((num < 1) || (num > max)))
  837 + {
  838 + throw std::runtime_error(
  839 + "number " + QUtil::int_to_string(num) + " out of range");
  840 + }
  841 + if (i == 0)
  842 + {
  843 + result.push_back(work.at(i));
  844 + }
  845 + else
  846 + {
  847 + int separator = work.at(i-1);
  848 + if (separator == comma)
  849 + {
  850 + result.push_back(num);
  851 + }
  852 + else if (separator == dash)
  853 + {
  854 + int lastnum = result.back();
  855 + if (num > lastnum)
  856 + {
  857 + for (int j = lastnum + 1; j <= num; ++j)
  858 + {
  859 + result.push_back(j);
  860 + }
  861 + }
  862 + else
  863 + {
  864 + for (int j = lastnum - 1; j >= num; --j)
  865 + {
  866 + result.push_back(j);
  867 + }
  868 + }
  869 + }
  870 + else
  871 + {
  872 + throw std::logic_error(
  873 + "INTERNAL ERROR parsing numeric range");
  874 + }
  875 + }
  876 + }
  877 + }
  878 + catch (std::runtime_error const& e)
  879 + {
  880 + std::string message;
  881 + if (p)
  882 + {
  883 + message = "error at * in numeric range " +
  884 + std::string(range, p - range) + "*" + p + ": " + e.what();
  885 + }
  886 + else
  887 + {
  888 + message = "error in numeric range " +
  889 + std::string(range) + ": " + e.what();
  890 + }
  891 + throw std::runtime_error(message);
  892 + }
  893 + return result;
  894 +}
... ...
libtests/build.mk
... ... @@ -13,6 +13,7 @@ BINS_libtests = \
13 13 json \
14 14 lzw \
15 15 md5 \
  16 + numrange \
16 17 pointer_holder \
17 18 predictors \
18 19 qutil \
... ...
libtests/numrange.cc 0 โ†’ 100644
  1 +#include <qpdf/QUtil.hh>
  2 +#include <iostream>
  3 +
  4 +static void test_numrange(char const* range)
  5 +{
  6 + if (range == 0)
  7 + {
  8 + std::cout << "null" << std::endl;
  9 + }
  10 + else
  11 + {
  12 + std::vector<int> result = QUtil::parse_numrange(range, 15);
  13 + std::cout << "numeric range " << range << " ->";
  14 + for (std::vector<int>::iterator iter = result.begin();
  15 + iter != result.end(); ++iter)
  16 + {
  17 + std::cout << " " << *iter;
  18 + }
  19 + std::cout << std::endl;
  20 + }
  21 +}
  22 +
  23 +int main(int argc, char* argv[])
  24 +{
  25 + try
  26 + {
  27 + test_numrange(argv[1]);
  28 + }
  29 + catch (std::exception& e)
  30 + {
  31 + std::cout << e.what() << std::endl;
  32 + return 2;
  33 + }
  34 +
  35 + return 0;
  36 +}
... ...
libtests/qtest/numrange.test 0 โ†’ 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +require TestDriver;
  7 +
  8 +my $td = new TestDriver('numrange');
  9 +
  10 +my @nrange_tests = (
  11 + [",5",
  12 + "error at * in numeric range *,5: unexpected separator",
  13 + 2],
  14 + ["4,,5",
  15 + "error at * in numeric range 4,*,5: unexpected separator",
  16 + 2],
  17 + ["4,5,",
  18 + "error at * in numeric range 4,5,*: number expected",
  19 + 2],
  20 + ["z1,",
  21 + "error at * in numeric range z*1,: digit not expected",
  22 + 2],
  23 + ["1z,",
  24 + "error at * in numeric range 1*z,: z not expected",
  25 + 2],
  26 + ["1-5?",
  27 + "error at * in numeric range 1-5*?: unexpected character",
  28 + 2],
  29 + ["1-30",
  30 + "error in numeric range 1-30: number 30 out of range",
  31 + 2],
  32 + ["1-10,0,5",
  33 + "error in numeric range 1-10,0,5: number 0 out of range",
  34 + 2],
  35 + ["1-10,1234,5",
  36 + "error in numeric range 1-10,1234,5: number 1234 out of range",
  37 + 2],
  38 + ["1,r,3",
  39 + "error in numeric range 1,r,3: number 16 out of range",
  40 + 2],
  41 + ["1,r16,3",
  42 + "error in numeric range 1,r16,3: number 0 out of range",
  43 + 2],
  44 + ["1,3,5-10,z-13,13,9,z,2,r2-r4",
  45 + "numeric range 1,3,5-10,z-13,13,9,z,2,r2-r4" .
  46 + " -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2 14 13 12",
  47 + 0],
  48 + ["r1-r15", # r\d+ at end
  49 + "numeric range r1-r15" .
  50 + " -> 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1",
  51 + 0],
  52 + );
  53 +foreach my $d (@nrange_tests)
  54 +{
  55 + my ($range, $output, $status) = @$d;
  56 + $td->runtest("numeric range $range",
  57 + {$td->COMMAND => ['numrange', $range],
  58 + $td->FILTER => "grep 'numeric range'"},
  59 + {$td->STRING => $output . "\n", $td->EXIT_STATUS => $status},
  60 + $td->NORMALIZE_NEWLINES);
  61 +}
  62 +
  63 +$td->report(scalar(@nrange_tests));
... ...
qpdf/qpdf.cc
... ... @@ -635,180 +635,25 @@ static void show_encryption(QPDF&amp; pdf, Options&amp; o)
635 635 }
636 636 }
637 637  
638   -static int maybe_from_end(int num, bool from_end, int max)
639   -{
640   - if (from_end)
641   - {
642   - if (num > max)
643   - {
644   - num = 0;
645   - }
646   - else
647   - {
648   - num = max + 1 - num;
649   - }
650   - }
651   - return num;
652   -}
653   -
654 638 static std::vector<int> parse_numrange(char const* range, int max,
655 639 bool throw_error = false)
656 640 {
657   - std::vector<int> result;
658   - char const* p = range;
659 641 try
660 642 {
661   - std::vector<int> work;
662   - static int const comma = -1;
663   - static int const dash = -2;
664   -
665   - enum { st_top,
666   - st_in_number,
667   - st_after_number } state = st_top;
668   - bool last_separator_was_dash = false;
669   - int cur_number = 0;
670   - bool from_end = false;
671   - while (*p)
672   - {
673   - char ch = *p;
674   - if (isdigit(ch))
675   - {
676   - if (! ((state == st_top) || (state == st_in_number)))
677   - {
678   - throw std::runtime_error("digit not expected");
679   - }
680   - state = st_in_number;
681   - cur_number *= 10;
682   - cur_number += (ch - '0');
683   - }
684   - else if (ch == 'z')
685   - {
686   - // z represents max
687   - if (! (state == st_top))
688   - {
689   - throw std::runtime_error("z not expected");
690   - }
691   - state = st_after_number;
692   - cur_number = max;
693   - }
694   - else if (ch == 'r')
695   - {
696   - if (! (state == st_top))
697   - {
698   - throw std::runtime_error("r not expected");
699   - }
700   - state = st_in_number;
701   - from_end = true;
702   - }
703   - else if ((ch == ',') || (ch == '-'))
704   - {
705   - if (! ((state == st_in_number) || (state == st_after_number)))
706   - {
707   - throw std::runtime_error("unexpected separator");
708   - }
709   - cur_number = maybe_from_end(cur_number, from_end, max);
710   - work.push_back(cur_number);
711   - cur_number = 0;
712   - from_end = false;
713   - if (ch == ',')
714   - {
715   - state = st_top;
716   - last_separator_was_dash = false;
717   - work.push_back(comma);
718   - }
719   - else if (ch == '-')
720   - {
721   - if (last_separator_was_dash)
722   - {
723   - throw std::runtime_error("unexpected dash");
724   - }
725   - state = st_top;
726   - last_separator_was_dash = true;
727   - work.push_back(dash);
728   - }
729   - }
730   - else
731   - {
732   - throw std::runtime_error("unexpected character");
733   - }
734   - ++p;
735   - }
736   - if ((state == st_in_number) || (state == st_after_number))
737   - {
738   - cur_number = maybe_from_end(cur_number, from_end, max);
739   - work.push_back(cur_number);
740   - }
741   - else
742   - {
743   - throw std::runtime_error("number expected");
744   - }
745   -
746   - p = 0;
747   - for (size_t i = 0; i < work.size(); i += 2)
748   - {
749   - int num = work.at(i);
750   - // max == 0 means we don't know the max and are just
751   - // testing for valid syntax.
752   - if ((max > 0) && ((num < 1) || (num > max)))
753   - {
754   - throw std::runtime_error(
755   - "number " + QUtil::int_to_string(num) + " out of range");
756   - }
757   - if (i == 0)
758   - {
759   - result.push_back(work.at(i));
760   - }
761   - else
762   - {
763   - int separator = work.at(i-1);
764   - if (separator == comma)
765   - {
766   - result.push_back(num);
767   - }
768   - else if (separator == dash)
769   - {
770   - int lastnum = result.back();
771   - if (num > lastnum)
772   - {
773   - for (int j = lastnum + 1; j <= num; ++j)
774   - {
775   - result.push_back(j);
776   - }
777   - }
778   - else
779   - {
780   - for (int j = lastnum - 1; j >= num; --j)
781   - {
782   - result.push_back(j);
783   - }
784   - }
785   - }
786   - else
787   - {
788   - throw std::logic_error(
789   - "INTERNAL ERROR parsing numeric range");
790   - }
791   - }
792   - }
  643 + return QUtil::parse_numrange(range, max);
793 644 }
794   - catch (std::runtime_error const& e)
  645 + catch (std::runtime_error& e)
795 646 {
796 647 if (throw_error)
797 648 {
798   - throw e;
799   - }
800   - if (p)
801   - {
802   - usage("error at * in numeric range " +
803   - std::string(range, p - range) + "*" + p + ": " + e.what());
  649 + throw(e);
804 650 }
805 651 else
806 652 {
807   - usage("error in numeric range " +
808   - std::string(range) + ": " + e.what());
  653 + usage(e.what());
809 654 }
810 655 }
811   - return result;
  656 + return std::vector<int>();
812 657 }
813 658  
814 659 static void
... ... @@ -1213,25 +1058,6 @@ parse_pages_options(
1213 1058 return result;
1214 1059 }
1215 1060  
1216   -static void test_numrange(char const* range)
1217   -{
1218   - if (range == 0)
1219   - {
1220   - std::cout << "null" << std::endl;
1221   - }
1222   - else
1223   - {
1224   - std::vector<int> result = parse_numrange(range, 15);
1225   - std::cout << "numeric range " << range << " ->";
1226   - for (std::vector<int>::iterator iter = result.begin();
1227   - iter != result.end(); ++iter)
1228   - {
1229   - std::cout << " " << *iter;
1230   - }
1231   - std::cout << std::endl;
1232   - }
1233   -}
1234   -
1235 1061 QPDFPageData::QPDFPageData(std::string const& filename,
1236 1062 QPDF* qpdf,
1237 1063 char const* range) :
... ... @@ -1429,14 +1255,7 @@ static void parse_options(int argc, char* argv[], Options&amp; o)
1429 1255 *parameter++ = 0;
1430 1256 }
1431 1257  
1432   - // Arguments that start with space are undocumented and
1433   - // are for use by the test suite.
1434   - if (strcmp(arg, " test-numrange") == 0)
1435   - {
1436   - test_numrange(parameter);
1437   - exit(0);
1438   - }
1439   - else if (strcmp(arg, "password") == 0)
  1258 + if (strcmp(arg, "password") == 0)
1440 1259 {
1441 1260 if (parameter == 0)
1442 1261 {
... ...
qpdf/qtest/qpdf.test
... ... @@ -1321,63 +1321,6 @@ $td-&gt;runtest(&quot;check output&quot;,
1321 1321  
1322 1322 show_ntests();
1323 1323 # ----------
1324   -$td->notify("--- Numeric range parsing tests ---");
1325   -my @nrange_tests = (
1326   - [",5",
1327   - "qpdf: error at * in numeric range *,5: unexpected separator",
1328   - 2],
1329   - ["4,,5",
1330   - "qpdf: error at * in numeric range 4,*,5: unexpected separator",
1331   - 2],
1332   - ["4,5,",
1333   - "qpdf: error at * in numeric range 4,5,*: number expected",
1334   - 2],
1335   - ["z1,",
1336   - "qpdf: error at * in numeric range z*1,: digit not expected",
1337   - 2],
1338   - ["1z,",
1339   - "qpdf: error at * in numeric range 1*z,: z not expected",
1340   - 2],
1341   - ["1-5?",
1342   - "qpdf: error at * in numeric range 1-5*?: unexpected character",
1343   - 2],
1344   - ["1-30",
1345   - "qpdf: error in numeric range 1-30: number 30 out of range",
1346   - 2],
1347   - ["1-10,0,5",
1348   - "qpdf: error in numeric range 1-10,0,5: number 0 out of range",
1349   - 2],
1350   - ["1-10,1234,5",
1351   - "qpdf: error in numeric range 1-10,1234,5: number 1234 out of range",
1352   - 2],
1353   - ["1,r,3",
1354   - "qpdf: error in numeric range 1,r,3: number 16 out of range",
1355   - 2],
1356   - ["1,r16,3",
1357   - "qpdf: error in numeric range 1,r16,3: number 0 out of range",
1358   - 2],
1359   - ["1,3,5-10,z-13,13,9,z,2,r2-r4",
1360   - "numeric range 1,3,5-10,z-13,13,9,z,2,r2-r4" .
1361   - " -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2 14 13 12",
1362   - 0],
1363   - ["r1-r15", # r\d+ at end
1364   - "numeric range r1-r15" .
1365   - " -> 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1",
1366   - 0],
1367   - );
1368   -$n_tests += scalar(@nrange_tests);
1369   -foreach my $d (@nrange_tests)
1370   -{
1371   - my ($range, $output, $status) = @$d;
1372   - $td->runtest("numeric range $range",
1373   - {$td->COMMAND => ['qpdf', '-- test-numrange=' . $range],
1374   - $td->FILTER => "grep 'numeric range'"},
1375   - {$td->STRING => $output . "\n", $td->EXIT_STATUS => $status},
1376   - $td->NORMALIZE_NEWLINES);
1377   -}
1378   -
1379   -show_ntests();
1380   -# ----------
1381 1324 $td->notify("--- Merging and Splitting ---");
1382 1325 $n_tests += 18;
1383 1326  
... ...