Skip to content

Commit

Permalink
Merge pull request #849 from raccoonyy/korean
Browse files Browse the repository at this point in the history
Implement basic Korean support
  • Loading branch information
takahi-i authored Jun 23, 2019
2 parents f5499b7 + 88db405 commit 01608de
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 12 deletions.
16 changes: 16 additions & 0 deletions redpen-cli/sample/sample-doc/ko/sampledoc-ko.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
= Instances Overview
Author's Name <person@email.address>
v1.2, 2015-08

This is the optional preamble (an untitled section body). Useful for writing simple sectionless documents consisting only of a preamble.

NOTE: The abstract, preface, appendix, bibliography, glossary and index section titles are significant ('specialsections').

:numbered!:
[abstract]
[suppress='Contraction WeakExpression']

= 눈송이 서버

서버 운영을 오래 해 본 사람이라도,처음 들어가는 서버에서는 마음 먹은 대로 문제를 해결하기가 어렵습니다. 이는 서버를 다루는 기술과는 별개로, 각 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다 해도, A 서버는 한 달 전에 구성했고 B 서버는 이제 막 구성했다면, 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 그리고 이러한 차이점들이 장애를 일으키고 말죠. 'A 서버는 잘 되는데 B 서버는 왜 죽었지?"와 같은 일 (혹은 그 반대)이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버(Snowflakes Server)라고도 합니다. 모든 눈송이의 모양이 다르듯, 서버들도 서로 다른 모습이라는 말이죠.
5 changes: 5 additions & 0 deletions redpen-cli/sample/sample-doc/ko/sampledoc-ko.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- @suppress Contraction WeakExpression -->
# 눈송이 서버

서버 운영을 오래 해 본 사람이라도,처음 들어가는 서버에서는 마음 먹은 대로 문제를 해결하기가 어렵습니다. 이는 서버를 다루는 기술과는 별개로, 각 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다 해도, A 서버는 한 달 전에 구성했고 B 서버는 이제 막 구성했다면, 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 그리고 이러한 차이점들이 장애를 일으키고 말죠. 'A 서버는 잘 되는데 B 서버는 왜 죽었지?"와 같은 일 (혹은 그 반대)이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버(Snowflakes Server)라고도 합니다. 모든 눈송이의 모양이 다르듯, 서버들도 서로 다른 모습이라는 말이죠.
5 changes: 5 additions & 0 deletions redpen-cli/sample/sample-doc/ko/sampledoc-ko.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#@# @suppress Contraction WeakExpression
= 눈송이 서버

서버 운영을 오래 해 본 사람이라도,처음 들어가는 서버에서는 마음 먹은 대로 문제를 해결하기가 어렵습니다. 이는 서버를 다루는 기술과는 별개로, 각 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다 해도, A 서버는 한 달 전에 구성했고 B 서버는 이제 막 구성했다면, 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 그리고 이러한 차이점들이 장애를 일으키고 말죠. 'A 서버는 잘 되는데 B 서버는 왜 죽었지?"와 같은 일 (혹은 그 반대)이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버(Snowflakes Server)라고도 합니다. 모든 눈송이의 모양이 다르듯, 서버들도 서로 다른 모습이라는 말이죠.
6 changes: 6 additions & 0 deletions redpen-cli/sample/sample-doc/ko/sampledoc-ko.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
########
눈송이 서버
########

서버 운영을 오래 해 본 사람이라도,처음 들어가는 서버에서는 마음 먹은 대로 문제를 해결하기가 어렵습니다. 이는 서버를 다루는 기술과는 별개로, 각 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다 해도, A 서버는 한 달 전에 구성했고 B 서버는 이제 막 구성했다면, 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 그리고 이러한 차이점들이 장애를 일으키고 말죠. 'A 서버는 잘 되는데 B 서버는 왜 죽었지?"와 같은 일 (혹은 그 반대)이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버(Snowflakes Server)라고도 합니다. 모든 눈송이의 모양이 다르듯, 서버들도 서로 다른 모습이라는 말이죠.
25 changes: 25 additions & 0 deletions redpen-cli/sample/sample-doc/ko/sampledoc-ko.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
\documentclass[a4paper, 10pt]{article}

\usepackage{url}
\usepackage{color}
\usepackage{listings}

\title{눈송이 서버}
\author{Alan Kim}

\begin{document}
\maketitle
\begin{abstract}
운영하면서 만들어지는 눈송이 서버들
\end{abstract}

% @suppress Contraction WeakExpression
\section{Summary}

서버 운영을 오래 해 본 사람이라도,처음 들어가는 서버에서는 마음 먹은 대로 문제를 해결하기가 어렵습니다. 이는 서버를 다루는 기술과는 별개로, 각 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다 해도, A 서버는 한 달 전에 구성했고 B 서버는 이제 막 구성했다면, 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 그리고 이러한 차이점들이 장애를 일으키고 말죠. 'A 서버는 잘 되는데 B 서버는 왜 죽었지?"와 같은 일 (혹은 그 반대)이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버(Snowflakes Server)라고도 합니다. 모든 눈송이의 모양이 다르듯, 서버들도 서로 다른 모습이라는 말이죠.

%% \bibliographystyle{plain}
%% \bibliography{reference}

\end{document}
2 changes: 2 additions & 0 deletions redpen-cli/sample/sample-doc/ko/sampledoc-ko.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
서버 운영을 오래 해 본 사람이라도,처음 들어가는 서버에서는 마음 먹은 대로 문제를 해결하기가 어렵습니다. 이는 서버를 다루는 기술과는 별개로, 각 서버마다 운영 기록이 다르기 때문입니다. 똑같은 일을 하는 두 서버가 있다 해도, A 서버는 한 달 전에 구성했고 B 서버는 이제 막 구성했다면, 운영체제부터 컴파일러, 설치된 패키지까지 완벽하게 같기는 쉽지 않습니다. 그리고 이러한 차이점들이 장애를 일으키고 말죠. 'A 서버는 잘 되는데 B 서버는 왜 죽었지?"와 같은 일 (혹은 그 반대)이 벌어지는 겁니다.
이렇게 서로 모양이 다른 서버들이 존재하는 상황을 눈송이 서버(Snowflakes Server)라고도 합니다. 모든 눈송이의 모양이 다르듯, 서버들도 서로 다른 모습이라는 말이죠.
12 changes: 12 additions & 0 deletions redpen-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@
</plugins>
</build>

<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>org.codelibs</groupId>
Expand Down Expand Up @@ -203,6 +210,11 @@
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
<dependency>
<groupId>com.github.shin285</groupId>
<artifactId>KOMORAN</artifactId>
<version>3.3.4</version>
</dependency>
</dependencies>

<profiles>
Expand Down
11 changes: 9 additions & 2 deletions redpen-core/src/main/java/cc/redpen/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import cc.redpen.RedPenException;
import cc.redpen.tokenizer.NeologdJapaneseTokenizer;
import cc.redpen.tokenizer.KoreanTokenizer;
import cc.redpen.tokenizer.RedPenTokenizer;
import cc.redpen.tokenizer.WhiteSpaceTokenizer;
import cc.redpen.validator.ValidatorFactory;
Expand Down Expand Up @@ -52,7 +53,7 @@ public class Configuration implements Serializable, Cloneable {
* @return default supported languages and variants that can be used with {@link #builder(String)}
*/
public static List<String> getDefaultConfigKeys() {
return asList("en", "ja", "ja.hankaku", "ja.zenkaku2", "ru");
return asList("en", "ja", "ja.hankaku", "ja.zenkaku2", "ru", "ko");
}

Configuration(File base, SymbolTable symbolTable, List<ValidatorConfiguration> validatorConfigs, String lang, boolean secure) {
Expand All @@ -66,7 +67,13 @@ public static List<String> getDefaultConfigKeys() {
}

private void initTokenizer() {
this.tokenizer = lang.equals("ja") ? new NeologdJapaneseTokenizer() : new WhiteSpaceTokenizer();
if (lang.equals("ja")) {
this.tokenizer = new NeologdJapaneseTokenizer();
} else if(lang.equals("ko")) {
this.tokenizer = new KoreanTokenizer();
} else {
this.tokenizer = new WhiteSpaceTokenizer();
}
}

/**
Expand Down
51 changes: 51 additions & 0 deletions redpen-core/src/main/java/cc/redpen/tokenizer/KoreanTokenizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* redpen: a text inspection tool
* Copyright (c) 2014-2015 Recruit Technologies Co., Ltd. and contributors
* (see CONTRIBUTORS.md)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.redpen.tokenizer;

import kr.co.shineware.nlp.komoran.core.Komoran;
import kr.co.shineware.nlp.komoran.model.KomoranResult;
import kr.co.shineware.nlp.komoran.model.Token;
import kr.co.shineware.nlp.komoran.constant.DEFAULT_MODEL;

import java.util.ArrayList;
import java.util.List;

public class KoreanTokenizer implements RedPenTokenizer {
private Komoran tokenizer;

public KoreanTokenizer() {
this.tokenizer = new Komoran(DEFAULT_MODEL.FULL);
}

@Override
public List<TokenElement> tokenize(String content) {
List<TokenElement> tokens = new ArrayList<>();
if (content == "") { return tokens; }

KomoranResult resultList = tokenizer.analyze(content);
List<Token> tokenList = resultList.getTokenList();

for (Token token : tokenList) {
List<String> pos = new ArrayList<String>();
pos.add(token.getPos());
tokens.add(new TokenElement(token.getMorph(), pos, token.getBeginIndex()));
}

return tokens;
}
}
25 changes: 15 additions & 10 deletions redpen-core/src/main/java/cc/redpen/util/LanguageDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@

public class LanguageDetector {
public String detectLanguage(String text) {
if (!has(text, StringUtils::isProbablyJapanese)) {
return has(text, StringUtils::isCyrillic) ? "ru" : "en";
}
if (has(text, StringUtils::isProbablyJapanese)) {
boolean zenkaku = text.indexOf('。') >= 0 || text.indexOf('、') >= 0 || text.indexOf('!') >= 0 || text.indexOf('?') >= 0;
boolean zenkaku2 = text.indexOf('.') >= 0 || text.indexOf(',') >= 0;
boolean hankaku = text.indexOf('.') >= 0 || text.indexOf(',') >= 0 || text.indexOf('!') >= 0 || text.indexOf('?') >= 0;

boolean zenkaku = text.indexOf('。') >= 0 || text.indexOf('、') >= 0 || text.indexOf('!') >= 0 || text.indexOf('?') >= 0;
boolean zenkaku2 = text.indexOf('.') >= 0 || text.indexOf(',') >= 0;
boolean hankaku = text.indexOf('.') >= 0 || text.indexOf(',') >= 0 || text.indexOf('!') >= 0 || text.indexOf('?') >= 0;
return zenkaku ? "ja" :
zenkaku2 ? "ja.zenkaku2" :
hankaku ? "ja.hankaku":
"ja";
}
else if (has(text, StringUtils::isCyrillic)) {
return "ru";
} else if (has(text, StringUtils::isKorean)) {
return "ko";
}

return zenkaku ? "ja" :
zenkaku2 ? "ja.zenkaku2" :
hankaku ? "ja.hankaku":
"ja";
return "en";
}

private boolean has(String text, Predicate<Character> func) {
Expand Down
5 changes: 5 additions & 0 deletions redpen-core/src/main/java/cc/redpen/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ public static boolean isBasicLatin(char c) {
public static boolean isCyrillic(char c) {
return Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CYRILLIC;
}

public static boolean isKorean(char c) {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
return block == HANGUL_SYLLABLES || block == HANGUL_JAMO;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* redpen: a text inspection tool
* Copyright (c) 2014-2015 Recruit Technologies Co., Ltd. and contributors
* (see CONTRIBUTORS.md)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.redpen.tokenizer;

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class KoreanTokenizerTest {
@Test
public void testTokenize() {
KoreanTokenizer tokenizer = new KoreanTokenizer();
List<TokenElement> tokens = tokenizer.tokenize("도움이 될 것이다.");
assertEquals(8, tokens.size());
assertEquals("도움", tokens.get(0).getSurface());
assertEquals("NNG", tokens.get(0).getTags().get(0));
assertEquals("이", tokens.get(1).getSurface());
assertEquals("JKS", tokens.get(1).getTags().get(0));
assertEquals("되", tokens.get(2).getSurface());
assertEquals("VV", tokens.get(2).getTags().get(0));
assertEquals("ㄹ", tokens.get(3).getSurface());
assertEquals("ETM", tokens.get(3).getTags().get(0));
assertEquals("것", tokens.get(4).getSurface());
assertEquals("NNB", tokens.get(4).getTags().get(0));
assertEquals("이", tokens.get(5).getSurface());
assertEquals("VCP", tokens.get(5).getTags().get(0));
assertEquals("다", tokens.get(6).getSurface());
assertEquals("EF", tokens.get(6).getTags().get(0));
assertEquals(".", tokens.get(7).getSurface());
assertEquals("SF", tokens.get(7).getTags().get(0));
}

@Test
public void testTokenizeVoid() {
KoreanTokenizer tokenizer = new KoreanTokenizer();
List<TokenElement> tokens = tokenizer.tokenize("");
assertEquals(0, tokens.size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ void russianUsesCyrillicLetters() throws Exception {
assertEquals("ru", detector.detectLanguage("Привет, Mary!"));
}

@Test
void KoreanUsesHangul() throws Exception {
assertEquals("ko", detector.detectLanguage("안녕하세요!"));
}

@Test
void japaneseIsDetectedForKatakana() throws Exception {
assertEquals("ja", detector.detectLanguage("コンピューター"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import cc.redpen.model.Document;
import cc.redpen.parser.DocumentParser;
import cc.redpen.tokenizer.NeologdJapaneseTokenizer;
import cc.redpen.tokenizer.KoreanTokenizer;
import cc.redpen.tokenizer.RedPenTokenizer;
import cc.redpen.tokenizer.WhiteSpaceTokenizer;
import cc.redpen.util.FormatterUtils;
Expand Down Expand Up @@ -188,6 +189,9 @@ public JSONObject tokenize(@FormParam("document") @DefaultValue("") String docum
case "ja":
tokenizer = new NeologdJapaneseTokenizer();
break;
case "ko":
tokenizer = new KoreanTokenizer();
break;
default:
tokenizer = new WhiteSpaceTokenizer();
break;
Expand Down
1 change: 1 addition & 0 deletions redpen-server/src/main/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ <h1><span class="redpen-red">Red</span>Pen Tokenizer Testbed</h1>
<div class="col-md-2">
<input type="radio" name="redpen-token-lang" value="ja" checked>&nbsp;JA<br/>
<input type="radio" name="redpen-token-lang" value="en">&nbsp;EN/RU<br/>
<input type="radio" name="redpen-token-lang" value="ko">&nbsp;KO<br/>
</div>
</div>

Expand Down

0 comments on commit 01608de

Please sign in to comment.