0001: /*
0002: * $Id: XfaForm.java 2768 2007-05-21 08:48:44Z blowagie $
0003: *
0004: * Copyright 2006 Paulo Soares
0005: *
0006: * The contents of this file are subject to the Mozilla Public License Version 1.1
0007: * (the "License"); you may not use this file except in compliance with the License.
0008: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
0009: *
0010: * Software distributed under the License is distributed on an "AS IS" basis,
0011: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0012: * for the specific language governing rights and limitations under the License.
0013: *
0014: * The Original Code is 'iText, a free JAVA-PDF library'.
0015: *
0016: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
0017: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
0018: * All Rights Reserved.
0019: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
0020: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
0021: *
0022: * Contributor(s): all the names of the contributors are added in the source code
0023: * where applicable.
0024: *
0025: * Alternatively, the contents of this file may be used under the terms of the
0026: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
0027: * provisions of LGPL are applicable instead of those above. If you wish to
0028: * allow use of your version of this file only under the terms of the LGPL
0029: * License and not to allow others to use your version of this file under
0030: * the MPL, indicate your decision by deleting the provisions above and
0031: * replace them with the notice and other provisions required by the LGPL.
0032: * If you do not delete the provisions above, a recipient may use your version
0033: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
0034: *
0035: * This library is free software; you can redistribute it and/or modify it
0036: * under the terms of the MPL as stated above or under the terms of the GNU
0037: * Library General Public License as published by the Free Software Foundation;
0038: * either version 2 of the License, or any later version.
0039: *
0040: * This library is distributed in the hope that it will be useful, but WITHOUT
0041: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0042: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
0043: * details.
0044: *
0045: * If you didn't download this code from the following link, you should check if
0046: * you aren't using an obsolete version:
0047: * http://www.lowagie.com/iText/
0048: */
0049:
0050: package com.lowagie.text.pdf;
0051:
0052: import java.io.ByteArrayInputStream;
0053: import java.io.ByteArrayOutputStream;
0054: import java.io.IOException;
0055: import java.util.ArrayList;
0056: import java.util.Collection;
0057: import java.util.EmptyStackException;
0058: import java.util.HashMap;
0059: import java.util.Iterator;
0060:
0061: import javax.xml.parsers.DocumentBuilder;
0062: import javax.xml.parsers.DocumentBuilderFactory;
0063: import javax.xml.parsers.ParserConfigurationException;
0064:
0065: import org.w3c.dom.Node;
0066: import org.xml.sax.SAXException;
0067:
0068: import com.lowagie.text.xml.XmlDomWriter;
0069:
0070: /**
0071: * Processes XFA forms.
0072: * @author Paulo Soares (psoares@consiste.pt)
0073: */
0074: public class XfaForm {
0075:
0076: private Xml2SomTemplate templateSom;
0077: private Xml2SomDatasets datasetsSom;
0078: private AcroFieldsSearch acroFieldsSom;
0079: private PdfReader reader;
0080: private boolean xfaPresent;
0081: private org.w3c.dom.Document domDocument;
0082: private boolean changed;
0083: private Node datasetsNode;
0084: public static final String XFA_DATA_SCHEMA = "http://www.xfa.org/schema/xfa-data/1.0/";
0085:
0086: /**
0087: * An empty constructor to build on.
0088: */
0089: public XfaForm() {
0090: }
0091:
0092: /**
0093: * A constructor from a <CODE>PdfReader</CODE>. It basically does everything
0094: * from finding the XFA stream to the XML parsing.
0095: * @param reader the reader
0096: * @throws java.io.IOException on error
0097: * @throws javax.xml.parsers.ParserConfigurationException on error
0098: * @throws org.xml.sax.SAXException on error
0099: */
0100: public XfaForm(PdfReader reader) throws IOException,
0101: ParserConfigurationException, SAXException {
0102: this .reader = reader;
0103: PdfDictionary af = (PdfDictionary) PdfReader
0104: .getPdfObjectRelease(reader.getCatalog().get(
0105: PdfName.ACROFORM));
0106: if (af == null) {
0107: xfaPresent = false;
0108: return;
0109: }
0110: PdfObject xfa = PdfReader.getPdfObjectRelease(af
0111: .get(PdfName.XFA));
0112: if (xfa == null) {
0113: xfaPresent = false;
0114: return;
0115: }
0116: xfaPresent = true;
0117: ByteArrayOutputStream bout = new ByteArrayOutputStream();
0118: if (xfa.isArray()) {
0119: ArrayList ar = ((PdfArray) xfa).getArrayList();
0120: for (int k = 1; k < ar.size(); k += 2) {
0121: PdfObject ob = PdfReader.getPdfObject((PdfObject) ar
0122: .get(k));
0123: if (ob instanceof PRStream) {
0124: byte[] b = PdfReader.getStreamBytes((PRStream) ob);
0125: bout.write(b);
0126: }
0127: }
0128: } else if (xfa instanceof PRStream) {
0129: byte[] b = PdfReader.getStreamBytes((PRStream) xfa);
0130: bout.write(b);
0131: }
0132: bout.close();
0133: DocumentBuilderFactory fact = DocumentBuilderFactory
0134: .newInstance();
0135: fact.setNamespaceAware(true);
0136: DocumentBuilder db = fact.newDocumentBuilder();
0137: domDocument = db.parse(new ByteArrayInputStream(bout
0138: .toByteArray()));
0139: Node n = domDocument.getFirstChild();
0140: n = n.getFirstChild();
0141: while (n != null) {
0142: if (n.getNodeType() == Node.ELEMENT_NODE) {
0143: String s = n.getLocalName();
0144: if (s.equals("template")) {
0145: templateSom = new Xml2SomTemplate(n);
0146: } else if (s.equals("datasets")) {
0147: datasetsNode = n;
0148: datasetsSom = new Xml2SomDatasets(n.getFirstChild());
0149: }
0150: }
0151: n = n.getNextSibling();
0152: }
0153: }
0154:
0155: /**
0156: * Sets the XFA key from a byte array. The old XFA is erased.
0157: * @param xfaData the data
0158: * @param reader the reader
0159: * @param writer the writer
0160: * @throws java.io.IOException on error
0161: */
0162: public static void setXfa(byte[] xfaData, PdfReader reader,
0163: PdfWriter writer) throws IOException {
0164: PdfDictionary af = (PdfDictionary) PdfReader
0165: .getPdfObjectRelease(reader.getCatalog().get(
0166: PdfName.ACROFORM));
0167: if (af == null) {
0168: return;
0169: }
0170: reader.killXref(af.get(PdfName.XFA));
0171: PdfStream str = new PdfStream(xfaData);
0172: str.flateCompress();
0173: PdfIndirectReference ref = writer.addToBody(str)
0174: .getIndirectReference();
0175: af.put(PdfName.XFA, ref);
0176: }
0177:
0178: /**
0179: * Sets the XFA key from the instance data. The old XFA is erased.
0180: * @param writer the writer
0181: * @throws java.io.IOException on error
0182: */
0183: public void setXfa(PdfWriter writer) throws IOException {
0184: setXfa(serializeDoc(domDocument), reader, writer);
0185: }
0186:
0187: /**
0188: * Serializes a XML document to a byte array.
0189: * @param n the XML document
0190: * @throws java.io.IOException on error
0191: * @return the serialized XML document
0192: */
0193: public static byte[] serializeDoc(Node n) throws IOException {
0194: XmlDomWriter xw = new XmlDomWriter();
0195: ByteArrayOutputStream fout = new ByteArrayOutputStream();
0196: xw.setOutput(fout, null);
0197: xw.setCanonical(false);
0198: xw.write(n);
0199: fout.close();
0200: return fout.toByteArray();
0201: }
0202:
0203: /**
0204: * Returns <CODE>true</CODE> if it is a XFA form.
0205: * @return <CODE>true</CODE> if it is a XFA form
0206: */
0207: public boolean isXfaPresent() {
0208: return xfaPresent;
0209: }
0210:
0211: /**
0212: * Gets the top level DOM document.
0213: * @return the top level DOM document
0214: */
0215: public org.w3c.dom.Document getDomDocument() {
0216: return domDocument;
0217: }
0218:
0219: /**
0220: * Finds the complete field name contained in the "classic" forms from a partial
0221: * name.
0222: * @param name the complete or partial name
0223: * @param af the fields
0224: * @return the complete name or <CODE>null</CODE> if not found
0225: */
0226: public String findFieldName(String name, AcroFields af) {
0227: HashMap items = af.getFields();
0228: if (items.containsKey(name))
0229: return name;
0230: if (acroFieldsSom == null) {
0231: acroFieldsSom = new AcroFieldsSearch(items.keySet());
0232: }
0233: if (acroFieldsSom.getAcroShort2LongName().containsKey(name))
0234: return (String) acroFieldsSom.getAcroShort2LongName().get(
0235: name);
0236: return acroFieldsSom.inverseSearchGlobal(Xml2Som
0237: .splitParts(name));
0238: }
0239:
0240: /**
0241: * Finds the complete SOM name contained in the datasets section from a
0242: * possibly partial name.
0243: * @param name the complete or partial name
0244: * @return the complete name or <CODE>null</CODE> if not found
0245: */
0246: public String findDatasetsName(String name) {
0247: if (datasetsSom.getName2Node().containsKey(name))
0248: return name;
0249: return datasetsSom
0250: .inverseSearchGlobal(Xml2Som.splitParts(name));
0251: }
0252:
0253: /**
0254: * Finds the <CODE>Node</CODE> contained in the datasets section from a
0255: * possibly partial name.
0256: * @param name the complete or partial name
0257: * @return the <CODE>Node</CODE> or <CODE>null</CODE> if not found
0258: */
0259: public Node findDatasetsNode(String name) {
0260: if (name == null)
0261: return null;
0262: name = findDatasetsName(name);
0263: if (name == null)
0264: return null;
0265: return (Node) datasetsSom.getName2Node().get(name);
0266: }
0267:
0268: /**
0269: * Gets all the text contained in the child nodes of this node.
0270: * @param n the <CODE>Node</CODE>
0271: * @return the text found or "" if no text was found
0272: */
0273: public static String getNodeText(Node n) {
0274: if (n == null)
0275: return "";
0276: return getNodeText(n, "");
0277:
0278: }
0279:
0280: private static String getNodeText(Node n, String name) {
0281: Node n2 = n.getFirstChild();
0282: while (n2 != null) {
0283: if (n2.getNodeType() == Node.ELEMENT_NODE) {
0284: name = getNodeText(n2, name);
0285: } else if (n2.getNodeType() == Node.TEXT_NODE) {
0286: name += n2.getNodeValue();
0287: }
0288: n2 = n2.getNextSibling();
0289: }
0290: return name;
0291: }
0292:
0293: /**
0294: * Sets the text of this node. All the child's node are deleted and a new
0295: * child text node is created.
0296: * @param n the <CODE>Node</CODE> to add the text to
0297: * @param text the text to add
0298: */
0299: public void setNodeText(Node n, String text) {
0300: if (n == null)
0301: return;
0302: Node nc = null;
0303: while ((nc = n.getFirstChild()) != null) {
0304: n.removeChild(nc);
0305: }
0306: if (n.getAttributes().getNamedItemNS(XFA_DATA_SCHEMA,
0307: "dataNode") != null)
0308: n.getAttributes().removeNamedItemNS(XFA_DATA_SCHEMA,
0309: "dataNode");
0310: n.appendChild(domDocument.createTextNode(text));
0311: changed = true;
0312: }
0313:
0314: /**
0315: * Sets the XFA form flag signaling that this is a valid XFA form.
0316: * @param xfaPresent the XFA form flag signaling that this is a valid XFA form
0317: */
0318: public void setXfaPresent(boolean xfaPresent) {
0319: this .xfaPresent = xfaPresent;
0320: }
0321:
0322: /**
0323: * Sets the top DOM document.
0324: * @param domDocument the top DOM document
0325: */
0326: public void setDomDocument(org.w3c.dom.Document domDocument) {
0327: this .domDocument = domDocument;
0328: }
0329:
0330: /**
0331: * Gets the <CODE>PdfReader</CODE> used by this instance.
0332: * @return the <CODE>PdfReader</CODE> used by this instance
0333: */
0334: public PdfReader getReader() {
0335: return reader;
0336: }
0337:
0338: /**
0339: * Sets the <CODE>PdfReader</CODE> to be used by this instance.
0340: * @param reader the <CODE>PdfReader</CODE> to be used by this instance
0341: */
0342: public void setReader(PdfReader reader) {
0343: this .reader = reader;
0344: }
0345:
0346: /**
0347: * Checks if this XFA form was changed.
0348: * @return <CODE>true</CODE> if this XFA form was changed
0349: */
0350: public boolean isChanged() {
0351: return changed;
0352: }
0353:
0354: /**
0355: * Sets the changed status of this XFA instance.
0356: * @param changed the changed status of this XFA instance
0357: */
0358: public void setChanged(boolean changed) {
0359: this .changed = changed;
0360: }
0361:
0362: /**
0363: * A structure to store each part of a SOM name and link it to the next part
0364: * beginning from the lower hierarchie.
0365: */
0366: public static class InverseStore {
0367: protected ArrayList part = new ArrayList();
0368: protected ArrayList follow = new ArrayList();
0369:
0370: /**
0371: * Gets the full name by traversing the hiearchie using only the
0372: * index 0.
0373: * @return the full name
0374: */
0375: public String getDefaultName() {
0376: InverseStore store = this ;
0377: while (true) {
0378: Object obj = store.follow.get(0);
0379: if (obj instanceof String)
0380: return (String) obj;
0381: store = (InverseStore) obj;
0382: }
0383: }
0384:
0385: /**
0386: * Search the current node for a similar name. A similar name starts
0387: * with the same name but has a differnt index. For example, "detail[3]"
0388: * is similar to "detail[9]". The main use is to discard names that
0389: * correspond to out of bounds records.
0390: * @param name the name to search
0391: * @return <CODE>true</CODE> if a similitude was found
0392: */
0393: public boolean isSimilar(String name) {
0394: int idx = name.indexOf('[');
0395: name = name.substring(0, idx + 1);
0396: for (int k = 0; k < part.size(); ++k) {
0397: if (((String) part.get(k)).startsWith(name))
0398: return true;
0399: }
0400: return false;
0401: }
0402: }
0403:
0404: /**
0405: * Another stack implementation. The main use is to facilitate
0406: * the porting to other languages.
0407: */
0408: public static class Stack2 extends ArrayList {
0409: private static final long serialVersionUID = -7451476576174095212L;
0410:
0411: /**
0412: * Looks at the object at the top of this stack without removing it from the stack.
0413: * @return the object at the top of this stack
0414: */
0415: public Object peek() {
0416: if (size() == 0)
0417: throw new EmptyStackException();
0418: return get(size() - 1);
0419: }
0420:
0421: /**
0422: * Removes the object at the top of this stack and returns that object as the value of this function.
0423: * @return the object at the top of this stack
0424: */
0425: public Object pop() {
0426: if (size() == 0)
0427: throw new EmptyStackException();
0428: Object ret = get(size() - 1);
0429: remove(size() - 1);
0430: return ret;
0431: }
0432:
0433: /**
0434: * Pushes an item onto the top of this stack.
0435: * @param item the item to be pushed onto this stack
0436: * @return the <CODE>item</CODE> argument
0437: */
0438: public Object push(Object item) {
0439: add(item);
0440: return item;
0441: }
0442:
0443: /**
0444: * Tests if this stack is empty.
0445: * @return <CODE>true</CODE> if and only if this stack contains no items; <CODE>false</CODE> otherwise
0446: */
0447: public boolean empty() {
0448: return size() == 0;
0449: }
0450: }
0451:
0452: /**
0453: * A class for some basic SOM processing.
0454: */
0455: public static class Xml2Som {
0456: /**
0457: * The order the names appear in the XML, depth first.
0458: */
0459: protected ArrayList order;
0460: /**
0461: * The mapping of full names to nodes.
0462: */
0463: protected HashMap name2Node;
0464: /**
0465: * The data to do a search from the bottom hierarchie.
0466: */
0467: protected HashMap inverseSearch;
0468: /**
0469: * A stack to be used when parsing.
0470: */
0471: protected Stack2 stack;
0472: /**
0473: * A temporary store for the repetition count.
0474: */
0475: protected int anform;
0476:
0477: /**
0478: * Escapes a SOM string fragment replacing "." with "\.".
0479: * @param s the unescaped string
0480: * @return the escaped string
0481: */
0482: public static String escapeSom(String s) {
0483: int idx = s.indexOf('.');
0484: if (idx < 0)
0485: return s;
0486: StringBuffer sb = new StringBuffer();
0487: int last = 0;
0488: while (idx >= 0) {
0489: sb.append(s.substring(last, idx));
0490: sb.append('\\');
0491: last = idx;
0492: idx = s.indexOf('.', idx + 1);
0493: }
0494: sb.append(s.substring(last));
0495: return sb.toString();
0496: }
0497:
0498: /**
0499: * Unescapes a SOM string fragment replacing "\." with ".".
0500: * @param s the escaped string
0501: * @return the unescaped string
0502: */
0503: public static String unescapeSom(String s) {
0504: int idx = s.indexOf('\\');
0505: if (idx < 0)
0506: return s;
0507: StringBuffer sb = new StringBuffer();
0508: int last = 0;
0509: while (idx >= 0) {
0510: sb.append(s.substring(last, idx));
0511: last = idx + 1;
0512: idx = s.indexOf('\\', idx + 1);
0513: }
0514: sb.append(s.substring(last));
0515: return sb.toString();
0516: }
0517:
0518: /**
0519: * Outputs the stack as the sequence of elements separated
0520: * by '.'.
0521: * @return the stack as the sequence of elements separated by '.'
0522: */
0523: protected String printStack() {
0524: if (stack.empty())
0525: return "";
0526: StringBuffer s = new StringBuffer();
0527: for (int k = 0; k < stack.size(); ++k)
0528: s.append('.').append((String) stack.get(k));
0529: return s.substring(1);
0530: }
0531:
0532: /**
0533: * Gets the name with the <CODE>#subform</CODE> removed.
0534: * @param s the long name
0535: * @return the short name
0536: */
0537: public static String getShortName(String s) {
0538: int idx = s.indexOf(".#subform[");
0539: if (idx < 0)
0540: return s;
0541: int last = 0;
0542: StringBuffer sb = new StringBuffer();
0543: while (idx >= 0) {
0544: sb.append(s.substring(last, idx));
0545: idx = s.indexOf("]", idx + 10);
0546: if (idx < 0)
0547: return sb.toString();
0548: last = idx + 1;
0549: idx = s.indexOf(".#subform[", last);
0550: }
0551: sb.append(s.substring(last));
0552: return sb.toString();
0553: }
0554:
0555: /**
0556: * Adds a SOM name to the search node chain.
0557: * @param unstack the SOM name
0558: */
0559: public void inverseSearchAdd(String unstack) {
0560: inverseSearchAdd(inverseSearch, stack, unstack);
0561: }
0562:
0563: /**
0564: * Adds a SOM name to the search node chain.
0565: * @param inverseSearch the start point
0566: * @param stack the stack with the separeted SOM parts
0567: * @param unstack the full name
0568: */
0569: public static void inverseSearchAdd(HashMap inverseSearch,
0570: Stack2 stack, String unstack) {
0571: String last = (String) stack.peek();
0572: InverseStore store = (InverseStore) inverseSearch.get(last);
0573: if (store == null) {
0574: store = new InverseStore();
0575: inverseSearch.put(last, store);
0576: }
0577: for (int k = stack.size() - 2; k >= 0; --k) {
0578: last = (String) stack.get(k);
0579: InverseStore store2;
0580: int idx = store.part.indexOf(last);
0581: if (idx < 0) {
0582: store.part.add(last);
0583: store2 = new InverseStore();
0584: store.follow.add(store2);
0585: } else
0586: store2 = (InverseStore) store.follow.get(idx);
0587: store = store2;
0588: }
0589: store.part.add("");
0590: store.follow.add(unstack);
0591: }
0592:
0593: /**
0594: * Searchs the SOM hiearchie from the bottom.
0595: * @param parts the SOM parts
0596: * @return the full name or <CODE>null</CODE> if not found
0597: */
0598: public String inverseSearchGlobal(ArrayList parts) {
0599: if (parts.isEmpty())
0600: return null;
0601: InverseStore store = (InverseStore) inverseSearch.get(parts
0602: .get(parts.size() - 1));
0603: if (store == null)
0604: return null;
0605: for (int k = parts.size() - 2; k >= 0; --k) {
0606: String part = (String) parts.get(k);
0607: int idx = store.part.indexOf(part);
0608: if (idx < 0) {
0609: if (store.isSimilar(part))
0610: return null;
0611: return store.getDefaultName();
0612: }
0613: store = (InverseStore) store.follow.get(idx);
0614: }
0615: return store.getDefaultName();
0616: }
0617:
0618: /**
0619: * Splits a SOM name in the individual parts.
0620: * @param name the full SOM name
0621: * @return the split name
0622: */
0623: public static Stack2 splitParts(String name) {
0624: while (name.startsWith("."))
0625: name = name.substring(1);
0626: Stack2 parts = new Stack2();
0627: int last = 0;
0628: int pos = 0;
0629: String part;
0630: while (true) {
0631: pos = last;
0632: while (true) {
0633: pos = name.indexOf('.', pos);
0634: if (pos < 0)
0635: break;
0636: if (name.charAt(pos - 1) == '\\')
0637: ++pos;
0638: else
0639: break;
0640: }
0641: if (pos < 0)
0642: break;
0643: part = name.substring(last, pos);
0644: if (!part.endsWith("]"))
0645: part += "[0]";
0646: parts.add(part);
0647: last = pos + 1;
0648: }
0649: part = name.substring(last);
0650: if (!part.endsWith("]"))
0651: part += "[0]";
0652: parts.add(part);
0653: return parts;
0654: }
0655:
0656: /**
0657: * Gets the order the names appear in the XML, depth first.
0658: * @return the order the names appear in the XML, depth first
0659: */
0660: public ArrayList getOrder() {
0661: return order;
0662: }
0663:
0664: /**
0665: * Sets the order the names appear in the XML, depth first
0666: * @param order the order the names appear in the XML, depth first
0667: */
0668: public void setOrder(ArrayList order) {
0669: this .order = order;
0670: }
0671:
0672: /**
0673: * Gets the mapping of full names to nodes.
0674: * @return the mapping of full names to nodes
0675: */
0676: public HashMap getName2Node() {
0677: return name2Node;
0678: }
0679:
0680: /**
0681: * Sets the mapping of full names to nodes.
0682: * @param name2Node the mapping of full names to nodes
0683: */
0684: public void setName2Node(HashMap name2Node) {
0685: this .name2Node = name2Node;
0686: }
0687:
0688: /**
0689: * Gets the data to do a search from the bottom hierarchie.
0690: * @return the data to do a search from the bottom hierarchie
0691: */
0692: public HashMap getInverseSearch() {
0693: return inverseSearch;
0694: }
0695:
0696: /**
0697: * Sets the data to do a search from the bottom hierarchie.
0698: * @param inverseSearch the data to do a search from the bottom hierarchie
0699: */
0700: public void setInverseSearch(HashMap inverseSearch) {
0701: this .inverseSearch = inverseSearch;
0702: }
0703: }
0704:
0705: /**
0706: * Processes the datasets section in the XFA form.
0707: */
0708: public static class Xml2SomDatasets extends Xml2Som {
0709: /**
0710: * Creates a new instance from the datasets node. This expects
0711: * not the datasets but the data node that comes below.
0712: * @param n the datasets node
0713: */
0714: public Xml2SomDatasets(Node n) {
0715: order = new ArrayList();
0716: name2Node = new HashMap();
0717: stack = new Stack2();
0718: anform = 0;
0719: inverseSearch = new HashMap();
0720: processDatasetsInternal(n);
0721: }
0722:
0723: /**
0724: * Inserts a new <CODE>Node</CODE> that will match the short name.
0725: * @param n the datasets top <CODE>Node</CODE>
0726: * @param shortName the short name
0727: * @return the new <CODE>Node</CODE> of the inserted name
0728: */
0729: public Node insertNode(Node n, String shortName) {
0730: Stack2 stack = splitParts(shortName);
0731: org.w3c.dom.Document doc = n.getOwnerDocument();
0732: Node n2 = null;
0733: n = n.getFirstChild();
0734: for (int k = 0; k < stack.size(); ++k) {
0735: String part = (String) stack.get(k);
0736: int idx = part.lastIndexOf('[');
0737: String name = part.substring(0, idx);
0738: idx = Integer.parseInt(part.substring(idx + 1, part
0739: .length() - 1));
0740: int found = -1;
0741: for (n2 = n.getFirstChild(); n2 != null; n2 = n2
0742: .getNextSibling()) {
0743: if (n2.getNodeType() == Node.ELEMENT_NODE) {
0744: String s = escapeSom(n2.getLocalName());
0745: if (s.equals(name)) {
0746: ++found;
0747: if (found == idx)
0748: break;
0749: }
0750: }
0751: }
0752: for (; found < idx; ++found) {
0753: n2 = doc.createElementNS(null, name);
0754: n2 = n.appendChild(n2);
0755: Node attr = doc.createAttributeNS(XFA_DATA_SCHEMA,
0756: "dataNode");
0757: attr.setNodeValue("dataGroup");
0758: n2.getAttributes().setNamedItemNS(attr);
0759: }
0760: n = n2;
0761: }
0762: inverseSearchAdd(inverseSearch, stack, shortName);
0763: name2Node.put(shortName, n2);
0764: order.add(shortName);
0765: return n2;
0766: }
0767:
0768: private static boolean hasChildren(Node n) {
0769: Node dataNodeN = n.getAttributes().getNamedItemNS(
0770: XFA_DATA_SCHEMA, "dataNode");
0771: if (dataNodeN != null) {
0772: String dataNode = dataNodeN.getNodeValue();
0773: if ("dataGroup".equals(dataNode))
0774: return true;
0775: else if ("dataValue".equals(dataNode))
0776: return false;
0777: }
0778: if (!n.hasChildNodes())
0779: return false;
0780: Node n2 = n.getFirstChild();
0781: while (n2 != null) {
0782: if (n2.getNodeType() == Node.ELEMENT_NODE) {
0783: return true;
0784: }
0785: n2 = n2.getNextSibling();
0786: }
0787: return false;
0788: }
0789:
0790: private void processDatasetsInternal(Node n) {
0791: HashMap ss = new HashMap();
0792: Node n2 = n.getFirstChild();
0793: while (n2 != null) {
0794: if (n2.getNodeType() == Node.ELEMENT_NODE) {
0795: String s = escapeSom(n2.getLocalName());
0796: Integer i = (Integer) ss.get(s);
0797: if (i == null)
0798: i = new Integer(0);
0799: else
0800: i = new Integer(i.intValue() + 1);
0801: ss.put(s, i);
0802: if (hasChildren(n2)) {
0803: stack.push(s + "[" + i.toString() + "]");
0804: processDatasetsInternal(n2);
0805: stack.pop();
0806: } else {
0807: stack.push(s + "[" + i.toString() + "]");
0808: String unstack = printStack();
0809: order.add(unstack);
0810: inverseSearchAdd(unstack);
0811: name2Node.put(unstack, n2);
0812: stack.pop();
0813: }
0814: }
0815: n2 = n2.getNextSibling();
0816: }
0817: }
0818: }
0819:
0820: /**
0821: * A class to process "classic" fields.
0822: */
0823: public static class AcroFieldsSearch extends Xml2Som {
0824: private HashMap acroShort2LongName;
0825:
0826: /**
0827: * Creates a new instance from a Collection with the full names.
0828: * @param items the Collection
0829: */
0830: public AcroFieldsSearch(Collection items) {
0831: inverseSearch = new HashMap();
0832: acroShort2LongName = new HashMap();
0833: for (Iterator it = items.iterator(); it.hasNext();) {
0834: String itemName = (String) it.next();
0835: String itemShort = getShortName(itemName);
0836: acroShort2LongName.put(itemShort, itemName);
0837: inverseSearchAdd(inverseSearch, splitParts(itemShort),
0838: itemName);
0839: }
0840: }
0841:
0842: /**
0843: * Gets the mapping from short names to long names. A long
0844: * name may contain the #subform name part.
0845: * @return the mapping from short names to long names
0846: */
0847: public HashMap getAcroShort2LongName() {
0848: return acroShort2LongName;
0849: }
0850:
0851: /**
0852: * Sets the mapping from short names to long names. A long
0853: * name may contain the #subform name part.
0854: * @param acroShort2LongName the mapping from short names to long names
0855: */
0856: public void setAcroShort2LongName(HashMap acroShort2LongName) {
0857: this .acroShort2LongName = acroShort2LongName;
0858: }
0859: }
0860:
0861: /**
0862: * Processes the template section in the XFA form.
0863: */
0864: public static class Xml2SomTemplate extends Xml2Som {
0865: private boolean dynamicForm;
0866: private int templateLevel;
0867:
0868: /**
0869: * Creates a new instance from the datasets node.
0870: * @param n the template node
0871: */
0872: public Xml2SomTemplate(Node n) {
0873: order = new ArrayList();
0874: name2Node = new HashMap();
0875: stack = new Stack2();
0876: anform = 0;
0877: templateLevel = 0;
0878: inverseSearch = new HashMap();
0879: processTemplate(n, null);
0880: }
0881:
0882: /**
0883: * Gets the field type as described in the <CODE>template</CODE> section of the XFA.
0884: * @param s the exact template name
0885: * @return the field type or <CODE>null</CODE> if not found
0886: */
0887: public String getFieldType(String s) {
0888: Node n = (Node) name2Node.get(s);
0889: if (n == null)
0890: return null;
0891: if (n.getLocalName().equals("exclGroup"))
0892: return "exclGroup";
0893: Node ui = n.getFirstChild();
0894: while (ui != null) {
0895: if (ui.getNodeType() == Node.ELEMENT_NODE
0896: && ui.getLocalName().equals("ui")) {
0897: break;
0898: }
0899: ui = ui.getNextSibling();
0900: }
0901: if (ui == null)
0902: return null;
0903: Node type = ui.getFirstChild();
0904: while (type != null) {
0905: if (type.getNodeType() == Node.ELEMENT_NODE
0906: && !(type.getLocalName().equals("extras") && type
0907: .getLocalName().equals("picture"))) {
0908: return type.getLocalName();
0909: }
0910: type = type.getNextSibling();
0911: }
0912: return null;
0913: }
0914:
0915: private void processTemplate(Node n, HashMap ff) {
0916: if (ff == null)
0917: ff = new HashMap();
0918: HashMap ss = new HashMap();
0919: Node n2 = n.getFirstChild();
0920: while (n2 != null) {
0921: if (n2.getNodeType() == Node.ELEMENT_NODE) {
0922: String s = n2.getLocalName();
0923: if (s.equals("subform")) {
0924: Node name = n2.getAttributes().getNamedItem(
0925: "name");
0926: String nn = "#subform";
0927: boolean annon = true;
0928: if (name != null) {
0929: nn = escapeSom(name.getNodeValue());
0930: annon = false;
0931: }
0932: Integer i;
0933: if (annon) {
0934: i = new Integer(anform);
0935: ++anform;
0936: } else {
0937: i = (Integer) ss.get(nn);
0938: if (i == null)
0939: i = new Integer(0);
0940: else
0941: i = new Integer(i.intValue() + 1);
0942: ss.put(nn, i);
0943: }
0944: stack.push(nn + "[" + i.toString() + "]");
0945: ++templateLevel;
0946: if (annon)
0947: processTemplate(n2, ff);
0948: else
0949: processTemplate(n2, null);
0950: --templateLevel;
0951: stack.pop();
0952: } else if (s.equals("field")
0953: || s.equals("exclGroup")) {
0954: Node name = n2.getAttributes().getNamedItem(
0955: "name");
0956: if (name != null) {
0957: String nn = escapeSom(name.getNodeValue());
0958: Integer i = (Integer) ff.get(nn);
0959: if (i == null)
0960: i = new Integer(0);
0961: else
0962: i = new Integer(i.intValue() + 1);
0963: ff.put(nn, i);
0964: stack.push(nn + "[" + i.toString() + "]");
0965: String unstack = printStack();
0966: order.add(unstack);
0967: inverseSearchAdd(unstack);
0968: name2Node.put(unstack, n2);
0969: stack.pop();
0970: }
0971: } else if (!dynamicForm && templateLevel > 0
0972: && s.equals("occur")) {
0973: int initial = 1;
0974: int min = 1;
0975: int max = 1;
0976: Node a = n2.getAttributes().getNamedItem(
0977: "initial");
0978: if (a != null)
0979: try {
0980: initial = Integer.parseInt(a
0981: .getNodeValue().trim());
0982: } catch (Exception e) {
0983: }
0984: a = n2.getAttributes().getNamedItem("min");
0985: if (a != null)
0986: try {
0987: min = Integer.parseInt(a.getNodeValue()
0988: .trim());
0989: } catch (Exception e) {
0990: }
0991: a = n2.getAttributes().getNamedItem("max");
0992: if (a != null)
0993: try {
0994: max = Integer.parseInt(a.getNodeValue()
0995: .trim());
0996: } catch (Exception e) {
0997: }
0998: if (initial != min || min != max)
0999: dynamicForm = true;
1000: }
1001: }
1002: n2 = n2.getNextSibling();
1003: }
1004: }
1005:
1006: /**
1007: * <CODE>true</CODE> if it's a dynamic form; <CODE>false</CODE>
1008: * if it's a static form.
1009: * @return <CODE>true</CODE> if it's a dynamic form; <CODE>false</CODE>
1010: * if it's a static form
1011: */
1012: public boolean isDynamicForm() {
1013: return dynamicForm;
1014: }
1015:
1016: /**
1017: * Sets the dynamic form flag. It doesn't change the template.
1018: * @param dynamicForm the dynamic form flag
1019: */
1020: public void setDynamicForm(boolean dynamicForm) {
1021: this .dynamicForm = dynamicForm;
1022: }
1023: }
1024:
1025: /**
1026: * Gets the class that contains the template processing section of the XFA.
1027: * @return the class that contains the template processing section of the XFA
1028: */
1029: public Xml2SomTemplate getTemplateSom() {
1030: return templateSom;
1031: }
1032:
1033: /**
1034: * Sets the class that contains the template processing section of the XFA
1035: * @param templateSom the class that contains the template processing section of the XFA
1036: */
1037: public void setTemplateSom(Xml2SomTemplate templateSom) {
1038: this .templateSom = templateSom;
1039: }
1040:
1041: /**
1042: * Gets the class that contains the datasets processing section of the XFA.
1043: * @return the class that contains the datasets processing section of the XFA
1044: */
1045: public Xml2SomDatasets getDatasetsSom() {
1046: return datasetsSom;
1047: }
1048:
1049: /**
1050: * Sets the class that contains the datasets processing section of the XFA.
1051: * @param datasetsSom the class that contains the datasets processing section of the XFA
1052: */
1053: public void setDatasetsSom(Xml2SomDatasets datasetsSom) {
1054: this .datasetsSom = datasetsSom;
1055: }
1056:
1057: /**
1058: * Gets the class that contains the "classic" fields processing.
1059: * @return the class that contains the "classic" fields processing
1060: */
1061: public AcroFieldsSearch getAcroFieldsSom() {
1062: return acroFieldsSom;
1063: }
1064:
1065: /**
1066: * Sets the class that contains the "classic" fields processing.
1067: * @param acroFieldsSom the class that contains the "classic" fields processing
1068: */
1069: public void setAcroFieldsSom(AcroFieldsSearch acroFieldsSom) {
1070: this .acroFieldsSom = acroFieldsSom;
1071: }
1072:
1073: /**
1074: * Gets the <CODE>Node</CODE> that corresponds to the datasets part.
1075: * @return the <CODE>Node</CODE> that corresponds to the datasets part
1076: */
1077: public Node getDatasetsNode() {
1078: return datasetsNode;
1079: }
1080: }
|