88import java .util .ArrayList ;
99import java .util .List ;
1010import java .util .Locale ;
11+ import java .util .Map ;
1112import java .util .regex .Pattern ;
1213
1314import org .apache .pdfbox .Loader ;
@@ -127,35 +128,42 @@ private String normalizeWhitespace(String text) {
127128 return fastregex .FastRegex .normalizeWhitespace (text );
128129 }
129130
131+ private static final javax .xml .stream .XMLInputFactory XML_FACTORY ;
132+ private static final Map <String , String > EXTENSION_TYPES = Map .of (
133+ ".pdf" , "application/pdf" ,
134+ ".rtf" , "text/rtf" ,
135+ ".md" , "text/markdown" ,
136+ ".markdown" , "text/markdown" ,
137+ ".doc" , "application/msword" ,
138+ ".docx" , "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ,
139+ ".csv" , "text/csv" ,
140+ ".xlsx" , "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
141+ ".png" , "image/ocr" ,
142+ ".jpg" , "image/ocr"
143+ );
144+
145+ static {
146+ XML_FACTORY = javax .xml .stream .XMLInputFactory .newDefaultFactory ();
147+ XML_FACTORY .setProperty (javax .xml .stream .XMLInputFactory .IS_SUPPORTING_EXTERNAL_ENTITIES , Boolean .FALSE );
148+ XML_FACTORY .setProperty (javax .xml .stream .XMLInputFactory .SUPPORT_DTD , Boolean .FALSE );
149+ }
150+
130151 private String detectType (String sourceName ) {
131152 if (sourceName == null ) {
132153 return "text/plain" ;
133154 }
134155
135156 String lower = sourceName .toLowerCase (Locale .ROOT );
136- if (lower .endsWith (".pdf" )) {
137- return "application/pdf" ;
138- }
139- if (lower .endsWith (".rtf" )) {
140- return "text/rtf" ;
141- }
142- if (lower .endsWith (".md" ) || lower .endsWith (".markdown" )) {
143- return "text/markdown" ;
144- }
145- if (lower .endsWith (".doc" )) {
146- return "application/msword" ;
147- }
148- if (lower .endsWith (".docx" )) {
149- return "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ;
150- }
151- if (lower .endsWith (".csv" )) {
152- return "text/csv" ;
153- }
154- if (lower .endsWith (".xlsx" )) {
155- return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ;
156- }
157- if (lower .endsWith (".png" ) || lower .endsWith (".jpg" ) || lower .endsWith (".jpeg" ) || lower .endsWith (".bmp" )) {
158- return "image/ocr" ;
157+ int dot = lower .lastIndexOf ('.' );
158+ if (dot != -1 ) {
159+ String ext = lower .substring (dot );
160+ String mapped = EXTENSION_TYPES .get (ext );
161+ if (mapped != null ) {
162+ return mapped ;
163+ }
164+ if (".jpeg" .equals (ext ) || ".bmp" .equals (ext )) {
165+ return "image/ocr" ;
166+ }
159167 }
160168 return "text/plain" ;
161169 }
@@ -168,17 +176,14 @@ private ParsedDocument parseCsv(Path path) throws IOException {
168176
169177 private ParsedDocument parseXlsx (Path path ) throws IOException {
170178 List <String > sharedStrings = new ArrayList <>();
171- StringBuilder textBuilder = new StringBuilder ();
179+ StringBuilder textBuilder = new StringBuilder (128 * 1024 );
172180
173181 try (java .util .zip .ZipFile zip = new java .util .zip .ZipFile (path .toFile ())) {
174182 // 1. Read sharedStrings.xml if present
175183 java .util .zip .ZipEntry sstEntry = zip .getEntry ("xl/sharedStrings.xml" );
176184 if (sstEntry != null ) {
177185 try (InputStream is = zip .getInputStream (sstEntry )) {
178- javax .xml .stream .XMLInputFactory factory = javax .xml .stream .XMLInputFactory .newDefaultFactory ();
179- factory .setProperty (javax .xml .stream .XMLInputFactory .IS_SUPPORTING_EXTERNAL_ENTITIES , Boolean .FALSE );
180- factory .setProperty (javax .xml .stream .XMLInputFactory .SUPPORT_DTD , Boolean .FALSE );
181- javax .xml .stream .XMLStreamReader reader = factory .createXMLStreamReader (is );
186+ javax .xml .stream .XMLStreamReader reader = XML_FACTORY .createXMLStreamReader (is );
182187
183188 StringBuilder currentText = null ;
184189 while (reader .hasNext ()) {
@@ -205,55 +210,56 @@ private ParsedDocument parseXlsx(Path path) throws IOException {
205210 }
206211 }
207212
208- // 2. Read sheet1.xml (primary sheet)
209- java .util .zip .ZipEntry sheetEntry = zip .getEntry ("xl/worksheets/sheet1.xml" );
210- if (sheetEntry != null ) {
211- try (InputStream is = zip .getInputStream (sheetEntry )) {
212- javax .xml .stream .XMLInputFactory factory = javax .xml .stream .XMLInputFactory .newDefaultFactory ();
213- factory .setProperty (javax .xml .stream .XMLInputFactory .IS_SUPPORTING_EXTERNAL_ENTITIES , Boolean .FALSE );
214- factory .setProperty (javax .xml .stream .XMLInputFactory .SUPPORT_DTD , Boolean .FALSE );
215- javax .xml .stream .XMLStreamReader reader = factory .createXMLStreamReader (is );
216-
217- String cellType = null ;
218- StringBuilder cellVal = null ;
219-
220- while (reader .hasNext ()) {
221- int event = reader .next ();
222- if (event == javax .xml .stream .XMLStreamConstants .START_ELEMENT ) {
223- String name = reader .getLocalName ();
224- if ("c" .equals (name )) {
225- cellType = reader .getAttributeValue (null , "t" );
226- } else if ("v" .equals (name )) {
227- cellVal = new StringBuilder ();
228- }
229- } else if (event == javax .xml .stream .XMLStreamConstants .CHARACTERS ) {
230- if (cellVal != null ) {
231- cellVal .append (reader .getText ());
232- }
233- } else if (event == javax .xml .stream .XMLStreamConstants .END_ELEMENT ) {
234- String name = reader .getLocalName ();
235- if ("v" .equals (name )) {
213+ // 2. Read all sheets (sheet1.xml, sheet2.xml, etc.)
214+ java .util .Enumeration <? extends java .util .zip .ZipEntry > entries = zip .entries ();
215+ while (entries .hasMoreElements ()) {
216+ java .util .zip .ZipEntry entry = entries .nextElement ();
217+ String entryName = entry .getName ();
218+ if (entryName .startsWith ("xl/worksheets/sheet" ) && entryName .endsWith (".xml" )) {
219+ try (InputStream is = zip .getInputStream (entry )) {
220+ javax .xml .stream .XMLStreamReader reader = XML_FACTORY .createXMLStreamReader (is );
221+
222+ String cellType = null ;
223+ StringBuilder cellVal = null ;
224+
225+ while (reader .hasNext ()) {
226+ int event = reader .next ();
227+ if (event == javax .xml .stream .XMLStreamConstants .START_ELEMENT ) {
228+ String name = reader .getLocalName ();
229+ if ("c" .equals (name )) {
230+ cellType = reader .getAttributeValue (null , "t" );
231+ } else if ("v" .equals (name )) {
232+ cellVal = new StringBuilder ();
233+ }
234+ } else if (event == javax .xml .stream .XMLStreamConstants .CHARACTERS ) {
236235 if (cellVal != null ) {
237- String rawVal = cellVal .toString ().trim ();
238- if ("s" .equals (cellType )) {
239- try {
240- int idx = Integer .parseInt (rawVal );
241- if (idx >= 0 && idx < sharedStrings .size ()) {
242- textBuilder .append (sharedStrings .get (idx )).append ("\t " );
243- }
244- } catch (NumberFormatException ignored ) {}
245- } else if (!rawVal .isEmpty ()) {
246- textBuilder .append (rawVal ).append ("\t " );
236+ cellVal .append (reader .getText ());
237+ }
238+ } else if (event == javax .xml .stream .XMLStreamConstants .END_ELEMENT ) {
239+ String name = reader .getLocalName ();
240+ if ("v" .equals (name )) {
241+ if (cellVal != null ) {
242+ String rawVal = cellVal .toString ().trim ();
243+ if ("s" .equals (cellType )) {
244+ try {
245+ int idx = Integer .parseInt (rawVal );
246+ if (idx >= 0 && idx < sharedStrings .size ()) {
247+ textBuilder .append (sharedStrings .get (idx )).append ("\t " );
248+ }
249+ } catch (NumberFormatException ignored ) {}
250+ } else if (!rawVal .isEmpty ()) {
251+ textBuilder .append (rawVal ).append ("\t " );
252+ }
253+ cellVal = null ;
247254 }
248- cellVal = null ;
255+ } else if ("row" .equals (name )) {
256+ textBuilder .append ("\n " );
249257 }
250- } else if ("row" .equals (name )) {
251- textBuilder .append ("\n " );
252258 }
253259 }
260+ } catch (Exception e ) {
261+ throw new IOException ("Failed parsing XLSX worksheet " + entryName + ": " + e .getMessage (), e );
254262 }
255- } catch (Exception e ) {
256- throw new IOException ("Failed parsing XLSX worksheet: " + e .getMessage (), e );
257263 }
258264 }
259265 }
@@ -275,7 +281,7 @@ private ParsedDocument parseImageOcr(Path path) throws IOException {
275281
276282 private ParsedDocument parsePdf (Path path ) throws IOException {
277283 try (PDDocument document = Loader .loadPDF (path .toFile ())) {
278- VisualParagraphPDFTextStripper2 stripper = new VisualParagraphPDFTextStripper2 ();
284+ VisualParagraphPDFTextStripper stripper = new VisualParagraphPDFTextStripper ();
279285 stripper .getText (document ); // process document text positions
280286 String raw = stripper .buildVisualText ();
281287 String normalized = normalize (raw , "application/pdf" );
0 commit comments