org.apache.xmlgraphics.ps.dsc.DSCParser Maven / Gradle / Ivy
Show all versions of xmlgraphics-commons Show documentation
/*
* 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.
*/
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.
* @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.
* @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.
* @param msg the warning message
*/
protected void warn(String msg) {
LOG.warn(msg);
}
/**
* 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.
* @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.
* @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.
* @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.
* @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.
* @return the current event
*/
public DSCEvent getCurrentEvent() {
return this.currentEvent;
}
/**
* 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
* @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.
* @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.
* @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.
* @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".
* @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.
* @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.
* @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.
* @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.
* @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.
* @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 {@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.
* @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.
* @return true if the check is enabled.
*/
public boolean isCheckEOF() {
return this.checkEOF;
}
}