001: /*
002: * Copyright 2004 by Paulo Soares.
003: *
004: * The contents of this file are subject to the Mozilla Public License Version 1.1
005: * (the "License"); you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
007: *
008: * Software distributed under the License is distributed on an "AS IS" basis,
009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
010: * for the specific language governing rights and limitations under the License.
011: *
012: * The Original Code is 'iText, a free JAVA-PDF library'.
013: *
014: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
015: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
016: * All Rights Reserved.
017: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
018: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
019: *
020: * Contributor(s): all the names of the contributors are added in the source code
021: * where applicable.
022: *
023: * Alternatively, the contents of this file may be used under the terms of the
024: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
025: * provisions of LGPL are applicable instead of those above. If you wish to
026: * allow use of your version of this file only under the terms of the LGPL
027: * License and not to allow others to use your version of this file under
028: * the MPL, indicate your decision by deleting the provisions above and
029: * replace them with the notice and other provisions required by the LGPL.
030: * If you do not delete the provisions above, a recipient may use your version
031: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
032: *
033: * This library is free software; you can redistribute it and/or modify it
034: * under the terms of the MPL as stated above or under the terms of the GNU
035: * Library General Public License as published by the Free Software Foundation;
036: * either version 2 of the License, or any later version.
037: *
038: * This library is distributed in the hope that it will be useful, but WITHOUT
039: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
040: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
041: * details.
042: *
043: * If you didn't download this code from the following link, you should check if
044: * you aren't using an obsolete version:
045: * http://www.lowagie.com/iText/
046: */
047: package com.lowagie.text.pdf;
048:
049: import java.io.BufferedWriter;
050: import java.io.IOException;
051: import java.io.InputStream;
052: import java.io.OutputStream;
053: import java.io.OutputStreamWriter;
054: import java.io.Reader;
055: import java.io.Writer;
056: import java.util.ArrayList;
057: import java.util.HashMap;
058: import java.util.Iterator;
059: import java.util.Map;
060: import java.util.StringTokenizer;
061:
062: import com.lowagie.text.xml.simpleparser.IanaEncodings;
063: import com.lowagie.text.xml.simpleparser.SimpleXMLDocHandler;
064: import com.lowagie.text.xml.simpleparser.SimpleXMLParser;
065:
066: /**
067: *
068: * @author Paulo Soares (psoares@consiste.pt)
069: */
070: public class SimpleNamedDestination implements SimpleXMLDocHandler {
071:
072: private HashMap xmlNames;
073: private HashMap xmlLast;
074:
075: private SimpleNamedDestination() {
076: }
077:
078: public static HashMap getNamedDestination(PdfReader reader,
079: boolean fromNames) {
080: IntHashtable pages = new IntHashtable();
081: int numPages = reader.getNumberOfPages();
082: for (int k = 1; k <= numPages; ++k)
083: pages.put(reader.getPageOrigRef(k).getNumber(), k);
084: HashMap names = fromNames ? reader
085: .getNamedDestinationFromNames() : reader
086: .getNamedDestinationFromStrings();
087: for (Iterator it = names.entrySet().iterator(); it.hasNext();) {
088: Map.Entry entry = (Map.Entry) it.next();
089: ArrayList arr = ((PdfArray) entry.getValue())
090: .getArrayList();
091: StringBuffer s = new StringBuffer();
092: try {
093: s.append(pages.get(((PdfIndirectReference) arr.get(0))
094: .getNumber()));
095: s.append(' ')
096: .append(arr.get(1).toString().substring(1));
097: for (int k = 2; k < arr.size(); ++k)
098: s.append(' ').append(arr.get(k).toString());
099: entry.setValue(s.toString());
100: } catch (Exception e) {
101: it.remove();
102: }
103: }
104: return names;
105: }
106:
107: /**
108: * Exports the destinations to XML. The DTD for this XML is:
109: * <p>
110: * <pre>
111: * <?xml version='1.0' encoding='UTF-8'?>
112: * <!ELEMENT Name (#PCDATA)>
113: * <!ATTLIST Name
114: * Page CDATA #IMPLIED
115: * >
116: * <!ELEMENT Destination (Name)*>
117: * </pre>
118: * @param names the names
119: * @param out the export destination. The stream is not closed
120: * @param encoding the encoding according to IANA conventions
121: * @param onlyASCII codes above 127 will always be escaped with &#nn; if <CODE>true</CODE>,
122: * whatever the encoding
123: * @throws IOException on error
124: */
125: public static void exportToXML(HashMap names, OutputStream out,
126: String encoding, boolean onlyASCII) throws IOException {
127: String jenc = IanaEncodings.getJavaEncoding(encoding);
128: Writer wrt = new BufferedWriter(new OutputStreamWriter(out,
129: jenc));
130: exportToXML(names, wrt, encoding, onlyASCII);
131: }
132:
133: /**
134: * Exports the destinations to XML.
135: * @param names the names
136: * @param wrt the export destination. The writer is not closed
137: * @param encoding the encoding according to IANA conventions
138: * @param onlyASCII codes above 127 will always be escaped with &#nn; if <CODE>true</CODE>,
139: * whatever the encoding
140: * @throws IOException on error
141: */
142: public static void exportToXML(HashMap names, Writer wrt,
143: String encoding, boolean onlyASCII) throws IOException {
144: wrt.write("<?xml version=\"1.0\" encoding=\"");
145: wrt.write(SimpleXMLParser.escapeXML(encoding, onlyASCII));
146: wrt.write("\"?>\n<Destination>\n");
147: for (Iterator it = names.entrySet().iterator(); it.hasNext();) {
148: Map.Entry entry = (Map.Entry) it.next();
149: String key = (String) entry.getKey();
150: String value = (String) entry.getValue();
151: wrt.write(" <Name Page=\"");
152: wrt.write(SimpleXMLParser.escapeXML(value, onlyASCII));
153: wrt.write("\">");
154: wrt.write(SimpleXMLParser.escapeXML(
155: escapeBinaryString(key), onlyASCII));
156: wrt.write("</Name>\n");
157: }
158: wrt.write("</Destination>\n");
159: wrt.flush();
160: }
161:
162: /**
163: * Import the names from XML.
164: * @param in the XML source. The stream is not closed
165: * @throws IOException on error
166: * @return the names
167: */
168: public static HashMap importFromXML(InputStream in)
169: throws IOException {
170: SimpleNamedDestination names = new SimpleNamedDestination();
171: SimpleXMLParser.parse(names, in);
172: return names.xmlNames;
173: }
174:
175: /**
176: * Import the names from XML.
177: * @param in the XML source. The reader is not closed
178: * @throws IOException on error
179: * @return the names
180: */
181: public static HashMap importFromXML(Reader in) throws IOException {
182: SimpleNamedDestination names = new SimpleNamedDestination();
183: SimpleXMLParser.parse(names, in);
184: return names.xmlNames;
185: }
186:
187: static PdfArray createDestinationArray(String value,
188: PdfWriter writer) {
189: PdfArray ar = new PdfArray();
190: StringTokenizer tk = new StringTokenizer(value);
191: int n = Integer.parseInt(tk.nextToken());
192: ar.add(writer.getPageReference(n));
193: if (!tk.hasMoreTokens()) {
194: ar.add(PdfName.XYZ);
195: ar.add(new float[] { 0, 10000, 0 });
196: } else {
197: String fn = tk.nextToken();
198: if (fn.startsWith("/"))
199: fn = fn.substring(1);
200: ar.add(new PdfName(fn));
201: for (int k = 0; k < 4 && tk.hasMoreTokens(); ++k) {
202: fn = tk.nextToken();
203: if (fn.equals("null"))
204: ar.add(PdfNull.PDFNULL);
205: else
206: ar.add(new PdfNumber(fn));
207: }
208: }
209: return ar;
210: }
211:
212: public static PdfDictionary outputNamedDestinationAsNames(
213: HashMap names, PdfWriter writer) {
214: PdfDictionary dic = new PdfDictionary();
215: for (Iterator it = names.entrySet().iterator(); it.hasNext();) {
216: Map.Entry entry = (Map.Entry) it.next();
217: try {
218: String key = (String) entry.getKey();
219: String value = (String) entry.getValue();
220: PdfArray ar = createDestinationArray(value, writer);
221: PdfName kn = new PdfName(key);
222: dic.put(kn, ar);
223: } catch (Exception e) {
224: // empty on purpose
225: }
226: }
227: return dic;
228: }
229:
230: public static PdfDictionary outputNamedDestinationAsStrings(
231: HashMap names, PdfWriter writer) throws IOException {
232: HashMap n2 = new HashMap(names);
233: for (Iterator it = n2.entrySet().iterator(); it.hasNext();) {
234: Map.Entry entry = (Map.Entry) it.next();
235: try {
236: String value = (String) entry.getValue();
237: PdfArray ar = createDestinationArray(value, writer);
238: entry.setValue(writer.addToBody(ar)
239: .getIndirectReference());
240: } catch (Exception e) {
241: it.remove();
242: }
243: }
244: return PdfNameTree.writeTree(n2, writer);
245: }
246:
247: public static String escapeBinaryString(String s) {
248: StringBuffer buf = new StringBuffer();
249: char cc[] = s.toCharArray();
250: int len = cc.length;
251: for (int k = 0; k < len; ++k) {
252: char c = cc[k];
253: if (c < ' ') {
254: buf.append('\\');
255: String octal = "00" + Integer.toOctalString((int) c);
256: buf.append(octal.substring(octal.length() - 3));
257: } else if (c == '\\')
258: buf.append("\\\\");
259: else
260: buf.append(c);
261: }
262: return buf.toString();
263: }
264:
265: public static String unEscapeBinaryString(String s) {
266: StringBuffer buf = new StringBuffer();
267: char cc[] = s.toCharArray();
268: int len = cc.length;
269: for (int k = 0; k < len; ++k) {
270: char c = cc[k];
271: if (c == '\\') {
272: if (++k >= len) {
273: buf.append('\\');
274: break;
275: }
276: c = cc[k];
277: if (c >= '0' && c <= '7') {
278: int n = c - '0';
279: ++k;
280: for (int j = 0; j < 2 && k < len; ++j) {
281: c = cc[k];
282: if (c >= '0' && c <= '7') {
283: ++k;
284: n = n * 8 + c - '0';
285: } else {
286: break;
287: }
288: }
289: --k;
290: buf.append((char) n);
291: } else
292: buf.append(c);
293: } else
294: buf.append(c);
295: }
296: return buf.toString();
297: }
298:
299: public void endDocument() {
300: }
301:
302: public void endElement(String tag) {
303: if (tag.equals("Destination")) {
304: if (xmlLast == null && xmlNames != null)
305: return;
306: else
307: throw new RuntimeException(
308: "Destination end tag out of place.");
309: }
310: if (!tag.equals("Name"))
311: throw new RuntimeException("Invalid end tag - " + tag);
312: if (xmlLast == null || xmlNames == null)
313: throw new RuntimeException("Name end tag out of place.");
314: if (!xmlLast.containsKey("Page"))
315: throw new RuntimeException("Page attribute missing.");
316: xmlNames.put(
317: unEscapeBinaryString((String) xmlLast.get("Name")),
318: xmlLast.get("Page"));
319: xmlLast = null;
320: }
321:
322: public void startDocument() {
323: }
324:
325: public void startElement(String tag, HashMap h) {
326: if (xmlNames == null) {
327: if (tag.equals("Destination")) {
328: xmlNames = new HashMap();
329: return;
330: } else
331: throw new RuntimeException(
332: "Root element is not Destination.");
333: }
334: if (!tag.equals("Name"))
335: throw new RuntimeException("Tag " + tag + " not allowed.");
336: if (xmlLast != null)
337: throw new RuntimeException("Nested tags are not allowed.");
338: xmlLast = new HashMap(h);
339: xmlLast.put("Name", "");
340: }
341:
342: public void text(String str) {
343: if (xmlLast == null)
344: return;
345: String name = (String) xmlLast.get("Name");
346: name += str;
347: xmlLast.put("Name", name);
348: }
349: }
|