/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: DSCParser.java 1804124 2017-08-04 14:13:54Z ssteiner $ */

package org.apache.xmlgraphics.ps.dsc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.NoSuchElementException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.ps.DSCConstants;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.dsc.events.DSCAtend;
import org.apache.xmlgraphics.ps.dsc.events.DSCComment;
import org.apache.xmlgraphics.ps.dsc.events.DSCEvent;
import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment;
import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment;
import org.apache.xmlgraphics.ps.dsc.events.PostScriptLine;
import org.apache.xmlgraphics.ps.dsc.events.UnparsedDSCComment;
import org.apache.xmlgraphics.ps.dsc.tools.DSCTools;

Parser for DSC-compliant PostScript files (DSC = Document Structuring Conventions). The parser is implemented as a pull parser but has the ability to act as a push parser through the DSCHandler interface.
/** * Parser for DSC-compliant PostScript files (DSC = Document Structuring Conventions). The parser * is implemented as a pull parser but has the ability to act as a push parser through the * DSCHandler interface. */
public class DSCParser implements DSCParserConstants { private static final Log LOG = LogFactory.getLog(DSCParser.class); private InputStream in; private BufferedReader reader; private boolean eofFound; private boolean checkEOF = true; private DSCEvent currentEvent; private DSCEvent nextEvent; private DSCListener nestedDocumentHandler; private DSCListener filterListener; private List listeners; private boolean listenersDisabled;
Creates a new DSC parser.
Params:
  • in – InputStream to read the PostScript file from (the stream is not closed by this class, the caller is responsible for that)
Throws:
/** * Creates a new DSC parser. * @param in InputStream to read the PostScript file from * (the stream is not closed by this class, the caller is responsible for that) * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
public DSCParser(InputStream in) throws IOException, DSCException { if (in.markSupported()) { this.in = in; } else { //Decorate for better performance this.in = new java.io.BufferedInputStream(this.in); } String encoding = "US-ASCII"; try { this.reader = new java.io.BufferedReader( new java.io.InputStreamReader(this.in, encoding)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Incompatible VM! " + e.getMessage()); } parseNext(); }
Returns the InputStream the PostScript code is read from.
Returns:the InputStream the PostScript code is read from
/** * Returns the InputStream the PostScript code is read from. * @return the InputStream the PostScript code is read from */
public InputStream getInputStream() { return this.in; }
This method is used to write out warning messages for the parsing process. Subclass to override this method. The default implementation writes to logger.
Params:
  • msg – the warning message
/** * This method is used to write out warning messages for the parsing process. Subclass to * override this method. The default implementation writes to logger. * @param msg the warning message */
protected void warn(String msg) { LOG.warn(msg); }
Reads one line from the input file
Throws:
Returns:the line or null if there are no more lines
/** * Reads one line from the input file * @return the line or null if there are no more lines * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
protected String readLine() throws IOException, DSCException { String line; line = this.reader.readLine(); checkLine(line); return line; } private void checkLine(String line) throws DSCException { if (line == null) { if (!eofFound) { throw new DSCException("%%EOF not found. File is not well-formed."); } } else if (line.length() > 255) { warn("Line longer than 255 characters. This file is not fully PostScript conforming."); } } private boolean isWhitespace(char c) { return c == ' ' || c == '\t'; } private DSCComment parseDSCLine(String line) throws IOException, DSCException { int colon = line.indexOf(':'); String name; StringBuilder value = new StringBuilder(); if (colon > 0) { name = line.substring(2, colon); int startOfValue = colon + 1; if (startOfValue < line.length()) { if (isWhitespace(line.charAt(startOfValue))) { startOfValue++; } value = new StringBuilder(line.substring(startOfValue).trim()); if (value.toString().equals(DSCConstants.ATEND.toString())) { return new DSCAtend(name); } } String nextLine; while (true) { this.reader.mark(512); nextLine = readLine(); if (nextLine == null) { break; } else if (!nextLine.startsWith("%%+")) { break; } value.append(nextLine.substring(3)); } this.reader.reset(); } else { name = line.substring(2); return parseDSCComment(name, null); } return parseDSCComment(name, value.toString()); } private DSCComment parseDSCComment(String name, String value) { DSCComment parsed = DSCCommentFactory.createDSCCommentFor(name); if (parsed != null) { try { parsed.parseValue(value); return parsed; } catch (Exception e) { //ignore and fall back to unparsed DSC comment } } UnparsedDSCComment unparsed = new UnparsedDSCComment(name); unparsed.parseValue(value); return unparsed; }
Starts the parser in push parsing mode sending events to the DSCHandler instance.
Params:
  • handler – the DSCHandler instance to send the events to
Throws:
/** * Starts the parser in push parsing mode sending events to the DSCHandler instance. * @param handler the DSCHandler instance to send the events to * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
public void parse(DSCHandler handler) throws IOException, DSCException { DSCHeaderComment header = DSCTools.checkAndSkipDSC30Header(this); handler.startDocument("%!" + header.getComment()); DSCEvent event; while (hasNext()) { event = nextEvent(); switch (event.getEventType()) { case HEADER_COMMENT: handler.startDocument("%!" + ((DSCHeaderComment)event).getComment()); break; case DSC_COMMENT: handler.handleDSCComment(event.asDSCComment()); break; case COMMENT: handler.comment(((PostScriptComment)event).getComment()); break; case LINE: handler.line(getLine()); break; case EOF: handler.endDocument(); break; default: throw new IllegalStateException("Illegal event type: " + event.getEventType()); } } }
Indicates whether there are additional items.
Returns:true if there are additonal items, false if the end of the file has been reached
/** * Indicates whether there are additional items. * @return true if there are additonal items, false if the end of the file has been reached */
public boolean hasNext() { return (this.nextEvent != null); }
Steps to the next item indicating the type of event.
Throws:
Returns:the type of event (See DSCParserConstants)
/** * Steps to the next item indicating the type of event. * @return the type of event (See {@link DSCParserConstants}) * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec * @throws NoSuchElementException If an attempt was made to advance beyond the end of the file */
public int next() throws IOException, DSCException { if (hasNext()) { this.currentEvent = nextEvent; parseNext(); processListeners(); return this.currentEvent.getEventType(); } else { throw new NoSuchElementException("There are no more events"); } } private void processListeners() throws IOException, DSCException { if (isListenersDisabled()) { return; } if (this.filterListener != null) { //Filter always comes first this.filterListener.processEvent(this.currentEvent, this); } if (this.listeners != null) { for (Object listener : this.listeners) { ((DSCListener) listener).processEvent(this.currentEvent, this); } } }
Steps to the next item returning the new event.
Throws:
Returns:the new event
/** * Steps to the next item returning the new event. * @return the new event * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
public DSCEvent nextEvent() throws IOException, DSCException { next(); return getCurrentEvent(); }
Returns the current event.
Returns:the current event
/** * Returns the current event. * @return the current event */
public DSCEvent getCurrentEvent() { return this.currentEvent; }
Returns the next event without moving the cursor to the next event.
Returns:the next event
/** * Returns the next event without moving the cursor to the next event. * @return the next event */
public DSCEvent peek() { return this.nextEvent; }
Parses the next event.
Throws:
  • IOException – In case of an I/O error
  • DSCException – In case of a violation of the DSC spec
/** * Parses the next event. * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
protected void parseNext() throws IOException, DSCException { String line = readLine(); if (line != null) { if (isCheckEOF() && eofFound && (line.length() > 0)) { throw new DSCException("Content found after EOF"); } if (line.startsWith("%%")) { DSCComment comment = parseDSCLine(line); if (comment.getEventType() == EOF) { this.eofFound = true; } this.nextEvent = comment; } else if (line.startsWith("%!")) { this.nextEvent = new DSCHeaderComment(line.substring(2)); } else if (line.startsWith("%")) { this.nextEvent = new PostScriptComment(line.substring(1)); } else { this.nextEvent = new PostScriptLine(line); } } else { this.nextEvent = null; } }
Returns the current PostScript line.
Throws:
Returns:the current PostScript line
/** * Returns the current PostScript line. * @return the current PostScript line * @throws IllegalStateException if the current event is not a normal PostScript line */
public String getLine() { if (this.currentEvent.getEventType() == LINE) { return ((PostScriptLine)this.currentEvent).getLine(); } else { throw new IllegalStateException("Current event is not a PostScript line"); } }
Advances to the next DSC comment with the given name.
Params:
  • name – the name of the DSC comment
Throws:
Returns:the requested DSC comment or null if the end of the file is reached
/** * Advances to the next DSC comment with the given name. * @param name the name of the DSC comment * @return the requested DSC comment or null if the end of the file is reached * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
public DSCComment nextDSCComment(String name) throws IOException, DSCException { return nextDSCComment(name, null); }
Advances to the next DSC comment with the given name.
Params:
  • name – the name of the DSC comment
  • gen – PSGenerator to pass the skipped events though to
Throws:
Returns:the requested DSC comment or null if the end of the file is reached
/** * Advances to the next DSC comment with the given name. * @param name the name of the DSC comment * @param gen PSGenerator to pass the skipped events though to * @return the requested DSC comment or null if the end of the file is reached * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
public DSCComment nextDSCComment(String name, PSGenerator gen) throws IOException, DSCException { while (hasNext()) { DSCEvent event = nextEvent(); if (event.isDSCComment()) { DSCComment comment = event.asDSCComment(); if (name.equals(comment.getName())) { return comment; } } if (gen != null) { event.generate(gen); //Pipe through to PSGenerator } } return null; }
Advances to the next PostScript comment with the given prefix. This is used to find comments following the DSC extension mechanism.

Example: To find FOP's custom comments, pass in "FOP" as a prefix. This will find comments like "%FOPFontSetup".

Params:
  • prefix – the prefix of the extension comment
  • gen – PSGenerator to pass the skipped events though to
Throws:
Returns:the requested PostScript comment or null if the end of the file is reached
/** * Advances to the next PostScript comment with the given prefix. This is used to find * comments following the DSC extension mechanism. * <p> * Example: To find FOP's custom comments, pass in "FOP" as a prefix. This will find comments * like "%FOPFontSetup". * @param prefix the prefix of the extension comment * @param gen PSGenerator to pass the skipped events though to * @return the requested PostScript comment or null if the end of the file is reached * @throws IOException In case of an I/O error * @throws DSCException In case of a violation of the DSC spec */
public PostScriptComment nextPSComment(String prefix, PSGenerator gen) throws IOException, DSCException { while (hasNext()) { DSCEvent event = nextEvent(); if (event.isComment()) { PostScriptComment comment = (PostScriptComment)event; if (comment.getComment().startsWith(prefix)) { return comment; } } if (gen != null) { event.generate(gen); //Pipe through to PSGenerator } } return null; }
Sets a filter for DSC events.
Params:
  • filter – the filter to use or null to disable filtering
/** * Sets a filter for DSC events. * @param filter the filter to use or null to disable filtering */
public void setFilter(DSCFilter filter) { if (filter != null) { this.filterListener = new FilteringEventListener(filter); } else { this.filterListener = null; } }
Adds a DSC event listener.
Params:
  • listener – the listener
/** * Adds a DSC event listener. * @param listener the listener */
public void addListener(DSCListener listener) { if (listener == null) { throw new NullPointerException("listener must not be null"); } if (this.listeners == null) { this.listeners = new java.util.ArrayList(); } this.listeners.add(listener); }
Removes a DSC event listener.
Params:
  • listener – the listener to remove
/** * Removes a DSC event listener. * @param listener the listener to remove */
public void removeListener(DSCListener listener) { if (this.listeners != null) { this.listeners.remove(listener); } }
Allows to disable all listeners. This can be used to disable any filtering, for example in nested documents.
Params:
  • value – true to disable all listeners, false to re-enable them
/** * Allows to disable all listeners. This can be used to disable any filtering, for example in * nested documents. * @param value true to disable all listeners, false to re-enable them */
public void setListenersDisabled(boolean value) { this.listenersDisabled = value; }
Indicates whether the listeners are currently disabled.
Returns:true if they are disabled
/** * Indicates whether the listeners are currently disabled. * @return true if they are disabled */
public boolean isListenersDisabled() { return this.listenersDisabled; }
Sets a NestedDocumentHandler which is used to skip nested documents like embedded EPS files. You can also process those parts in a special way.

It is suggested to use the more generally usable addListener(DSCListener) and removeListener(DSCListener) instead. NestedDocumentHandler is internally mapped onto a DSCListener.

Params:
  • handler – the NestedDocumentHandler instance or null to disable the feature
/** * Sets a NestedDocumentHandler which is used to skip nested documents like embedded EPS files. * You can also process those parts in a special way. * <p> * It is suggested to use the more generally usable {@link #addListener(DSCListener)} and * {@link #removeListener(DSCListener)} instead. NestedDocumentHandler is internally * mapped onto a {@link DSCListener}. * @param handler the NestedDocumentHandler instance or null to disable the feature */
public void setNestedDocumentHandler(final NestedDocumentHandler handler) { if (handler == null) { removeListener(this.nestedDocumentHandler); } else { MyDSCListener l = new MyDSCListener(); l.handler = handler; addListener(l); } } static class MyDSCListener implements DSCListener { private NestedDocumentHandler handler; public void processEvent(DSCEvent event, DSCParser parser) throws IOException, DSCException { handler.handle(event, parser); } }
Tells the parser whether to check for content after the EOF comment. This can be disabled to skip nested documents.
Params:
  • value – true if the check is enabled
/** * Tells the parser whether to check for content after the EOF comment. * This can be disabled to skip nested documents. * @param value true if the check is enabled */
public void setCheckEOF(boolean value) { this.checkEOF = value; }
Indicates whether the parser is configured to check for content after the EOF comment.
Returns:true if the check is enabled.
/** * Indicates whether the parser is configured to check for content after the EOF comment. * @return true if the check is enabled. */
public boolean isCheckEOF() { return this.checkEOF; } }