001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id$ */
019:
020: package org.apache.xmlgraphics.ps.dsc;
021:
022: import java.io.BufferedReader;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.UnsupportedEncodingException;
026: import java.util.NoSuchElementException;
027:
028: import org.apache.xmlgraphics.ps.DSCConstants;
029: import org.apache.xmlgraphics.ps.PSGenerator;
030: import org.apache.xmlgraphics.ps.dsc.events.DSCAtend;
031: import org.apache.xmlgraphics.ps.dsc.events.DSCComment;
032: import org.apache.xmlgraphics.ps.dsc.events.DSCEvent;
033: import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment;
034: import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment;
035: import org.apache.xmlgraphics.ps.dsc.events.PostScriptLine;
036: import org.apache.xmlgraphics.ps.dsc.events.UnparsedDSCComment;
037: import org.apache.xmlgraphics.ps.dsc.tools.DSCTools;
038:
039: /**
040: * Parser for DSC-compliant PostScript files (DSC = Document Structuring Conventions). The parser
041: * is implemented as a pull parser but has the ability to act as a push parser through the
042: * DSCHandler interface.
043: */
044: public class DSCParser implements DSCParserConstants {
045:
046: private InputStream in;
047: private BufferedReader reader;
048: private boolean eofFound = false;
049: private DSCEvent currentEvent;
050: private DSCEvent nextEvent;
051: private DSCFilter filter;
052: private NestedDocumentHandler nestedDocumentHandler;
053:
054: /**
055: * Creates a new DSC parser.
056: * @param in InputStream to read the PostScript file from
057: * (the stream is not closed by this class, the caller is responsible for that)
058: * @throws IOException In case of an I/O error
059: * @throws DSCException In case of a violation of the DSC spec
060: */
061: public DSCParser(InputStream in) throws IOException, DSCException {
062: if (in.markSupported()) {
063: this .in = in;
064: } else {
065: //Decorate for better performance
066: this .in = new java.io.BufferedInputStream(this .in);
067: }
068: String encoding = "US-ASCII";
069: try {
070: this .reader = new java.io.BufferedReader(
071: new java.io.InputStreamReader(this .in, encoding));
072: } catch (UnsupportedEncodingException e) {
073: throw new RuntimeException("Incompatible VM! "
074: + e.getMessage());
075: }
076: parseNext();
077: }
078:
079: /**
080: * Returns the InputStream the PostScript code is read from.
081: * @return the InputStream the PostScript code is read from
082: */
083: public InputStream getInputStream() {
084: return this .in;
085: }
086:
087: /**
088: * This method is used to write out warning messages for the parsing process. Subclass to
089: * override this method. The default implementation writes to System.err.
090: * @param msg the warning message
091: */
092: protected void warn(String msg) {
093: System.err.println(msg);
094: }
095:
096: /**
097: * Reads one line from the input file
098: * @return the line or null if there are no more lines
099: * @throws IOException In case of an I/O error
100: * @throws DSCException In case of a violation of the DSC spec
101: */
102: protected String readLine() throws IOException, DSCException {
103: String line;
104: line = this .reader.readLine();
105: checkLine(line);
106:
107: return line;
108: }
109:
110: private void checkLine(String line) throws DSCException {
111: if (line == null) {
112: if (!eofFound) {
113: throw new DSCException(
114: "%%EOF not found. File is not well-formed.");
115: }
116: } else if (line.length() > 255) {
117: warn("Line longer than 255 characters. This file is not fully PostScript conforming.");
118: }
119: }
120:
121: private final boolean isWhitespace(char c) {
122: return c == ' ' || c == '\t';
123: }
124:
125: private DSCComment parseDSCLine(String line) throws IOException,
126: DSCException {
127: int colon = line.indexOf(':');
128: String name, value;
129: if (colon > 0) {
130: name = line.substring(2, colon);
131: int startOfValue = colon + 1;
132: if (isWhitespace(line.charAt(startOfValue))) {
133: startOfValue++;
134: }
135: value = line.substring(startOfValue).trim();
136: if (value.equals(DSCConstants.ATEND.toString())) {
137: return new DSCAtend(name);
138: }
139: String nextLine;
140: while (true) {
141: this .reader.mark(512);
142: nextLine = readLine();
143: if (nextLine == null) {
144: break;
145: } else if (!nextLine.startsWith("%%+")) {
146: break;
147: }
148: value = value + nextLine.substring(3);
149: }
150: this .reader.reset();
151: } else {
152: name = line.substring(2);
153: value = null;
154: }
155: return parseDSCComment(name, value);
156: }
157:
158: private DSCComment parseDSCComment(String name, String value) {
159: DSCComment parsed = DSCCommentFactory.createDSCCommentFor(name);
160: if (parsed != null) {
161: parsed.parseValue(value);
162: return parsed;
163: } else {
164: UnparsedDSCComment unparsed = new UnparsedDSCComment(name);
165: unparsed.parseValue(value);
166: return unparsed;
167: }
168: }
169:
170: /**
171: * Starts the parser in push parsing mode sending events to the DSCHandler instance.
172: * @param handler the DSCHandler instance to send the events to
173: * @throws IOException In case of an I/O error
174: * @throws DSCException In case of a violation of the DSC spec
175: */
176: public void parse(DSCHandler handler) throws IOException,
177: DSCException {
178: DSCHeaderComment header = DSCTools
179: .checkAndSkipDSC30Header(this );
180: handler.startDocument("%!" + header.getComment());
181: DSCEvent event;
182: while (hasNext()) {
183: event = nextEvent();
184: switch (event.getEventType()) {
185: case HEADER_COMMENT:
186: handler.startDocument("%!"
187: + ((DSCHeaderComment) event).getComment());
188: break;
189: case DSC_COMMENT:
190: handler.handleDSCComment(event.asDSCComment());
191: break;
192: case COMMENT:
193: handler.comment(((PostScriptComment) event)
194: .getComment());
195: break;
196: case LINE:
197: handler.line(getLine());
198: break;
199: case EOF:
200: this .eofFound = true;
201: handler.endDocument();
202: break;
203: default:
204: throw new IllegalStateException("Illegal event type: "
205: + event.getEventType());
206: }
207: }
208: }
209:
210: /**
211: * Indicates whether there are additional items.
212: * @return true if there are additonal items, false if the end of the file has been reached
213: */
214: public boolean hasNext() {
215: return (this .nextEvent != null);
216: }
217:
218: /**
219: * Steps to the next item indicating the type of event.
220: * @return the type of event (See {@link DSCParserConstants})
221: * @throws IOException In case of an I/O error
222: * @throws DSCException In case of a violation of the DSC spec
223: * @throws NoSuchElementException If an attempt was made to advance beyond the end of the file
224: */
225: public int next() throws IOException, DSCException {
226: if (hasNext()) {
227: this .currentEvent = nextEvent;
228: parseNext();
229: if (this .nestedDocumentHandler != null) {
230: this .nestedDocumentHandler.handle(this .currentEvent,
231: this );
232: }
233: return this .currentEvent.getEventType();
234: } else {
235: throw new NoSuchElementException("There are no more events");
236: }
237: }
238:
239: /**
240: * Steps to the next item returning the new event.
241: * @return the new event
242: * @throws IOException In case of an I/O error
243: * @throws DSCException In case of a violation of the DSC spec
244: */
245: public DSCEvent nextEvent() throws IOException, DSCException {
246: next();
247: return getCurrentEvent();
248: }
249:
250: /**
251: * Returns the current event.
252: * @return the current event
253: */
254: public DSCEvent getCurrentEvent() {
255: return this .currentEvent;
256: }
257:
258: /**
259: * Returns the next event without moving the cursor to the next event.
260: * @return the next event
261: */
262: public DSCEvent peek() {
263: return this .nextEvent;
264: }
265:
266: /**
267: * Parses the next event.
268: * @throws IOException In case of an I/O error
269: * @throws DSCException In case of a violation of the DSC spec
270: */
271: protected void parseNext() throws IOException, DSCException {
272: String line = readLine();
273: if (line != null) {
274: if (eofFound && (line.length() > 0)) {
275: throw new DSCException("Content found after EOF");
276: }
277: if (line.startsWith("%%")) {
278: DSCComment comment = parseDSCLine(line);
279: if (comment.getEventType() == EOF) {
280: this .eofFound = true;
281: }
282: this .nextEvent = comment;
283: } else if (line.startsWith("%!")) {
284: this .nextEvent = new DSCHeaderComment(line.substring(2));
285: } else if (line.startsWith("%")) {
286: this .nextEvent = new PostScriptComment(line
287: .substring(1));
288: } else {
289: this .nextEvent = new PostScriptLine(line);
290: }
291: if (this .filter != null && !filter.accept(this .nextEvent)) {
292: parseNext(); //skip
293: }
294: } else {
295: this .nextEvent = null;
296: }
297: }
298:
299: /**
300: * Returns the current PostScript line.
301: * @return the current PostScript line
302: * @throws IllegalStateException if the current event is not a normal PostScript line
303: */
304: public String getLine() {
305: if (this .currentEvent.getEventType() == LINE) {
306: return ((PostScriptLine) this .currentEvent).getLine();
307: } else {
308: throw new IllegalStateException(
309: "Current event is not a PostScript line");
310: }
311: }
312:
313: /**
314: * Advances to the next DSC comment with the given name.
315: * @param name the name of the DSC comment
316: * @return the requested DSC comment or null if the end of the file is reached
317: * @throws IOException In case of an I/O error
318: * @throws DSCException In case of a violation of the DSC spec
319: */
320: public DSCComment nextDSCComment(String name) throws IOException,
321: DSCException {
322: return nextDSCComment(name, null);
323: }
324:
325: /**
326: * Advances to the next DSC comment with the given name.
327: * @param name the name of the DSC comment
328: * @param gen PSGenerator to pass the skipped events though to
329: * @return the requested DSC comment or null if the end of the file is reached
330: * @throws IOException In case of an I/O error
331: * @throws DSCException In case of a violation of the DSC spec
332: */
333: public DSCComment nextDSCComment(String name, PSGenerator gen)
334: throws IOException, DSCException {
335: while (hasNext()) {
336: DSCEvent event = nextEvent();
337: if (event.isDSCComment()) {
338: DSCComment comment = event.asDSCComment();
339: if (name.equals(comment.getName())) {
340: return comment;
341: }
342: }
343: if (gen != null) {
344: event.generate(gen); //Pipe through to PSGenerator
345: }
346: }
347: return null;
348: }
349:
350: /**
351: * Advances to the next PostScript comment with the given prefix. This is used to find
352: * comments following the DSC extension mechanism.
353: * <p>
354: * Example: To find FOP's custom comments, pass in "FOP" as a prefix. This will find comments
355: * like "%FOPFontSetup".
356: * @param prefix the prefix of the extension comment
357: * @param gen PSGenerator to pass the skipped events though to
358: * @return the requested PostScript comment or null if the end of the file is reached
359: * @throws IOException In case of an I/O error
360: * @throws DSCException In case of a violation of the DSC spec
361: */
362: public PostScriptComment nextPSComment(String prefix,
363: PSGenerator gen) throws IOException, DSCException {
364: while (hasNext()) {
365: DSCEvent event = nextEvent();
366: if (event.isComment()) {
367: PostScriptComment comment = (PostScriptComment) event;
368: if (comment.getComment().startsWith(prefix)) {
369: return comment;
370: }
371: }
372: if (gen != null) {
373: event.generate(gen); //Pipe through to PSGenerator
374: }
375: }
376: return null;
377: }
378:
379: /**
380: * Sets a filter for DSC events.
381: * @param filter the filter to use or null to disable filtering
382: */
383: public void setFilter(DSCFilter filter) {
384: this .filter = filter;
385: }
386:
387: /**
388: * Sets a NestedDocumentHandler which is used to skip nested documents like embedded EPS files.
389: * You can also process those parts in a special way.
390: * @param handler the NestedDocumentHandler instance or null to disable the feature
391: */
392: public void setNestedDocumentHandler(NestedDocumentHandler handler) {
393: this.nestedDocumentHandler = handler;
394: }
395:
396: }
|