/** * Copyright 2006 by Know-Center, Graz, Austria * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a * joint initiative of the Federal Chancellery Austria and Graz University of * Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. * * $Id: Placeholder.java,v 1.5 2006/10/31 08:17:50 wprinz Exp $ */ package at.knowcenter.wag.egov.egiz.pdf; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.sound.midi.SysexMessage; import org.apache.commons.codec.net.URLCodec; import org.apache.log4j.Logger; import at.knowcenter.wag.egov.egiz.cfg.ConfigLogger; import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException; import at.knowcenter.wag.exactparser.ByteArrayUtils; /** * Helper class that provides functionality for dealing with placeholders and * replacements in pdf. * * @author wprinz */ public abstract class Placeholder { /** * The logger definition. */ private static final Logger logger_ = ConfigLogger.getLogger(Placeholder.class); /** * Escapes the String to be a suitable Literal String.. * * @param data * The String to be escaped. * @return Returns the escaped PDF String. */ public static byte[] escapePDFString(byte[] data) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = 0; i < data.length; i++) { byte[] escaped_bytes = escapeByte(data[i]); baos.write(escaped_bytes); } return baos.toByteArray(); } catch (IOException e) { logger_.error(e.getMessage(), e); return null; } } /** * Unescapes the PDF String. * * @param data * The escaped String. * @return Returns the unescaped String. */ public static byte[] unescapePDFString(byte[] data) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = 0; i < data.length; i++) { if (data[i] == '\\' && data[i + 1] == '\\') { baos.write('\\'); i++; continue; } if (data[i] == '\\' && data[i + 1] == '(') { baos.write('('); i++; continue; } if (data[i] == '\\' && data[i + 1] == ')') { baos.write(')'); i++; continue; } baos.write(data[i]); } return baos.toByteArray(); } /** * Reconstructs the string from a partition of placeholders. * * @param pdf * The PDF to read the string from. * @param sis * The list of StringInfo objects that specify the bytes of the * string in the pdf. * @return Returns the extracted and reconverted string. * @throws IOException * Forwarded exception. */ public static String reconstructStringFromPartition(byte[] pdf, List sis, byte[] enc) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Iterator it = sis.iterator(); while (it.hasNext()) { StringInfo si = (StringInfo) it.next(); for (int i = si.string_start; i < si.string_start + si.string_length; i++) { if (pdf[i] != 0) { baos.write(pdf[i]); } } } baos.close(); byte[] bytes = baos.toByteArray(); byte[] unescaped_bytes = unescapePDFString(bytes); if (!ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_WIN) && !ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_URL)) { String enc_str = new String(enc, "US-ASCII"); logger_.warn("The encoding " + enc_str + " is not known by this application - trying to proceed anyways."); } String text = new String(unescaped_bytes, "windows-1252"); String str = text; if (ByteArrayUtils.compareByteArrays(enc, 0, BinarySignature.ENCODING_URL)) { str = unapplyURLEncoding(str); } return str; } /** * Prepares the given String to a byte array that can be substituted into the * placeholder. * * @param text * The text to be prepared for substitution. * @return Returns the prepared byte array. */ public static byte[] applyWinAnsiEncoding(String text) { // text = text.replace("\\", "\\\\"); // text = text.replace("(", "\\("); // text = text.replace(")", "\\)"); byte[] replace_bytes; try { replace_bytes = text.getBytes("windows-1252");// CP1252 = WinAnsiEncoding // test the opposite way: // String restored_string = new String (replace_bytes, "windows-1252"); // if (!restored_string.equals(text)) // { // String url_encoded = URLEncoder.encode(text); // replace_bytes = url_encoded.getBytes("windows-1252"); // } } catch (UnsupportedEncodingException e) { logger_.error(e.getMessage(), e); return null; } return replace_bytes; } /** * Unapplies the WinAnsi encoding. * * @param replace_bytes * The bytes. * @return Returns the decoded String. */ public static String unapplyWinAnsiEncoding(byte[] replace_bytes) { try { String text = new String(replace_bytes, "windows-1252"); return text; } catch (UnsupportedEncodingException e) { logger_.error(e.getMessage(), e); return null; } } /** * Applies the URL encoding to the text. * * @param text * The text * @return Returns the URL and WinAnsi encoded text. */ public static byte[] applyURLEncoding(String text) { URLCodec utf8_url_codec = new URLCodec("UTF-8"); String url_encoded = null; try { url_encoded = utf8_url_codec.encode(text, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Couldn't url encode : " + text, e); } // String url_encoded = URLEncoder.encode(text); return applyWinAnsiEncoding(url_encoded); } /** * Unapplies the WinAnsi and URL encoding. * * @param winansi_str * The Winansi and URL text. * @return Returns the decoded text. */ public static String unapplyURLEncoding(String winansi_str) { URLCodec utf8_url_codec = new URLCodec("UTF-8"); String url_decoded = null; try { url_decoded = utf8_url_codec.decode(winansi_str, "UTF-8"); } catch (Exception e) { throw new RuntimeException("Couldn't url decode : " + winansi_str, e); } // String url_decoded = URLDecoder.decode(winansi_str); return url_decoded; } /** * Restores the String from a previously prepared byte array. * * @param pdf_string * The byte array. * @return Returns the unprepared String. */ public static String unprepareAndUnescapeString(byte[] pdf_string) { try { String text = new String(pdf_string, "windows-1252"); // This makes problems when "+" appears. // if (isURLEncoded(text)) // { // text = URLDecoder.decode(text); // } // text = text.replace("\\)", ")"); // text = text.replace("\\(", "("); // text = text.replace("\\\\", "\\"); // TODO: replace jdk1.5-code with jdf1.4-code (should be tested) /* */ text = text.replaceAll("\\\\\\)", ")"); text = text.replaceAll("\\\\\\(", "("); text = text.replaceAll("\\\\\\\\", "\\\\"); return text; } catch (UnsupportedEncodingException e) { logger_.error(e.getMessage(), e); return null; } } /** * Checks the presence of typical URL encoded characters to tell if the string * is URL encoded. * *

* This heuristic checks if there are any non URL encoded characters in the * String, like ASCII control characters, which aren't allowed in the * URLEncoding characterset. *

* * @param text * The text under suspicion. * @return Returns true if the String is URL encoded, false otherwise. */ protected static boolean isURLEncoded(String text) { if (text.indexOf(' ') >= 0) { return false; } for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (0x00 <= c && c <= 0x1f) { return false; } if (c == 0x7F) { return false; } if (0x80 <= c) { return false; } } return true; } /** * Tells, if a break can occur behind the given character. * * @param character * The character. * @return Returns true, if a break may occur behind the character, false * otherwise. */ protected static boolean canBreakAfter(byte character) { return (character == ' ' || character == '.' || character == ',' || character == ';' || character == '-' || character == '\n') ; } /** * Scans the given PDF content stream for literal PDF strings. * * @param pdf * The PDF. * @param stream_start * The start of the content stream to be scanned. * @param stream_next * The end of the content stream. * @return Returns a list of StringInfo objects specifying the strings that * could be found. */ public static List parseStrings(byte[] pdf, int stream_start, int stream_next) { List strings = new ArrayList(); StringInfo cur_string = null; for (int i = stream_start; i < stream_next; i++) { byte cur_byte = pdf[i]; if (cur_byte == '(' && pdf[i - 1] != '\\') { cur_string = new StringInfo(); cur_string.pdf = pdf; cur_string.string_start = i + 1; cur_string.string_length = -1; // logger_.debug("String start = " + cur_string.string_start); continue; } if (cur_byte == ')' && pdf[i - 1] != '\\') { cur_string.string_length = i - cur_string.string_start; // logger_.debug("String length = " + cur_string.string_length); strings.add(cur_string); cur_string = null; continue; } } return strings; } /** * Escapes the data byte if necessary. * *

* Before bytes can be written into the pdf Strings, they have to be escaped. * Special care has to be taken that escaped sequences are not split due to * line breaks. This could have fatal consequences and usually renders the * whole document invalid. *

* * @param data * The data byte to be escaped. * @return Returns a new byte array escaping the data byte. If the byte needs * not to be escaped, this new array will contain only the original * data byte. */ public static byte[] escapeByte(byte data) { if (data == '\\') { return new byte[] { '\\', '\\' }; } if (data == '(') { return new byte[] { '\\', '(' }; } if (data == ')') { return new byte[] { '\\', ')' }; } return new byte[] { data }; } /** * Replaces the placeholder with the given String breaking lines with a given * tolerance. * * @param pdf * The PDF. * @param sis * The list of StringInfo objects describing the positions where the * String should be filled in. * @param replace_bytes * The unescaped bytes to be filled in. Escaping is performed by this * method. * @param tolerance * The tolerance for line wrapping. The tolerance counts from the end * of a StringInfo backwards to its start. If a word that starts * within the tolerance doesn't fit, it is wrapped into the next * line. * @throws PDFDocumentException * Forwarded exception. */ public static void replacePlaceholderWithTolerance(byte[] pdf, List sis, byte[] replace_bytes, int tolerance) throws PDFDocumentException { try { // String rep_str = new String(replace_bytes); SplitStrings ss = new SplitStrings(pdf, sis); int read_index = 0; while (read_index < replace_bytes.length) { if (!ss.isValidLine()) { break; } byte[] token = readToken(replace_bytes, read_index); // String token_str = new String(token); byte[] escaped_token = escapeToken(token); if (ss.fits(escaped_token)) { ss.write(escaped_token); read_index += token.length; continue; } else { if (ss.getAvailable() < tolerance) { ss.newline(); continue; } else { // break the token for (; read_index < replace_bytes.length; read_index++) { byte data = replace_bytes[read_index]; byte[] escaped_data = escapeByte(data); if (ss.fits(escaped_data)) { ss.write(escaped_data); } else { ss.newline(); break; } } continue; } } } ss.fillRest(); if (read_index < replace_bytes.length) { logger_.error("The replace string was longer than the reserved placeholder."); throw new PlaceholderException(null, replace_bytes.length - read_index); } } catch (IOException e) { throw new PDFDocumentException(201, e); } } protected static byte[] readToken(byte[] bytes, int index) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (; index < bytes.length; index++) { byte data = bytes[index]; // byte [] escaped_data = escapeByte(data); baos.write(data); if (canBreakAfter(data)) { break; } } return baos.toByteArray(); } protected static byte[] escapeToken(byte[] token) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = 0; i < token.length; i++) { byte[] escaped_data = escapeByte(token[i]); baos.write(escaped_data); } return baos.toByteArray(); } }