2
* $Id: XfaForm.java 3536 2008-07-07 23:23:10Z xlv $
4
* Copyright 2006 Paulo Soares
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/
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.
14
* The Original Code is 'iText, a free JAVA-PDF library'.
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.
22
* Contributor(s): all the names of the contributors are added in the source code
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.
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.
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
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/
50
package com.lowagie.text.pdf;
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;
61
import javax.xml.parsers.DocumentBuilder;
62
import javax.xml.parsers.DocumentBuilderFactory;
63
import javax.xml.parsers.ParserConfigurationException;
65
import org.w3c.dom.Node;
66
import org.xml.sax.SAXException;
68
import com.lowagie.text.xml.XmlDomWriter;
71
* Processes XFA forms.
72
* @author Paulo Soares (psoares@consiste.pt)
74
public class XfaForm {
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/";
88
* An empty constructor to build on.
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
100
public static PdfObject getXfaObject(PdfReader reader) {
101
PdfDictionary af = (PdfDictionary)PdfReader.getPdfObjectRelease(reader.getCatalog().get(PdfName.ACROFORM));
105
return PdfReader.getPdfObjectRelease(af.get(PdfName.XFA));
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
116
public XfaForm(PdfReader reader) throws IOException, ParserConfigurationException, SAXException {
117
this.reader = reader;
118
PdfObject xfa = getXfaObject(reader);
124
ByteArrayOutputStream bout = new ByteArrayOutputStream();
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);
135
else if (xfa instanceof PRStream) {
136
byte[] b = PdfReader.getStreamBytes((PRStream)xfa);
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();
148
n = n.getFirstChild();
150
if (n.getNodeType() == Node.ELEMENT_NODE) {
151
String s = n.getLocalName();
152
if (s.equals("template")) {
154
templateSom = new Xml2SomTemplate(n);
156
else if (s.equals("datasets")) {
158
datasetsSom = new Xml2SomDatasets(n.getFirstChild());
161
n = n.getNextSibling();
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
172
public static void setXfa(XfaForm form, PdfReader reader, PdfWriter writer) throws IOException {
173
PdfDictionary af = (PdfDictionary)PdfReader.getPdfObjectRelease(reader.getCatalog().get(PdfName.ACROFORM));
177
PdfObject xfa = getXfaObject(reader);
179
ArrayList ar = ((PdfArray)xfa).getArrayList();
182
for (int k = 0; k < ar.size(); k += 2) {
183
PdfString s = (PdfString)ar.get(k);
184
if ("template".equals(s.toString())) {
187
if ("datasets".equals(s.toString())) {
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));
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);
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
216
public void setXfa(PdfWriter writer) throws IOException {
217
setXfa(this, reader, writer);
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
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);
233
return fout.toByteArray();
237
* Returns <CODE>true</CODE> if it is a XFA form.
238
* @return <CODE>true</CODE> if it is a XFA form
240
public boolean isXfaPresent() {
245
* Gets the top level DOM document.
246
* @return the top level DOM document
248
public org.w3c.dom.Document getDomDocument() {
254
* Finds the complete field name contained in the "classic" forms from a partial
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
260
public String findFieldName(String name, AcroFields af) {
261
HashMap items = af.getFields();
262
if (items.containsKey(name))
264
if (acroFieldsSom == null) {
265
acroFieldsSom = new AcroFieldsSearch(items.keySet());
267
if (acroFieldsSom.getAcroShort2LongName().containsKey(name))
268
return (String)acroFieldsSom.getAcroShort2LongName().get(name);
269
return acroFieldsSom.inverseSearchGlobal(Xml2Som.splitParts(name));
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
278
public String findDatasetsName(String name) {
279
if (datasetsSom.getName2Node().containsKey(name))
281
return datasetsSom.inverseSearchGlobal(Xml2Som.splitParts(name));
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
290
public Node findDatasetsNode(String name) {
293
name = findDatasetsName(name);
296
return (Node)datasetsSom.getName2Node().get(name);
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
304
public static String getNodeText(Node n) {
307
return getNodeText(n, "");
311
private static String getNodeText(Node n, String name) {
312
Node n2 = n.getFirstChild();
314
if (n2.getNodeType() == Node.ELEMENT_NODE) {
315
name = getNodeText(n2, name);
317
else if (n2.getNodeType() == Node.TEXT_NODE) {
318
name += n2.getNodeValue();
320
n2 = n2.getNextSibling();
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
331
public void setNodeText(Node n, String text) {
335
while ((nc = n.getFirstChild()) != null) {
338
if (n.getAttributes().getNamedItemNS(XFA_DATA_SCHEMA, "dataNode") != null)
339
n.getAttributes().removeNamedItemNS(XFA_DATA_SCHEMA, "dataNode");
340
n.appendChild(domDocument.createTextNode(text));
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
348
public void setXfaPresent(boolean xfaPresent) {
349
this.xfaPresent = xfaPresent;
353
* Sets the top DOM document.
354
* @param domDocument the top DOM document
356
public void setDomDocument(org.w3c.dom.Document domDocument) {
357
this.domDocument = domDocument;
361
* Gets the <CODE>PdfReader</CODE> used by this instance.
362
* @return the <CODE>PdfReader</CODE> used by this instance
364
public PdfReader getReader() {
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
372
public void setReader(PdfReader reader) {
373
this.reader = reader;
377
* Checks if this XFA form was changed.
378
* @return <CODE>true</CODE> if this XFA form was changed
380
public boolean isChanged() {
385
* Sets the changed status of this XFA instance.
386
* @param changed the changed status of this XFA instance
388
public void setChanged(boolean changed) {
389
this.changed = changed;
393
* A structure to store each part of a SOM name and link it to the next part
394
* beginning from the lower hierarchy.
396
public static class InverseStore {
397
protected ArrayList part = new ArrayList();
398
protected ArrayList follow = new ArrayList();
401
* Gets the full name by traversing the hierarchy using only the
403
* @return the full name
405
public String getDefaultName() {
406
InverseStore store = this;
408
Object obj = store.follow.get(0);
409
if (obj instanceof String)
411
store = (InverseStore)obj;
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
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))
435
* Another stack implementation. The main use is to facilitate
436
* the porting to other languages.
438
public static class Stack2 extends ArrayList {
439
private static final long serialVersionUID = -7451476576174095212L;
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
445
public Object peek() {
447
throw new EmptyStackException();
448
return get(size() - 1);
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
455
public Object pop() {
457
throw new EmptyStackException();
458
Object ret = get(size() - 1);
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
468
public Object push(Object item) {
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
477
public boolean empty() {
483
* A class for some basic SOM processing.
485
public static class Xml2Som {
487
* The order the names appear in the XML, depth first.
489
protected ArrayList order;
491
* The mapping of full names to nodes.
493
protected HashMap name2Node;
495
* The data to do a search from the bottom hierarchy.
497
protected HashMap inverseSearch;
499
* A stack to be used when parsing.
501
protected Stack2 stack;
503
* A temporary store for the repetition count.
505
protected int anform;
508
* Escapes a SOM string fragment replacing "." with "\.".
509
* @param s the unescaped string
510
* @return the escaped string
512
public static String escapeSom(String s) {
513
int idx = s.indexOf('.');
516
StringBuffer sb = new StringBuffer();
519
sb.append(s.substring(last, idx));
522
idx = s.indexOf('.', idx + 1);
524
sb.append(s.substring(last));
525
return sb.toString();
529
* Unescapes a SOM string fragment replacing "\." with ".".
530
* @param s the escaped string
531
* @return the unescaped string
533
public static String unescapeSom(String s) {
534
int idx = s.indexOf('\\');
537
StringBuffer sb = new StringBuffer();
540
sb.append(s.substring(last, idx));
542
idx = s.indexOf('\\', idx + 1);
544
sb.append(s.substring(last));
545
return sb.toString();
549
* Outputs the stack as the sequence of elements separated
551
* @return the stack as the sequence of elements separated by '.'
553
protected String printStack() {
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);
563
* Gets the name with the <CODE>#subform</CODE> removed.
564
* @param s the long name
565
* @return the short name
567
public static String getShortName(String s) {
568
int idx = s.indexOf(".#subform[");
572
StringBuffer sb = new StringBuffer();
574
sb.append(s.substring(last, idx));
575
idx = s.indexOf("]", idx + 10);
577
return sb.toString();
579
idx = s.indexOf(".#subform[", last);
581
sb.append(s.substring(last));
582
return sb.toString();
586
* Adds a SOM name to the search node chain.
587
* @param unstack the SOM name
589
public void inverseSearchAdd(String unstack) {
590
inverseSearchAdd(inverseSearch, stack, unstack);
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
599
public static void inverseSearchAdd(HashMap inverseSearch, Stack2 stack, String unstack) {
600
String last = (String)stack.peek();
601
InverseStore store = (InverseStore)inverseSearch.get(last);
603
store = new InverseStore();
604
inverseSearch.put(last, store);
606
for (int k = stack.size() - 2; k >= 0; --k) {
607
last = (String)stack.get(k);
609
int idx = store.part.indexOf(last);
611
store.part.add(last);
612
store2 = new InverseStore();
613
store.follow.add(store2);
616
store2 = (InverseStore)store.follow.get(idx);
620
store.follow.add(unstack);
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
628
public String inverseSearchGlobal(ArrayList parts) {
631
InverseStore store = (InverseStore)inverseSearch.get(parts.get(parts.size() - 1));
634
for (int k = parts.size() - 2; k >= 0; --k) {
635
String part = (String)parts.get(k);
636
int idx = store.part.indexOf(part);
638
if (store.isSimilar(part))
640
return store.getDefaultName();
642
store = (InverseStore)store.follow.get(idx);
644
return store.getDefaultName();
648
* Splits a SOM name in the individual parts.
649
* @param name the full SOM name
650
* @return the split name
652
public static Stack2 splitParts(String name) {
653
while (name.startsWith("."))
654
name = name.substring(1);
655
Stack2 parts = new Stack2();
662
pos = name.indexOf('.', pos);
665
if (name.charAt(pos - 1) == '\\')
672
part = name.substring(last, pos);
673
if (!part.endsWith("]"))
678
part = name.substring(last);
679
if (!part.endsWith("]"))
686
* Gets the order the names appear in the XML, depth first.
687
* @return the order the names appear in the XML, depth first
689
public ArrayList getOrder() {
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
697
public void setOrder(ArrayList order) {
702
* Gets the mapping of full names to nodes.
703
* @return the mapping of full names to nodes
705
public HashMap getName2Node() {
710
* Sets the mapping of full names to nodes.
711
* @param name2Node the mapping of full names to nodes
713
public void setName2Node(HashMap name2Node) {
714
this.name2Node = name2Node;
718
* Gets the data to do a search from the bottom hierarchy.
719
* @return the data to do a search from the bottom hierarchy
721
public HashMap getInverseSearch() {
722
return inverseSearch;
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
729
public void setInverseSearch(HashMap inverseSearch) {
730
this.inverseSearch = inverseSearch;
735
* Processes the datasets section in the XFA form.
737
public static class Xml2SomDatasets extends Xml2Som {
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
743
public Xml2SomDatasets(Node n) {
744
order = new ArrayList();
745
name2Node = new HashMap();
746
stack = new Stack2();
748
inverseSearch = new HashMap();
749
processDatasetsInternal(n);
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
758
public Node insertNode(Node n, String shortName) {
759
Stack2 stack = splitParts(shortName);
760
org.w3c.dom.Document doc = n.getOwnerDocument();
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));
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)) {
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);
788
inverseSearchAdd(inverseSearch, stack, shortName);
789
name2Node.put(shortName, n2);
790
order.add(shortName);
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))
800
else if ("dataValue".equals(dataNode))
803
if (!n.hasChildNodes())
805
Node n2 = n.getFirstChild();
807
if (n2.getNodeType() == Node.ELEMENT_NODE) {
810
n2 = n2.getNextSibling();
815
private void processDatasetsInternal(Node n) {
816
HashMap ss = new HashMap();
817
Node n2 = n.getFirstChild();
819
if (n2.getNodeType() == Node.ELEMENT_NODE) {
820
String s = escapeSom(n2.getLocalName());
821
Integer i = (Integer)ss.get(s);
825
i = new Integer(i.intValue() + 1);
827
if (hasChildren(n2)) {
828
stack.push(s + "[" + i.toString() + "]");
829
processDatasetsInternal(n2);
833
stack.push(s + "[" + i.toString() + "]");
834
String unstack = printStack();
836
inverseSearchAdd(unstack);
837
name2Node.put(unstack, n2);
841
n2 = n2.getNextSibling();
847
* A class to process "classic" fields.
849
public static class AcroFieldsSearch extends Xml2Som {
850
private HashMap acroShort2LongName;
853
* Creates a new instance from a Collection with the full names.
854
* @param items the Collection
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);
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
872
public HashMap getAcroShort2LongName() {
873
return acroShort2LongName;
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
881
public void setAcroShort2LongName(HashMap acroShort2LongName) {
882
this.acroShort2LongName = acroShort2LongName;
887
* Processes the template section in the XFA form.
889
public static class Xml2SomTemplate extends Xml2Som {
890
private boolean dynamicForm;
891
private int templateLevel;
894
* Creates a new instance from the datasets node.
895
* @param n the template node
897
public Xml2SomTemplate(Node n) {
898
order = new ArrayList();
899
name2Node = new HashMap();
900
stack = new Stack2();
903
inverseSearch = new HashMap();
904
processTemplate(n, null);
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
912
public String getFieldType(String s) {
913
Node n = (Node)name2Node.get(s);
916
if (n.getLocalName().equals("exclGroup"))
918
Node ui = n.getFirstChild();
920
if (ui.getNodeType() == Node.ELEMENT_NODE && ui.getLocalName().equals("ui")) {
923
ui = ui.getNextSibling();
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();
932
type = type.getNextSibling();
937
private void processTemplate(Node n, HashMap ff) {
940
HashMap ss = new HashMap();
941
Node n2 = n.getFirstChild();
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;
950
nn = escapeSom(name.getNodeValue());
955
i = new Integer(anform);
959
i = (Integer)ss.get(nn);
963
i = new Integer(i.intValue() + 1);
966
stack.push(nn + "[" + i.toString() + "]");
969
processTemplate(n2, ff);
971
processTemplate(n2, null);
975
else if (s.equals("field") || s.equals("exclGroup")) {
976
Node name = n2.getAttributes().getNamedItem("name");
978
String nn = escapeSom(name.getNodeValue());
979
Integer i = (Integer)ff.get(nn);
983
i = new Integer(i.intValue() + 1);
985
stack.push(nn + "[" + i.toString() + "]");
986
String unstack = printStack();
988
inverseSearchAdd(unstack);
989
name2Node.put(unstack, n2);
993
else if (!dynamicForm && templateLevel > 0 && s.equals("occur")) {
997
Node a = n2.getAttributes().getNamedItem("initial");
999
try{initial = Integer.parseInt(a.getNodeValue().trim());}catch(Exception e){}
1000
a = n2.getAttributes().getNamedItem("min");
1002
try{min = Integer.parseInt(a.getNodeValue().trim());}catch(Exception e){}
1003
a = n2.getAttributes().getNamedItem("max");
1005
try{max = Integer.parseInt(a.getNodeValue().trim());}catch(Exception e){}
1006
if (initial != min || min != max)
1010
n2 = n2.getNextSibling();
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
1020
public boolean isDynamicForm() {
1025
* Sets the dynamic form flag. It doesn't change the template.
1026
* @param dynamicForm the dynamic form flag
1028
public void setDynamicForm(boolean dynamicForm) {
1029
this.dynamicForm = dynamicForm;
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
1037
public Xml2SomTemplate getTemplateSom() {
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
1045
public void setTemplateSom(Xml2SomTemplate templateSom) {
1046
this.templateSom = templateSom;
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
1053
public Xml2SomDatasets getDatasetsSom() {
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
1061
public void setDatasetsSom(Xml2SomDatasets datasetsSom) {
1062
this.datasetsSom = datasetsSom;
1066
* Gets the class that contains the "classic" fields processing.
1067
* @return the class that contains the "classic" fields processing
1069
public AcroFieldsSearch getAcroFieldsSom() {
1070
return acroFieldsSom;
1074
* Sets the class that contains the "classic" fields processing.
1075
* @param acroFieldsSom the class that contains the "classic" fields processing
1077
public void setAcroFieldsSom(AcroFieldsSearch acroFieldsSom) {
1078
this.acroFieldsSom = acroFieldsSom;
1082
* Gets the <CODE>Node</CODE> that corresponds to the datasets part.
1083
* @return the <CODE>Node</CODE> that corresponds to the datasets part
1085
public Node getDatasetsNode() {
1086
return datasetsNode;