hanspell 0.9.7 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2018 Hyunrae Cho
3
+ Copyright (c) 2018 Hyeonrae Jo
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,20 +1,22 @@
1
1
  # hanspell
2
2
 
3
- `hanspell`은 (주)다음과 부산대학교 인공지능연구실/(주)나라인포테크의 웹 서비스를 이용한 한글 맞춤법 검사기입니다.
3
+ `hanspell`은 (주)다음과 네이버(주) 웹 서비스를 이용한 한글 맞춤법 검사기입니다.
4
4
 
5
5
  [비주얼 스튜디오 코드 한스펠](https://github.com/9beach/vscode-hanspell)과 [하스켈](https://www.haskell.org/)로 작성한 [hanspell-hs](https://github.com/9beach/hanspell-hs)도 있으니 참고하세요.
6
6
 
7
- [![Build Status](https://travis-ci.org/9beach/hanspell.svg?branch=master)](https://travis-ci.org/9beach/hanspell) [![npm version](https://badge.fury.io/js/hanspell.svg)](https://badge.fury.io/js/hanspell)
7
+ [![npm version](https://badge.fury.io/js/hanspell.svg)](https://badge.fury.io/js/hanspell)
8
8
 
9
9
  ## 설치
10
10
 
11
- [Node.js](https://nodejs.org/ko/) 설치한 뒤, 다음을 실행하면 `hanspell`을 설치합니다.
11
+ [한스펠 릴리스](https://github.com/9beach/hanspell/releases)에서 실행파일을 다운로드 하세요.
12
+
13
+ 소스를 받아서 설치하려면 [Node.js](https://nodejs.org/ko/) 18 이상을 설치한 뒤 다음을 실행하세요.
12
14
 
13
15
  ```sh
14
16
  npm install -g hanspell
15
17
  ```
16
18
 
17
- Node.js 환경에 따라 `sudo` 명령이 필요할 수도 있습니다..
19
+ Node.js 환경에 따라 `sudo` 명령이 필요할 수도 있습니다.
18
20
 
19
21
  ```sh
20
22
  sudo npm install -g hanspell
@@ -24,11 +26,11 @@ sudo npm install -g hanspell
24
26
 
25
27
  ```console
26
28
  $ hanspell-cli -h
27
- 사용법: hanspell-cli [-d | -p | -a | -h]
29
+ 사용법: hanspell-cli [-d | -n | -a | -h]
28
30
 
29
31
  옵션:
30
32
  -d, --daum [default] 다음 서비스를 이용해서 맞춤법을 교정합니다
31
- -p, --pnu 부산대학교 서비스를 이용해서 맞춤법을 교정합니다
33
+ -n, --naver 네이버 서비스를 이용해서 맞춤법을 교정합니다
32
34
  -a, --all 두 서비스의 모든 결과를 반영해서 맞춤법을 교정합니다
33
35
  -h, --info 도움말을 출력합니다
34
36
 
@@ -133,7 +135,7 @@ Node.js 프로젝트에서 `hanspell` 라이브러리를 사용하려면 다음
133
135
  cd my-project && npm install --save hanspell
134
136
  ```
135
137
 
136
- `hanspell` 라이브러리에는 `spellCheckByDAUM` 함수와 `spellCheckByPNU` 함수가 있습니다. 다음은 사용 예입니다.
138
+ `hanspell` 라이브러리에는 `spellCheckByDAUM` 함수와 `spellCheckByNAVER` 함수가 있습니다. 다음은 사용 예입니다.
137
139
 
138
140
  ```javascript
139
141
  // hanspell-example.js
@@ -148,10 +150,10 @@ const error = function (err) {
148
150
  };
149
151
 
150
152
  hanspell.spellCheckByDAUM(sentence, 6000, console.log, end, error);
151
- hanspell.spellCheckByPNU(sentence, 6000, console.log, end, error);
153
+ hanspell.spellCheckByNAVER(sentence, 6000, console.log, end, error);
152
154
  ```
153
155
 
154
- 다음의 결과가 예상됩니다.
156
+ 다음과 비슷한 결과가 예상됩니다.
155
157
 
156
158
  ```console
157
159
  [
@@ -167,23 +169,42 @@ hanspell.spellCheckByPNU(sentence, 6000, console.log, end, error);
167
169
  {
168
170
  token: '리랜드는',
169
171
  suggestions: [ '이랜드는' ],
170
- info: '철자 검사를 해 보니 이 어절은 분석할 수 없으므로...'
172
+ info: '맞춤법 오류입니다.'
171
173
  },
172
174
  {
173
- token: '굵은게',
174
- suggestions: [ '굵은 ', '굵은데' ],
175
- info: '어미의 사용이 잘못되었습니다. 문서 작성시 필요에...'
175
+ token: '굵은게,',
176
+ suggestions: [ '굵은 게,' ],
177
+ info: '띄어쓰기 오류입니다.'
176
178
  }
177
179
  ]
178
180
  // check ends
179
181
  ```
180
182
 
181
- 두 함수의 호출 결과는 모두 `token`, `suggestions` 속성을 가집니다.
182
- `spellCheckByDAUM`은 `type`, `context` 속성을, `spellCheckByPNU`는 `info` 속성을 추가로 가집니다.
183
+ 두 함수의 호출 결과는 모두 `token`, `suggestions`, `info` 속성을 가집니다.
184
+ `spellCheckByDAUM`은 `type`, `context` 속성을 추가로 가집니다.
185
+
186
+ TypeScript 사용자는 별도 설정 없이 타입이 자동으로 인식됩니다.
187
+
188
+ ```typescript
189
+ import { spellCheckByNAVER, NaverTypo } from 'hanspell';
190
+
191
+ spellCheckByNAVER(
192
+ '안뇽하세요.',
193
+ 6000,
194
+ (data: NaverTypo[]) => console.log(data),
195
+ () => console.log('// check ends'),
196
+ (err) => console.error(err),
197
+ );
198
+ ```
199
+
200
+
201
+ 네이버 검사기는 교정 후보를 항상 하나만 제공하고, `info`는 오류 분류(맞춤법/
202
+ 띄어쓰기/표준어 추천)만 알려 줍니다.
183
203
 
184
- 위의 예시에서 `sentence`가 300 단어 또는 1000자를 넘으면, 인자로 전달된
204
+ 위의 예시에서 `sentence`가 250 단어 또는 1000자를 넘으면, 인자로 전달된
185
205
  `console.log`는 여러 번 호출되지만 `end`는 마지막에 한 번만 호출됩니다.
186
206
 
187
207
  ## 라이선스 고지
188
208
 
189
- 이 프로그램의 소스 코드는 MIT 라이선스를 따르지만, 부산대학교 인공지능연구실/(주)나라인포테크의 맞춤법 웹 서비스는 권리자가 고지한 대로 개인이나 학생만 무료로 사용할 수 있습니다.
209
+ 이 프로그램의 소스 코드는 MIT 라이선스를 따릅니다. 다음과 네이버의 맞춤법 웹
210
+ 서비스는 각 제공사의 이용 약관에 따라 사용해야 합니다.
package/lib/cli.js CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  const chalk = require('chalk');
8
8
  const fs = require('fs');
9
- const minimatch = require('minimatch');
9
+ const { minimatch } = require('minimatch');
10
10
  const readline = require('readline');
11
11
 
12
12
  const hanspell = require('./index');
@@ -20,6 +20,7 @@ const typomap = new Map();
20
20
  // Node v8 supports zero-length lookahead and lookbehind assertions.
21
21
  const lookaround = (() => {
22
22
  try {
23
+ // eslint-disable-next-line prefer-regex-literals
23
24
  RegExp('(?<=[^ㄱ-ㅎㅏ-ㅣ가-힣])test(?=[^ㄱ-ㅎㅏ-ㅣ가-힣])');
24
25
  return true;
25
26
  } catch (err) {
@@ -94,15 +95,15 @@ function checkDAUM() {
94
95
  hanspell.spellCheckByDAUM(sentence, HTTP_TIMEOUT, fixTypos, printSentence);
95
96
  }
96
97
 
97
- // Spell check by PNU service.
98
- function checkPNU() {
99
- hanspell.spellCheckByPNU(sentence, HTTP_TIMEOUT, fixTypos, printSentence);
98
+ // Spell check by NAVER service.
99
+ function checkNAVER() {
100
+ hanspell.spellCheckByNAVER(sentence, HTTP_TIMEOUT, fixTypos, printSentence);
100
101
  }
101
102
 
102
- // Spell check by PNU service and DAUM service.
103
+ // Spell check by both NAVER and DAUM services.
103
104
  function checkAll() {
104
105
  const input = sentence;
105
- hanspell.spellCheckByPNU(input, HTTP_TIMEOUT, fixTypos, () =>
106
+ hanspell.spellCheckByNAVER(input, HTTP_TIMEOUT, fixTypos, () =>
106
107
  hanspell.spellCheckByDAUM(input, HTTP_TIMEOUT, fixTypos, printSentence),
107
108
  );
108
109
  }
@@ -151,11 +152,11 @@ function readAndCheck(check) {
151
152
  }
152
153
  }
153
154
 
154
- const HELP = `사용법: hanspell-cli [-d | -p | -a | -h]
155
+ const HELP = `사용법: hanspell-cli [-d | -n | -a | -h]
155
156
 
156
157
  옵션:
157
158
  -d, --daum [default] 다음 서비스를 이용해서 맞춤법을 교정합니다
158
- -p, --pnu 부산대학교 서비스를 이용해서 맞춤법을 교정합니다
159
+ -n, --naver 네이버 서비스를 이용해서 맞춤법을 교정합니다
159
160
  -a, --all 두 서비스의 모든 결과를 반영해서 맞춤법을 교정합니다
160
161
  -h, --info 도움말을 출력합니다
161
162
 
@@ -175,8 +176,8 @@ process.argv.slice(2).forEach((opt) => {
175
176
  readAndCheck(checkAll);
176
177
  } else if (opt === '-d' || opt === '--daum') {
177
178
  readAndCheck(checkDAUM);
178
- } else if (opt === '-p' || opt === '--pnu') {
179
- readAndCheck(checkPNU);
179
+ } else if (opt === '-n' || opt === '--naver') {
180
+ readAndCheck(checkNAVER);
180
181
  } else {
181
182
  console.log(HELP);
182
183
  process.exit(1);
@@ -2,13 +2,16 @@
2
2
  * @fileOverview Interface for DAUM spell checker.
3
3
  */
4
4
 
5
- const Entities = require('html-entities').AllHtmlEntities;
6
- const request = require('request');
5
+ const { decode } = require('html-entities');
7
6
 
8
7
  const split = require('./split-string').byLength;
9
8
 
10
- const entities = new Entities();
11
- const { decode } = entities;
9
+ const DAUM_URL = 'https://dic.daum.net/grammar_checker.do';
10
+ const DAUM_MAX_CHARS = 1000;
11
+ const DAUM_MIN_INTERVAL = 400;
12
+ const DAUM_UA =
13
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
14
+ '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
12
15
 
13
16
  // Parses attribute from server response.
14
17
  function getAttr(string, key) {
@@ -64,67 +67,69 @@ function parseJSON(response) {
64
67
  return typos;
65
68
  }
66
69
 
67
- const DAUM_URL = 'https://dic.daum.net/grammar_checker.do';
68
- const DAUM_MAX_CHARS = 1000;
69
- const DAUM_MIN_INTERVAL = 400;
70
+ async function postOne(sentence, timeout) {
71
+ const controller = new AbortController();
72
+ const timer = setTimeout(() => controller.abort(), timeout);
73
+ try {
74
+ const res = await fetch(DAUM_URL, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'User-Agent': DAUM_UA,
78
+ 'Content-Type': 'application/x-www-form-urlencoded',
79
+ },
80
+ body: new URLSearchParams({ sentence }),
81
+ signal: controller.signal,
82
+ });
83
+ const body = await res.text();
84
+ return { status: res.status, body };
85
+ } finally {
86
+ clearTimeout(timer);
87
+ }
88
+ }
70
89
 
71
90
  // Splits a long sentence, and makes spell check requests to the server.
72
91
  // `check` is called at each short sentence with the parsed JSON parameter.
73
92
  function spellCheck(sentence, timeout, check, end, error) {
74
93
  if (sentence.length === 0) {
75
- if (end !== null) {
76
- end();
77
- }
94
+ if (end) end();
78
95
  return;
79
96
  }
80
97
 
81
98
  // Removes HTML tags.
82
- sentence = sentence.replace(/<[^ㄱ-ㅎㅏ-ㅣ가-힣>]+>/g, '');
83
- const data = split(sentence, '.,\n', DAUM_MAX_CHARS);
99
+ const cleaned = sentence.replace(/<[^ㄱ-ㅎㅏ-ㅣ가-힣>]+>/g, '');
100
+ const data = split(cleaned, '.,\n', DAUM_MAX_CHARS);
84
101
  let count = data.length;
85
102
 
86
- const getResponse = (err, response, body) => {
87
- count -= 1;
88
- if (!err && response.statusCode === 200) {
103
+ const handle = async (part) => {
104
+ try {
105
+ const { status, body } = await postOne(part, timeout);
106
+ if (status !== 200) throw new Error(`HTTP ${status}`);
89
107
  if (body.indexOf('="screen_out">맞춤법 검사기 본문</h2>') === -1) {
90
108
  console.error(
91
109
  `-- 한스펠 오류: 다음 서비스가 유효하지 않은 양식을 반환했습니다. (${DAUM_URL})`,
92
110
  );
93
- console.log(body);
94
- if (error) error(err);
111
+ if (error) error(new Error('invalid response'));
95
112
  } else {
96
113
  check(parseJSON(body));
97
114
  }
98
- } else {
115
+ } catch (err) {
99
116
  console.error(
100
117
  '-- 한스펠 오류: 다음 서버의 접속 오류로 일부 문장 교정에 실패했습니다.',
101
118
  );
102
119
  if (error) error(err);
120
+ } finally {
121
+ count -= 1;
122
+ if (count === 0 && end) end();
103
123
  }
104
- if (count === 0 && end !== null) end();
105
124
  };
106
125
 
107
126
  let i = 0;
108
-
109
- function post() {
110
- request.post(
111
- {
112
- url: DAUM_URL,
113
- timeout,
114
- form: {
115
- sentence: data[i],
116
- },
117
- },
118
- getResponse,
119
- );
120
-
127
+ function next() {
128
+ handle(data[i]);
121
129
  i += 1;
122
- if (i < data.length) {
123
- setTimeout(post, DAUM_MIN_INTERVAL);
124
- }
130
+ if (i < data.length) setTimeout(next, DAUM_MIN_INTERVAL);
125
131
  }
126
-
127
- post();
132
+ next();
128
133
  }
129
134
 
130
135
  module.exports = spellCheck;
package/lib/index.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Type definitions for hanspell.
3
+ */
4
+
5
+ export interface SpellCheckTypo {
6
+ token: string;
7
+ suggestions: string[];
8
+ info?: string;
9
+ }
10
+
11
+ export interface DaumTypo extends SpellCheckTypo {
12
+ type: string;
13
+ context: string;
14
+ }
15
+
16
+ export type NaverTypo = SpellCheckTypo;
17
+
18
+ export type CheckCallback<T extends SpellCheckTypo = SpellCheckTypo> = (
19
+ data: T[],
20
+ ) => void;
21
+ export type EndCallback = () => void;
22
+ export type ErrorCallback = (err: Error | unknown) => void;
23
+
24
+ export function spellCheckByDAUM(
25
+ sentence: string,
26
+ timeout: number,
27
+ check: CheckCallback<DaumTypo>,
28
+ end?: EndCallback | null,
29
+ error?: ErrorCallback,
30
+ ): void;
31
+
32
+ export function spellCheckByNAVER(
33
+ sentence: string,
34
+ timeout: number,
35
+ check: CheckCallback<NaverTypo>,
36
+ end?: EndCallback | null,
37
+ error?: ErrorCallback,
38
+ ): void;
package/lib/index.js CHANGED
@@ -3,6 +3,6 @@
3
3
  */
4
4
 
5
5
  const spellCheckByDAUM = require('./daum-spell-check');
6
- const spellCheckByPNU = require('./pnu-spell-check');
6
+ const spellCheckByNAVER = require('./naver-spell-check');
7
7
 
8
- module.exports = { spellCheckByDAUM, spellCheckByPNU };
8
+ module.exports = { spellCheckByDAUM, spellCheckByNAVER };
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @fileOverview Interface for Naver spell checker.
3
+ */
4
+
5
+ const { decode } = require('html-entities');
6
+
7
+ const split = require('./split-string').byWordCount;
8
+
9
+ const NAVER_MAX_WORDS = 250;
10
+ const NAVER_PROXY_URL =
11
+ 'https://m.search.naver.com/p/csearch/ocontent/util/SpellerProxy';
12
+ const NAVER_PASSPORT_PAGE =
13
+ 'https://search.naver.com/search.naver?query=%EB%A7%9E%EC%B6%A4%EB%B2%95+%EA%B2%80%EC%82%AC%EA%B8%B0';
14
+ const NAVER_UA =
15
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
16
+ '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
17
+
18
+ const COLOR_INFO = {
19
+ red: '맞춤법 오류입니다.',
20
+ green: '띄어쓰기 오류입니다.',
21
+ blue: '표준어 의심이거나 대치어 추천입니다.',
22
+ violet: '통계적 교정입니다.',
23
+ };
24
+
25
+ let cachedPassportKey = null;
26
+
27
+ async function fetchPassportKey() {
28
+ const res = await fetch(NAVER_PASSPORT_PAGE, {
29
+ headers: { 'User-Agent': NAVER_UA },
30
+ });
31
+ const body = await res.text();
32
+ const m = body.match(/passportKey=([a-f0-9]+)/);
33
+ if (!m) {
34
+ throw new Error('네이버 passportKey 추출 실패');
35
+ }
36
+ return m[1];
37
+ }
38
+
39
+ async function getPassportKey(forceRefresh) {
40
+ if (!forceRefresh && cachedPassportKey) return cachedPassportKey;
41
+ cachedPassportKey = await fetchPassportKey();
42
+ return cachedPassportKey;
43
+ }
44
+
45
+ // Strips the `jQuery(...)` JSONP wrapper.
46
+ function unwrapJsonp(body) {
47
+ const start = body.indexOf('(');
48
+ const end = body.lastIndexOf(')');
49
+ if (start === -1 || end === -1 || end <= start) {
50
+ throw new Error('네이버 응답 파싱 실패');
51
+ }
52
+ return JSON.parse(body.substring(start + 1, end));
53
+ }
54
+
55
+ // Parses Naver response into `[{ token, suggestions, info }]`.
56
+ function parseResult(result) {
57
+ if (!result || result.errata_count === 0) return [];
58
+
59
+ const origins = [];
60
+ const reSpan = /<span class='result_underline'>([\s\S]*?)<\/span>/g;
61
+ let m = reSpan.exec(result.origin_html);
62
+ while (m !== null) {
63
+ origins.push(m[1]);
64
+ m = reSpan.exec(result.origin_html);
65
+ }
66
+
67
+ const fixes = [];
68
+ const reEm = /<em class='([a-z]+)_text'>([\s\S]*?)<\/em>/g;
69
+ m = reEm.exec(result.html);
70
+ while (m !== null) {
71
+ fixes.push({ color: m[1], text: m[2] });
72
+ m = reEm.exec(result.html);
73
+ }
74
+
75
+ const len = Math.min(origins.length, fixes.length);
76
+ const typos = [];
77
+ for (let i = 0; i < len; i += 1) {
78
+ typos.push({
79
+ token: decode(origins[i]),
80
+ suggestions: [decode(fixes[i].text)],
81
+ info: COLOR_INFO[fixes[i].color] || '',
82
+ });
83
+ }
84
+ return typos;
85
+ }
86
+
87
+ async function callNaver(text, timeout) {
88
+ const controller = new AbortController();
89
+ const timer = setTimeout(() => controller.abort(), timeout);
90
+
91
+ const tryOnce = async () => {
92
+ const key = await getPassportKey(false);
93
+ const url = `${NAVER_PROXY_URL}?_callback=jQuery&q=${encodeURIComponent(
94
+ text,
95
+ )}&where=nexearch&color_blindness=0&passportKey=${key}`;
96
+ return fetch(url, {
97
+ headers: {
98
+ 'User-Agent': NAVER_UA,
99
+ Referer: 'https://search.naver.com/',
100
+ },
101
+ signal: controller.signal,
102
+ });
103
+ };
104
+
105
+ try {
106
+ let res = await tryOnce();
107
+ let body = await res.text();
108
+ if (body.includes('유효한 키가 아닙니다')) {
109
+ cachedPassportKey = null;
110
+ res = await tryOnce();
111
+ body = await res.text();
112
+ }
113
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
114
+ return body;
115
+ } finally {
116
+ clearTimeout(timer);
117
+ }
118
+ }
119
+
120
+ // Splits a long sentence, and makes spell check requests to the server.
121
+ // `check` is called at each short sentence with the parsed array.
122
+ function spellCheck(sentence, timeout, check, end, error) {
123
+ if (sentence.length === 0) {
124
+ if (end) end();
125
+ return;
126
+ }
127
+
128
+ // Removes HTML tags.
129
+ const cleaned = sentence.replace(/<[^ㄱ-ㅎㅏ-ㅣ가-힣>]+>/g, '');
130
+ const parts = split(cleaned, NAVER_MAX_WORDS);
131
+ let count = parts.length;
132
+
133
+ const handleOne = async (part) => {
134
+ try {
135
+ const body = await callNaver(part, timeout);
136
+ const json = unwrapJsonp(body);
137
+ const result = json && json.message && json.message.result;
138
+ if (!result) {
139
+ const err = (json && json.message && json.message.error) || '응답 없음';
140
+ console.error(
141
+ `-- 한스펠 오류: 네이버 서비스가 유효하지 않은 양식을 반환했습니다. (${err})`,
142
+ );
143
+ if (error) error(new Error(err));
144
+ } else {
145
+ check(parseResult(result));
146
+ }
147
+ } catch (err) {
148
+ console.error(
149
+ '-- 한스펠 오류: 네이버 서버의 접속 오류로 일부 문장 교정에 실패했습니다.',
150
+ );
151
+ if (error) error(err);
152
+ } finally {
153
+ count -= 1;
154
+ if (count === 0 && end) end();
155
+ }
156
+ };
157
+
158
+ parts.forEach(handleOne);
159
+ }
160
+
161
+ module.exports = spellCheck;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hanspell",
3
- "version": "0.9.7",
4
- "description": "(주)다음과 부산대학교 인공지능연구실/(주)나라인포테크의 온라인 서비스를 이용한 한글 맞춤법 검사기.",
3
+ "version": "0.10.0",
4
+ "description": "(주)다음과 네이버(주) 온라인 서비스를 이용한 한글 맞춤법 검사기.",
5
5
  "homepage": "https://github.com/9beach/hanspell",
6
6
  "author": {
7
7
  "name": "9beach",
@@ -16,6 +16,10 @@
16
16
  },
17
17
  "license": "MIT",
18
18
  "main": "lib/index.js",
19
+ "types": "lib/index.d.ts",
20
+ "files": [
21
+ "lib/"
22
+ ],
19
23
  "scripts": {
20
24
  "all": "npm run lint && npm test",
21
25
  "test": "mocha && test/cli.test.sh",
@@ -25,6 +29,9 @@
25
29
  "bin": {
26
30
  "hanspell-cli": "lib/cli.js"
27
31
  },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
28
35
  "keywords": [
29
36
  "한글",
30
37
  "맞춤법",
@@ -38,20 +45,17 @@
38
45
  "SpellCheck"
39
46
  ],
40
47
  "dependencies": {
41
- "chalk": "^2.4.2",
42
- "html-entities": "^1.2.1",
43
- "minimatch": "^3.0.4",
44
- "readline": "^1.3.0",
45
- "request": "^2.65.0",
46
- "unescape": "^1.0.1"
48
+ "chalk": "^4.1.2",
49
+ "html-entities": "^2.6.0",
50
+ "minimatch": "^10.2.5"
47
51
  },
48
52
  "devDependencies": {
49
- "eslint": "^7.19.0",
50
- "eslint-config-airbnb-base": "^14.2.1",
51
- "eslint-config-prettier": "^7.2.0",
52
- "eslint-plugin-import": "^2.22.1",
53
- "eslint-plugin-prettier": "^3.3.1",
54
- "mocha": "^9.1.2",
55
- "prettier": "^2.2.1"
53
+ "eslint": "^8.57.1",
54
+ "eslint-config-airbnb-base": "^15.0.0",
55
+ "eslint-config-prettier": "^9.1.0",
56
+ "eslint-plugin-import": "^2.32.0",
57
+ "eslint-plugin-prettier": "^5.5.5",
58
+ "mocha": "^11.7.6",
59
+ "prettier": "^3.8.3"
56
60
  }
57
61
  }
package/.eslintrc.yml DELETED
@@ -1,15 +0,0 @@
1
- env:
2
- browser: true
3
- commonjs: true
4
- es2021: true
5
- mocha: true
6
- extends:
7
- - eslint:recommended
8
- - airbnb-base
9
- - plugin:prettier/recommended
10
- parserOptions:
11
- ecmaVersion: 12
12
- rules:
13
- max-len: ["error", {"code": 80, "ignoreUrls": true}]
14
- prettier/prettier: "error"
15
- no-console: "off"
package/.prettierrc DELETED
@@ -1,6 +0,0 @@
1
- singleQuote: true
2
- semi: true
3
- useTabs: false
4
- tabWidth: 2
5
- trailingComma: "all"
6
- printWidth: 80
package/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - 15
4
- - 10
5
- before_script:
6
- - npm run lint
@@ -1,108 +0,0 @@
1
- /**
2
- * @fileOverview Interface for Pusan National University spell checker.
3
- */
4
-
5
- const request = require('request');
6
- const Entities = require('html-entities').AllHtmlEntities;
7
-
8
- const split = require('./split-string').byWordCount;
9
-
10
- const entities = new Entities();
11
- const { decode } = entities;
12
-
13
- // Parses server response.
14
- function parseJSON(response) {
15
- try {
16
- return response
17
- .match(/\tdata = \[.*;/g)
18
- .map((data) => JSON.parse(data.substring(8, data.length - 1)))[0][0]
19
- .errInfo.map((pnutypo) => {
20
- let suggestions = pnutypo.candWord.replace(/\|$/, '');
21
- if (suggestions === '') {
22
- suggestions = decode(pnutypo.orgStr);
23
- }
24
- const info = pnutypo.help
25
- .replace(/< *[bB][rR] *\/>/g, '\n')
26
- .replace(/\n\n/g, '\n')
27
- .replace(/\n\(예\) /g, '\n(예)\n')
28
- .replace(/ \(예\) /g, '\n(예)\n')
29
- .replace(/ */g, '\n');
30
-
31
- return {
32
- token: decode(pnutypo.orgStr),
33
- suggestions: decode(suggestions).split('|'),
34
- info: decode(info),
35
- };
36
- });
37
- } catch (err) {
38
- if (
39
- response.indexOf(
40
- '기술적 한계로 찾지 못한 맞춤법 오류나 문법 오류가 있을 수 있습니다.',
41
- ) !== -1
42
- ) {
43
- console.error(
44
- '-- 한스펠 오류: 부산대 서비스가 유효하지 않은 양식을 반환했습니다.',
45
- );
46
- }
47
- }
48
-
49
- return [];
50
- }
51
-
52
- const PNU_MAX_WORDS = 250;
53
- const PNU_URL = 'http://speller.cs.pusan.ac.kr/results';
54
-
55
- // Splits a long sentence, and makes spell check requests to the server.
56
- // `check` is called at each short sentence with the parsed JSON parameter.
57
- function spellCheck(sentence, timeout, check, end, error) {
58
- if (sentence.length === 0) {
59
- if (end !== null) {
60
- end();
61
- }
62
- return;
63
- }
64
-
65
- // Removes HTML tags.
66
- sentence = sentence.replace(/<[^ㄱ-ㅎㅏ-ㅣ가-힣>]+>/g, '');
67
- // Due to PNU server's weird behavior, replaces '\n' to '\n '.
68
- const data = split(
69
- `${sentence.replace(/([^\r])\n/g, '$1\r\n')}\r\n`,
70
- PNU_MAX_WORDS,
71
- );
72
- let count = data.length;
73
-
74
- const getResponse = (err, response, body) => {
75
- if (!err && response.statusCode === 200) {
76
- if (body.indexOf('<title>한국어 맞춤법/문법 검사기</title>') === -1) {
77
- console.error(
78
- `-- 한스펠 오류: 부산대 서비스가 유효하지 않은 양식을 반환했습니다. (${PNU_URL})`,
79
- );
80
- if (error) error(err);
81
- } else {
82
- check(parseJSON(body));
83
- }
84
- } else {
85
- console.error(
86
- '-- 한스펠 오류: 부산대 서버의 접속 오류로 일부 문장 교정에 실패했습니다.',
87
- );
88
- if (error) error(err);
89
- }
90
- count -= 1;
91
- if (count === 0 && end !== null) end();
92
- };
93
-
94
- data.forEach((part) =>
95
- request.post(
96
- {
97
- url: PNU_URL,
98
- timeout,
99
- form: {
100
- text1: part,
101
- },
102
- },
103
- getResponse,
104
- ),
105
- );
106
- }
107
-
108
- module.exports = spellCheck;