001/*
002 * $Id: SequenceList.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.util.LinkedList;
047import java.util.List;
048import java.util.ListIterator;
049
050/**
051 * This class expands a string into a list of numbers. The main use is to select a
052 * range of pages.
053 * <p>
054 * The general syntax is:<br>
055 * [!][o][odd][e][even]start-end
056 * <p>
057 * You can have multiple ranges separated by commas ','. The '!' modifier removes the
058 * range from what is already selected. The range changes are incremental, that is,
059 * numbers are added or deleted as the range appears. The start or the end, but not both, can be omitted.
060 */
061public class SequenceList {
062    protected static final int COMMA = 1;
063    protected static final int MINUS = 2;
064    protected static final int NOT = 3;
065    protected static final int TEXT = 4;
066    protected static final int NUMBER = 5;
067    protected static final int END = 6;
068    protected static final char EOT = '\uffff';
069
070    private static final int FIRST = 0;
071    private static final int DIGIT = 1;
072    private static final int OTHER = 2;
073    private static final int DIGIT2 = 3;
074    private static final String NOT_OTHER = "-,!0123456789";
075
076    protected char text[];
077    protected int ptr;
078    protected int number;
079    protected String other;
080
081    protected int low;
082    protected int high;
083    protected boolean odd;
084    protected boolean even;
085    protected boolean inverse;
086
087    protected SequenceList(String range) {
088        ptr = 0;
089        text = range.toCharArray();
090    }
091
092    protected char nextChar() {
093        while (true) {
094            if (ptr >= text.length)
095                return EOT;
096            char c = text[ptr++];
097            if (c > ' ')
098                return c;
099        }
100    }
101
102    protected void putBack() {
103        --ptr;
104        if (ptr < 0)
105            ptr = 0;
106    }
107
108    protected int getType() {
109        StringBuffer buf = new StringBuffer();
110        int state = FIRST;
111        while (true) {
112            char c = nextChar();
113            if (c == EOT) {
114                if (state == DIGIT) {
115                    number = Integer.parseInt(other = buf.toString());
116                    return NUMBER;
117                }
118                else if (state == OTHER) {
119                    other = buf.toString().toLowerCase();
120                    return TEXT;
121                }
122                return END;
123            }
124            switch (state) {
125                case FIRST:
126                    switch (c) {
127                        case '!':
128                            return NOT;
129                        case '-':
130                            return MINUS;
131                        case ',':
132                            return COMMA;
133                    }
134                    buf.append(c);
135                    if (c >= '0' && c <= '9')
136                        state = DIGIT;
137                    else
138                        state = OTHER;
139                    break;
140                case DIGIT:
141                    if (c >= '0' && c <= '9')
142                        buf.append(c);
143                    else {
144                        putBack();
145                        number = Integer.parseInt(other = buf.toString());
146                        return NUMBER;
147                    }
148                    break;
149                case OTHER:
150                    if (NOT_OTHER.indexOf(c) < 0)
151                        buf.append(c);
152                    else {
153                        putBack();
154                        other = buf.toString().toLowerCase();
155                        return TEXT;
156                    }
157                    break;
158            }
159        }
160    }
161
162    private void otherProc() {
163        if (other.equals("odd") || other.equals("o")) {
164            odd = true;
165            even = false;
166        }
167        else if (other.equals("even") || other.equals("e")) {
168            odd = false;
169            even = true;
170        }
171    }
172
173    protected boolean getAttributes() {
174        low = -1;
175        high = -1;
176        odd = even = inverse = false;
177        int state = OTHER;
178        while (true) {
179            int type = getType();
180            if (type == END || type == COMMA) {
181                if (state == DIGIT)
182                    high = low;
183                return type == END;
184            }
185            switch (state) {
186                case OTHER:
187                    switch (type) {
188                        case NOT:
189                            inverse = true;
190                            break;
191                        case MINUS:
192                            state = DIGIT2;
193                            break;
194                        default:
195                            if (type == NUMBER) {
196                                low = number;
197                                state = DIGIT;
198                            }
199                            else
200                                otherProc();
201                            break;
202                    }
203                    break;
204                case DIGIT:
205                    switch (type) {
206                        case NOT:
207                            inverse = true;
208                            state = OTHER;
209                            high = low;
210                            break;
211                        case MINUS:
212                            state = DIGIT2;
213                            break;
214                        default:
215                            high = low;
216                            state = OTHER;
217                            otherProc();
218                            break;
219                    }
220                    break;
221                case DIGIT2:
222                    switch (type) {
223                        case NOT:
224                            inverse = true;
225                            state = OTHER;
226                            break;
227                        case MINUS:
228                            break;
229                        case NUMBER:
230                            high = number;
231                            state = OTHER;
232                            break;
233                        default:
234                            state = OTHER;
235                            otherProc();
236                            break;
237                    }
238                    break;
239            }
240        }
241    }
242
243    /**
244     * Generates a list of numbers from a string.
245     * @param ranges the comma separated ranges
246     * @param maxNumber the maximum number in the range
247     * @return a list with the numbers as <CODE>Integer</CODE>
248     */
249    public static List<Integer> expand(String ranges, int maxNumber) {
250        SequenceList parse = new SequenceList(ranges);
251        LinkedList<Integer> list = new LinkedList<Integer>();
252        boolean sair = false;
253        while (!sair) {
254            sair = parse.getAttributes();
255            if (parse.low == -1 && parse.high == -1 && !parse.even && !parse.odd)
256                continue;
257            if (parse.low < 1)
258                parse.low = 1;
259            if (parse.high < 1 || parse.high > maxNumber)
260                parse.high = maxNumber;
261            if (parse.low > maxNumber)
262                parse.low = maxNumber;
263
264            //System.out.println("low="+parse.low+",high="+parse.high+",odd="+parse.odd+",even="+parse.even+",inverse="+parse.inverse);
265            int inc = 1;
266            if (parse.inverse) {
267                if (parse.low > parse.high) {
268                    int t = parse.low;
269                    parse.low = parse.high;
270                    parse.high = t;
271                }
272                for (ListIterator<Integer> it = list.listIterator(); it.hasNext();) {
273                    int n = it.next().intValue();
274                    if (parse.even && (n & 1) == 1)
275                        continue;
276                    if (parse.odd && (n & 1) == 0)
277                        continue;
278                    if (n >= parse.low && n <= parse.high)
279                        it.remove();
280                }
281            }
282            else {
283                if (parse.low > parse.high) {
284                    inc = -1;
285                    if (parse.odd || parse.even) {
286                        --inc;
287                        if (parse.even)
288                            parse.low &= ~1;
289                        else
290                            parse.low -= (parse.low & 1) == 1 ? 0 : 1;
291                    }
292                    for (int k = parse.low; k >= parse.high; k += inc)
293                        list.add(Integer.valueOf(k));
294                }
295                else {
296                    if (parse.odd || parse.even) {
297                        ++inc;
298                        if (parse.odd)
299                            parse.low |= 1;
300                        else
301                            parse.low += (parse.low & 1) == 1 ? 1 : 0;
302                    }
303                    for (int k = parse.low; k <= parse.high; k += inc) {
304                        list.add(Integer.valueOf(k));
305                    }
306                }
307            }
308//            for (int k = 0; k < list.size(); ++k)
309//                System.out.print(((Integer)list.get(k)).intValue() + ",");
310//            System.out.println();
311        }
312        return list;
313    }
314}