001/*
002 *  Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.agentsflex.core.file2text;
017
018
019import com.agentsflex.core.file2text.extractor.FileExtractor;
020import com.agentsflex.core.file2text.extractor.ExtractorRegistry;
021import com.agentsflex.core.file2text.source.*;
022
023import java.io.File;
024import java.io.InputStream;
025import java.util.List;
026import java.util.stream.Collectors;
027
028public class File2TextService {
029    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(File2TextService.class);
030    private final ExtractorRegistry registry;
031
032    public File2TextService() {
033        this(new ExtractorRegistry());
034    }
035
036    public File2TextService(ExtractorRegistry registry) {
037        this.registry = registry;
038    }
039
040    public ExtractorRegistry getRegistry() {
041        return registry;
042    }
043
044    public String extractTextFromHttpUrl(String httpUrl) {
045        return extractTextFromSource(new HttpDocumentSource(httpUrl));
046    }
047
048    public String extractTextFromHttpUrl(String httpUrl, String fileName) {
049        return extractTextFromSource(new HttpDocumentSource(httpUrl, fileName));
050    }
051
052    public String extractTextFromHttpUrl(String httpUrl, String fileName, String mimeType) {
053        return extractTextFromSource(new HttpDocumentSource(httpUrl, fileName, mimeType));
054    }
055
056    public String extractTextFromFile(File file) {
057        return extractTextFromSource(new FileDocumentSource(file));
058    }
059
060    public String extractTextFromStream(InputStream is, String fileName, String mimeType) {
061        return extractTextFromSource(new ByteStreamDocumentSource(is, fileName, mimeType));
062    }
063
064    public String extractTextFromBytes(byte[] bytes, String fileName, String mimeType) {
065        return extractTextFromSource(new ByteArrayDocumentSource(bytes, fileName, mimeType));
066    }
067
068
069    /**
070     * 从 DocumentSource 提取纯文本
071     * 支持多 Extractor 降级重试
072     *
073     * @param source 文档输入源
074     * @return 提取的文本(非空去空格),若无法提取则抛出异常
075     * @throws IllegalArgumentException 输入源为空
076     */
077    public String extractTextFromSource(DocumentSource source) {
078        if (source == null) {
079            throw new IllegalArgumentException("DocumentSource cannot be null");
080        }
081
082        try {
083            // 获取可用的 Extractor(按优先级排序)
084            List<FileExtractor> candidates = registry.findExtractors(source);
085            if (candidates.isEmpty()) {
086                log.warn("No extractor supports this document: " + safeFileName(source));
087                return null;
088            }
089
090            // 日志:输出候选 Extractor
091            log.info("Trying extractors for {}: {}", safeFileName(source),
092                candidates.stream()
093                    .map(e -> e.getClass().getSimpleName())
094                    .collect(Collectors.joining(", ")));
095
096
097            for (FileExtractor extractor : candidates) {
098                try {
099                    log.debug("Trying {} on {}", extractor.getClass().getSimpleName(), safeFileName(source));
100
101                    String text = extractor.extractText(source);
102                    if (text != null && !text.trim().isEmpty()) {
103                        log.debug("Success with {}: extracted {} chars",
104                            extractor.getClass().getSimpleName(), text.length());
105                        return text;
106                    } else {
107                        log.debug("Extractor {} returned null", extractor.getClass().getSimpleName());
108                    }
109                } catch (Exception e) {
110                    log.warn("Extractor {} failed on {}: {}",
111                        extractor.getClass().getSimpleName(),
112                        safeFileName(source),
113                        e.toString());
114                }
115            }
116
117            log.warn(String.format("All %d extractors failed for: %s", candidates.size(), safeFileName(source)));
118            return null;
119        } finally {
120            source.cleanup();
121        }
122
123    }
124
125    private String safeFileName(DocumentSource source) {
126        try {
127            return source.getFileName() != null ? source.getFileName() : "unknown";
128        } catch (Exception e) {
129            return "unknown";
130        }
131    }
132}