설계 요약

CSV 읽기

page_number 기준으로 텍스트 병합

rule 기반 문단 분리

빈 줄

번호 패턴

헤더 패턴

SemanticBlock 객체로 저장

아래 코드는:

CSV 읽고

페이지 단위 병합

rule-based 분리

semantic 블록 리스트 반환

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Pattern;

public class SemanticChunker {

    static class SemanticBlock {
        int pageNumber;
        String text;

        public SemanticBlock(int pageNumber, String text) {
            this.pageNumber = pageNumber;
            this.text = text;
        }
    }

    public static void main(String[] args) throws Exception {
        Path csvPath = Paths.get("input.csv");

        Map<Integer, StringBuilder> pageMap = readAndMergeByPage(csvPath);

        List<SemanticBlock> blocks = ruleBasedSplit(pageMap);

        System.out.println("총 semantic block 수: " + blocks.size());
    }

    // 1️⃣ CSV 읽고 page 기준 병합
    private static Map<Integer, StringBuilder> readAndMergeByPage(Path csvPath) throws IOException {

        Map<Integer, StringBuilder> pageMap = new LinkedHashMap<>();

        try (BufferedReader br = Files.newBufferedReader(csvPath, StandardCharsets.UTF_8)) {

            String line = br.readLine(); // header skip

            while ((line = br.readLine()) != null) {

                // 간단 CSV 파싱 (text에 쉼표 없다는 전제)
                String[] parts = line.split(",", 3);
                if (parts.length < 3) continue;

                int page = Integer.parseInt(parts[0].trim());
                String text = parts[2].replace("\"", "").trim();

                pageMap
                    .computeIfAbsent(page, k -> new StringBuilder())
                    .append(text)
                    .append("\n");
            }
        }

        return pageMap;
    }

    // 2️⃣ rule-based 문단 분리
    private static List<SemanticBlock> ruleBasedSplit(Map<Integer, StringBuilder> pageMap) {

        List<SemanticBlock> result = new ArrayList<>();

        Pattern headerPattern = Pattern.compile(
                "^(제\\d+장|\\d+\\.\\s|[IVX]+\\.\\s|[①-⑳]|-\\s).*"
        );

        for (Map.Entry<Integer, StringBuilder> entry : pageMap.entrySet()) {

            int page = entry.getKey();
            String pageText = entry.getValue().toString();

            String[] lines = pageText.split("\\r?\\n");

            StringBuilder currentBlock = new StringBuilder();

            for (String line : lines) {

                line = line.trim();
                if (line.isEmpty()) {
                    // 빈 줄 → block 종료
                    if (currentBlock.length() > 0) {
                        result.add(new SemanticBlock(page, currentBlock.toString().trim()));
                        currentBlock.setLength(0);
                    }
                    continue;
                }

                // 헤더 패턴이면 강제 분리
                if (headerPattern.matcher(line).matches()) {
                    if (currentBlock.length() > 0) {
                        result.add(new SemanticBlock(page, currentBlock.toString().trim()));
                        currentBlock.setLength(0);
                    }
                    result.add(new SemanticBlock(page, line));
                } else {
                    currentBlock.append(line).append(" ");
                }
            }

            // 페이지 끝 처리
            if (currentBlock.length() > 0) {
                result.add(new SemanticBlock(page, currentBlock.toString().trim()));
            }
        }

        return result;
    }
}

이 코드가 하는 일

  1. CSV를 page별로 다시 묶는다
  2. 줄바꿈 기준 분리
  3. 헤더/번호 패턴 강제 분리
  4. semantic block 리스트 생성

OCR JSON

Flatten CSV

Rule-based CSV (block_id, page_number, element_ids, merged_text)

Token-safe chunk CSV ← 지금 만들 단계

LLM 처리

Leave a Reply

Your email address will not be published. Required fields are marked *