@@ -74,77 +74,85 @@ class XFAExtractor {
7474
7575 void extract (InputStream xfaIs , XHTMLContentHandler xhtml , Metadata m , ParseContext context )
7676 throws XMLStreamException , SAXException {
77+ // The reader loop below can throw XMLStreamException on malformed XFA
78+ // XML; the caller (AbstractPDF2XHTML.extractAcroForm) catches that and
79+ // falls through to the AcroForm path, which emits more content. If the
80+ // xfa_content div is left open at the throw point, that fallback
81+ // content nests under it and the outer </body> can't balance. Wrap the
82+ // body in try/finally so the open is always paired with a close.
7783 xhtml .startElement ("div" , "class" , "xfa_content" );
78-
79- //TODO - replace this with multivalued map? This isn't
80- //actually metadata, just a handy data structure.
81- Metadata pdfObjRToValues = new Metadata ();
82-
83- //for now, store and dump the fields in insertion order
84- Map <String , XFAField > namedFields = new LinkedHashMap <>();
85-
86- //The strategy is to cache the fields in fields
87- //and cache the values in pdfObjRToValues while
88- //handling the text etc along the way.
89- //
90- //As a final step, dump the merged fields and the values.
91-
92- XMLStreamReader reader = XMLReaderUtils .getXMLInputFactory (context ).createXMLStreamReader (xfaIs );
93- while (reader .hasNext ()) {
94- switch (reader .next ()) {
95- case XMLStreamConstants .START_ELEMENT :
96- QName name = reader .getName ();
97- String localName = name .getLocalPart ();
98- if (xfaTemplateMatcher .reset (name .getNamespaceURI ()).find () &&
99- FIELD_LN .equals (name .getLocalPart ())) {
100- handleField (reader , namedFields );
101- } else if (XFA_DATA .equals (name )) { //full qname match is important!
102- loadData (reader , pdfObjRToValues );
103- } else if (textMatcher .reset (localName ).find ()) {
104- scrapeTextUntil (reader , xhtml , name );
105- }
106- break ;
107- case XMLStreamConstants .END_ELEMENT :
108- break ;
84+ try {
85+ //TODO - replace this with multivalued map? This isn't
86+ //actually metadata, just a handy data structure.
87+ Metadata pdfObjRToValues = new Metadata ();
88+
89+ //for now, store and dump the fields in insertion order
90+ Map <String , XFAField > namedFields = new LinkedHashMap <>();
91+
92+ //The strategy is to cache the fields in fields
93+ //and cache the values in pdfObjRToValues while
94+ //handling the text etc along the way.
95+ //
96+ //As a final step, dump the merged fields and the values.
97+
98+ XMLStreamReader reader =
99+ XMLReaderUtils .getXMLInputFactory (context ).createXMLStreamReader (xfaIs );
100+ while (reader .hasNext ()) {
101+ switch (reader .next ()) {
102+ case XMLStreamConstants .START_ELEMENT :
103+ QName name = reader .getName ();
104+ String localName = name .getLocalPart ();
105+ if (xfaTemplateMatcher .reset (name .getNamespaceURI ()).find () &&
106+ FIELD_LN .equals (name .getLocalPart ())) {
107+ handleField (reader , namedFields );
108+ } else if (XFA_DATA .equals (name )) { //full qname match is important!
109+ loadData (reader , pdfObjRToValues );
110+ } else if (textMatcher .reset (localName ).find ()) {
111+ scrapeTextUntil (reader , xhtml , name );
112+ }
113+ break ;
114+ case XMLStreamConstants .END_ELEMENT :
115+ break ;
116+ }
109117 }
110- }
111118
112- if (namedFields .size () == 0 ) {
113- xhtml .endElement ("div" );
114- return ;
115- }
116- //now dump fields and values
117- xhtml .startElement ("div" , "class" , "xfa_form" );
118- xhtml .startElement ("ol" );
119- StringBuilder sb = new StringBuilder ();
120- for (Map .Entry <String , XFAField > e : namedFields .entrySet ()) {
121- String fieldName = e .getKey ();
122- XFAField field = e .getValue ();
123- String displayFieldName =
124- (field .toolTip == null || field .toolTip .isBlank ()) ? fieldName :
125- field .toolTip ;
126- String [] fieldValues = pdfObjRToValues .getValues (fieldName );
127- if (fieldValues .length == 0 ) {
128- fieldValues = new String []{"" };
119+ if (namedFields .size () == 0 ) {
120+ return ;
129121 }
130- for (String fieldValue : fieldValues ) {
131- AttributesImpl attrs = new AttributesImpl ();
132- attrs .addAttribute ("" , "fieldName" , "fieldName" , "CDATA" , fieldName );
133-
134- sb .append (displayFieldName ).append (": " );
135- if (fieldValue != null ) {
136- sb .append (fieldValue );
122+ //now dump fields and values
123+ xhtml .startElement ("div" , "class" , "xfa_form" );
124+ xhtml .startElement ("ol" );
125+ StringBuilder sb = new StringBuilder ();
126+ for (Map .Entry <String , XFAField > e : namedFields .entrySet ()) {
127+ String fieldName = e .getKey ();
128+ XFAField field = e .getValue ();
129+ String displayFieldName =
130+ (field .toolTip == null || field .toolTip .isBlank ()) ? fieldName :
131+ field .toolTip ;
132+ String [] fieldValues = pdfObjRToValues .getValues (fieldName );
133+ if (fieldValues .length == 0 ) {
134+ fieldValues = new String []{"" };
137135 }
136+ for (String fieldValue : fieldValues ) {
137+ AttributesImpl attrs = new AttributesImpl ();
138+ attrs .addAttribute ("" , "fieldName" , "fieldName" , "CDATA" , fieldName );
138139
139- xhtml .startElement ("li" , attrs );
140- xhtml .characters (sb .toString ());
141- xhtml .endElement ("li" );
142- sb .setLength (0 );
140+ sb .append (displayFieldName ).append (": " );
141+ if (fieldValue != null ) {
142+ sb .append (fieldValue );
143+ }
144+
145+ xhtml .startElement ("li" , attrs );
146+ xhtml .characters (sb .toString ());
147+ xhtml .endElement ("li" );
148+ sb .setLength (0 );
149+ }
143150 }
151+ xhtml .endElement ("ol" );
152+ xhtml .endElement ("div" );
153+ } finally {
154+ xhtml .endElement ("div" );
144155 }
145- xhtml .endElement ("ol" );
146- xhtml .endElement ("div" );
147- xhtml .endElement ("div" );
148156 }
149157
150158 //try to scrape the text until the endElement
0 commit comments