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}