001/* 002 * $Id: SimpleNamedDestination.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.BufferedWriter; 047import java.io.IOException; 048import java.io.InputStream; 049import java.io.OutputStream; 050import java.io.OutputStreamWriter; 051import java.io.Reader; 052import java.io.Writer; 053import java.util.HashMap; 054import java.util.Map; 055import java.util.StringTokenizer; 056 057import com.itextpdf.text.error_messages.MessageLocalization; 058import com.itextpdf.text.xml.simpleparser.IanaEncodings; 059import com.itextpdf.text.xml.simpleparser.SimpleXMLDocHandler; 060import com.itextpdf.text.xml.simpleparser.SimpleXMLParser; 061 062/** 063 * 064 * @author Paulo Soares 065 */ 066public final class SimpleNamedDestination implements SimpleXMLDocHandler { 067 068 private HashMap<String, String> xmlNames; 069 private HashMap<String, String> xmlLast; 070 071 private SimpleNamedDestination() { 072 } 073 074 public static HashMap<String, String> getNamedDestination(PdfReader reader, boolean fromNames) { 075 IntHashtable pages = new IntHashtable(); 076 int numPages = reader.getNumberOfPages(); 077 for (int k = 1; k <= numPages; ++k) 078 pages.put(reader.getPageOrigRef(k).getNumber(), k); 079 HashMap<String, PdfObject> names = fromNames ? reader.getNamedDestinationFromNames() : reader.getNamedDestinationFromStrings(); 080 HashMap<String, String> n2 = new HashMap<String, String>(names.size()); 081 for (Map.Entry<String, PdfObject> entry: names.entrySet()) { 082 PdfArray arr = (PdfArray)entry.getValue(); 083 StringBuffer s = new StringBuffer(); 084 try { 085 s.append(pages.get(arr.getAsIndirectObject(0).getNumber())); 086 s.append(' ').append(arr.getPdfObject(1).toString().substring(1)); 087 for (int k = 2; k < arr.size(); ++k) 088 s.append(' ').append(arr.getPdfObject(k).toString()); 089 n2.put(entry.getKey(), s.toString()); 090 } 091 catch (Exception e) { 092 } 093 } 094 return n2; 095 } 096 097 /** 098 * Exports the destinations to XML. The DTD for this XML is: 099 * <p> 100 * <pre> 101 * <?xml version='1.0' encoding='UTF-8'?> 102 * <!ELEMENT Name (#PCDATA)> 103 * <!ATTLIST Name 104 * Page CDATA #IMPLIED 105 * > 106 * <!ELEMENT Destination (Name)*> 107 * </pre> 108 * @param names the names 109 * @param out the export destination. The stream is not closed 110 * @param encoding the encoding according to IANA conventions 111 * @param onlyASCII codes above 127 will always be escaped with &#nn; if <CODE>true</CODE>, 112 * whatever the encoding 113 * @throws IOException on error 114 * @since 5.0.1 (generic type in signature) 115 */ 116 public static void exportToXML(HashMap<String, String> names, OutputStream out, String encoding, boolean onlyASCII) throws IOException { 117 String jenc = IanaEncodings.getJavaEncoding(encoding); 118 Writer wrt = new BufferedWriter(new OutputStreamWriter(out, jenc)); 119 exportToXML(names, wrt, encoding, onlyASCII); 120 } 121 122 /** 123 * Exports the destinations to XML. 124 * @param names the names 125 * @param wrt the export destination. The writer is not closed 126 * @param encoding the encoding according to IANA conventions 127 * @param onlyASCII codes above 127 will always be escaped with &#nn; if <CODE>true</CODE>, 128 * whatever the encoding 129 * @throws IOException on error 130 * @since 5.0.1 (generic type in signature) 131 */ 132 public static void exportToXML(HashMap<String, String> names, Writer wrt, String encoding, boolean onlyASCII) throws IOException { 133 wrt.write("<?xml version=\"1.0\" encoding=\""); 134 wrt.write(SimpleXMLParser.escapeXML(encoding, onlyASCII)); 135 wrt.write("\"?>\n<Destination>\n"); 136 for (Map.Entry<String, String> entry: names.entrySet()) { 137 String key = entry.getKey(); 138 String value = entry.getValue(); 139 wrt.write(" <Name Page=\""); 140 wrt.write(SimpleXMLParser.escapeXML(value, onlyASCII)); 141 wrt.write("\">"); 142 wrt.write(SimpleXMLParser.escapeXML(escapeBinaryString(key), onlyASCII)); 143 wrt.write("</Name>\n"); 144 } 145 wrt.write("</Destination>\n"); 146 wrt.flush(); 147 } 148 149 /** 150 * Import the names from XML. 151 * @param in the XML source. The stream is not closed 152 * @throws IOException on error 153 * @return the names 154 */ 155 public static HashMap<String, String> importFromXML(InputStream in) throws IOException { 156 SimpleNamedDestination names = new SimpleNamedDestination(); 157 SimpleXMLParser.parse(names, in); 158 return names.xmlNames; 159 } 160 161 /** 162 * Import the names from XML. 163 * @param in the XML source. The reader is not closed 164 * @throws IOException on error 165 * @return the names 166 */ 167 public static HashMap<String, String> importFromXML(Reader in) throws IOException { 168 SimpleNamedDestination names = new SimpleNamedDestination(); 169 SimpleXMLParser.parse(names, in); 170 return names.xmlNames; 171 } 172 173 static PdfArray createDestinationArray(String value, PdfWriter writer) { 174 PdfArray ar = new PdfArray(); 175 StringTokenizer tk = new StringTokenizer(value); 176 int n = Integer.parseInt(tk.nextToken()); 177 ar.add(writer.getPageReference(n)); 178 if (!tk.hasMoreTokens()) { 179 ar.add(PdfName.XYZ); 180 ar.add(new float[]{0, 10000, 0}); 181 } 182 else { 183 String fn = tk.nextToken(); 184 if (fn.startsWith("/")) 185 fn = fn.substring(1); 186 ar.add(new PdfName(fn)); 187 for (int k = 0; k < 4 && tk.hasMoreTokens(); ++k) { 188 fn = tk.nextToken(); 189 if (fn.equals("null")) 190 ar.add(PdfNull.PDFNULL); 191 else 192 ar.add(new PdfNumber(fn)); 193 } 194 } 195 return ar; 196 } 197 198 public static PdfDictionary outputNamedDestinationAsNames(HashMap<String, String> names, PdfWriter writer) { 199 PdfDictionary dic = new PdfDictionary(); 200 for (Map.Entry<String, String> entry: names.entrySet()) { 201 try { 202 String key = entry.getKey(); 203 String value = entry.getValue(); 204 PdfArray ar = createDestinationArray(value, writer); 205 PdfName kn = new PdfName(key); 206 dic.put(kn, ar); 207 } 208 catch (Exception e) { 209 // empty on purpose 210 } 211 } 212 return dic; 213 } 214 215 public static PdfDictionary outputNamedDestinationAsStrings(HashMap<String, String> names, PdfWriter writer) throws IOException { 216 HashMap<String, PdfObject> n2 = new HashMap<String, PdfObject>(names.size()); 217 for (Map.Entry<String, String> entry: names.entrySet()) { 218 try { 219 String value = entry.getValue(); 220 PdfArray ar = createDestinationArray(value, writer); 221 n2.put(entry.getKey(), writer.addToBody(ar).getIndirectReference()); 222 } 223 catch (Exception e) { 224 } 225 } 226 return PdfNameTree.writeTree(n2, writer); 227 } 228 229 public static String escapeBinaryString(String s) { 230 StringBuffer buf = new StringBuffer(); 231 char cc[] = s.toCharArray(); 232 int len = cc.length; 233 for (int k = 0; k < len; ++k) { 234 char c = cc[k]; 235 if (c < ' ') { 236 buf.append('\\'); 237 String octal = "00" + Integer.toOctalString(c); 238 buf.append(octal.substring(octal.length() - 3)); 239 } 240 else if (c == '\\') 241 buf.append("\\\\"); 242 else 243 buf.append(c); 244 } 245 return buf.toString(); 246 } 247 248 public static String unEscapeBinaryString(String s) { 249 StringBuffer buf = new StringBuffer(); 250 char cc[] = s.toCharArray(); 251 int len = cc.length; 252 for (int k = 0; k < len; ++k) { 253 char c = cc[k]; 254 if (c == '\\') { 255 if (++k >= len) { 256 buf.append('\\'); 257 break; 258 } 259 c = cc[k]; 260 if (c >= '0' && c <= '7') { 261 int n = c - '0'; 262 ++k; 263 for (int j = 0; j < 2 && k < len; ++j) { 264 c = cc[k]; 265 if (c >= '0' && c <= '7') { 266 ++k; 267 n = n * 8 + c - '0'; 268 } 269 else { 270 break; 271 } 272 } 273 --k; 274 buf.append((char)n); 275 } 276 else 277 buf.append(c); 278 } 279 else 280 buf.append(c); 281 } 282 return buf.toString(); 283 } 284 285 public void endDocument() { 286 } 287 288 public void endElement(String tag) { 289 if (tag.equals("Destination")) { 290 if (xmlLast == null && xmlNames != null) 291 return; 292 else 293 throw new RuntimeException(MessageLocalization.getComposedMessage("destination.end.tag.out.of.place")); 294 } 295 if (!tag.equals("Name")) 296 throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.end.tag.1", tag)); 297 if (xmlLast == null || xmlNames == null) 298 throw new RuntimeException(MessageLocalization.getComposedMessage("name.end.tag.out.of.place")); 299 if (!xmlLast.containsKey("Page")) 300 throw new RuntimeException(MessageLocalization.getComposedMessage("page.attribute.missing")); 301 xmlNames.put(unEscapeBinaryString(xmlLast.get("Name")), xmlLast.get("Page")); 302 xmlLast = null; 303 } 304 305 public void startDocument() { 306 } 307 308 public void startElement(String tag, Map<String, String> h) { 309 if (xmlNames == null) { 310 if (tag.equals("Destination")) { 311 xmlNames = new HashMap<String, String>(); 312 return; 313 } 314 else 315 throw new RuntimeException(MessageLocalization.getComposedMessage("root.element.is.not.destination")); 316 } 317 if (!tag.equals("Name")) 318 throw new RuntimeException(MessageLocalization.getComposedMessage("tag.1.not.allowed", tag)); 319 if (xmlLast != null) 320 throw new RuntimeException(MessageLocalization.getComposedMessage("nested.tags.are.not.allowed")); 321 xmlLast = new HashMap<String, String>(h); 322 xmlLast.put("Name", ""); 323 } 324 325 public void text(String str) { 326 if (xmlLast == null) 327 return; 328 String name = xmlLast.get("Name"); 329 name += str; 330 xmlLast.put("Name", name); 331 } 332}