/** * @version $Id: * * 2012/07/23 16:11:11 * @author shingo-takahashi * * Copyright 2011-2014 TIDAコンソーシアム All Rights Reserved. */ package com.tida_okinawa.corona.correction.common; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.apache.pdfbox.pdfparser.PDFParser; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.util.PDFTextStripper; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import com.tida_okinawa.corona.common.Encoding; import com.tida_okinawa.corona.correction.CorrectionPreferenceInitializer; import com.tida_okinawa.corona.correction.Messages; import com.tida_okinawa.corona.correction.data.CoronaDocumentDefinition; import com.tida_okinawa.corona.correction.data.CoronaDocumentInformation; import com.tida_okinawa.corona.correction.data.DocumentSplitType; /** * ドキュメント分割<br/> * * {@link CoronaDocumentInformation} の定義情報を用いて、入力ファイルを分割する.<br/> * {@link CorrectionPreferenceInitializer} にて、定義情報を編集する。 * * @author shingo-takahashi * */ public class DocumentSpliter { private String encode = Encoding.UTF_8.toString(); private List<String> output = new ArrayList<String>(); private List<CoronaDocumentDefinition> allDefinitions = new ArrayList<CoronaDocumentDefinition>(); private List<Integer> divPointList; private Map<Integer, Integer> deletePointMap; /** * コンストラクタ */ public DocumentSpliter() { super(); } /** * @param docInfo * 分割定義情報クラス */ public void setDocumentInformation(CoronaDocumentInformation docInfo) { if (docInfo == null) { throw new IllegalArgumentException("docInfo must not null"); //$NON-NLS-1$ } // 有効な定義リストを文頭、全体ごとにまとめる for (CoronaDocumentDefinition dd : docInfo.getDefinitions()) { if (dd.isEnabled()) { if (dd.getPosition() == CoronaDocumentDefinition.PHRASE) { allDefinitions.add(dd); } else if (dd.getPosition() == CoronaDocumentDefinition.WHOLE) { allDefinitions.add(dd); } } } } /** * 入出力に使用するエンコードを設定する * * @param encode * エンコード */ public void setEncode(String encode) { this.encode = encode; } /** * 出力 * * @return 文字列リスト */ public List<String> getOutput() { return output; } private final static void openErrorDialog(String title, String message) { Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); if (shell != null) { MessageDialog.openError(shell, title, message); } } /** * 分割処理 * * @param input * 入力ファイル * @return 成否 */ public boolean split(File input) { if (input == null) { throw new IllegalArgumentException("input file must not null"); //$NON-NLS-1$ } /* テキスト構造解析に食わせるためのInputStream */ final InputStream is; if (input.getPath().endsWith(".pdf")) { //$NON-NLS-1$ /* * PDFファイルからテキストを抽出する */ FileInputStream pdfStream = null; try { pdfStream = new FileInputStream(input.getPath()); PDFParser pdfParser = new PDFParser(pdfStream); pdfParser.parse(); // 分析 PDDocument pdf = pdfParser.getPDDocument(); PDFTextStripper stripper = new PDFTextStripper(); String spdf2txt = stripper.getText(pdf); is = new ByteArrayInputStream(spdf2txt.getBytes()); } catch (FileNotFoundException e) { openErrorDialog(Messages.ErrorTitle_FailedReadFile, Messages.bind(Messages.ErrorMessage_FileNotFound, input.getPath())); e.printStackTrace(); return false; } catch (IOException e) { openErrorDialog(Messages.ErrorTitle_FailedReadFile, Messages.bind(Messages.ErrorMessage_FailedReadFile, input.getPath())); e.printStackTrace(); return false; } finally { if (pdfStream != null) { try { pdfStream.close(); } catch (IOException e1) { } } } } else { /* * それ以外(*.txtとか) */ setEncode(Encoding.Shift_JIS.toString()); try { is = new FileInputStream(input.getPath()); } catch (FileNotFoundException e) { openErrorDialog(Messages.ErrorTitle_FailedReadFile, Messages.bind(Messages.ErrorMessage_FileNotFound, input.getPath())); e.printStackTrace(); return false; } } final String Regex_HeadSpace = "^[ \\s]+"; //$NON-NLS-1$ final String Regex_TailSpace = "[ \\s]+$"; //$NON-NLS-1$ BufferedReader br = null; try { // 1行ごと判定 br = new BufferedReader(new InputStreamReader(is, encode)); String line; StringBuilder buff = new StringBuilder(100); divPointList = new ArrayList<Integer>(); deletePointMap = new TreeMap<Integer, Integer>(new Comparator<Integer>() { @Override public int compare(Integer i1, Integer i2) { return i2.compareTo(i1); } }); if (allDefinitions.size() > 0) { while ((line = br.readLine()) != null) { /* * 行頭・行末の空白文字(全角、半角、タブ)除去をする。 * また、DB登録時にエラーになるので'を置換する */ line = line.replaceAll(Regex_HeadSpace, "").replaceAll(Regex_TailSpace, "").replace("'", "\"").replace("\\", "\\\\"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ divPointList.clear(); deletePointMap.clear(); if (line.length() > 0) { for (CoronaDocumentDefinition definition : allDefinitions) { if (definition.getPosition() == CoronaDocumentDefinition.PHRASE) { // 文頭チェック checkPhrase(line, buff, definition); } else if (definition.getPosition() == CoronaDocumentDefinition.WHOLE) { // 全体チェック checkWhole(line, buff, definition); } } buff.append(line).append("\n"); //$NON-NLS-1$ // 文章を分割 divisionRecord(buff); divideWriting(buff); } else { // 段落チェック if (buff.length() > 0) { divideWriting(buff); output.add(buff.toString()); buff.setLength(0); } } } if (buff.length() > 0) { divideWriting(buff); output.add(buff.toString()); } } else { while ((line = br.readLine()) != null) { line = line.replaceAll(Regex_HeadSpace, "").replaceAll(Regex_TailSpace, "").replaceAll("'", "\"").replace("\\", "\\\\"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ buff.append(line); } if (buff.length() > 0) { divideWriting(buff); output.add(buff.toString()); } } } catch (IOException e) { openErrorDialog(Messages.ErrorTitle_FailedReadFile, Messages.bind(Messages.ErrorMessage_FailedReadFile, input.getPath())); e.printStackTrace(); return false; } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } } return true; } /** * 分割情報の適用範囲が文頭になっているものが、対象のテキストに合致するかチェックする.<br/> * buffには、次の1ブロックとつながる可能性のある文章が蓄えられている。 <br/> * 例えば、区切り文字が★で、 * <p> * ★これが1ブロック目です。★これが2<br/> * ブロック目です。 * </p> * というように、1つのブロックが改行で区切られたとき、2ブロック目を正しく認識するためにbuffが使われている。 * * @param line * チェック対象の文章 * @param buff * 文区切り用バッファ * @param definition * ドキュメント解析の定義内容 */ private void checkPhrase(String line, StringBuilder buff, CoronaDocumentDefinition definition) { boolean isHit = true; /* 定義内容の位置が「文頭」の場合 */ if (definition.getType() == CoronaDocumentDefinition.CHAR) { /* 定義内容の対象が「1文字」の場合 */ String regex = "^[" + definition.getDefinition() + "].*"; //$NON-NLS-1$ //$NON-NLS-2$ if (line.matches(regex)) { /* 1文字一致した場合 */ int divPoint = buff.length(); if (!divPointList.contains(divPoint)) { divPointList.add(divPoint); } if (definition.getTrim() == CoronaDocumentDefinition.TRIM) { /* 定義内容の「除去」が有効な場合 */ if (!deletePointMap.containsKey(divPoint)) { deletePointMap.put(divPoint, 1); } } } } else { /* 定義内容の対象が「文字列」の場合 */ /* TODO 検査が弱い。DocumentSpliter2を見て直すべき */ int specialCharIndex = DocumentSplitType.getStartPoint(definition.getDefinition()); if (specialCharIndex != -1) { int defPoint = 0; int linePoint = 0; /* 特殊文字を含んでいる場合 */ if (specialCharIndex > 0) { /* 特殊文字の前に記号などの文字がある場合 */ // 記号の数だけチェック for (; defPoint < specialCharIndex; defPoint++, linePoint++) { if (linePoint >= line.length()) { /* 定義より文が短い場合 */ isHit = false; break; } if (definition.getDefinition().charAt(defPoint) != line.charAt(linePoint)) { isHit = false; break; } } } // 特殊文字または特殊文字前の文字を確認した後の処理 for (; defPoint < definition.getDefinition().length(); defPoint++, linePoint++) { if (linePoint >= line.length()) { /* 定義より文が短い場合 */ isHit = false; break; } if (specialCharIndex == defPoint) { /* 特殊文字の場合 */ int endPoint = lastIndexOf(definition.getDefinition().substring(defPoint), line, linePoint); if (endPoint == -1) { isHit = false; break; } /* * TODO 特殊文字の文字数を求める * 乱暴だけど、特殊文字サイズ分シフト * iはインクリメントされるので、特殊文字より1少なく足している */ defPoint += 5; linePoint += endPoint; } else if (definition.getDefinition().charAt(defPoint) != line.charAt((linePoint))) { // 特殊文字がヒットした後の場合 isHit = false; break; } } if (isHit) { /* 全て一致した場合 */ int divPoint = buff.length(); if (!divPointList.contains(divPoint)) { divPointList.add(divPoint); } if (definition.getTrim() == CoronaDocumentDefinition.TRIM) { /* 定義内容の「除去」が有効な場合 */ updateDeleteMap(divPoint, linePoint); } } } else { int defPoint = 0; int linePoint = 0; /* 特殊文字を含まない場合 */ for (; defPoint < definition.getDefinition().length(); defPoint++, linePoint++) { if (linePoint >= line.length()) { /* 定義より文が短い場合 */ isHit = false; break; } if (definition.getDefinition().charAt(defPoint) != line.charAt(linePoint)) { /* 定義の文字と文中の文字が一致しない場合 */ isHit = false; break; } } if (isHit) { /* 全て一致した場合 */ int divPoint = buff.length(); if (!divPointList.contains(divPoint)) { divPointList.add(divPoint); } if (definition.getTrim() == CoronaDocumentDefinition.TRIM) { /* 定義内容の「除去」が有効な場合 */ updateDeleteMap(divPoint, linePoint); } } } } } /** * 分割情報の適用範囲が全体になっているものが、対象テキストに合致するかチェックする * * @param line * テキスト * @param buff * チェック対象の文章 かつ、区切り用バッファ * @param definition * ドキュメント解析の定義内容 */ private void checkWhole(String line, StringBuilder buff, CoronaDocumentDefinition definition) { /* 文頭の1文字ずつ削除して比較 */ for (int linePoint = 0; linePoint < line.length(); linePoint++) { if (definition.getType() == CoronaDocumentDefinition.CHAR) { /* 定義内容の対象が「1文字」の場合 */ for (char defWord : definition.getDefinition().toCharArray()) { if (line.charAt(linePoint) == defWord) { int divPoint = buff.length() + linePoint; if (!divPointList.contains(divPoint)) { divPointList.add(divPoint); } if (definition.getTrim() == CoronaDocumentDefinition.TRIM) { /* 定義内容の「除去」が有効な場合 */ if (!deletePointMap.containsKey(divPoint)) { deletePointMap.put(divPoint, 1); } } else { continue; } } } } else { boolean isHit = true; int specialCharStartPoint = DocumentSplitType.getStartPoint(definition.getDefinition()); // 定義内容の特殊文字開始位置を取得、特殊文字がない場合-1 if (specialCharStartPoint != -1) { int defPoint = 0; int textPoint = linePoint; /* 特殊文字を含んでいる場合 */ if (specialCharStartPoint > 0) { /* 特殊文字の前に記号などの文字がある場合 */ for (; defPoint < specialCharStartPoint; defPoint++, textPoint++) { if (textPoint >= line.length()) { /* 定義より文が短い場合 */ isHit = false; break; } if (definition.getDefinition().charAt(defPoint) != line.charAt(textPoint)) { /* 定義の文字と文中の文字が一致しない場合 */ isHit = false; break; } } if (!isHit) { continue; } } for (; defPoint < definition.getDefinition().length(); defPoint++, textPoint++) { if (textPoint >= line.length()) { /* 定義より文が短い場合 */ isHit = false; break; } if (specialCharStartPoint == defPoint) { /* 特殊文字の場合 */ int endPoint = lastIndexOf(definition.getDefinition().substring(defPoint), line, textPoint); if (endPoint != -1) { /* * TODO 特殊文字の文字数を求める * 乱暴だけど、特殊文字サイズ分シフト * iはインクリメントされるので、特殊文字より1少なく足している */ defPoint += 5; textPoint += endPoint; } else { isHit = false; break; } } else if (definition.getDefinition().charAt(defPoint) != line.charAt(textPoint)) { /* 定義の文字と文中の文字が一致しない場合 */ isHit = false; break; } } if (isHit) { /* 全て一致した場合 */ int divPoint = buff.length() + linePoint; if (!divPointList.contains(divPoint)) { divPointList.add(divPoint); } if (definition.getTrim() == CoronaDocumentDefinition.TRIM) { /* 定義内容の「除去」が有効な場合 */ updateDeleteMap(divPoint, textPoint - linePoint); } // 一致した文字数の分だけインクリメント linePoint = textPoint - 1; } } else { /* 特殊文字を含まない場合 */ int defPoint = linePoint; // 定義の文字数の分だけ繰り返し for (char ch : definition.getDefinition().toCharArray()) { if (ch != line.charAt(defPoint)) { /* 定義の文字と文中の文字が一致しない場合 */ isHit = false; break; } defPoint++; } if (isHit) { /* 全て一致した場合 */ int divPoint = buff.length() + linePoint; if (!divPointList.contains(divPoint)) { divPointList.add(divPoint); } if (definition.getTrim() == CoronaDocumentDefinition.TRIM) { /* 定義内容の「除去」が有効な場合 */ updateDeleteMap(divPoint, defPoint - linePoint); } // 一致した文字数の分だけインクリメント linePoint = defPoint - 1; } } } } } /** * 定義内容の位置が全般の場合にtargetの先頭から何文字目まで特殊文字にヒットするか判定する. * 例)10文字目までヒットするなら9が返る。 * * @param splitText * 特殊文字から始まる分割定義情報 * @param line * StringBuffer * @param startPoint * 特殊文字をStringBufferから検索する際の開始位置 * @return 最終HIT位置 * */ private static int lastIndexOf(String splitText, String line, int startPoint) { String regex; // タイプ別に正規表現を if (0 == splitText.indexOf(DocumentSplitType.NUMBER.getValue())) { regex = "[0-90-9]+"; //$NON-NLS-1$ } else if (0 == splitText.indexOf(DocumentSplitType.KANA.getValue())) { regex = "[ヲ-゚ァ-ヴー]+"; //$NON-NLS-1$ } else if (0 == splitText.indexOf(DocumentSplitType.ABC.getValue())) { regex = "[a-zA-Za-zA-Z]+"; //$NON-NLS-1$ } else { return -1; } // TODO もっと効率よくできない? DocumentSpliter2を見て直す int i = startPoint; int buffLength = line.length() - 0; for (; i < buffLength; i++) { if (!String.valueOf(line.charAt(i)).matches(regex)) { return i - 1 - startPoint; } } /* 末尾にヒットした時、-1が返る問題に対応 */ if (i == buffLength) { return i - 1 - startPoint; } return -1; } private void updateDeleteMap(int key, int deleteLength) { /* 定義内容の「除去」が有効な場合 */ if (deletePointMap.containsKey(key)) { if (deletePointMap.get(key) < deleteLength) { deletePointMap.put(key, deleteLength); } } else { deletePointMap.put(key, deleteLength); } } private void divisionRecord(StringBuilder buff) { if (divPointList.indexOf(0) == -1) { // 文頭でヒットしていない場合 divPointList.add(0); } // 分割位置を昇順にソート Collections.sort(divPointList); if (deletePointMap.size() > 0) { // 削除位置の重複チェック deletePointExtentCheck(); } // 削除する文字数分区切り位置をシフトさせる処理 for (Entry<Integer, Integer> e : deletePointMap.entrySet()) { Integer startPoint = e.getKey(); Integer deleteLength = e.getValue(); int elementNum = 0; for (Integer divPoint : divPointList) { if (startPoint < divPoint) { divPointList.set(elementNum, (divPoint - deleteLength)); } elementNum += 1; } // StringBufferからヒットした文字列を削除 buff.delete(startPoint, startPoint + deleteLength); } // 分割位置リストを用いてStringBufferからレコードに分割 if (divPointList.size() > 0) { int elementNum = 0; for (; elementNum < divPointList.size() - 1; elementNum++) { if (divPointList.get(elementNum) != divPointList.get(elementNum + 1)) { String divLine = buff.substring(divPointList.get(elementNum), divPointList.get(elementNum + 1)); if (!divLine.equals("")) { //$NON-NLS-1$ output.add(divLine); } } } // 分割された最後のレコードのみを残す buff.delete(divPointList.get(0), divPointList.get(elementNum)); } } private void deletePointExtentCheck() { // 削除範囲が被っているポイントを削除する処理 boolean endCheck = false; while (!endCheck) { boolean updateCheck = false; for (Iterator<Entry<Integer, Integer>> itr1 = deletePointMap.entrySet().iterator(); itr1.hasNext();) { // 比較先の削除範囲を取得 Entry<Integer, Integer> e1 = itr1.next(); Integer startPoint1 = e1.getKey(); Integer deleteLength1 = e1.getValue(); Integer endPoint1 = startPoint1 + deleteLength1; for (Iterator<Entry<Integer, Integer>> itr2 = deletePointMap.entrySet().iterator(); itr2.hasNext();) { // 比較元の削除範囲を取得 Entry<Integer, Integer> e2 = itr2.next(); Integer startPoint2 = e2.getKey(); Integer endPoint2 = startPoint2 + e2.getValue(); if (((startPoint1.intValue() < startPoint2.intValue()) && (startPoint2.intValue() < (endPoint1.intValue()))) || (startPoint2.intValue() == (endPoint1.intValue()))) { // 比較先の範囲内に比較元の削除開始位置が重複する場合 if ((endPoint1) < (endPoint2)) { // 削除終了位置が比較先の範囲を超えて重複する場合 deleteLength1 = ((endPoint2) - (endPoint1)) + deleteLength1; deletePointMap.put(startPoint1, deleteLength1); } itr2.remove(); divPointList.remove(startPoint2); updateCheck = true; break; } else if (startPoint1 > startPoint2) { // 削除開始位置が範囲を超えた場合に次の範囲を検索 break; } } if (updateCheck) { // 削除位置が更新された場合、deletePointMapを更新する為、For文から一旦Break break; } } if (!updateCheck) { // 重複する削除位置がなくなった場合 for (Entry<Integer, Integer> e : deletePointMap.entrySet()) { Integer startPoint = e.getKey(); Integer endPoint = startPoint + e.getValue(); // 分割位置が削除範囲内に含まれていれば削除する処理 for (Iterator<Integer> itr = divPointList.iterator(); itr.hasNext();) { Integer divPoint = itr.next(); if ((startPoint < divPoint) && (divPoint < endPoint)) { // 分割位置が削除範囲内の場合 itr.remove(); } else if (endPoint < divPoint) { // 分割位置が削除範囲を超えた場合 break; } } } endCheck = true; } } } private void divideWriting(StringBuilder buff) { while (buff.length() > 25000) { int endPoint = buff.lastIndexOf("。", 25000) + 1; //$NON-NLS-1$ output.add(buff.substring(0, endPoint)); buff.delete(0, endPoint); } } }