11package fastcontentparse ;
22
33import java .io .IOException ;
4- import java .io .InputStream ;
4+ import java .io .Writer ;
55import java .nio .charset .StandardCharsets ;
66import java .nio .file .Files ;
77import java .nio .file .Path ;
8- import java .util .ArrayList ;
98import java .util .List ;
109import java .util .Locale ;
1110import java .util .Map ;
12- import java .util .regex .Pattern ;
1311
12+ import fastocr .FastOCR ;
13+ import fastregex .FastRegex ;
1414import org .apache .pdfbox .Loader ;
1515import org .apache .pdfbox .pdmodel .PDDocument ;
16- import org .apache .pdfbox .text .PDFTextStripper ;
1716
17+ /**
18+ * High-performance, zero-bloat content extraction and normalization engine.
19+ * <p>
20+ * Routes supported document formats (PDF, XLSX, CSV, RTF, Markdown, Plaintext, OCR images)
21+ * to dedicated lightweight streaming parsers and normalizes textual output into clean UTF-8 tokens.
22+ */
1823public class FastContentParse {
1924
25+ /**
26+ * Parses a file from the local file system into a {@link ParsedDocument}.
27+ *
28+ * @param path target file path
29+ * @return normalized document representation with detected MIME type
30+ * @throws NullPointerException if {@code path} is {@code null}
31+ * @throws IOException if the file does not exist, cannot be read, or parsing fails
32+ */
2033 public ParsedDocument parseFile (Path path ) throws IOException {
2134 if (path == null ) {
2235 throw new NullPointerException ("path must not be null" );
@@ -52,16 +65,36 @@ public ParsedDocument parseFile(Path path) throws IOException {
5265 return parseString (raw , fileName , type );
5366 }
5467
68+ /**
69+ * Parses an in-memory string inferred by a source file name.
70+ *
71+ * @param rawText raw text content
72+ * @param sourceName original filename used for MIME type detection
73+ * @return normalized document representation
74+ */
5575 public ParsedDocument parseString (String rawText , String sourceName ) {
5676 return parseString (rawText , sourceName , detectType (sourceName ));
5777 }
5878
79+ /**
80+ * Parses an in-memory string with explicit MIME type routing and normalization.
81+ *
82+ * @param rawText raw text content
83+ * @param sourceName original filename or identifier
84+ * @param explicitType explicit MIME type or {@code null} to infer from {@code sourceName}
85+ * @return normalized document representation
86+ */
5987 public ParsedDocument parseString (String rawText , String sourceName , String explicitType ) {
6088 String type = explicitType != null ? explicitType : detectType (sourceName );
6189 String normalized = normalize (rawText , type );
6290 return new ParsedDocument (type , normalized );
6391 }
6492
93+ /**
94+ * Deprecated method stub retained for migration compatibility.
95+ *
96+ * @deprecated Use the dedicated FastContentChunk library for token and character chunking.
97+ */
6598 @ Deprecated (forRemoval = true )
6699 public List <String > chunkText (String text , int maxChunkSize , int overlap ) {
67100 if (text == null || text .isBlank ()) {
@@ -78,7 +111,7 @@ private String normalize(String rawText, String explicitType) {
78111 String text = rawText .replace ("\r \n " , "\n " ).replace ('\r' , '\n' );
79112
80113 if (explicitType != null && explicitType .toLowerCase (Locale .ROOT ).contains ("rtf" )) {
81- text = stripRtf (text );
114+ text = RtfStripper . strip (text );
82115 }
83116
84117 if (explicitType != null && explicitType .toLowerCase (Locale .ROOT ).contains ("pdf" )) {
@@ -88,50 +121,10 @@ private String normalize(String rawText, String explicitType) {
88121 return normalizeWhitespace (text );
89122 }
90123
91- private String stripRtf (String input ) {
92- if (input == null || input .isEmpty ()) {
93- return "" ;
94- }
95-
96- StringBuilder sb = new StringBuilder (input .length ());
97- int i = 0 ;
98- final int len = input .length ();
99-
100- while (i < len ) {
101- char c = input .charAt (i );
102-
103- if (c == '{' || c == '}' ) {
104- i ++;
105- continue ;
106- }
107-
108- if (c == '\\' ) {
109- i ++;
110- if (i >= len ) break ;
111-
112- char next = input .charAt (i );
113- if (Character .isLetter (next )) {
114- while (i < len && Character .isLetter (input .charAt (i ))) i ++;
115- while (i < len && (Character .isDigit (input .charAt (i )) || input .charAt (i ) == '-' )) i ++;
116- if (i < len && input .charAt (i ) == ' ' ) i ++;
117- } else {
118- i ++;
119- }
120- continue ;
121- }
122-
123- sb .append (c );
124- i ++;
125- }
126-
127- return normalizeWhitespace (sb .toString ());
128- }
129-
130124 private String normalizeWhitespace (String text ) {
131- return fastregex . FastRegex .normalizeWhitespace (text );
125+ return FastRegex .normalizeWhitespace (text );
132126 }
133127
134- private static final javax .xml .stream .XMLInputFactory XML_FACTORY ;
135128 private static final Map <String , String > EXTENSION_TYPES = Map .ofEntries (
136129 Map .entry (".pdf" , "application/pdf" ),
137130 Map .entry (".rtf" , "text/rtf" ),
@@ -147,12 +140,6 @@ private String normalizeWhitespace(String text) {
147140 Map .entry (".bmp" , "image/ocr" )
148141 );
149142
150- static {
151- XML_FACTORY = javax .xml .stream .XMLInputFactory .newDefaultFactory ();
152- XML_FACTORY .setProperty (javax .xml .stream .XMLInputFactory .IS_SUPPORTING_EXTERNAL_ENTITIES , Boolean .FALSE );
153- XML_FACTORY .setProperty (javax .xml .stream .XMLInputFactory .SUPPORT_DTD , Boolean .FALSE );
154- }
155-
156143 private String detectType (String sourceName ) {
157144 if (sourceName == null ) {
158145 return "text/plain" ;
@@ -177,102 +164,14 @@ private ParsedDocument parseCsv(Path path) throws IOException {
177164 }
178165
179166 private ParsedDocument parseXlsx (Path path ) throws IOException {
180- List <String > sharedStrings = new ArrayList <>();
181- StringBuilder textBuilder = new StringBuilder (128 * 1024 );
182-
183- try (java .util .zip .ZipFile zip = new java .util .zip .ZipFile (path .toFile ())) {
184- // 1. Read sharedStrings.xml if present
185- java .util .zip .ZipEntry sstEntry = zip .getEntry ("xl/sharedStrings.xml" );
186- if (sstEntry != null ) {
187- try (InputStream is = zip .getInputStream (sstEntry )) {
188- javax .xml .stream .XMLStreamReader reader = XML_FACTORY .createXMLStreamReader (is );
189-
190- StringBuilder currentText = null ;
191- while (reader .hasNext ()) {
192- int event = reader .next ();
193- if (event == javax .xml .stream .XMLStreamConstants .START_ELEMENT ) {
194- if ("t" .equals (reader .getLocalName ())) {
195- currentText = new StringBuilder ();
196- }
197- } else if (event == javax .xml .stream .XMLStreamConstants .CHARACTERS ) {
198- if (currentText != null ) {
199- currentText .append (reader .getText ());
200- }
201- } else if (event == javax .xml .stream .XMLStreamConstants .END_ELEMENT ) {
202- if ("t" .equals (reader .getLocalName ())) {
203- if (currentText != null ) {
204- sharedStrings .add (currentText .toString ());
205- currentText = null ;
206- }
207- }
208- }
209- }
210- } catch (Exception e ) {
211- throw new IOException ("Failed parsing XLSX shared strings: " + e .getMessage (), e );
212- }
213- }
214-
215- // 2. Read all sheets (sheet1.xml, sheet2.xml, etc.)
216- java .util .Enumeration <? extends java .util .zip .ZipEntry > entries = zip .entries ();
217- while (entries .hasMoreElements ()) {
218- java .util .zip .ZipEntry entry = entries .nextElement ();
219- String entryName = entry .getName ();
220- if (entryName .startsWith ("xl/worksheets/sheet" ) && entryName .endsWith (".xml" )) {
221- try (InputStream is = zip .getInputStream (entry )) {
222- javax .xml .stream .XMLStreamReader reader = XML_FACTORY .createXMLStreamReader (is );
223-
224- String cellType = null ;
225- StringBuilder cellVal = null ;
226-
227- while (reader .hasNext ()) {
228- int event = reader .next ();
229- if (event == javax .xml .stream .XMLStreamConstants .START_ELEMENT ) {
230- String name = reader .getLocalName ();
231- if ("c" .equals (name )) {
232- cellType = reader .getAttributeValue (null , "t" );
233- } else if ("v" .equals (name )) {
234- cellVal = new StringBuilder ();
235- }
236- } else if (event == javax .xml .stream .XMLStreamConstants .CHARACTERS ) {
237- if (cellVal != null ) {
238- cellVal .append (reader .getText ());
239- }
240- } else if (event == javax .xml .stream .XMLStreamConstants .END_ELEMENT ) {
241- String name = reader .getLocalName ();
242- if ("v" .equals (name )) {
243- if (cellVal != null ) {
244- String rawVal = cellVal .toString ().trim ();
245- if ("s" .equals (cellType )) {
246- try {
247- int idx = Integer .parseInt (rawVal );
248- if (idx >= 0 && idx < sharedStrings .size ()) {
249- textBuilder .append (sharedStrings .get (idx )).append ("\t " );
250- }
251- } catch (NumberFormatException ignored ) {}
252- } else if (!rawVal .isEmpty ()) {
253- textBuilder .append (rawVal ).append ("\t " );
254- }
255- cellVal = null ;
256- }
257- } else if ("row" .equals (name )) {
258- textBuilder .append ("\n " );
259- }
260- }
261- }
262- } catch (Exception e ) {
263- throw new IOException ("Failed parsing XLSX worksheet " + entryName + ": " + e .getMessage (), e );
264- }
265- }
266- }
267- }
268-
269- String normalized = normalize (textBuilder .toString (), "text/plain" );
167+ String raw = XlsxStreamingParser .extractText (path );
168+ String normalized = normalize (raw , "text/plain" );
270169 return new ParsedDocument ("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" , normalized );
271170 }
272171
273172 private ParsedDocument parseImageOcr (Path path ) throws IOException {
274173 try {
275- fastocr . FastOCR ocr = new fastocr . FastOCR ("en" );
174+ FastOCR ocr = new FastOCR ("en" );
276175 String text = ocr .read (path .toAbsolutePath ().toString ());
277176 String normalized = normalize (text , "text/plain" );
278177 return new ParsedDocument ("image/ocr" , normalized );
@@ -284,7 +183,7 @@ private ParsedDocument parseImageOcr(Path path) throws IOException {
284183 private ParsedDocument parsePdf (Path path ) throws IOException {
285184 try (PDDocument document = Loader .loadPDF (path .toFile ())) {
286185 VisualParagraphPDFTextStripper stripper = new VisualParagraphPDFTextStripper ();
287- stripper .writeText (document , java . io . Writer .nullWriter ());
186+ stripper .writeText (document , Writer .nullWriter ());
288187 String raw = stripper .buildVisualText ();
289188 String normalized = normalize (raw , "application/pdf" );
290189 return new ParsedDocument ("application/pdf" , normalized );
0 commit comments