001/*
002 * $Id: FdfWriter.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.IOException;
047import java.io.OutputStream;
048import java.util.ArrayList;
049import java.util.HashMap;
050import java.util.Map;
051import java.util.StringTokenizer;
052
053import com.itextpdf.text.DocWriter;
054import com.itextpdf.text.pdf.AcroFields.Item;
055
056/** Writes an FDF form.
057 * @author Paulo Soares
058 */
059public class FdfWriter {
060    private static final byte[] HEADER_FDF = DocWriter.getISOBytes("%FDF-1.4\n%\u00e2\u00e3\u00cf\u00d3\n");
061    HashMap<String, Object> fields = new HashMap<String, Object>();
062
063    /** The PDF file associated with the FDF. */
064    private String file;
065
066    /** Creates a new FdfWriter. */
067    public FdfWriter() {
068    }
069
070    /** Writes the content to a stream.
071     * @param os the stream
072     * @throws IOException on error
073     */
074    public void writeTo(OutputStream os) throws IOException {
075        Wrt wrt = new Wrt(os, this);
076        wrt.writeTo();
077    }
078
079    @SuppressWarnings("unchecked")
080    boolean setField(String field, PdfObject value) {
081        HashMap<String, Object> map = fields;
082        StringTokenizer tk = new StringTokenizer(field, ".");
083        if (!tk.hasMoreTokens())
084            return false;
085        while (true) {
086            String s = tk.nextToken();
087            Object obj = map.get(s);
088            if (tk.hasMoreTokens()) {
089                if (obj == null) {
090                    obj = new HashMap<String, Object>();
091                    map.put(s, obj);
092                    map = (HashMap<String, Object>)obj;
093                    continue;
094                }
095                else if (obj instanceof HashMap)
096                    map = (HashMap<String, Object>)obj;
097                else
098                    return false;
099            }
100            else {
101                if (!(obj instanceof HashMap)) {
102                    map.put(s, value);
103                    return true;
104                }
105                else
106                    return false;
107            }
108        }
109    }
110
111    @SuppressWarnings("unchecked")
112    void iterateFields(HashMap<String, Object> values, HashMap<String, Object> map, String name) {
113        for (Map.Entry<String, Object> entry: map.entrySet()) {
114            String s = entry.getKey();
115            Object obj = entry.getValue();
116            if (obj instanceof HashMap)
117                iterateFields(values, (HashMap<String, Object>)obj, name + "." + s);
118            else
119                values.put((name + "." + s).substring(1), obj);
120        }
121    }
122
123    /** Removes the field value.
124     * @param field the field name
125     * @return <CODE>true</CODE> if the field was found and removed,
126     * <CODE>false</CODE> otherwise
127     */
128    @SuppressWarnings("unchecked")
129    public boolean removeField(String field) {
130        HashMap<String, Object> map = fields;
131        StringTokenizer tk = new StringTokenizer(field, ".");
132        if (!tk.hasMoreTokens())
133            return false;
134        ArrayList<Object> hist = new ArrayList<Object>();
135        while (true) {
136            String s = tk.nextToken();
137            Object obj = map.get(s);
138            if (obj == null)
139                return false;
140            hist.add(map);
141            hist.add(s);
142            if (tk.hasMoreTokens()) {
143                if (obj instanceof HashMap)
144                    map = (HashMap<String, Object>)obj;
145                else
146                    return false;
147            }
148            else {
149                if (obj instanceof HashMap)
150                    return false;
151                else
152                    break;
153            }
154        }
155        for (int k = hist.size() - 2; k >= 0; k -= 2) {
156            map = (HashMap<String, Object>)hist.get(k);
157            String s = (String)hist.get(k + 1);
158            map.remove(s);
159            if (!map.isEmpty())
160                break;
161        }
162        return true;
163    }
164
165    /** Gets all the fields. The map is keyed by the fully qualified
166     * field name and the values are <CODE>PdfObject</CODE>.
167     * @return a map with all the fields
168     */
169    public HashMap<String, Object> getFields() {
170        HashMap<String, Object> values = new HashMap<String, Object>();
171        iterateFields(values, fields, "");
172        return values;
173    }
174
175    /** Gets the field value.
176     * @param field the field name
177     * @return the field value or <CODE>null</CODE> if not found
178     */
179    @SuppressWarnings("unchecked")
180    public String getField(String field) {
181        HashMap<String, Object> map = fields;
182        StringTokenizer tk = new StringTokenizer(field, ".");
183        if (!tk.hasMoreTokens())
184            return null;
185        while (true) {
186            String s = tk.nextToken();
187            Object obj = map.get(s);
188            if (obj == null)
189                return null;
190            if (tk.hasMoreTokens()) {
191                if (obj instanceof HashMap)
192                    map = (HashMap<String, Object>)obj;
193                else
194                    return null;
195            }
196            else {
197                if (obj instanceof HashMap)
198                    return null;
199                else {
200                    if (((PdfObject)obj).isString())
201                        return ((PdfString)obj).toUnicodeString();
202                    else
203                        return PdfName.decodeName(obj.toString());
204                }
205            }
206        }
207    }
208
209    /** Sets the field value as a name.
210     * @param field the fully qualified field name
211     * @param value the value
212     * @return <CODE>true</CODE> if the value was inserted,
213     * <CODE>false</CODE> if the name is incompatible with
214     * an existing field
215     */
216    public boolean setFieldAsName(String field, String value) {
217        return setField(field, new PdfName(value));
218    }
219
220    /** Sets the field value as a string.
221     * @param field the fully qualified field name
222     * @param value the value
223     * @return <CODE>true</CODE> if the value was inserted,
224     * <CODE>false</CODE> if the name is incompatible with
225     * an existing field
226     */
227    public boolean setFieldAsString(String field, String value) {
228        return setField(field, new PdfString(value, PdfObject.TEXT_UNICODE));
229    }
230
231    /**
232     * Sets the field value as a <CODE>PDFAction</CODE>.
233     * For example, this method allows setting a form submit button action using {@link PdfAction#createSubmitForm(String, Object[], int)}.
234     * This method creates an <CODE>A</CODE> entry for the specified field in the underlying FDF file.
235     * Method contributed by Philippe Laflamme (plaflamme)
236     * @param field the fully qualified field name
237     * @param action the field's action
238     * @return <CODE>true</CODE> if the value was inserted,
239     * <CODE>false</CODE> if the name is incompatible with
240     * an existing field
241     * @since   2.1.5
242     */
243    public boolean setFieldAsAction(String field, PdfAction action) {
244        return setField(field, action);
245    }
246
247    /** Sets all the fields from this <CODE>FdfReader</CODE>
248     * @param fdf the <CODE>FdfReader</CODE>
249     */
250    public void setFields(FdfReader fdf) {
251        HashMap<String, PdfDictionary> map = fdf.getFields();
252        for (Map.Entry<String, PdfDictionary> entry: map.entrySet()) {
253            String key = entry.getKey();
254            PdfDictionary dic = entry.getValue();
255            PdfObject v = dic.get(PdfName.V);
256            if (v != null) {
257                setField(key, v);
258            }
259            v = dic.get(PdfName.A); // (plaflamme)
260            if (v != null) {
261                setField(key, v);
262            }
263        }
264    }
265
266    /** Sets all the fields from this <CODE>PdfReader</CODE>
267     * @param pdf the <CODE>PdfReader</CODE>
268     */
269    public void setFields(PdfReader pdf) {
270        setFields(pdf.getAcroFields());
271    }
272
273    /** Sets all the fields from this <CODE>AcroFields</CODE>
274     * @param af the <CODE>AcroFields</CODE>
275     */
276    public void setFields(AcroFields af) {
277        for (Map.Entry<String, Item> entry: af.getFields().entrySet()) {
278            String fn = entry.getKey();
279            AcroFields.Item item = entry.getValue();
280            PdfDictionary dic = item.getMerged(0);
281            PdfObject v = PdfReader.getPdfObjectRelease(dic.get(PdfName.V));
282            if (v == null)
283                continue;
284            PdfObject ft = PdfReader.getPdfObjectRelease(dic.get(PdfName.FT));
285            if (ft == null || PdfName.SIG.equals(ft))
286                continue;
287            setField(fn, v);
288        }
289    }
290
291    /** Gets the PDF file name associated with the FDF.
292     * @return the PDF file name associated with the FDF
293     */
294    public String getFile() {
295        return this.file;
296    }
297
298    /** Sets the PDF file name associated with the FDF.
299     * @param file the PDF file name associated with the FDF
300     *
301     */
302    public void setFile(String file) {
303        this.file = file;
304    }
305
306    static class Wrt extends PdfWriter {
307        private FdfWriter fdf;
308
309        Wrt(OutputStream os, FdfWriter fdf) throws IOException {
310            super(new PdfDocument(), os);
311            this.fdf = fdf;
312            this.os.write(HEADER_FDF);
313            body = new PdfBody(this);
314        }
315
316        void writeTo() throws IOException {
317            PdfDictionary dic = new PdfDictionary();
318            dic.put(PdfName.FIELDS, calculate(fdf.fields));
319            if (fdf.file != null)
320                dic.put(PdfName.F, new PdfString(fdf.file, PdfObject.TEXT_UNICODE));
321            PdfDictionary fd = new PdfDictionary();
322            fd.put(PdfName.FDF, dic);
323            PdfIndirectReference ref = addToBody(fd).getIndirectReference();
324            os.write(getISOBytes("trailer\n"));
325            PdfDictionary trailer = new PdfDictionary();
326            trailer.put(PdfName.ROOT, ref);
327            trailer.toPdf(null, os);
328            os.write(getISOBytes("\n%%EOF\n"));
329            os.close();
330        }
331
332
333        @SuppressWarnings("unchecked")
334        PdfArray calculate(HashMap<String, Object> map) throws IOException {
335            PdfArray ar = new PdfArray();
336            for (Map.Entry<String, Object> entry: map.entrySet()) {
337                String key = entry.getKey();
338                Object v = entry.getValue();
339                PdfDictionary dic = new PdfDictionary();
340                dic.put(PdfName.T, new PdfString(key, PdfObject.TEXT_UNICODE));
341                if (v instanceof HashMap) {
342                    dic.put(PdfName.KIDS, calculate((HashMap<String, Object>)v));
343                }
344                else if(v instanceof PdfAction) {       // (plaflamme)
345                        dic.put(PdfName.A, (PdfAction)v);
346                }
347                else {
348                    dic.put(PdfName.V, (PdfObject)v);
349                }
350                ar.add(dic);
351            }
352            return ar;
353        }
354    }
355}