~ubuntu-branches/ubuntu/karmic/libitext-java/karmic

« back to all changes in this revision

Viewing changes to core/com/lowagie/text/pdf/XfaForm.java

  • Committer: Bazaar Package Importer
  • Author(s): Adriaan Peeters
  • Date: 2008-11-23 12:26:51 UTC
  • mfrom: (5.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20081123122651-ab7juwjz41q1123k
Tags: 2.1.4-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Id: XfaForm.java 3536 2008-07-07 23:23:10Z xlv $
 
3
 *
 
4
 * Copyright 2006 Paulo Soares
 
5
 *
 
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 
7
 * (the "License"); you may not use this file except in compliance with the License.
 
8
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 
9
 *
 
10
 * Software distributed under the License is distributed on an "AS IS" basis,
 
11
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
12
 * for the specific language governing rights and limitations under the License.
 
13
 *
 
14
 * The Original Code is 'iText, a free JAVA-PDF library'.
 
15
 *
 
16
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 
17
 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
 
18
 * All Rights Reserved.
 
19
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 
20
 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
 
21
 *
 
22
 * Contributor(s): all the names of the contributors are added in the source code
 
23
 * where applicable.
 
24
 *
 
25
 * Alternatively, the contents of this file may be used under the terms of the
 
26
 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
 
27
 * provisions of LGPL are applicable instead of those above.  If you wish to
 
28
 * allow use of your version of this file only under the terms of the LGPL
 
29
 * License and not to allow others to use your version of this file under
 
30
 * the MPL, indicate your decision by deleting the provisions above and
 
31
 * replace them with the notice and other provisions required by the LGPL.
 
32
 * If you do not delete the provisions above, a recipient may use your version
 
33
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
 
34
 *
 
35
 * This library is free software; you can redistribute it and/or modify it
 
36
 * under the terms of the MPL as stated above or under the terms of the GNU
 
37
 * Library General Public License as published by the Free Software Foundation;
 
38
 * either version 2 of the License, or any later version.
 
39
 *
 
40
 * This library is distributed in the hope that it will be useful, but WITHOUT
 
41
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
42
 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
 
43
 * details.
 
44
 *
 
45
 * If you didn't download this code from the following link, you should check if
 
46
 * you aren't using an obsolete version:
 
47
 * http://www.lowagie.com/iText/
 
48
 */
 
49
 
 
50
package com.lowagie.text.pdf;
 
51
 
 
52
import java.io.ByteArrayInputStream;
 
53
import java.io.ByteArrayOutputStream;
 
54
import java.io.IOException;
 
55
import java.util.ArrayList;
 
56
import java.util.Collection;
 
57
import java.util.EmptyStackException;
 
58
import java.util.HashMap;
 
59
import java.util.Iterator;
 
60
 
 
61
import javax.xml.parsers.DocumentBuilder;
 
62
import javax.xml.parsers.DocumentBuilderFactory;
 
63
import javax.xml.parsers.ParserConfigurationException;
 
64
 
 
65
import org.w3c.dom.Node;
 
66
import org.xml.sax.SAXException;
 
67
 
 
68
import com.lowagie.text.xml.XmlDomWriter;
 
69
 
 
70
/**
 
71
 * Processes XFA forms.
 
72
 * @author Paulo Soares (psoares@consiste.pt)
 
73
 */
 
74
public class XfaForm {
 
75
 
 
76
    private Xml2SomTemplate templateSom;
 
77
    private Node templateNode;
 
78
    private Xml2SomDatasets datasetsSom;
 
79
    private Node datasetsNode;
 
80
    private AcroFieldsSearch acroFieldsSom;
 
81
    private PdfReader reader;
 
82
    private boolean xfaPresent;
 
83
    private org.w3c.dom.Document domDocument;
 
84
    private boolean changed;
 
85
    public static final String XFA_DATA_SCHEMA = "http://www.xfa.org/schema/xfa-data/1.0/";
 
86
    
 
87
    /**
 
88
     * An empty constructor to build on.
 
89
     */
 
90
    public XfaForm() {
 
91
    }
 
92
    
 
93
    /**
 
94
     * Return the XFA Object, could be an array, could be a Stream.
 
95
     * Returns null f no XFA Object is present.
 
96
     * @param   reader  a PdfReader instance
 
97
     * @return  the XFA object
 
98
     * @since   2.1.3
 
99
     */
 
100
    public static PdfObject getXfaObject(PdfReader reader) {
 
101
        PdfDictionary af = (PdfDictionary)PdfReader.getPdfObjectRelease(reader.getCatalog().get(PdfName.ACROFORM));
 
102
        if (af == null) {
 
103
            return null;
 
104
        }
 
105
        return PdfReader.getPdfObjectRelease(af.get(PdfName.XFA));
 
106
    }
 
107
    
 
108
    /**
 
109
     * A constructor from a <CODE>PdfReader</CODE>. It basically does everything
 
110
     * from finding the XFA stream to the XML parsing.
 
111
     * @param reader the reader
 
112
     * @throws java.io.IOException on error
 
113
     * @throws javax.xml.parsers.ParserConfigurationException on error
 
114
     * @throws org.xml.sax.SAXException on error
 
115
     */
 
116
    public XfaForm(PdfReader reader) throws IOException, ParserConfigurationException, SAXException {
 
117
        this.reader = reader;
 
118
        PdfObject xfa = getXfaObject(reader);
 
119
        if (xfa == null) {
 
120
                xfaPresent = false;
 
121
                return;
 
122
        }
 
123
        xfaPresent = true;
 
124
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
 
125
        if (xfa.isArray()) {
 
126
            ArrayList ar = ((PdfArray)xfa).getArrayList();
 
127
            for (int k = 1; k < ar.size(); k += 2) {
 
128
                PdfObject ob = PdfReader.getPdfObject((PdfObject)ar.get(k));
 
129
                if (ob instanceof PRStream) {
 
130
                    byte[] b = PdfReader.getStreamBytes((PRStream)ob);
 
131
                    bout.write(b);
 
132
                }
 
133
            }
 
134
        }
 
135
        else if (xfa instanceof PRStream) {
 
136
            byte[] b = PdfReader.getStreamBytes((PRStream)xfa);
 
137
            bout.write(b);
 
138
        }
 
139
        bout.close();
 
140
        DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
 
141
        fact.setNamespaceAware(true);
 
142
        DocumentBuilder db = fact.newDocumentBuilder();
 
143
        domDocument = db.parse(new ByteArrayInputStream(bout.toByteArray()));   
 
144
        Node n = domDocument.getFirstChild();
 
145
        while (n.getChildNodes().getLength() == 0) {
 
146
                n = n.getNextSibling();
 
147
        }
 
148
        n = n.getFirstChild();
 
149
        while (n != null) {
 
150
            if (n.getNodeType() == Node.ELEMENT_NODE) {
 
151
                String s = n.getLocalName();
 
152
                if (s.equals("template")) {
 
153
                        templateNode = n;
 
154
                    templateSom = new Xml2SomTemplate(n);
 
155
                }
 
156
                else if (s.equals("datasets")) {
 
157
                    datasetsNode = n;
 
158
                    datasetsSom = new Xml2SomDatasets(n.getFirstChild());
 
159
                }
 
160
            }
 
161
            n = n.getNextSibling();
 
162
        }
 
163
    }
 
164
    
 
165
    /**
 
166
     * Sets the XFA key from a byte array. The old XFA is erased.
 
167
     * @param form the data
 
168
     * @param reader the reader
 
169
     * @param writer the writer
 
170
     * @throws java.io.IOException on error
 
171
     */
 
172
    public static void setXfa(XfaForm form, PdfReader reader, PdfWriter writer) throws IOException {
 
173
        PdfDictionary af = (PdfDictionary)PdfReader.getPdfObjectRelease(reader.getCatalog().get(PdfName.ACROFORM));
 
174
        if (af == null) {
 
175
            return;
 
176
        }
 
177
        PdfObject xfa = getXfaObject(reader);
 
178
        if (xfa.isArray()) {
 
179
                ArrayList ar = ((PdfArray)xfa).getArrayList();
 
180
            int t = -1;
 
181
            int d = -1;
 
182
            for (int k = 0; k < ar.size(); k += 2) {
 
183
                PdfString s = (PdfString)ar.get(k);
 
184
                if ("template".equals(s.toString())) {
 
185
                        t = k + 1;
 
186
                }
 
187
                if ("datasets".equals(s.toString())) {
 
188
                        d = k + 1;
 
189
                }
 
190
            }
 
191
            if (t > -1 && d > -1) {
 
192
                reader.killXref((PdfIndirectReference)ar.get(t));
 
193
                reader.killXref((PdfIndirectReference)ar.get(d));
 
194
                PdfStream tStream = new PdfStream(serializeDoc(form.templateNode));
 
195
                tStream.flateCompress(writer.getCompressionLevel());
 
196
                ar.set(t, writer.addToBody(tStream).getIndirectReference());
 
197
                PdfStream dStream = new PdfStream(serializeDoc(form.datasetsNode));
 
198
                dStream.flateCompress(writer.getCompressionLevel());
 
199
                ar.set(d, writer.addToBody(dStream).getIndirectReference());
 
200
                af.put(PdfName.XFA, new PdfArray(ar));
 
201
                return;
 
202
            }
 
203
        }
 
204
        reader.killXref(af.get(PdfName.XFA));
 
205
        PdfStream str = new PdfStream(serializeDoc(form.domDocument));
 
206
        str.flateCompress(writer.getCompressionLevel());
 
207
        PdfIndirectReference ref = writer.addToBody(str).getIndirectReference();
 
208
        af.put(PdfName.XFA, ref);
 
209
    }
 
210
 
 
211
    /**
 
212
     * Sets the XFA key from the instance data. The old XFA is erased.
 
213
     * @param writer the writer
 
214
     * @throws java.io.IOException on error
 
215
     */
 
216
    public void setXfa(PdfWriter writer) throws IOException {
 
217
        setXfa(this, reader, writer);
 
218
    }
 
219
 
 
220
    /**
 
221
     * Serializes a XML document to a byte array.
 
222
     * @param n the XML document
 
223
     * @throws java.io.IOException on error
 
224
     * @return the serialized XML document
 
225
     */
 
226
    public static byte[] serializeDoc(Node n) throws IOException {
 
227
        XmlDomWriter xw = new XmlDomWriter();
 
228
        ByteArrayOutputStream fout = new ByteArrayOutputStream();
 
229
        xw.setOutput(fout, null);
 
230
        xw.setCanonical(false);
 
231
        xw.write(n);
 
232
        fout.close();
 
233
        return fout.toByteArray();
 
234
    }
 
235
    
 
236
    /**
 
237
     * Returns <CODE>true</CODE> if it is a XFA form.
 
238
     * @return <CODE>true</CODE> if it is a XFA form
 
239
     */
 
240
    public boolean isXfaPresent() {
 
241
        return xfaPresent;
 
242
    }
 
243
 
 
244
    /**
 
245
     * Gets the top level DOM document.
 
246
     * @return the top level DOM document
 
247
     */
 
248
    public org.w3c.dom.Document getDomDocument() {
 
249
        return domDocument;
 
250
    }
 
251
    
 
252
    
 
253
    /**
 
254
     * Finds the complete field name contained in the "classic" forms from a partial
 
255
     * name.
 
256
     * @param name the complete or partial name
 
257
     * @param af the fields
 
258
     * @return the complete name or <CODE>null</CODE> if not found
 
259
     */
 
260
    public String findFieldName(String name, AcroFields af) {
 
261
        HashMap items = af.getFields();
 
262
        if (items.containsKey(name))
 
263
            return name;
 
264
        if (acroFieldsSom == null) {
 
265
            acroFieldsSom = new AcroFieldsSearch(items.keySet());
 
266
        }
 
267
        if (acroFieldsSom.getAcroShort2LongName().containsKey(name))
 
268
            return (String)acroFieldsSom.getAcroShort2LongName().get(name);
 
269
        return acroFieldsSom.inverseSearchGlobal(Xml2Som.splitParts(name));
 
270
    }
 
271
    
 
272
    /**
 
273
     * Finds the complete SOM name contained in the datasets section from a 
 
274
     * possibly partial name.
 
275
     * @param name the complete or partial name
 
276
     * @return the complete name or <CODE>null</CODE> if not found
 
277
     */
 
278
    public String findDatasetsName(String name) {
 
279
        if (datasetsSom.getName2Node().containsKey(name))
 
280
            return name;
 
281
        return datasetsSom.inverseSearchGlobal(Xml2Som.splitParts(name));
 
282
    }
 
283
 
 
284
    /**
 
285
     * Finds the <CODE>Node</CODE> contained in the datasets section from a 
 
286
     * possibly partial name.
 
287
     * @param name the complete or partial name
 
288
     * @return the <CODE>Node</CODE> or <CODE>null</CODE> if not found
 
289
     */
 
290
    public Node findDatasetsNode(String name) {
 
291
        if (name == null)
 
292
            return null;
 
293
        name = findDatasetsName(name);
 
294
        if (name == null)
 
295
            return null;
 
296
        return (Node)datasetsSom.getName2Node().get(name);
 
297
    }
 
298
 
 
299
    /**
 
300
     * Gets all the text contained in the child nodes of this node.
 
301
     * @param n the <CODE>Node</CODE>
 
302
     * @return the text found or "" if no text was found
 
303
     */
 
304
    public static String getNodeText(Node n) {
 
305
        if (n == null)
 
306
            return "";
 
307
        return getNodeText(n, "");
 
308
        
 
309
    }
 
310
    
 
311
    private static String getNodeText(Node n, String name) {
 
312
        Node n2 = n.getFirstChild();
 
313
        while (n2 != null) {
 
314
            if (n2.getNodeType() == Node.ELEMENT_NODE) {
 
315
                name = getNodeText(n2, name);
 
316
            }
 
317
            else if (n2.getNodeType() == Node.TEXT_NODE) {
 
318
                name += n2.getNodeValue();
 
319
            }
 
320
            n2 = n2.getNextSibling();
 
321
        }
 
322
        return name;
 
323
    }
 
324
    
 
325
    /**
 
326
     * Sets the text of this node. All the child's node are deleted and a new
 
327
     * child text node is created.
 
328
     * @param n the <CODE>Node</CODE> to add the text to
 
329
     * @param text the text to add
 
330
     */
 
331
    public void setNodeText(Node n, String text) {
 
332
        if (n == null)
 
333
            return;
 
334
        Node nc = null;
 
335
        while ((nc = n.getFirstChild()) != null) {
 
336
            n.removeChild(nc);
 
337
        }
 
338
        if (n.getAttributes().getNamedItemNS(XFA_DATA_SCHEMA, "dataNode") != null)
 
339
            n.getAttributes().removeNamedItemNS(XFA_DATA_SCHEMA, "dataNode");
 
340
        n.appendChild(domDocument.createTextNode(text));
 
341
        changed = true;
 
342
    }
 
343
    
 
344
    /**
 
345
     * Sets the XFA form flag signaling that this is a valid XFA form.
 
346
     * @param xfaPresent the XFA form flag signaling that this is a valid XFA form
 
347
     */
 
348
    public void setXfaPresent(boolean xfaPresent) {
 
349
        this.xfaPresent = xfaPresent;
 
350
    }
 
351
 
 
352
    /**
 
353
     * Sets the top DOM document.
 
354
     * @param domDocument the top DOM document
 
355
     */
 
356
    public void setDomDocument(org.w3c.dom.Document domDocument) {
 
357
        this.domDocument = domDocument;
 
358
    }
 
359
 
 
360
    /**
 
361
     * Gets the <CODE>PdfReader</CODE> used by this instance.
 
362
     * @return the <CODE>PdfReader</CODE> used by this instance
 
363
     */
 
364
    public PdfReader getReader() {
 
365
        return reader;
 
366
    }
 
367
 
 
368
    /**
 
369
     * Sets the <CODE>PdfReader</CODE> to be used by this instance.
 
370
     * @param reader the <CODE>PdfReader</CODE> to be used by this instance
 
371
     */
 
372
    public void setReader(PdfReader reader) {
 
373
        this.reader = reader;
 
374
    }
 
375
 
 
376
    /**
 
377
     * Checks if this XFA form was changed.
 
378
     * @return <CODE>true</CODE> if this XFA form was changed
 
379
     */
 
380
    public boolean isChanged() {
 
381
        return changed;
 
382
    }
 
383
 
 
384
    /**
 
385
     * Sets the changed status of this XFA instance.
 
386
     * @param changed the changed status of this XFA instance
 
387
     */
 
388
    public void setChanged(boolean changed) {
 
389
        this.changed = changed;
 
390
    }
 
391
    
 
392
    /**
 
393
     * A structure to store each part of a SOM name and link it to the next part
 
394
     * beginning from the lower hierarchy.
 
395
     */
 
396
    public static class InverseStore {
 
397
        protected ArrayList part = new ArrayList();
 
398
        protected ArrayList follow = new ArrayList();
 
399
        
 
400
        /**
 
401
         * Gets the full name by traversing the hierarchy using only the
 
402
         * index 0.
 
403
         * @return the full name
 
404
         */
 
405
        public String getDefaultName() {
 
406
            InverseStore store = this;
 
407
            while (true) {
 
408
                Object obj = store.follow.get(0);
 
409
                if (obj instanceof String)
 
410
                    return (String)obj;
 
411
                store = (InverseStore)obj;
 
412
            }
 
413
        }
 
414
        
 
415
        /**
 
416
         * Search the current node for a similar name. A similar name starts
 
417
         * with the same name but has a different index. For example, "detail[3]" 
 
418
         * is similar to "detail[9]". The main use is to discard names that
 
419
         * correspond to out of bounds records.
 
420
         * @param name the name to search
 
421
         * @return <CODE>true</CODE> if a similitude was found
 
422
         */
 
423
        public boolean isSimilar(String name) {
 
424
            int idx = name.indexOf('[');
 
425
            name = name.substring(0, idx + 1);
 
426
            for (int k = 0; k < part.size(); ++k) {
 
427
                if (((String)part.get(k)).startsWith(name))
 
428
                    return true;
 
429
            }
 
430
            return false;
 
431
        }
 
432
    }
 
433
 
 
434
    /**
 
435
     * Another stack implementation. The main use is to facilitate
 
436
     * the porting to other languages.
 
437
     */
 
438
    public static class Stack2 extends ArrayList {
 
439
        private static final long serialVersionUID = -7451476576174095212L;
 
440
 
 
441
                /**
 
442
         * Looks at the object at the top of this stack without removing it from the stack.
 
443
         * @return the object at the top of this stack
 
444
         */
 
445
        public Object peek() {
 
446
            if (size() == 0)
 
447
                throw new EmptyStackException();
 
448
            return get(size() - 1);
 
449
        }
 
450
        
 
451
        /**
 
452
         * Removes the object at the top of this stack and returns that object as the value of this function.
 
453
         * @return the object at the top of this stack 
 
454
         */
 
455
        public Object pop() {
 
456
            if (size() == 0)
 
457
                throw new EmptyStackException();
 
458
            Object ret = get(size() - 1);
 
459
            remove(size() - 1);
 
460
            return ret;
 
461
        }
 
462
        
 
463
        /**
 
464
         * Pushes an item onto the top of this stack.
 
465
         * @param item the item to be pushed onto this stack
 
466
         * @return the <CODE>item</CODE> argument
 
467
         */
 
468
        public Object push(Object item) {
 
469
            add(item);
 
470
            return item;
 
471
        }
 
472
        
 
473
        /**
 
474
         * Tests if this stack is empty.
 
475
         * @return <CODE>true</CODE> if and only if this stack contains no items; <CODE>false</CODE> otherwise
 
476
         */
 
477
        public boolean empty() {
 
478
            return size() == 0;
 
479
        }
 
480
    }
 
481
    
 
482
    /**
 
483
     * A class for some basic SOM processing.
 
484
     */
 
485
    public static class Xml2Som {
 
486
        /**
 
487
         * The order the names appear in the XML, depth first.
 
488
         */
 
489
        protected ArrayList order;
 
490
        /**
 
491
         * The mapping of full names to nodes.
 
492
         */
 
493
        protected HashMap name2Node;
 
494
        /**
 
495
         * The data to do a search from the bottom hierarchy.
 
496
         */
 
497
        protected HashMap inverseSearch;
 
498
        /**
 
499
         * A stack to be used when parsing.
 
500
         */
 
501
        protected Stack2 stack;
 
502
        /**
 
503
         * A temporary store for the repetition count.
 
504
         */
 
505
        protected int anform;
 
506
 
 
507
        /**
 
508
         * Escapes a SOM string fragment replacing "." with "\.".
 
509
         * @param s the unescaped string
 
510
         * @return the escaped string
 
511
         */
 
512
        public static String escapeSom(String s) {
 
513
            int idx = s.indexOf('.');
 
514
            if (idx < 0)
 
515
                return s;
 
516
            StringBuffer sb = new StringBuffer();
 
517
            int last = 0;
 
518
            while (idx >= 0) {
 
519
                sb.append(s.substring(last, idx));
 
520
                sb.append('\\');
 
521
                last = idx;
 
522
                idx = s.indexOf('.', idx + 1);
 
523
            }
 
524
            sb.append(s.substring(last));
 
525
            return sb.toString();
 
526
        }
 
527
 
 
528
        /**
 
529
         * Unescapes a SOM string fragment replacing "\." with ".".
 
530
         * @param s the escaped string
 
531
         * @return the unescaped string
 
532
         */
 
533
        public static String unescapeSom(String s) {
 
534
            int idx = s.indexOf('\\');
 
535
            if (idx < 0)
 
536
                return s;
 
537
            StringBuffer sb = new StringBuffer();
 
538
            int last = 0;
 
539
            while (idx >= 0) {
 
540
                sb.append(s.substring(last, idx));
 
541
                last = idx + 1;
 
542
                idx = s.indexOf('\\', idx + 1);
 
543
            }
 
544
            sb.append(s.substring(last));
 
545
            return sb.toString();
 
546
        }
 
547
 
 
548
        /**
 
549
         * Outputs the stack as the sequence of elements separated
 
550
         * by '.'.
 
551
         * @return the stack as the sequence of elements separated by '.'
 
552
         */
 
553
        protected String printStack() {
 
554
            if (stack.empty())
 
555
                return "";
 
556
            StringBuffer s = new StringBuffer();
 
557
            for (int k = 0; k < stack.size(); ++k)
 
558
                s.append('.').append((String)stack.get(k));
 
559
            return s.substring(1);
 
560
        }
 
561
        
 
562
        /**
 
563
         * Gets the name with the <CODE>#subform</CODE> removed.
 
564
         * @param s the long name
 
565
         * @return the short name
 
566
         */
 
567
        public static String getShortName(String s) {
 
568
            int idx = s.indexOf(".#subform[");
 
569
            if (idx < 0)
 
570
                return s;
 
571
            int last = 0;
 
572
            StringBuffer sb = new StringBuffer();
 
573
            while (idx >= 0) {
 
574
                sb.append(s.substring(last, idx));
 
575
                idx = s.indexOf("]", idx + 10);
 
576
                if (idx < 0)
 
577
                    return sb.toString();
 
578
                last = idx + 1;
 
579
                idx = s.indexOf(".#subform[", last);
 
580
            }
 
581
            sb.append(s.substring(last));
 
582
            return sb.toString();
 
583
        }
 
584
        
 
585
        /**
 
586
         * Adds a SOM name to the search node chain.
 
587
         * @param unstack the SOM name
 
588
         */
 
589
        public void inverseSearchAdd(String unstack) {
 
590
            inverseSearchAdd(inverseSearch, stack, unstack);
 
591
        }
 
592
        
 
593
        /**
 
594
         * Adds a SOM name to the search node chain.
 
595
         * @param inverseSearch the start point
 
596
         * @param stack the stack with the separated SOM parts
 
597
         * @param unstack the full name
 
598
         */
 
599
        public static void inverseSearchAdd(HashMap inverseSearch, Stack2 stack, String unstack) {
 
600
            String last = (String)stack.peek();
 
601
            InverseStore store = (InverseStore)inverseSearch.get(last);
 
602
            if (store == null) {
 
603
                store = new InverseStore();
 
604
                inverseSearch.put(last, store);
 
605
            }
 
606
            for (int k = stack.size() - 2; k >= 0; --k) {
 
607
                last = (String)stack.get(k);
 
608
                InverseStore store2;
 
609
                int idx = store.part.indexOf(last);
 
610
                if (idx < 0) {
 
611
                    store.part.add(last);
 
612
                    store2 = new InverseStore();
 
613
                    store.follow.add(store2);
 
614
                }
 
615
                else
 
616
                    store2 = (InverseStore)store.follow.get(idx);
 
617
                store = store2;
 
618
            }
 
619
            store.part.add("");
 
620
            store.follow.add(unstack);
 
621
        }
 
622
 
 
623
        /**
 
624
         * Searches the SOM hierarchy from the bottom.
 
625
         * @param parts the SOM parts
 
626
         * @return the full name or <CODE>null</CODE> if not found
 
627
         */
 
628
        public String inverseSearchGlobal(ArrayList parts) {
 
629
            if (parts.isEmpty())
 
630
                return null;
 
631
            InverseStore store = (InverseStore)inverseSearch.get(parts.get(parts.size() - 1));
 
632
            if (store == null)
 
633
                return null;
 
634
            for (int k = parts.size() - 2; k >= 0; --k) {
 
635
                String part = (String)parts.get(k);
 
636
                int idx = store.part.indexOf(part);
 
637
                if (idx < 0) {
 
638
                    if (store.isSimilar(part))
 
639
                        return null;
 
640
                    return store.getDefaultName();
 
641
                }
 
642
                store = (InverseStore)store.follow.get(idx);
 
643
            }
 
644
            return store.getDefaultName();
 
645
        }
 
646
    
 
647
        /**
 
648
         * Splits a SOM name in the individual parts.
 
649
         * @param name the full SOM name
 
650
         * @return the split name
 
651
         */
 
652
        public static Stack2 splitParts(String name) {
 
653
            while (name.startsWith("."))
 
654
                name = name.substring(1);
 
655
            Stack2 parts = new Stack2();
 
656
            int last = 0;
 
657
            int pos = 0;
 
658
            String part;
 
659
            while (true) {
 
660
                pos = last;
 
661
                while (true) {
 
662
                    pos = name.indexOf('.', pos);
 
663
                    if (pos < 0)
 
664
                        break;
 
665
                    if (name.charAt(pos - 1) == '\\')
 
666
                        ++pos;
 
667
                    else
 
668
                        break;
 
669
                }
 
670
                if (pos < 0)
 
671
                    break;
 
672
                part = name.substring(last, pos);
 
673
                if (!part.endsWith("]"))
 
674
                    part += "[0]";
 
675
                parts.add(part);
 
676
                last = pos + 1;
 
677
            }
 
678
            part = name.substring(last);
 
679
            if (!part.endsWith("]"))
 
680
                part += "[0]";
 
681
            parts.add(part);
 
682
            return parts;
 
683
        }
 
684
 
 
685
        /**
 
686
         * Gets the order the names appear in the XML, depth first.
 
687
         * @return the order the names appear in the XML, depth first
 
688
         */
 
689
        public ArrayList getOrder() {
 
690
            return order;
 
691
        }
 
692
 
 
693
        /**
 
694
         * Sets the order the names appear in the XML, depth first
 
695
         * @param order the order the names appear in the XML, depth first
 
696
         */
 
697
        public void setOrder(ArrayList order) {
 
698
            this.order = order;
 
699
        }
 
700
 
 
701
        /**
 
702
         * Gets the mapping of full names to nodes.
 
703
         * @return the mapping of full names to nodes
 
704
         */
 
705
        public HashMap getName2Node() {
 
706
            return name2Node;
 
707
        }
 
708
 
 
709
        /**
 
710
         * Sets the mapping of full names to nodes.
 
711
         * @param name2Node the mapping of full names to nodes
 
712
         */
 
713
        public void setName2Node(HashMap name2Node) {
 
714
            this.name2Node = name2Node;
 
715
        }
 
716
 
 
717
        /**
 
718
         * Gets the data to do a search from the bottom hierarchy.
 
719
         * @return the data to do a search from the bottom hierarchy
 
720
         */
 
721
        public HashMap getInverseSearch() {
 
722
            return inverseSearch;
 
723
        }
 
724
 
 
725
        /**
 
726
         * Sets the data to do a search from the bottom hierarchy.
 
727
         * @param inverseSearch the data to do a search from the bottom hierarchy
 
728
         */
 
729
        public void setInverseSearch(HashMap inverseSearch) {
 
730
            this.inverseSearch = inverseSearch;
 
731
        }
 
732
    }
 
733
    
 
734
    /**
 
735
     * Processes the datasets section in the XFA form.
 
736
     */
 
737
    public static class Xml2SomDatasets extends Xml2Som {
 
738
        /**
 
739
         * Creates a new instance from the datasets node. This expects
 
740
         * not the datasets but the data node that comes below.
 
741
         * @param n the datasets node
 
742
         */
 
743
        public Xml2SomDatasets(Node n) {
 
744
            order = new ArrayList();
 
745
            name2Node = new HashMap();
 
746
            stack = new Stack2();
 
747
            anform = 0;
 
748
            inverseSearch = new HashMap();
 
749
            processDatasetsInternal(n);
 
750
        }
 
751
 
 
752
        /**
 
753
         * Inserts a new <CODE>Node</CODE> that will match the short name.
 
754
         * @param n the datasets top <CODE>Node</CODE>
 
755
         * @param shortName the short name
 
756
         * @return the new <CODE>Node</CODE> of the inserted name
 
757
         */
 
758
        public Node insertNode(Node n, String shortName) {
 
759
            Stack2 stack = splitParts(shortName);
 
760
            org.w3c.dom.Document doc = n.getOwnerDocument();
 
761
            Node n2 = null;
 
762
            n = n.getFirstChild();
 
763
            for (int k = 0; k < stack.size(); ++k) {
 
764
                String part = (String)stack.get(k);
 
765
                int idx = part.lastIndexOf('[');
 
766
                String name = part.substring(0, idx);
 
767
                idx = Integer.parseInt(part.substring(idx + 1, part.length() - 1));
 
768
                int found = -1;
 
769
                for (n2 = n.getFirstChild(); n2 != null; n2 = n2.getNextSibling()) {
 
770
                    if (n2.getNodeType() == Node.ELEMENT_NODE) {
 
771
                        String s = escapeSom(n2.getLocalName());
 
772
                        if (s.equals(name)) {
 
773
                            ++found;
 
774
                            if (found == idx)
 
775
                                break;
 
776
                        }
 
777
                    }
 
778
                }
 
779
                for (; found < idx; ++found) {
 
780
                    n2 = doc.createElementNS(null, name);
 
781
                    n2 = n.appendChild(n2);
 
782
                    Node attr = doc.createAttributeNS(XFA_DATA_SCHEMA, "dataNode");
 
783
                    attr.setNodeValue("dataGroup");
 
784
                    n2.getAttributes().setNamedItemNS(attr);
 
785
                }
 
786
                n = n2;
 
787
            }
 
788
            inverseSearchAdd(inverseSearch, stack, shortName);
 
789
            name2Node.put(shortName, n2);
 
790
            order.add(shortName);
 
791
            return n2;
 
792
        }
 
793
        
 
794
        private static boolean hasChildren(Node n) {
 
795
            Node dataNodeN = n.getAttributes().getNamedItemNS(XFA_DATA_SCHEMA, "dataNode");
 
796
            if (dataNodeN != null) {
 
797
                String dataNode = dataNodeN.getNodeValue();
 
798
                if ("dataGroup".equals(dataNode))
 
799
                    return true;
 
800
                else if ("dataValue".equals(dataNode))
 
801
                    return false;
 
802
            }
 
803
            if (!n.hasChildNodes())
 
804
                return false;
 
805
            Node n2 = n.getFirstChild();
 
806
            while (n2 != null) {
 
807
                if (n2.getNodeType() == Node.ELEMENT_NODE) {
 
808
                    return true;
 
809
                }
 
810
                n2 = n2.getNextSibling();
 
811
            }
 
812
            return false;
 
813
        }
 
814
 
 
815
        private void processDatasetsInternal(Node n) {
 
816
            HashMap ss = new HashMap();
 
817
            Node n2 = n.getFirstChild();
 
818
            while (n2 != null) {
 
819
                if (n2.getNodeType() == Node.ELEMENT_NODE) {
 
820
                    String s = escapeSom(n2.getLocalName());
 
821
                    Integer i = (Integer)ss.get(s);
 
822
                    if (i == null)
 
823
                        i = new Integer(0);
 
824
                    else
 
825
                        i = new Integer(i.intValue() + 1);
 
826
                    ss.put(s, i);
 
827
                    if (hasChildren(n2)) {
 
828
                        stack.push(s + "[" + i.toString() + "]");
 
829
                        processDatasetsInternal(n2);
 
830
                        stack.pop();
 
831
                    }
 
832
                    else {
 
833
                        stack.push(s + "[" + i.toString() + "]");
 
834
                        String unstack = printStack();
 
835
                        order.add(unstack);
 
836
                        inverseSearchAdd(unstack);
 
837
                        name2Node.put(unstack, n2);
 
838
                        stack.pop();
 
839
                    }
 
840
                }
 
841
                n2 = n2.getNextSibling();
 
842
            }
 
843
        }
 
844
    }
 
845
 
 
846
    /**
 
847
     * A class to process "classic" fields.
 
848
     */
 
849
    public static class AcroFieldsSearch extends Xml2Som {
 
850
        private HashMap acroShort2LongName;
 
851
        
 
852
        /**
 
853
         * Creates a new instance from a Collection with the full names.
 
854
         * @param items the Collection
 
855
         */
 
856
        public AcroFieldsSearch(Collection items) {
 
857
            inverseSearch = new HashMap();
 
858
            acroShort2LongName = new HashMap();
 
859
            for (Iterator it = items.iterator(); it.hasNext();) {
 
860
                String itemName = (String)it.next();
 
861
                String itemShort = getShortName(itemName);
 
862
                acroShort2LongName.put(itemShort, itemName);
 
863
                inverseSearchAdd(inverseSearch, splitParts(itemShort), itemName);
 
864
            }
 
865
        }
 
866
 
 
867
        /**
 
868
         * Gets the mapping from short names to long names. A long 
 
869
         * name may contain the #subform name part.
 
870
         * @return the mapping from short names to long names
 
871
         */
 
872
        public HashMap getAcroShort2LongName() {
 
873
            return acroShort2LongName;
 
874
        }
 
875
 
 
876
        /**
 
877
         * Sets the mapping from short names to long names. A long 
 
878
         * name may contain the #subform name part.
 
879
         * @param acroShort2LongName the mapping from short names to long names
 
880
         */
 
881
        public void setAcroShort2LongName(HashMap acroShort2LongName) {
 
882
            this.acroShort2LongName = acroShort2LongName;
 
883
        }
 
884
    }
 
885
 
 
886
    /**
 
887
     * Processes the template section in the XFA form.
 
888
     */
 
889
    public static class Xml2SomTemplate extends Xml2Som {
 
890
        private boolean dynamicForm;
 
891
        private int templateLevel;
 
892
        
 
893
        /**
 
894
         * Creates a new instance from the datasets node.
 
895
         * @param n the template node
 
896
         */
 
897
        public Xml2SomTemplate(Node n) {
 
898
            order = new ArrayList();
 
899
            name2Node = new HashMap();
 
900
            stack = new Stack2();
 
901
            anform = 0;
 
902
            templateLevel = 0;
 
903
            inverseSearch = new HashMap();
 
904
            processTemplate(n, null);
 
905
        }
 
906
 
 
907
        /**
 
908
         * Gets the field type as described in the <CODE>template</CODE> section of the XFA.
 
909
         * @param s the exact template name
 
910
         * @return the field type or <CODE>null</CODE> if not found
 
911
         */
 
912
        public String getFieldType(String s) {
 
913
            Node n = (Node)name2Node.get(s);
 
914
            if (n == null)
 
915
                return null;
 
916
            if (n.getLocalName().equals("exclGroup"))
 
917
                return "exclGroup";
 
918
            Node ui = n.getFirstChild();
 
919
            while (ui != null) {
 
920
                if (ui.getNodeType() == Node.ELEMENT_NODE && ui.getLocalName().equals("ui")) {
 
921
                    break;
 
922
                }
 
923
                ui = ui.getNextSibling();
 
924
            }
 
925
            if (ui == null)
 
926
                return null;
 
927
            Node type = ui.getFirstChild();
 
928
            while (type != null) {
 
929
                if (type.getNodeType() == Node.ELEMENT_NODE && !(type.getLocalName().equals("extras") && type.getLocalName().equals("picture"))) {
 
930
                    return type.getLocalName();
 
931
                }
 
932
                type = type.getNextSibling();
 
933
            }
 
934
            return null;
 
935
        }
 
936
 
 
937
        private void processTemplate(Node n, HashMap ff) {
 
938
            if (ff == null)
 
939
                ff = new HashMap();
 
940
            HashMap ss = new HashMap();
 
941
            Node n2 = n.getFirstChild();
 
942
            while (n2 != null) {
 
943
                if (n2.getNodeType() == Node.ELEMENT_NODE) {
 
944
                    String s = n2.getLocalName();
 
945
                    if (s.equals("subform")) {
 
946
                        Node name = n2.getAttributes().getNamedItem("name");
 
947
                        String nn = "#subform";
 
948
                        boolean annon = true;
 
949
                        if (name != null) {
 
950
                            nn = escapeSom(name.getNodeValue());
 
951
                            annon = false;
 
952
                        }
 
953
                        Integer i;
 
954
                        if (annon) {
 
955
                            i = new Integer(anform);
 
956
                            ++anform;
 
957
                        }
 
958
                        else {
 
959
                            i = (Integer)ss.get(nn);
 
960
                            if (i == null)
 
961
                                i = new Integer(0);
 
962
                            else
 
963
                                i = new Integer(i.intValue() + 1);
 
964
                            ss.put(nn, i);
 
965
                        }
 
966
                        stack.push(nn + "[" + i.toString() + "]");
 
967
                        ++templateLevel;
 
968
                        if (annon)
 
969
                            processTemplate(n2, ff);
 
970
                        else
 
971
                            processTemplate(n2, null);
 
972
                        --templateLevel;
 
973
                        stack.pop();
 
974
                    }
 
975
                    else if (s.equals("field") || s.equals("exclGroup")) {
 
976
                        Node name = n2.getAttributes().getNamedItem("name");
 
977
                        if (name != null) {
 
978
                            String nn = escapeSom(name.getNodeValue());
 
979
                            Integer i = (Integer)ff.get(nn);
 
980
                            if (i == null)
 
981
                                i = new Integer(0);
 
982
                            else
 
983
                                i = new Integer(i.intValue() + 1);
 
984
                            ff.put(nn, i);
 
985
                            stack.push(nn + "[" + i.toString() + "]");
 
986
                            String unstack = printStack();
 
987
                            order.add(unstack);
 
988
                            inverseSearchAdd(unstack);
 
989
                            name2Node.put(unstack, n2);
 
990
                            stack.pop();
 
991
                        }
 
992
                    }
 
993
                    else if (!dynamicForm && templateLevel > 0 && s.equals("occur")) {
 
994
                        int initial = 1;
 
995
                        int min = 1;
 
996
                        int max = 1;
 
997
                        Node a = n2.getAttributes().getNamedItem("initial");
 
998
                        if (a != null)
 
999
                            try{initial = Integer.parseInt(a.getNodeValue().trim());}catch(Exception e){}
 
1000
                        a = n2.getAttributes().getNamedItem("min");
 
1001
                        if (a != null)
 
1002
                            try{min = Integer.parseInt(a.getNodeValue().trim());}catch(Exception e){}
 
1003
                        a = n2.getAttributes().getNamedItem("max");
 
1004
                        if (a != null)
 
1005
                            try{max = Integer.parseInt(a.getNodeValue().trim());}catch(Exception e){}
 
1006
                        if (initial != min || min != max)
 
1007
                            dynamicForm = true;
 
1008
                    }
 
1009
                }
 
1010
                n2 = n2.getNextSibling();
 
1011
            }
 
1012
        }
 
1013
 
 
1014
        /**
 
1015
         * <CODE>true</CODE> if it's a dynamic form; <CODE>false</CODE>
 
1016
         * if it's a static form.
 
1017
         * @return <CODE>true</CODE> if it's a dynamic form; <CODE>false</CODE>
 
1018
         * if it's a static form
 
1019
         */
 
1020
        public boolean isDynamicForm() {
 
1021
            return dynamicForm;
 
1022
        }
 
1023
 
 
1024
        /**
 
1025
         * Sets the dynamic form flag. It doesn't change the template.
 
1026
         * @param dynamicForm the dynamic form flag
 
1027
         */
 
1028
        public void setDynamicForm(boolean dynamicForm) {
 
1029
            this.dynamicForm = dynamicForm;
 
1030
        }
 
1031
    }
 
1032
 
 
1033
    /**
 
1034
     * Gets the class that contains the template processing section of the XFA.
 
1035
     * @return the class that contains the template processing section of the XFA
 
1036
     */
 
1037
    public Xml2SomTemplate getTemplateSom() {
 
1038
        return templateSom;
 
1039
    }
 
1040
 
 
1041
    /**
 
1042
     * Sets the class that contains the template processing section of the XFA
 
1043
     * @param templateSom the class that contains the template processing section of the XFA
 
1044
     */
 
1045
    public void setTemplateSom(Xml2SomTemplate templateSom) {
 
1046
        this.templateSom = templateSom;
 
1047
    }
 
1048
 
 
1049
    /**
 
1050
     * Gets the class that contains the datasets processing section of the XFA.
 
1051
     * @return the class that contains the datasets processing section of the XFA
 
1052
     */
 
1053
    public Xml2SomDatasets getDatasetsSom() {
 
1054
        return datasetsSom;
 
1055
    }
 
1056
 
 
1057
    /**
 
1058
     * Sets the class that contains the datasets processing section of the XFA.
 
1059
     * @param datasetsSom the class that contains the datasets processing section of the XFA
 
1060
     */
 
1061
    public void setDatasetsSom(Xml2SomDatasets datasetsSom) {
 
1062
        this.datasetsSom = datasetsSom;
 
1063
    }
 
1064
 
 
1065
    /**
 
1066
     * Gets the class that contains the "classic" fields processing.
 
1067
     * @return the class that contains the "classic" fields processing
 
1068
     */
 
1069
    public AcroFieldsSearch getAcroFieldsSom() {
 
1070
        return acroFieldsSom;
 
1071
    }
 
1072
 
 
1073
    /**
 
1074
     * Sets the class that contains the "classic" fields processing.
 
1075
     * @param acroFieldsSom the class that contains the "classic" fields processing
 
1076
     */
 
1077
    public void setAcroFieldsSom(AcroFieldsSearch acroFieldsSom) {
 
1078
        this.acroFieldsSom = acroFieldsSom;
 
1079
    }
 
1080
    
 
1081
    /**
 
1082
     * Gets the <CODE>Node</CODE> that corresponds to the datasets part.
 
1083
     * @return the <CODE>Node</CODE> that corresponds to the datasets part
 
1084
     */
 
1085
    public Node getDatasetsNode() {
 
1086
        return datasetsNode;
 
1087
    }
 
1088
}