Commit fa3664357b6fd23b6d74c6835bbf6c8e911892aa
1 parent
313ba081
Move numrange code from qpdf.cc to QUtil.cc
Also move tests to libtests.
Showing
7 changed files
with
286 additions
and
244 deletions
include/qpdf/QUtil.hh
| @@ -26,6 +26,7 @@ | @@ -26,6 +26,7 @@ | ||
| 26 | #include <qpdf/Types.h> | 26 | #include <qpdf/Types.h> |
| 27 | #include <string> | 27 | #include <string> |
| 28 | #include <list> | 28 | #include <list> |
| 29 | +#include <vector> | ||
| 29 | #include <stdexcept> | 30 | #include <stdexcept> |
| 30 | #include <stdio.h> | 31 | #include <stdio.h> |
| 31 | #include <time.h> | 32 | #include <time.h> |
| @@ -220,6 +221,11 @@ namespace QUtil | @@ -220,6 +221,11 @@ namespace QUtil | ||
| 220 | 221 | ||
| 221 | QPDF_DLL | 222 | QPDF_DLL |
| 222 | bool is_number(char const*); | 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 | #endif // QUTIL_HH | 231 | #endif // QUTIL_HH |
libqpdf/QUtil.cc
| @@ -718,3 +718,177 @@ QUtil::strcasecmp(char const *s1, char const *s2) | @@ -718,3 +718,177 @@ QUtil::strcasecmp(char const *s1, char const *s2) | ||
| 718 | return ::strcasecmp(s1, s2); | 718 | return ::strcasecmp(s1, s2); |
| 719 | #endif | 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
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& pdf, Options& o) | @@ -635,180 +635,25 @@ static void show_encryption(QPDF& pdf, Options& 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 | static std::vector<int> parse_numrange(char const* range, int max, | 638 | static std::vector<int> parse_numrange(char const* range, int max, |
| 655 | bool throw_error = false) | 639 | bool throw_error = false) |
| 656 | { | 640 | { |
| 657 | - std::vector<int> result; | ||
| 658 | - char const* p = range; | ||
| 659 | try | 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 | if (throw_error) | 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 | else | 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 | static void | 659 | static void |
| @@ -1213,25 +1058,6 @@ parse_pages_options( | @@ -1213,25 +1058,6 @@ parse_pages_options( | ||
| 1213 | return result; | 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 | QPDFPageData::QPDFPageData(std::string const& filename, | 1061 | QPDFPageData::QPDFPageData(std::string const& filename, |
| 1236 | QPDF* qpdf, | 1062 | QPDF* qpdf, |
| 1237 | char const* range) : | 1063 | char const* range) : |
| @@ -1429,14 +1255,7 @@ static void parse_options(int argc, char* argv[], Options& o) | @@ -1429,14 +1255,7 @@ static void parse_options(int argc, char* argv[], Options& o) | ||
| 1429 | *parameter++ = 0; | 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 | if (parameter == 0) | 1260 | if (parameter == 0) |
| 1442 | { | 1261 | { |
qpdf/qtest/qpdf.test
| @@ -1321,63 +1321,6 @@ $td->runtest("check output", | @@ -1321,63 +1321,6 @@ $td->runtest("check output", | ||
| 1321 | 1321 | ||
| 1322 | show_ntests(); | 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 | $td->notify("--- Merging and Splitting ---"); | 1324 | $td->notify("--- Merging and Splitting ---"); |
| 1382 | $n_tests += 18; | 1325 | $n_tests += 18; |
| 1383 | 1326 |