Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic Korean support #849

Merged
merged 4 commits into from
Jun 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>
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