├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── .jsdoc.json ├── .npmignore ├── .travis.yml ├── API.js ├── ExtUtil.js ├── LICENSE ├── README.md ├── Util.js ├── _config.yml ├── babel.config.js ├── common.js ├── data.js ├── docs ├── .nojekyll ├── API.js.html ├── ExtUtil.js.html ├── Util.js.html ├── data.js.html ├── index.html ├── module-koalanlp_API.html ├── module-koalanlp_ExtUtil.html ├── module-koalanlp_Util.html ├── module-koalanlp_data.CoreferenceGroup.html ├── module-koalanlp_data.DAGEdge.html ├── module-koalanlp_data.DepEdge.html ├── module-koalanlp_data.Entity.html ├── module-koalanlp_data.Morpheme.html ├── module-koalanlp_data.RoleEdge.html ├── module-koalanlp_data.Sentence.html ├── module-koalanlp_data.SyntaxTree.html ├── module-koalanlp_data.Tree.html ├── module-koalanlp_data.Word.html ├── module-koalanlp_data.html ├── module-koalanlp_proc.Dictionary.html ├── module-koalanlp_proc.EntityRecognizer.html ├── module-koalanlp_proc.Parser.html ├── module-koalanlp_proc.RoleLabeler.html ├── module-koalanlp_proc.SentenceSplitter.html ├── module-koalanlp_proc.Tagger.html ├── module-koalanlp_proc.UTagger.html ├── module-koalanlp_proc.html ├── module-koalanlp_types.CoarseEntityType.html ├── module-koalanlp_types.DependencyTag.html ├── module-koalanlp_types.POS.html ├── module-koalanlp_types.PhraseTag.html ├── module-koalanlp_types.RoleType.html ├── module-koalanlp_types.html ├── proc.js.html ├── scripts │ ├── collapse.js │ ├── linenumber.js │ ├── nav.js │ ├── polyfill.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js │ └── search.js ├── styles │ ├── jsdoc.css │ └── prettify.css └── types.js.html ├── index.js ├── jest.config.js ├── jvm.js ├── package.json ├── proc.js ├── publish.sh ├── scripts ├── khaiii_install.sh └── utagger_install.sh ├── src ├── API.js ├── ExtUtil.js ├── Util.js ├── common.js ├── data.js ├── jvm.js ├── proc.js └── types.js ├── test ├── datacheck.js ├── dictionary.js ├── execute.all.js ├── extension.js ├── khaiiiTest.js ├── proc.js ├── proc_common.js ├── type.js └── utaggerTest.js ├── types.js └── yarn.lock /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 기초정보 2 | - Node.js 버전은요? (Version of the Node.js)? 3 | - KoalaNLP 버전은요? (Version of KoalaNLP)? 4 | - 문제의 유형은 어떤 것인가요? (Type of this issue)? 5 | - [ ] 버그리포트. Bug report 6 | - [ ] 개선점제안. Idea/Suggestion for a improvement 7 | - [ ] 사용법질문. Question about using KoalaNLP 8 | - [ ] 기타. Other 9 | 10 | ### 재연을 위한 정보 11 | - 어떤 문장을 시도하셨습니까? (Which sentence/paragraph did you try?) 12 | ```text 13 | [Write the sentence here/여기에 문장을 넣어주세요] 14 | ``` 15 | - KoalaNLP를 사용한 코드 부분을 보여주세요. (Please show your code snippet which uses KoalaNLP.) 16 | ```scala 17 | [Write your code snippet here. Don't forget to change the language type at the above line, 'scala'.] 18 | [여기에 사용한 코드부분을 보여주세요. 윗줄에 'scala'라고 되어있는 언어유형을 사용중인 언어로 바꾸는것을 권합니다.] 19 | ``` 20 | 21 | ### 본문 22 | - 아래에 본문을 입력해주세요. (Describe your issue here.) 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | ### 이슈를 참조해주세요. (Reference of the issue) 2 | 이 Pull Request는 이슈 #[번호]를 수정하기 위한 것입니다. 3 | This is a fix for issue #[number] 4 | 5 | ### 무엇이 변경되었나요? (Description of the changes) 6 | - 어떤 파일의, 어떤 부분을, 어떻게 바꾸셨나요? 7 | - Which file did you modified? And how? 8 | 9 | ### 확인자 (Reviewer) 10 | - 누가 이 Request를 확인하고 승인해야하나요? 아래와 같이 Mention해 주세요. 11 | - Who are responsible for review? Please mention them. 12 | Author @[author]. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | # Output of 'npm pack' 46 | *.tgz 47 | 48 | # Yarn Integrity file 49 | .yarn-integrity 50 | 51 | ### JetBrains template 52 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 53 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 54 | 55 | # User-specific stuff: 56 | .idea 57 | 58 | ## File-based project format: 59 | *.iws 60 | 61 | ## Plugin-specific files: 62 | 63 | # IntelliJ 64 | /out/ 65 | 66 | # mpeltonen/sbt-idea plugin 67 | .idea_modules/ 68 | 69 | # JIRA plugin 70 | atlassian-ide-plugin.xml 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | fabric.properties 77 | 78 | coverage/ -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "plugins/markdown" 4 | ], 5 | "sourceType": "module", 6 | "source": { 7 | "include": ["src/", "README.md"], 8 | "exclude": ["src/jvm.js"] 9 | }, 10 | "tags": { 11 | "allowUnknownTags": true, 12 | "dictionaries": ["jsdoc","closure"] 13 | }, 14 | "templates": { 15 | "cleverLinks": false, 16 | "monospaceLinks": true 17 | }, 18 | "opts": { 19 | "template": "node_modules/docdash", 20 | "encoding": "utf8", 21 | "destination": "docs/", 22 | "recurse": true, 23 | "verbose": true 24 | }, 25 | "docdash": { 26 | "sort": false, 27 | "sectionOrder": [ 28 | "Modules", 29 | "Classes", 30 | "Externals", 31 | "Events", 32 | "Namespaces", 33 | "Mixins", 34 | "Tutorials", 35 | "Interfaces" 36 | ], 37 | "search": true, 38 | "collapse": true, 39 | "typedefs": false, 40 | "menu": { 41 | "사용법 안내": { 42 | "href": "https://koalanlp.github.io/koalanlp/usage", 43 | "target":"_blank", 44 | "class":"menu-item", 45 | "id":"website_link" 46 | }, 47 | "참고: Java/Kotlin API": { 48 | "href": "https://koalanlp.github.io/koalanlp", 49 | "target":"_blank", 50 | "class":"menu-item", 51 | "id":"website_link" 52 | }, 53 | "참고: Scala API": { 54 | "href": "https://koalanlp.github.io/scala-support", 55 | "target":"_blank", 56 | "class":"menu-item", 57 | "id":"website_link" 58 | }, 59 | "참고: Python3 API": { 60 | "href": "https://koalanlp.github.io/python-support", 61 | "target":"_blank", 62 | "class":"menu-item", 63 | "id":"website_link" 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | src/ 3 | docs/ 4 | test/ 5 | *.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | matrix: 3 | include: 4 | - name: "Windows with OpenJDK 11 (LTS)" 5 | os: windows 6 | language: shell 7 | filter_secrets: false 8 | before_install: 9 | - choco install openjdk11 -y 10 | - choco install nodejs-lts -y 11 | - choco install yarn -y 12 | - export PATH=$(cmd.exe //c "refreshenv > nul & C:\Progra~1\Git\bin\bash -c 'echo \$PATH' ") 13 | - java --version 14 | - node -v 15 | env: 16 | - JAVA_HOME="/c/Progra~1/OpenJDK/openjdk-11.0.11_9" 17 | cache: 18 | directories: 19 | - $HOME/.m2 20 | - $HOME/utagger 21 | - name: "OSX11.3 with OpenJDK 11 (LTS)" 22 | os: osx 23 | osx_image: xcode12u 24 | language: shell 25 | jdk: openjdk11 26 | before_install: 27 | - brew install yarn 28 | - java --version 29 | - node -v 30 | - export JAVA_HOME=$(dirname $(readlink $(which javac)))/java_home 31 | - export DYLD_INSERT_LIBRARIES="${JAVA_HOME}/lib/server/libjvm.dylib" 32 | - echo $JAVA_HOME 33 | env: 34 | - LDFLAGS="${LDFLAGS} -L$(brew --prefix zlib)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix openssl)/lib" 35 | - CPPFLAGS="${CPPFLAGS} -I$(brew --prefix zlib)/include -I$(brew --prefix readline)/include -I$(brew --prefix openssl)/include -I$(xcrun --show-sdk-path)/include -I${JAVA_HOME}/include/darwin/" 36 | - PKG_CONFIG_PATH="${PKG_CONFIG_PATH} $(brew --prefix zlib)/lib/pkgconfig" 37 | cache: 38 | directories: 39 | - $HOME/.m2 40 | - $HOME/khaiii-orig 41 | - $HOME/.hunter 42 | - name: "Ubuntu Focal with OpenJDK 11 (LTS)" 43 | dist: focal 44 | jdk: openjdk11 45 | node_js: "lts/*" 46 | before_install: 47 | - java --version 48 | - node -v 49 | cache: 50 | directories: 51 | - $HOME/.m2 52 | - $HOME/khaiii-orig 53 | - $HOME/.hunter 54 | script: 55 | - yarn run test 56 | after_success: 57 | - if [ $TRAVIS_OS_NAME == 'linux' ]; then ./node_modules/.bin/codecov; fi 58 | install: 59 | - yarn install --force 60 | - if [ $TRAVIS_OS_NAME != 'windows' ]; then bash ./scripts/khaiii_install.sh; fi 61 | - if [ $TRAVIS_OS_NAME == 'windows' ]; then bash ./scripts/utagger_install.sh; fi 62 | env: 63 | global: 64 | - secure: kj3rDRzVlZo83j1IJKVn9aYLW6/S9ZL8voe3Bc3wr9kFdKIG13yIfxqIvnxSOt2dvX49gEYSBAurXhDIQXSWsq96cC0QDIGU2Htkw5wHU7RMpFf0CNfIuetbQOVxp088IFMOJeBQDHHoZTU77NEPg+QFyJMJqTiYTdJtZkKuMsn9MB6QJsAg5S7BbEKjMpMwXIe7FsMlNgbLOqUapX5RVp0OPN8pWV0SZxl8Z4z4i0D1XtElhYaIRJM6cg3X5G8maoBqov1D0iHq5w//RoH52CshxO0UUApKKEPAhpScDjkvdM4EtVm1lzeWD8coRhQ2aALKHpRZAoICJAVhmkjIa8SFJ4r/TlEDEiBtXdS2CkHES3KMi6dE6fC8o3UfEMrPfWTIhMhm/F0YtnQuGO7upym+AUdwVaxlx3JPop05wMwtPTaERKl3IDF1COh6TOWCMQUXfkI8jIpZvbTxf0SVDUOa8XlSORdFO3MRbUbk/k6Hmh29Ms16jRj4bPDVx8TXHHxL4pQ30JcyCuXh2H5Z0zRjAbeLCDxVgDp0ycvfxt+DR9jWpsyGaglfCFYr99nqqtDhexxXOmh52dpfVKLw1Ybx3Cy3Gy0TiDnX+6HKxmECC1hmrzEotXmTQM3UixMo8hTMrx439rdvbChZ3SfJTB/bBk/6nLWznkXdD7bq5AY= 65 | - secure: kZzjdvKk1+PuMzKIdoSmuaD6P8VTRCJeWizVq8/QCTXBAO9MYvjsG7/OkRsdobDsVnPc+XVuQzWPv/VFytCfQHO1YeC5UmAqfM+r/X3UQT8w3yi8M/ZR+Y8Q6D+6s3grldd3ocVvoSwasuxRuTBGcnIRA5QOfvh7r43JIBGGm+C3REenVplJZchJTuE+kAgw4eSCbt0AArE3Evtm7N9bnQNdoNCdjoLdW0aP3zqNc/2hvaYGLyeX4gdrVF19QUqEvpqNYI7dYg4IYgD+ISAmESx+dd84IjSZ8Q/hkNbgQ2/c0IWYmr3aqxOI2+YebISn/d1VFox4PIF8McJmb4LZ1y9FwVV/ZjLCuv4MUgk63Ytq9mM2QRJS8mqPjS4ei1yHI50Wn3US2BOWGQyvv0F/zMJU4VVy9Uze0B6Z9JUJEk9hQAHMq3Epc8CO4Bupka/buLg5j++dD0ksa2i9K/R+3CWGr5nFJB+hMKnDUZAtx2v93C/ENEQtJnsYYDRwx6qa5s+l3TmvS8mCXzyZ+oOwr8JOgiGbxvASq93+3a+2dF/hl4nXfOysh49x71fszztNzuyMek/OQxsPOl6ielkRApIDILTg+D6rHTVxeBaeriBohSOReLg7bW0XqxBNvuhncSapW7j4LwIyZv+F+TqlpHhWySME0+5PgZfyU3AMO/c= 66 | - KHAIII_RSC=$HOME/khaiii-orig/build/share/khaiii 67 | - KHAIII_LIB=$HOME/khaiii-orig/build/lib -------------------------------------------------------------------------------- /API.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.query = query; 7 | exports.getPackage = getPackage; 8 | exports.PACKAGE_REQUIRE_ASSEMBLY = exports.CORE = exports.UTAGGER = exports.KHAIII = exports.ETRI = exports.DAON = exports.OKT = exports.RHINO = exports.ARIRANG = exports.EUNJEON = exports.KKMA = exports.KMR = exports.HNN = void 0; 9 | 10 | var _jvm = require("./jvm"); 11 | 12 | /** 13 | * KoalaNLP가 지원하는 패키지의 목록을 키값으로 가지고 있습니다. 14 | * 15 | * * **주의** API를 import한다고 하여 자동으로 초기화가 되지 않으니 꼭 {@link module:koalanlp/Util.initialize}를 실행하시기 바랍니다. 16 | * 17 | * @module koalanlp/API 18 | * @example 19 | * import * as API from 'koalanlp/API'; 20 | * **/ 21 | 22 | /** 23 | * @public 24 | * @typedef {string} API 25 | */ 26 | 27 | /** 28 | * 한나눔. 29 | * 현재 버전이 최신입니다. 문장분리, 품사분석, 구문분석, 의존분석이 가능합니다. 30 | * @type API 31 | * @example 32 | * import {HNN} from 'koalanlp/API'; 33 | */ 34 | const HNN = 'hnn'; 35 | /** 36 | * 코모란. 37 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 38 | * @type API 39 | * @example 40 | * import {KMR} from 'koalanlp/API'; 41 | */ 42 | 43 | exports.HNN = HNN; 44 | const KMR = 'kmr'; 45 | /** 46 | * 꼬꼬마. 47 | * 현재 버전이 최신입니다. 품사분석, 의존분석만 가능합니다. 48 | * @type API 49 | * @example 50 | * import {KKMA} from 'koalanlp/API'; 51 | */ 52 | 53 | exports.KMR = KMR; 54 | const KKMA = 'kkma'; 55 | /** 56 | * 은전한닢. 57 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 58 | * @type API 59 | * @example 60 | * import {EUNJEON} from 'koalanlp/API'; 61 | */ 62 | 63 | exports.KKMA = KKMA; 64 | const EUNJEON = 'eunjeon'; 65 | /** 66 | * 아리랑. 67 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 68 | * @type API 69 | * @example 70 | * import {ARIRANG} from 'koalanlp/API'; 71 | */ 72 | 73 | exports.EUNJEON = EUNJEON; 74 | const ARIRANG = 'arirang'; 75 | /** 76 | * 라이노. 77 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 78 | * @type API 79 | * @example 80 | * import {RHINO} from 'koalanlp/API'; 81 | */ 82 | 83 | exports.ARIRANG = ARIRANG; 84 | const RHINO = 'rhino'; 85 | /** 86 | * 트위터. 87 | * 현재 버전이 최신입니다. 문장분리, 품사분석만 가능합니다. 88 | * @type API 89 | * @example 90 | * import {OKT} from 'koalanlp/API'; 91 | */ 92 | 93 | exports.RHINO = RHINO; 94 | const OKT = 'okt'; 95 | /** 96 | * 다온. 97 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 98 | * @type API 99 | * @example 100 | * import {DAON} from 'koalanlp/API'; 101 | */ 102 | 103 | exports.OKT = OKT; 104 | const DAON = 'daon'; 105 | /** 106 | * ETRI Open API. 107 | * 현재 버전이 최신입니다. 108 | * @type API 109 | * @example 110 | * import {ETRI} from 'koalanlp/API'; 111 | */ 112 | 113 | exports.DAON = DAON; 114 | const ETRI = 'etri'; 115 | /** 116 | * Kakao Khaiii (Experimental) 117 | * 현재 버전이 최신입니다. 118 | * 119 | * **(참고)** 120 | * - 이 기능은 현재 실험적인 기능입니다. 121 | * - Khaiii는 C++로 개발되어 별도 설치가 필요합니다. [Khaiii](https://github.com/kakao/khaiii) 공식 홈페이지에서 설치법을 확인하시고, 설치하시면 됩니다. 122 | * 123 | * @type API 124 | * @example 125 | * import {KHAIII} from 'koalanlp/API'; 126 | */ 127 | 128 | exports.ETRI = ETRI; 129 | const KHAIII = 'khaiii'; 130 | /** 131 | * UTagger (Experimental) 132 | * 현재 버전이 최신입니다. 133 | * 134 | * **(참고)** 135 | * - 이 기능은 현재 실험적인 기능입니다. 136 | * - UTagger는 C로 개발되어 별도 설치가 필요합니다. [UTagger install](https://koalanlp.github.io/usage/Install-UTagger.md)에서 설치법을 확인하시고, 설치하시면 됩니다. 137 | * 138 | * @type API 139 | * @example 140 | * import {UTAGGER} from 'koalanlp/API'; 141 | */ 142 | 143 | exports.KHAIII = KHAIII; 144 | const UTAGGER = 'utagger'; 145 | /** 146 | * 분석기 Interface 정의 라이브러리. 147 | * 현재 버전이 최신입니다. 편의기능을 제공하며 타 분석기 참조시 함께 참조됩니다. 148 | * @type API 149 | * @example 150 | * import {CORE} from 'koalanlp/API'; 151 | */ 152 | 153 | exports.UTAGGER = UTAGGER; 154 | const CORE = 'core'; 155 | /** 156 | * 'assembly' classifier 필요 여부 157 | * @private 158 | */ 159 | 160 | exports.CORE = CORE; 161 | const PACKAGE_REQUIRE_ASSEMBLY = [HNN, KKMA, ARIRANG, RHINO, DAON]; 162 | /** 163 | * 해당 API가 분석기를 지원하는지 확인함. 164 | * @param {!API} api 분석기 API 165 | * @param {!string} type 분석기 유형 166 | * @returns {Object} 지원한다면 해당 분석기 Java 클래스. 167 | * @private 168 | */ 169 | 170 | exports.PACKAGE_REQUIRE_ASSEMBLY = PACKAGE_REQUIRE_ASSEMBLY; 171 | 172 | function query(api, type) { 173 | try { 174 | return _jvm.JVM.koalaClassOf(api, type); 175 | } catch (e) { 176 | throw Error(`API.${api}에서 ${type}을 불러오는 데 실패했습니다! ${type}이 없거나, 다운로드가 완전하지 않았을 수 있습니다. Cause: ${e}`); 177 | } 178 | } 179 | /** 180 | * API의 패키지 이름 반환 181 | * @param {string} api 분석기 API 182 | * @return {string} 패키지 이름 접미사 183 | * @private 184 | */ 185 | 186 | 187 | function getPackage(api) { 188 | return api.toLowerCase(); 189 | } -------------------------------------------------------------------------------- /ExtUtil.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.alphaToHangul = alphaToHangul; 7 | exports.hangulToAlpha = hangulToAlpha; 8 | exports.isAlphaPronounced = isAlphaPronounced; 9 | exports.isHanja = isHanja; 10 | exports.isCJKHanja = isCJKHanja; 11 | exports.hanjaToHangul = hanjaToHangul; 12 | exports.isCompleteHangul = isCompleteHangul; 13 | exports.isIncompleteHangul = isIncompleteHangul; 14 | exports.isHangul = isHangul; 15 | exports.isHangulEnding = isHangulEnding; 16 | exports.isChosungJamo = isChosungJamo; 17 | exports.isJungsungJamo = isJungsungJamo; 18 | exports.isJongsungJamo = isJongsungJamo; 19 | exports.isJongsungEnding = isJongsungEnding; 20 | exports.getChosung = getChosung; 21 | exports.getJungsung = getJungsung; 22 | exports.getJongsung = getJongsung; 23 | exports.dissembleHangul = dissembleHangul; 24 | exports.assembleHangulTriple = assembleHangulTriple; 25 | exports.assembleHangul = assembleHangul; 26 | exports.correctVerbApply = correctVerbApply; 27 | exports.ChoToJong = exports.HanLastList = exports.HanSecondList = exports.HanFirstList = void 0; 28 | 29 | var _jvm = require("./jvm"); 30 | 31 | var _underscore = _interopRequireDefault(require("underscore")); 32 | 33 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 34 | 35 | /** 36 | * 여러 편의기능을 모아놓은 Module입니다. 37 | * @module koalanlp/ExtUtil 38 | * @example 39 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 40 | **/ 41 | 42 | /** 43 | * 초성 조합형 문자열 리스트 (UNICODE 순서) 44 | * 45 | * 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' 46 | * 47 | * @type {ReadonlyArray} 48 | * @example 49 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 50 | * console.log(ExtUtil.HanFirstList[0]); 51 | */ 52 | const HanFirstList = Object.freeze(_underscore.default.range(0x1100, 0x1112 + 1).map(x => String.fromCharCode(x))); 53 | /** 54 | * 중성 조합형 문자열 리스트 (UNICODE 순서) 55 | * 56 | * 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ' 57 | * 58 | * @type {ReadonlyArray} 59 | * @example 60 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 61 | * console.log(ExtUtil.HanSecondList[0]); 62 | */ 63 | 64 | exports.HanFirstList = HanFirstList; 65 | const HanSecondList = Object.freeze(_underscore.default.range(0x1161, 0x1175 + 1).map(x => String.fromCharCode(x))); 66 | /** 67 | * 종성 조합형 문자열 리스트 (UNICODE 순서). 가장 첫번째는 null (받침 없음) 68 | * 69 | * undefined, 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 70 | * 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' 71 | * 72 | * @type {ReadonlyArray} 73 | * @example 74 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 75 | * console.log(ExtUtil.HanLastList[0]); 76 | */ 77 | 78 | exports.HanSecondList = HanSecondList; 79 | const HanLastList = Object.freeze(_underscore.default.range(0x11A7, 0x11C2 + 1).map(x => { 80 | if (x === 0x11A7) return undefined;else return String.fromCharCode(x); 81 | })); 82 | /** 83 | * 초성 문자를 종성 조합형 문자로 변경할 수 있는 map 84 | * @type {Readonly>} 85 | * @example 86 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 87 | * console.log(ExtUtil.ChoToJong.get('ㄵ')); 88 | */ 89 | 90 | exports.HanLastList = HanLastList; 91 | const ChoToJong = Object.freeze(new Map([['\u1100', '\u11A8'], // ㄱ 92 | ['\u1101', '\u11A9'], // ㄲ 93 | ['\u1102', '\u11AB'], // ㄴ 94 | ['\u1103', '\u11AE'], // ㄷ 95 | ['\u1105', '\u11AF'], // ㄹ 96 | ['\u1106', '\u11B7'], // ㅁ 97 | ['\u1107', '\u11B8'], // ㅂ 98 | ['\u1109', '\u11BA'], // ㅅ 99 | ['\u110A', '\u11BB'], // ㅆ 100 | ['\u110B', '\u11BC'], // ㅇ 101 | ['\u110C', '\u11BD'], // ㅈ 102 | ['\u110E', '\u11BE'], // ㅊ 103 | ['\u110F', '\u11BF'], // ㅋ 104 | ['\u1110', '\u11C0'], // ㅌ 105 | ['\u1111', '\u11C1'], // ㅍ 106 | ['\u1112', '\u11C2'], // ㅎ 107 | // 아래는 완성형 문자 108 | ['ㄱ', '\u11A8'], // ㄱ 109 | ['ㄲ', '\u11A9'], ['ㄳ', '\u11AA'], ['ㄴ', '\u11AB'], // ㄴ 110 | ['ㄵ', '\u11AC'], ['ㄶ', '\u11AD'], ['ㄷ', '\u11AE'], // ㄷ 111 | ['ㄹ', '\u11AF'], // ㄹ 112 | ['ㄺ', '\u11B0'], ['ㄻ', '\u11B1'], ['ㄼ', '\u11B2'], ['ㄽ', '\u11B3'], ['ㄾ', '\u11B4'], ['ㄿ', '\u11B5'], ['ㅀ', '\u11B6'], ['ㅁ', '\u11B7'], // ㅁ 113 | ['ㅂ', '\u11B8'], // ㅂ 114 | ['ㅄ', '\u11B9'], ['ㅅ', '\u11BA'], // ㅅ 115 | ['ㅆ', '\u11BB'], // ㅆ 116 | ['ㅇ', '\u11BC'], // ㅇ 117 | ['ㅈ', '\u11BD'], // ㅈ 118 | ['ㅊ', '\u11BE'], // ㅊ 119 | ['ㅋ', '\u11BF'], // ㅋ 120 | ['ㅌ', '\u11C0'], // ㅌ 121 | ['ㅍ', '\u11C1'], // ㅍ 122 | ['ㅎ', '\u11C2'] // ㅎ 123 | ])); 124 | /** 125 | * 주어진 문자열에서 알파벳이 발음되는 대로 국문 문자열로 표기하여 값으로 돌려줍니다. 126 | * 127 | * @param {!string} text 알파벳을 발음할 문자열 128 | * @returns {string} 국문 발음 표기된 문자열 129 | * @example 130 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 131 | * console.log(ExtUtil.alphaToHangul("갤럭시S")); 132 | */ 133 | 134 | exports.ChoToJong = ChoToJong; 135 | 136 | function alphaToHangul(text) { 137 | return _jvm.JVM.koalaClassOf('ExtUtil').alphaToHangul(text).toString(); 138 | } 139 | /** 140 | * 주어진 문자열에 적힌 알파벳 발음을 알파벳으로 변환하여 문자열로 반환합니다. 141 | * @param {!string} text 국문 발음 표기된 문자열 142 | * @returns {string} 영문 변환된 문자열 143 | * @example 144 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 145 | * console.log(ExtUtil.hangulToAlpah("갤럭시에스")); 146 | */ 147 | 148 | 149 | function hangulToAlpha(text) { 150 | return _jvm.JVM.koalaClassOf('ExtUtil').hangulToAlpha(text).toString(); 151 | } 152 | /** 153 | * 주어진 문자열이 알파벳이 발음되는 대로 표기된 문자열인지 확인합니다. 154 | * @param {!string} text 확인할 문자열 155 | * @returns {boolean} 영문 발음으로만 구성되었다면 true 156 | * @example 157 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 158 | * console.log(ExtUtil.isAlphaPronounced("갤럭시에스")); 159 | */ 160 | 161 | 162 | function isAlphaPronounced(text) { 163 | return _jvm.JVM.koalaClassOf('ExtUtil').isAlphaPronounced(text); 164 | } 165 | /** 166 | * 문자열을 순회하면서 charFunction을 실행합니다. 167 | * @param {!string} text 순회할 문자열 168 | * @param charFunction 문자별로 실행할 함수 169 | * @returns {Array} 문자별 결과. 170 | * @private 171 | */ 172 | 173 | 174 | function stringRepeat(text, charFunction) { 175 | let result = []; 176 | 177 | for (let ch of text) { 178 | let res = charFunction(_jvm.JVM.char(ch)); 179 | res = res === null ? undefined : res; 180 | result.push(res); 181 | } 182 | 183 | return result; 184 | } 185 | /** 186 | * 문자열의 각 문자가 한자 범위인지 확인합니다. 187 | * @param {!string} text 확인할 문자열 188 | * @returns {boolean[]} 문자열 문자의 위치마다 한자인지 아닌지를 표기한 리스트. 한자라면 True. 189 | * @example 190 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 191 | * console.log(ExtUtil.isHanja("貝波通水")); 192 | */ 193 | 194 | 195 | function isHanja(text) { 196 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isHanja); 197 | } 198 | /** 199 | * 현재 문자가 한중일 통합한자, 통합한자 확장 - A, 호환용 한자 범위인지 확인합니다. 200 | * (국사편찬위원회 한자음가사전은 해당 범위에서만 정의되어 있어, 별도 확인합니다.) 201 | * 202 | * @param {!string} text 확인할 문자열 203 | * @returns {boolean[]} 문자열 문자의 위치마다 한자인지 아닌지를 표기한 리스트. 한자라면 True. 204 | * @example 205 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 206 | * console.log(ExtUtil.isCJKHanja("貝波通水")); 207 | */ 208 | 209 | 210 | function isCJKHanja(text) { 211 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isCJKHanja); 212 | } 213 | /** 214 | * 국사편찬위원회 한자음가사전에 따라 한자 표기된 내용을 국문 표기로 전환합니다. 215 | * 216 | * 참고: 217 | * 218 | * * [headCorrection] 값이 true인 경우, whitespace에 따라오는 문자에 두음법칙을 자동 적용함. (기본값 true) 219 | * * 단, 다음 의존명사는 예외: 냥(兩), 년(年), 리(里), 리(理), 량(輛) 220 | * * 다음 두음법칙은 사전을 조회하지 않기 때문에 적용되지 않음에 유의 221 | * - 한자 파생어나 합성어에서 원 단어의 두음법칙: 예) "신여성"이 옳은 표기이나 "신녀성"으로 표기됨 222 | * - 외자가 아닌 이름: 예) "허난설헌"이 옳은 표기이나 "허란설헌"으로 표기됨 223 | * 224 | * @param {!string} text 국문 표기로 전환할 문자열 225 | * @param {boolean} [headCorrection=true] 두음법칙 적용 여부 226 | * @returns {string} 국문 표기로 전환된 문자열 227 | * @example 228 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 229 | * console.log(ExtUtil.hanjaToHangul("貝波通水")); 230 | */ 231 | 232 | 233 | function hanjaToHangul(text, headCorrection = true) { 234 | return _jvm.JVM.koalaClassOf('ExtUtil').hanjaToHangul(text, headCorrection).toString(); 235 | } 236 | /** 237 | * 현재 문자가 초성, 중성, 종성(선택적)을 다 갖춘 문자인지 확인합니다. 238 | * 239 | * @param {!string} text 확인할 문자열 240 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 241 | * @example 242 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 243 | * console.log(ExtUtil.isCompleteHangul("Sing! 노래하라")); 244 | */ 245 | 246 | 247 | function isCompleteHangul(text) { 248 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isCompleteHangul); 249 | } 250 | /** 251 | * 현재 문자가 불완전한 한글 문자인지 확인합니다. 252 | * 253 | * @param {!string} text 확인할 문자열 254 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 255 | * @example 256 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 257 | * console.log(ExtUtil.isIncompleteHangul("Sing! 노래하라")); 258 | */ 259 | 260 | 261 | function isIncompleteHangul(text) { 262 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isIncompleteHangul); 263 | } 264 | /** 265 | * 현재 문자가 한글 완성형 또는 조합용 문자인지 확인합니다. 266 | * 267 | * @param {!string} text 확인할 문자열 268 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 269 | * @example 270 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 271 | * console.log(ExtUtil.isHangul("Sing! 노래하라")); 272 | */ 273 | 274 | 275 | function isHangul(text) { 276 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isHangul); 277 | } 278 | /** 279 | * 현재 문자열이 한글 (완성/조합)로 끝나는지 확인합니다. 280 | * 281 | * @param {!string} text 확인할 문자열 282 | * @returns {boolean} 맞다면 True. 283 | * @example 284 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 285 | * console.log(ExtUtil.isHangulEnding("Sing! 노래하라")); 286 | */ 287 | 288 | 289 | function isHangulEnding(text) { 290 | return _jvm.JVM.koalaClassOf('ExtUtil').isHangulEnding(text); 291 | } 292 | /** 293 | * 현재 문자가 현대 한글 초성 자음 문자인지 확인합니다. 294 | * 295 | * @param {!string} text 확인할 문자열 296 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 297 | * @example 298 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 299 | * console.log(ExtUtil.isChosungJamo("\u1100")); 300 | */ 301 | 302 | 303 | function isChosungJamo(text) { 304 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isChosungJamo); 305 | } 306 | /** 307 | * 현재 문자가 현대 한글 중성 모음 문자인지 확인합니다. 308 | * 309 | * @param {!string} text 확인할 문자열 310 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 311 | * @example 312 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 313 | * console.log(ExtUtil.isJungsungJamo("\u1161")); 314 | */ 315 | 316 | 317 | function isJungsungJamo(text) { 318 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isJungsungJamo); 319 | } 320 | /** 321 | * 현재 문자가 현대 한글 종성 자음 문자인지 확인합니다. 322 | * 323 | * @param {!string} text 확인할 문자열 324 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 325 | * @example 326 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 327 | * console.log(ExtUtil.isJungsungJamo("\u11A8")); 328 | */ 329 | 330 | 331 | function isJongsungJamo(text) { 332 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').isJongsungJamo); 333 | } 334 | /** 335 | * 현재 문자열이 종성으로 끝인지 확인합니다. 336 | * 337 | * @param {!string} text 확인할 문자열 338 | * @returns {boolean} 맞다면 True. 339 | * @example 340 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 341 | * console.log(ExtUtil.isJongsungEnding("Sing! 노래하라")); 342 | */ 343 | 344 | 345 | function isJongsungEnding(text) { 346 | return _jvm.JVM.koalaClassOf('ExtUtil').isJongsungEnding(text); 347 | } 348 | /** 349 | * 현재 문자에서 초성 자음문자를 분리합니다. 초성이 없으면 None. 350 | * @param {!string} text 분리할 문자열 351 | * @returns {string[]} 분리된 각 초성이 들어간 리스트. 352 | * @example 353 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 354 | * console.log(ExtUtil.getChosung("제주도의 푸른 밤")); 355 | */ 356 | 357 | 358 | function getChosung(text) { 359 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').getChosung); 360 | } 361 | /** 362 | * 현재 문자에서 중성 모음문자를 분리합니다. 중성이 없으면 None. 363 | * @param {!string} text 분리할 문자열 364 | * @returns {string[]} 분리된 각 중성이 들어간 리스트. 365 | * @example 366 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 367 | * console.log(ExtUtil.getJungsung("제주도의 푸른 밤")); 368 | */ 369 | 370 | 371 | function getJungsung(text) { 372 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').getJungsung); 373 | } 374 | /** 375 | * 현재 문자에서 종성 자음문자를 분리합니다. 종성이 없으면 None. 376 | * @param {!string} text 분리할 문자열 377 | * @returns {string[]} 분리된 각 종성이 들어간 리스트. 378 | * @example 379 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 380 | * console.log(ExtUtil.getJongsung("제주도의 푸른 밤")); 381 | */ 382 | 383 | 384 | function getJongsung(text) { 385 | return stringRepeat(text, _jvm.JVM.koalaClassOf('ExtUtil').getJongsung); 386 | } 387 | /** 388 | * 현재 문자열을 초성, 중성, 종성 자음문자로 분리하여 새 문자열을 만듭니다. 종성이 없으면 종성은 쓰지 않습니다. 389 | * @param {string} text 분해할 문자열 390 | * @returns {string} 분해된 문자열 391 | * @example 392 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 393 | * console.log(ExtUtil.dissembleHangul("제주도의 푸른 밤")); 394 | */ 395 | 396 | 397 | function dissembleHangul(text) { 398 | return _jvm.JVM.koalaClassOf('ExtUtil').dissembleHangul(text).toString(); 399 | } 400 | /** 401 | * 초성을 [cho] 문자로, 중성을 [jung] 문자로, 종성을 [jong] 문자로 갖는 한글 문자를 재구성합니다. 402 | * 403 | * @param {string} [cho=undefined] 초성 문자. (0x1100-1112) 기본값 ㅇ 자모 404 | * @param {string} [jung=undefined] 중성 문자, (0x1161-1175) 기본값 ㅡ 자모 405 | * @param {string} [jong=undefined] 종성 문자, (0x11a8-11c2) 기본값 종성 없음 406 | * @returns {string} 초성, 중성, 종성을 조합하여 문자를 만듭니다. 407 | * @example 408 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 409 | * console.log(ExtUtil.assembleHangulTriple('\u1100', '\u1161', '\u11A8')); 410 | */ 411 | 412 | 413 | function assembleHangulTriple(cho = undefined, jung = undefined, jong = undefined) { 414 | return _jvm.JVM.koalaClassOf('ExtUtil').assembleHangul(_jvm.JVM.char(cho), _jvm.JVM.char(jung), _jvm.JVM.char(jong)); 415 | } 416 | /** 417 | * 주어진 문자열에서 초성, 중성, 종성이 연달아 나오는 경우 이를 조합하여 한글 문자를 재구성합니다. 418 | * 419 | * @param {string} text 조합할 문자열 420 | * @returns {string} 조합형 문자들이 조합된 문자열. 조합이 불가능한 문자는 그대로 남습니다. 421 | * @example 422 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 423 | * console.log(ExtUtil.assembleHangul("제주도의 푸른 밤인 \u1100\u1161\u11A8")); 424 | */ 425 | 426 | 427 | function assembleHangul(text) { 428 | return _jvm.JVM.koalaClassOf('ExtUtil').assembleHangulString(text).toString(); 429 | } 430 | /** 431 | * 주어진 용언의 원형 [verb]이 뒷 부분 [rest]와 같이 어미가 붙어 활용될 때, 불규칙 활용 용언과 모음조화를 교정합니다. 432 | * 433 | * @param {string} verb 용언 원형인 어근을 표현한 String. '-다.' 와 같은 어미는 없는 어근 상태입니다. 434 | * @param {boolean} isVerb 동사인지 형용사인지 나타내는 지시자. 동사이면 true. 435 | * @param {string} rest 어근에 붙일 어미를 표현한 String. 436 | * @returns {string} 모음조화나 불규칙 활용이 교정된 원형+어미 결합 437 | * @example 438 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 439 | * console.log(ExtUtil.correctVerbApply("밀리",true,"어")); 440 | */ 441 | 442 | 443 | function correctVerbApply(verb, isVerb, rest) { 444 | return _jvm.JVM.koalaClassOf('ExtUtil').correctVerbApply(verb, isVerb, rest); 445 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bugeun Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeJS-KoalaNLP 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/koalanlp.svg?style=flat-square)](https://github.com/koalanlp/nodejs-support) 4 | [![MIT License](https://img.shields.io/badge/license-MIT-green.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) 5 | [![JS Doc](https://img.shields.io/badge/JS-Doc-blue.svg?style=flat-square)](https://koalanlp.github.io/nodejs-support/) 6 | 7 | [![Build Status](https://img.shields.io/travis/koalanlp/nodejs-support.svg?style=flat-square&branch=master)](https://travis-ci.com/koalanlp/nodejs-support) 8 | [![codecov](https://codecov.io/gh/koalanlp/nodejs-support/branch/master/graph/badge.svg)](https://codecov.io/gh/koalanlp/nodejs-support) 9 | 10 | [![분석기별 품사비교표](https://img.shields.io/badge/%ED%92%88%EC%82%AC-%EB%B9%84%EA%B5%90%ED%91%9C-blue.svg?style=flat-square)](https://docs.google.com/spreadsheets/d/1OGM4JDdLk6URuegFKXg1huuKWynhg_EQnZYgTmG4h0s/edit?usp=sharing) 11 | [![KoalaNLP](https://img.shields.io/badge/Java-KoalaNLP-blue.svg?style=flat-square)](https://koalanlp.github.io/koalanlp) 12 | [![python](https://img.shields.io/badge/Python-Supprt-blue.svg?style=flat-square)](https://koalanlp.github.io/python-support) 13 | [![scala](https://img.shields.io/badge/Scala-Support-blue.svg?style=flat-square)](https://koalanlp.github.io/scala-support) 14 | 15 | # 소개 16 | 17 | 이 프로젝트는 __서로 다른 형태의 형태소 분석기를__ 모아, 18 | __동일한 인터페이스__ 아래에서 사용할 수 있도록 하는 것이 목적입니다. 19 | 20 | * 김상준님의 [Daon 분석기](https://github.com/rasoio/daon/tree/master/daon-core) 21 | * Shineware의 [코모란 v3.3.8](https://github.com/shin285/KOMORAN) 22 | * 서울대의 [꼬꼬마 형태소/구문 분석기 v2.1](http://kkma.snu.ac.kr/documents/index.jsp) 23 | * ETRI의 [공공 인공지능 Open API](http://aiopen.etri.re.kr/) 24 | * OpenKoreanText의 [오픈 소스 한국어 처리기 v2.3.1](http://openkoreantext.org) (구 Twitter 한국어 분석기) 25 | * 은전한닢 프로젝트의 [SEunjeon(S은전) v1.5.0](https://bitbucket.org/eunjeon/seunjeon) (Mecab-ko의 Scala/Java 판본) 26 | * 이수명님의 [Arirang Morpheme Analyzer](http://cafe.naver.com/korlucene) 1-1 27 | * 최석재님의 [RHINO v2.5.4](https://github.com/SukjaeChoi/RHINO) 28 | * KAIST의 [한나눔 형태소 분석기](http://kldp.net/projects/hannanum/)와 [NLP_HUB 구문분석기](http://semanticweb.kaist.ac.kr/home/index.php/NLP_HUB) 29 | * Kakao의 [카이(Khaiii) v0.4](https://github.com/kakao/khaiii) (별도설치 필요: [설치법](https://github.com/kakao/khaiii/wiki/빌드-및-설치)) 30 | * 울산대학교의 [UTagger 2018년 10월 31일자](http://nlplab.ulsan.ac.kr/doku.php?id=start) 1-2, (별도설치 필요: [설치법](https://koalanlp.github.io/koalanlp/usage/Install-UTagger.md)) 31 | 32 | > 주1-1 Arirang 분석기의 출력을 형태소분석에 적합하게 조금 다듬었으므로, 원본과 약간 다른 결과를 낼 수도 있습니다. 33 | > 34 | > 주1-2 UTagger의 2019-7 버전도 공개되어 있지만, 리눅스 개발환경을 위한 라이브러리 파일이 공개되어있지 않아 지원하지 않습니다. 35 | 36 | 분석기의 개선이나 추가 등을 하고 싶으시다면, 37 | * 개발이 직접 가능하시다면 pull request를 보내주세요. 테스트 후 반영할 수 있도록 하겠습니다. 38 | * 개발이 어렵다면 issue tracker에 등록해주세요. 검토 후 답변해드리겠습니다. 39 | 40 | ## 특징 41 | 42 | KoalaNLP는 다음과 같은 특징을 가지고 있습니다. 43 | 44 | 1. 복잡한 설정이 필요없는 텍스트 분석: 45 | 46 | 모델은 자동으로 Maven으로 배포되기 때문에, 각 모델을 별도로 설치할 필요가 없습니다. 47 | 48 | 2. 코드 2~3 줄로 수행하는 텍스트 처리: 49 | 50 | 모델마다 다른 복잡한 설정 과정, 초기화 과정은 필요하지 않습니다. Dependency에 추가하고, 객체를 생성하고, 분석 메소드를 호출하는 3줄이면 끝납니다. 51 | 52 | 3. 모델에 상관 없는 동일한 코드, 동일한 결과: 53 | 54 | 모델마다 실행 방법, 실행 결과를 표현하는 형태가 다릅니다. KoalaNLP는 이를 정부 및 관계기관의 표준안에 따라 표준화합니다. 따라서 모델에 독립적으로 응용 프로그램 개발이 가능합니다. 55 | 56 | 4. [Java, Kotlin](https://koalanlp.github.io/koalanlp), [Scala](https://koalanlp.github.io/scala-support), [Python 3](https://koalanlp.github.io/python-support), [NodeJS](https://koalanlp.github.io/nodejs-support) 에서 크게 다르지 않은 코드: 57 | 58 | KoalaNLP는 여러 프로그래밍 언어에서 사용할 수 있습니다. 어디서 개발을 하더라도 크게 코드가 다르지 않습니다. 59 | 60 | # License 조항 61 | 62 | KoalaNLP의 프로젝트와 인터페이스 통합을 위한 코드는 63 | 소스코드에 저작권 귀속에 대한 별도 지시사항이 없는 한 [*MIT License*](https://tldrlegal.com/license/mit-license)을 따르며, 64 | 원본 분석기의 License와 저작권은 각 저작권자가 지정한 바를 따릅니다. 65 | 66 | 단, GPL의 저작권 조항에 따라, GPL 하에서 이용이 허가되는 패키지들의 저작권은 해당 저작권 규정을 따릅니다. 67 | 68 | * Hannanum 및 NLP_HUB: [GPL v3](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)) 69 | * KKMA: [GPL v2](https://tldrlegal.com/license/gnu-general-public-license-v2) (GPL v2를 따르지 않더라도, 상업적 이용시 별도 협의 가능) 70 | * KOMORAN 3.x: [Apache License 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) 71 | * Open Korean Text: [Apache License 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) 72 | * SEunjeon: [Apache License 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) 73 | * 아리랑: [Apache License 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) 74 | * RHINO: [GPL v3](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)) (참고: 다운로드 위치별로 조항 상이함) 75 | * Daon: 지정된 조항 없음 76 | * ETRI: 별도 API 키 발급 동의 필요 77 | * Khaiii: [Apache License 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) 78 | * UTagger: 교육 및 연구용으로 사용시 제한 없음. 상업용인 경우 울산대와 기술이전 등의 유료 협약 필요 79 | 80 | # 사용법 81 | 82 | 상세한 사항은 [Usage](https://koalanlp.github.io/koalanlp/usage/) 또는 [![JS Doc](https://img.shields.io/badge/JS-Doc-blue.svg?style=flat-square)](https://koalanlp.github.io/nodejs-support/) 을 참고하십시오. 83 | 84 | ## Dependency 추가 85 | 우선 Java 8 및 NodeJS 8 이상을 설치하고, `JAVA_HOME`을 환경변수에 등록해주십시오. 86 | 그런 다음, 아래와 같이 설치하십시오. (현재 nodejs-koalanlp 버전은 [![NPM Version](https://img.shields.io/npm/v/koalanlp.svg?style=flat-square)](https://github.com/koalanlp/nodejs-support) 입니다.) 87 | 88 | ```bash 89 | $ npm install koalanlp --save 90 | ``` 91 | 92 | ### Packages 93 | 각 형태소 분석기는 별도의 패키지로 나뉘어 있습니다. 94 | 95 | | 패키지명 | 설명 | 사용 가능 버전 | License (원본) | 96 | | ------------------ | ------------------------------------------------------------------ | ---------------- | ----------------- | 97 | | API.KMR | 코모란 Wrapper, 분석범위: 형태소 | [![Ver-KMR](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-kmr.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-kmr%22) | Apache 2.0 | 98 | | API.EUNJEON | 은전한닢 Wrapper, 분석범위: 형태소 | [![Ver-EJN](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-eunjeon.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-eunjeon%22) | Apache 2.0 | 99 | | API.ARIRANG | 아리랑 Wrapper, 분석범위: 형태소 | [![Ver-ARR](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-arirang.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-arirang%22) | Apache 2.0 | 100 | | API.RHINO | RHINO Wrapper, 분석범위: 형태소 | [![Ver-RHI](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-rhino.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-rhino%22) | GPL v3 | 101 | | API.DAON | Daon Wrapper, 분석범위: 형태소 | [![Ver-DAN](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-daon.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-daon%22) | MIT(별도 지정 없음) | 102 | | API.KHAIII | Khaiii Wrapper, 분석범위: 형태소 주2-3 | [![Ver-KHA](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-khaiii.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-khaiii%22) | Apache 2.0 | 103 | | API.UTAGGER | 울산대 UTagger Wrapper / 분석범위: 형태소 2-4 | [![Ver-UTA](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-utagger.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-utagger%22) | 교육/연구용 무료, 상업용 별도협약 | 104 | | API.OKT | Open Korean Text Wrapper, 분석범위: 문장분리, 형태소 | [![Ver-OKT](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-okt.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-okt%22) | Apache 2.0 | 105 | | API.KKMA | 꼬꼬마 Wrapper, 분석범위: 형태소, 의존구문 | [![Ver-KKM](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-kkma.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-kkma%22) | GPL v2 | 106 | | API.HNN | 한나눔 Wrapper, 분석범위: 문장분리, 형태소, 구문분석, 의존구문 | [![Ver-HNN](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-hnn.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-hnn%22) | GPL v3 | 107 | | API.ETRI | ETRI Open API Wrapper, 분석범위: 형태소, 구문분석, 의존구문, 개체명, 의미역 2-2 | [![Ver-ETR](https://img.shields.io/maven-central/v/kr.bydelta/koalanlp-etri.svg?style=flat-square&label=r)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22koalanlp-etri%22) | MIT2-2 | 108 | 109 | > 주2-2 ETRI의 경우 Open API를 접근하기 위한 코드 부분은 KoalaNLP의 License 정책에 귀속되지만, Open API 접근 이후의 사용권에 관한 조항은 ETRI에서 별도로 정한 바를 따릅니다. 110 | > 따라서, ETRI의 사용권 조항에 동의하시고 키를 발급하셔야 하며, 다음 위치에서 발급을 신청할 수 있습니다: [키 발급 신청](http://aiopen.etri.re.kr/key_main.php) 111 | > 112 | > 주2-3 Khaiii 분석기의 경우는 Java가 아닌 C++로 구현되어 사용 전 분석기의 설치가 필요합니다. Python3.6 및 CMake 3.10+만 설치되어 있다면 설치 자체가 복잡한 편은 아니니 [여기](https://github.com/kakao/khaiii/blob/v0.1/doc/setup.md)를 참조하여 설치해보세요. (단, v0.1에서는 빌드시 'python3' 호출시 'python3.6'이 연결되어야 합니다.) 참고로, KoalaNLP가 Travis CI에서 패키지를 자동 테스트하기 위해 구현된 bash script는 [여기](https://github.com/koalanlp/koalanlp/blob/master/khaiii/install.sh)에 있습니다. 113 | > 114 | > 주2-4 UTagger 분석기의 경우에도 C/C++로 구현되어, 사용 전 분석기의 설치가 필요합니다. 윈도우와 리눅스(우분투, CentOS)용 라이브러리 파일만 제공되며, 설치 방법은 [여기](https://koalanlp.github.io/koalanlp/usage/Install-UTagger.md)를 참조하십시오. 115 | 116 | ## 간단한 예시 117 | `koalanlp`는, `node-java` 및 `node-java-maven` 패키지의 도움을 받아, 필요한 java dependency를 자동으로 가져옵니다. 118 | 119 | > [참고] 최초 사용시 또는, 최신 패키지로 업데이트 되는 경우, dependency를 찾아오는 데 시간이 소요될 수 있습니다. 120 | > 다운로드 진행 중에 취소하시면 다운로드 된 패키지가 corrupt 될 수 있습니다. 121 | > 이 경우, Maven repository 저장 공간인 (`~/.m2`) 폴더에서 오류가 나는 패키지를 삭제하시고 다시 시작하십시오. 122 | 123 | 다음과 같이 사용합니다. (Node > 8, ECMAScript2017 기준) 124 | 125 | ### Async/Await 방식으로 사용할 때 (권장) 126 | 127 | ```js 128 | const {KMR, KKMA} = require('koalanlp/API'); 129 | const {initialize} = require('koalanlp/Util'); 130 | const {Tagger, Parser} = require('koalanlp/proc'); 131 | 132 | async function executor(){ 133 | await initialize({packages: {KMR: '2.0.4', KKMA: '2.0.4'}, verbose: true}); 134 | 135 | let tagger = new Tagger(KMR); 136 | let tagged = await tagger("안녕하세요. 눈이 오는 설날 아침입니다."); 137 | for(const sent of tagged) { 138 | console.log(sent.toString()); 139 | } 140 | 141 | let parser = new Parser(KKMA); 142 | let parsed = await parser("안녕하세요. 눈이 오는 설날 아침입니다."); 143 | for(const sent of parsed){ 144 | console.log(sent.toString()); 145 | for(const dep of sent.dependencies){ 146 | console.log(dep.toString()); 147 | } 148 | } 149 | } 150 | 151 | executor().then( 152 | () => console.log('finished!'), 153 | (error) => console.error('Error Occurred!', error) 154 | ); 155 | ``` 156 | 157 | ### Promise 방식으로 사용할 때 158 | 159 | ```js 160 | const {KMR, KKMA} = require('koalanlp/API'); 161 | const {initialize} = require('koalanlp/Util'); 162 | const {Tagger, Parser} = require('koalanlp/proc'); 163 | 164 | initialize({packages: {KMR: '2.0.4', KKMA: '2.0.4'}, verbose: true}) 165 | .then(() => { 166 | let tagger = new Tagger(KMR); 167 | tagger("안녕하세요. 눈이 오는 설날 아침입니다.").then((tagged) => { 168 | for (const sent of tagged) { 169 | console.log(sent.toString()); 170 | } 171 | }, (error) => console.error('Error Occurred', error)); 172 | 173 | let parser = new Parser(KKMA); 174 | parser("안녕하세요. 눈이 오는 설날 아침입니다.").then((parsed) => { 175 | for (const sent of parsed) { 176 | console.log(sent.toString()); 177 | for (const dep of sent.dependencies) { 178 | console.log(dep.toString()); 179 | } 180 | } 181 | }, (error) => console.error('Error Occurred', error)); 182 | 183 | console.log('finished!'); 184 | }, (error) => console.error('Error Occurred!', error)); 185 | ``` 186 | 187 | ### Synchronous 방식으로 사용할 때 188 | 189 | ```js 190 | const {KMR, KKMA} = require('koalanlp/API'); 191 | const {initialize} = require('koalanlp/Util'); 192 | const {Tagger, Parser} = require('koalanlp/proc'); 193 | 194 | // initialize 함수는 asynchronous만 지원합니다. 195 | initialize({packages: {KMR: '2.0.4', KKMA: '2.0.4'}, verbose: true}) 196 | .then(() => { 197 | let tagger = new Tagger(KMR); 198 | let tagged = tagger.tagSync("안녕하세요. 눈이 오는 설날 아침입니다."); 199 | for(const sent of tagged) { 200 | console.log(sent.toString()); 201 | } 202 | 203 | let parser = new Parser(KKMA); 204 | let parsed = parser.analyzeSync("안녕하세요. 눈이 오는 설날 아침입니다."); 205 | for(const sent of parsed){ 206 | console.log(sent.toString()); 207 | for(const dep of sent.dependencies){ 208 | console.log(dep.toString()); 209 | } 210 | } 211 | }); 212 | ``` 213 | 214 | # 결과 비교 215 | [Sample:결과비교](https://koalanlp.github.io/sample/comparison)를 참조해주세요. 216 | -------------------------------------------------------------------------------- /Util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.initialize = initialize; 7 | exports.contains = contains; 8 | 9 | require("core-js/modules/es6.array.sort"); 10 | 11 | var API = _interopRequireWildcard(require("./API")); 12 | 13 | var _jvm = require("./jvm"); 14 | 15 | var _types = require("./types"); 16 | 17 | var _underscore = _interopRequireDefault(require("underscore")); 18 | 19 | var _common = require("./common"); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } 24 | 25 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 26 | 27 | /** 28 | * 초기화 및 기타 작업에 필요한 함수를 제공합니다. 29 | * 30 | * @module koalanlp/Util 31 | * @example 32 | * import * as Util from 'koalanlp/Util'; 33 | */ 34 | 35 | /** 36 | * @private 37 | * @param api 38 | */ 39 | async function queryVersion(api) { 40 | const request = require('request'); 41 | 42 | let url = `https://repo1.maven.org/maven2/kr/bydelta/koalanlp-${api}/`; 43 | let result = await new Promise((resolve, reject) => { 44 | request(url, { 45 | headers: { 46 | 'User-Agent': 'curl/7.58.0' 47 | } 48 | }, // Query as if CURL did. 49 | (error, res, body) => { 50 | if (error) reject(error);else resolve(body); 51 | }); 52 | }); 53 | let matches = result.match(new RegExp('href="(\\d+\\.\\d+\\.\\d+(-[A-Za-z]+(\\.\\d+)?)?)/"', 'g')); 54 | matches = matches.map(line => line.substring(6, line.length - 2)); 55 | let version = matches.sort().reverse()[0]; 56 | console.info(`[INFO] Latest version of kr.bydelta:koalanlp-${api} (${version}) will be used`); 57 | return version; 58 | } 59 | /** 60 | * API와 버전을 받아 Artifact 객체 구성 61 | * @param {API} api 분석기 패키지 이름 62 | * @param {!string} version 버전 또는 LATEST 63 | * @return {{groupId: string, artifactId: string, version: string}} Artifact 객체 64 | * @private 65 | */ 66 | 67 | 68 | async function makeDependencyItem(api, version) { 69 | let isAssembly = API.PACKAGE_REQUIRE_ASSEMBLY.includes(api); 70 | 71 | if (typeof version === 'undefined' || version.toUpperCase() === 'LATEST') { 72 | version = await queryVersion(api); 73 | } 74 | 75 | let obj = { 76 | "groupId": "kr.bydelta", 77 | "artifactId": `koalanlp-${api}`, 78 | "version": version 79 | }; 80 | 81 | if (isAssembly) { 82 | obj.classifier = "assembly"; 83 | } 84 | 85 | return obj; 86 | } 87 | /** 88 | * Remote Maven Repository list 89 | * @type {Object[]} 90 | * @private 91 | */ 92 | 93 | 94 | let remoteRepos = [{ 95 | id: 'sonatype', 96 | url: 'https://oss.sonatype.org/content/repositories/public/' 97 | }, { 98 | id: "jitpack.io", 99 | url: "https://jitpack.io/" 100 | }, { 101 | id: 'jcenter', 102 | url: 'https://jcenter.bintray.com/' 103 | }, { 104 | id: 'maven-central-1', 105 | url: 'https://repo1.maven.org/maven2/' 106 | }, { 107 | id: 'maven-central-2', 108 | url: 'http://insecure.repo1.maven.org/maven2/' 109 | }, { 110 | id: 'kotlin-dev', 111 | url: 'https://dl.bintray.com/kotlin/kotlin-dev/' 112 | }]; 113 | 114 | function versionSplit(ver) { 115 | let dashAt = ver.indexOf('-'); 116 | 117 | if (dashAt !== -1) { 118 | let semver = ver.substr(0, dashAt).split('\\.'); 119 | let tag = ver.substr(dashAt + 1); 120 | semver = semver.map(parseInt); 121 | semver.push(tag); 122 | return semver; 123 | } else { 124 | let semver = ver.split('\\.'); 125 | return semver.map(parseInt); 126 | } 127 | } 128 | /** @private */ 129 | 130 | 131 | function isRightNewer(ver1, ver2) { 132 | let semver1 = versionSplit(ver1); 133 | let semver2 = versionSplit(ver2); 134 | let length = Math.max(semver1.length, semver2.length); 135 | 136 | for (let i of _underscore.default.range(length)) { 137 | let comp1 = semver1[i]; 138 | let comp2 = semver2[i]; 139 | if (!(0, _common.isDefined)(comp2)) return true; // 왼쪽은 Tag가 있지만 오른쪽은 없는 상태. (오른쪽이 더 최신) 140 | 141 | if (!(0, _common.isDefined)(comp1)) return false; // 반대: 왼쪽이 더 최신 142 | 143 | if (comp1 !== comp2) return comp1 < comp2; // comp2 가 더 높으면 최신. 144 | } 145 | 146 | return false; 147 | } 148 | /** 149 | * 자바 및 의존패키지를 Maven Repository에서 다운받고, 자바 환경을 실행합니다. 150 | * 151 | * @param {Object} options 152 | * @param {Object.} options.packages 사용할 패키지와 그 버전들. 153 | * @param {string[]} [options.javaOptions=["-Xmx4g", "-Dfile.encoding=utf-8"]] JVM 초기화 조건 154 | * @param {boolean} [options.verbose=true] 더 상세히 초기화 과정을 보여줄 것인지의 여부. 155 | * @param {!string} [options.tempJsonName='koalanlp.json'] Maven 실행을 위해 임시로 작성할 파일의 이름. 156 | * @example 157 | * import {initialize} from 'koalanlp/Util'; 158 | * import {ETRI} from 'koalanlp/API'; 159 | * 160 | * // Promise 방식 161 | * let promise = initialize({'packages': {ETRI: '2.0.4'}}); 162 | * promise.then(...); 163 | * 164 | * // Async/Await 방식 (async function 내부에서) 165 | * await initialize({ETRI: '2.0.4'}); 166 | * ... 167 | */ 168 | 169 | 170 | async function initialize(options) { 171 | (0, _common.assert)(options.packages, "packages는 설정되어야 하는 값입니다."); 172 | let packages = options.packages; 173 | let verbose = (0, _common.isDefined)(options.verbose) ? options.verbose : false; 174 | let javaOptions = options.javaOptions || ["-Xmx4g", "-Dfile.encoding=utf-8"]; 175 | let tempJsonName = options.tempJsonName || 'koalanlp.json'; 176 | /***** 자바 초기화 ******/ 177 | 178 | let java = require('java'); 179 | 180 | if (!_jvm.JVM.canLoadPackages(packages)) { 181 | throw Error(`JVM은 두번 이상 초기화될 수 없습니다. ${packages}를 불러오려고 시도했지만 이미 ${_jvm.JVM.PACKAGES}를 불러온 상태입니다.`); 182 | } 183 | 184 | java.options.push(...javaOptions); 185 | java.asyncOptions = { 186 | asyncSuffix: undefined, 187 | // Async Callback 무력화 188 | syncSuffix: '', 189 | // Synchronized call은 접미사 없음 190 | promiseSuffix: 'Promise', 191 | // Promise Callback 설정 192 | promisify: require('util').promisify 193 | }; 194 | /***** Maven 설정 *****/ 195 | 196 | const os = require('os'); 197 | 198 | const fs = require('fs'); 199 | 200 | const path = require('path'); 201 | 202 | const mvn = require('node-java-maven'); // 의존 패키지 목록을 JSON으로 작성하기 203 | 204 | 205 | let dependencies = await Promise.all(Object.keys(packages).map(pack => makeDependencyItem(API.getPackage(pack), packages[pack]))); // Package 버전 업데이트 (Compatiblity check 위함) 206 | 207 | for (const pack of dependencies) { 208 | packages[pack.artifactId.replace('koalanlp-', '').toUpperCase()] = pack.version; 209 | } // 저장하기 210 | 211 | 212 | let packPath = path.join(os.tmpdir(), tempJsonName); 213 | fs.writeFileSync(packPath, JSON.stringify({ 214 | java: { 215 | dependencies: dependencies, 216 | exclusions: [{ 217 | groupId: "com.jsuereth", 218 | artifactId: "sbt-pgp" 219 | }] 220 | } 221 | })); 222 | 223 | let threads = require('os').cpus().length; 224 | 225 | threads = Math.max(threads - 1, 1); 226 | let promise = new Promise((resolve, reject) => { 227 | mvn({ 228 | packageJsonPath: packPath, 229 | debug: verbose, 230 | repositories: remoteRepos, 231 | concurrency: threads 232 | }, function (err, mvnResults) { 233 | if (err) { 234 | console.error('필요한 의존패키지를 전부 다 가져오지는 못했습니다.'); 235 | reject(err); 236 | } else { 237 | let cleanClasspath = {}; 238 | 239 | for (const dependency of Object.values(mvnResults.dependencies)) { 240 | let group = dependency.groupId; 241 | let artifact = dependency.artifactId; 242 | let version = dependency.version; 243 | let key = `${group}:${artifact}`; 244 | 245 | if (!(0, _common.isDefined)(cleanClasspath[key]) || isRightNewer(cleanClasspath[key].version, version)) { 246 | cleanClasspath[key] = { 247 | version: version, 248 | path: dependency.jarPath 249 | }; 250 | } 251 | } 252 | 253 | for (const dependency of Object.values(cleanClasspath)) { 254 | if (!(0, _common.isDefined)(dependency.path)) continue; 255 | if (verbose) console.debug(`Classpath에 ${dependency.path} 추가`); 256 | java.classpath.push(path.resolve(dependency.path)); 257 | } 258 | 259 | _jvm.JVM.init(java, packages); // Enum 초기화. 260 | 261 | 262 | _types.POS.values(); 263 | 264 | _types.PhraseTag.values(); 265 | 266 | _types.DependencyTag.values(); 267 | 268 | _types.RoleType.values(); 269 | 270 | _types.CoarseEntityType.values(); 271 | 272 | resolve(); 273 | } 274 | }); 275 | }); 276 | return await promise; 277 | } 278 | /** 279 | * 주어진 문자열 리스트에 구문분석 표지자/의존구문 표지자/의미역 표지/개체명 분류가 포함되는지 확인합니다. 280 | * @param {string[]} stringList 분류가 포함되는지 확인할 문자열 목록 281 | * @param {(POS|PhraseTag|DependencyTag|CoarseEntityType|RoleType)} tag 포함되는지 확인할 구문분석 표지자/의존구문 표지자/의미역 표지/개체명 분류 282 | * @return {boolean} 포함되면 true. 283 | * @example 284 | * import { contains } from 'koalanlp/Util'; 285 | * contains(['S', 'NP'], PhraseTag.NP); 286 | */ 287 | 288 | 289 | function contains(stringList, tag) { 290 | if (tag instanceof _types.POS || tag instanceof _types.PhraseTag || tag instanceof _types.DependencyTag || tag instanceof _types.RoleType || tag instanceof _types.CoarseEntityType) { 291 | return _jvm.JVM.koalaClassOf('Util').contains(_jvm.JVM.listOf(stringList), tag.reference); 292 | } else { 293 | return false; 294 | } 295 | } -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [ 2 | ['@babel/env', { 3 | 'targets': { 4 | 'node': '8.0.0' 5 | }, 6 | 'useBuiltIns': 'usage', 7 | 'corejs': 2, 8 | }] 9 | ]; 10 | 11 | const plugins = [ 12 | ['@babel/plugin-proposal-class-properties'] 13 | ]; 14 | 15 | module.exports = { presets, plugins }; 16 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.isDefined = isDefined; 7 | exports.getOrUndefined = getOrUndefined; 8 | exports.assert = assert; 9 | exports.typeCheck = typeCheck; 10 | 11 | /** @private */ 12 | function isDefined(value) { 13 | return typeof value !== 'undefined' && value !== null; 14 | } 15 | /** @private */ 16 | 17 | 18 | function getOrUndefined(value) { 19 | return isDefined(value) ? value : undefined; 20 | } 21 | /** @private */ 22 | 23 | 24 | function assert(condition, msg) { 25 | if (!condition) throw TypeError(msg); 26 | } 27 | /** @private */ 28 | 29 | 30 | function typeCheck(values, ...types) { 31 | types = new Set(types); 32 | new Set(values.map(x => { 33 | let t = typeof x; 34 | if (t === 'object') t = x.constructor.name; 35 | return t; 36 | })).forEach(t => { 37 | assert(types.has(t), `[${[...types].toString()}] 중 하나를 기대했지만 ${t} 타입이 들어왔습니다!`); 38 | }); 39 | } -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/scripts/collapse.js: -------------------------------------------------------------------------------- 1 | function hideAllButCurrent(){ 2 | //by default all submenut items are hidden 3 | //but we need to rehide them for search 4 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) { 5 | parent.style.display = "none"; 6 | }); 7 | 8 | //only current page (if it exists) should be opened 9 | var file = window.location.pathname.split("/").pop().replace(/\.html/, ''); 10 | document.querySelectorAll("nav > ul > li > a").forEach(function(parent) { 11 | var href = parent.attributes.href.value.replace(/\.html/, ''); 12 | if (file === href) { 13 | parent.parentNode.querySelectorAll("ul li").forEach(function(elem) { 14 | elem.style.display = "block"; 15 | }); 16 | } 17 | }); 18 | } 19 | 20 | hideAllButCurrent(); -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/nav.js: -------------------------------------------------------------------------------- 1 | function scrollToNavItem() { 2 | var path = window.location.href.split('/').pop().replace(/\.html/, ''); 3 | document.querySelectorAll('nav a').forEach(function(link) { 4 | var href = link.attributes.href.value.replace(/\.html/, ''); 5 | if (path === href) { 6 | link.scrollIntoView({block: 'center'}); 7 | return; 8 | } 9 | }) 10 | } 11 | 12 | scrollToNavItem(); 13 | -------------------------------------------------------------------------------- /docs/scripts/polyfill.js: -------------------------------------------------------------------------------- 1 | //IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/ 2 | if (typeof(NodeList.prototype.forEach)!==typeof(alert)){ 3 | NodeList.prototype.forEach=Array.prototype.forEach; 4 | } -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p ul > li:not(.level-hide)").forEach(function(elem) { 16 | elem.style.display = "block"; 17 | }); 18 | 19 | if (typeof hideAllButCurrent === "function"){ 20 | //let's do what ever collapse wants to do 21 | hideAllButCurrent(); 22 | } else { 23 | //menu by default should be opened 24 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 25 | elem.style.display = "block"; 26 | }); 27 | } 28 | } else { 29 | //we are searching 30 | document.documentElement.setAttribute(searchAttr, ''); 31 | 32 | //show all parents 33 | document.querySelectorAll("nav > ul > li").forEach(function(elem) { 34 | elem.style.display = "block"; 35 | }); 36 | //hide all results 37 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 38 | elem.style.display = "none"; 39 | }); 40 | //show results matching filter 41 | document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) { 42 | if (!contains(elem.parentNode, search)) { 43 | return; 44 | } 45 | elem.parentNode.style.display = "block"; 46 | }); 47 | //hide parents without children 48 | document.querySelectorAll("nav > ul > li").forEach(function(parent) { 49 | var countSearchA = 0; 50 | parent.querySelectorAll("a").forEach(function(elem) { 51 | if (contains(elem, search)) { 52 | countSearchA++; 53 | } 54 | }); 55 | 56 | var countUl = 0; 57 | var countUlVisible = 0; 58 | parent.querySelectorAll("ul").forEach(function(ulP) { 59 | // count all elements that match the search 60 | if (contains(ulP, search)) { 61 | countUl++; 62 | } 63 | 64 | // count all visible elements 65 | var children = ulP.children 66 | for (i=0; i ul { 227 | padding: 0 10px; 228 | } 229 | 230 | nav > ul > li > a { 231 | color: #606; 232 | margin-top: 10px; 233 | } 234 | 235 | nav ul ul a { 236 | color: hsl(207, 1%, 60%); 237 | border-left: 1px solid hsl(207, 10%, 86%); 238 | } 239 | 240 | nav ul ul a, 241 | nav ul ul a:active { 242 | padding-left: 20px 243 | } 244 | 245 | nav h2 { 246 | font-size: 13px; 247 | margin: 10px 0 0 0; 248 | padding: 0; 249 | } 250 | 251 | nav > h2 > a { 252 | margin: 10px 0 -10px; 253 | color: #606 !important; 254 | } 255 | 256 | footer { 257 | color: hsl(0, 0%, 28%); 258 | margin-left: 250px; 259 | display: block; 260 | padding: 15px; 261 | font-style: italic; 262 | font-size: 90%; 263 | } 264 | 265 | .ancestors { 266 | color: #999 267 | } 268 | 269 | .ancestors a { 270 | color: #999 !important; 271 | } 272 | 273 | .clear { 274 | clear: both 275 | } 276 | 277 | .important { 278 | font-weight: bold; 279 | color: #950B02; 280 | } 281 | 282 | .yes-def { 283 | text-indent: -1000px 284 | } 285 | 286 | .type-signature { 287 | color: #CA79CA 288 | } 289 | 290 | .type-signature:last-child { 291 | color: #eee; 292 | } 293 | 294 | .name, .signature { 295 | font-family: Consolas, Monaco, 'Andale Mono', monospace 296 | } 297 | 298 | .signature { 299 | color: #fc83ff; 300 | } 301 | 302 | .details { 303 | margin-top: 6px; 304 | border-left: 2px solid #DDD; 305 | line-height: 20px; 306 | font-size: 14px; 307 | } 308 | 309 | .details dt { 310 | width: auto; 311 | float: left; 312 | padding-left: 10px; 313 | } 314 | 315 | .details dd { 316 | margin-left: 70px; 317 | margin-top: 6px; 318 | margin-bottom: 6px; 319 | } 320 | 321 | .details ul { 322 | margin: 0 323 | } 324 | 325 | .details ul { 326 | list-style-type: none 327 | } 328 | 329 | .details pre.prettyprint { 330 | margin: 0 331 | } 332 | 333 | .details .object-value { 334 | padding-top: 0 335 | } 336 | 337 | .description { 338 | margin-bottom: 1em; 339 | margin-top: 1em; 340 | } 341 | 342 | .code-caption { 343 | font-style: italic; 344 | font-size: 107%; 345 | margin: 0; 346 | } 347 | 348 | .prettyprint { 349 | font-size: 14px; 350 | overflow: auto; 351 | } 352 | 353 | .prettyprint.source { 354 | width: inherit; 355 | line-height: 18px; 356 | display: block; 357 | background-color: #0d152a; 358 | color: #aeaeae; 359 | } 360 | 361 | .prettyprint code { 362 | line-height: 18px; 363 | display: block; 364 | background-color: #0d152a; 365 | color: #4D4E53; 366 | } 367 | 368 | .prettyprint > code { 369 | padding: 15px; 370 | } 371 | 372 | .prettyprint .linenums code { 373 | padding: 0 15px 374 | } 375 | 376 | .prettyprint .linenums li:first-of-type code { 377 | padding-top: 15px 378 | } 379 | 380 | .prettyprint code span.line { 381 | display: inline-block 382 | } 383 | 384 | .prettyprint.linenums { 385 | padding-left: 70px; 386 | -webkit-user-select: none; 387 | -moz-user-select: none; 388 | -ms-user-select: none; 389 | user-select: none; 390 | } 391 | 392 | .prettyprint.linenums ol { 393 | padding-left: 0 394 | } 395 | 396 | .prettyprint.linenums li { 397 | border-left: 3px #34446B solid; 398 | } 399 | 400 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 401 | background-color: #34446B; 402 | } 403 | 404 | .prettyprint.linenums li * { 405 | -webkit-user-select: text; 406 | -moz-user-select: text; 407 | -ms-user-select: text; 408 | user-select: text; 409 | } 410 | 411 | table { 412 | border-spacing: 0; 413 | border: 1px solid #ddd; 414 | border-collapse: collapse; 415 | border-radius: 3px; 416 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 417 | width: 100%; 418 | font-size: 14px; 419 | margin: 1em 0; 420 | } 421 | 422 | td, th { 423 | margin: 0px; 424 | text-align: left; 425 | vertical-align: top; 426 | padding: 10px; 427 | display: table-cell; 428 | } 429 | 430 | thead tr, thead tr { 431 | background-color: #fff; 432 | font-weight: bold; 433 | border-bottom: 1px solid #ddd; 434 | } 435 | 436 | .params .type { 437 | white-space: nowrap; 438 | } 439 | 440 | .params code { 441 | white-space: pre; 442 | } 443 | 444 | .params td, .params .name, .props .name, .name code { 445 | color: #4D4E53; 446 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 447 | font-size: 100%; 448 | } 449 | 450 | .params td { 451 | border-top: 1px solid #eee 452 | } 453 | 454 | .params td.description > p:first-child, .props td.description > p:first-child { 455 | margin-top: 0; 456 | padding-top: 0; 457 | } 458 | 459 | .params td.description > p:last-child, .props td.description > p:last-child { 460 | margin-bottom: 0; 461 | padding-bottom: 0; 462 | } 463 | 464 | span.param-type, .params td .param-type, .param-type dd { 465 | color: #606; 466 | font-family: Consolas, Monaco, 'Andale Mono', monospace 467 | } 468 | 469 | .param-type dt, .param-type dd { 470 | display: inline-block 471 | } 472 | 473 | .param-type { 474 | margin: 14px 0; 475 | } 476 | 477 | .disabled { 478 | color: #454545 479 | } 480 | 481 | /* navicon button */ 482 | .navicon-button { 483 | display: none; 484 | position: relative; 485 | padding: 2.0625rem 1.5rem; 486 | transition: 0.25s; 487 | cursor: pointer; 488 | -webkit-user-select: none; 489 | -moz-user-select: none; 490 | -ms-user-select: none; 491 | user-select: none; 492 | opacity: .8; 493 | } 494 | .navicon-button .navicon:before, .navicon-button .navicon:after { 495 | transition: 0.25s; 496 | } 497 | .navicon-button:hover { 498 | transition: 0.5s; 499 | opacity: 1; 500 | } 501 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 502 | transition: 0.25s; 503 | } 504 | .navicon-button:hover .navicon:before { 505 | top: .825rem; 506 | } 507 | .navicon-button:hover .navicon:after { 508 | top: -.825rem; 509 | } 510 | 511 | /* navicon */ 512 | .navicon { 513 | position: relative; 514 | width: 2.5em; 515 | height: .3125rem; 516 | background: #000; 517 | transition: 0.3s; 518 | border-radius: 2.5rem; 519 | } 520 | .navicon:before, .navicon:after { 521 | display: block; 522 | content: ""; 523 | height: .3125rem; 524 | width: 2.5rem; 525 | background: #000; 526 | position: absolute; 527 | z-index: -1; 528 | transition: 0.3s 0.25s; 529 | border-radius: 1rem; 530 | } 531 | .navicon:before { 532 | top: .625rem; 533 | } 534 | .navicon:after { 535 | top: -.625rem; 536 | } 537 | 538 | /* open */ 539 | .nav-trigger:checked + label:not(.steps) .navicon:before, 540 | .nav-trigger:checked + label:not(.steps) .navicon:after { 541 | top: 0 !important; 542 | } 543 | 544 | .nav-trigger:checked + label .navicon:before, 545 | .nav-trigger:checked + label .navicon:after { 546 | transition: 0.5s; 547 | } 548 | 549 | /* Minus */ 550 | .nav-trigger:checked + label { 551 | -webkit-transform: scale(0.75); 552 | transform: scale(0.75); 553 | } 554 | 555 | /* × and + */ 556 | .nav-trigger:checked + label.plus .navicon, 557 | .nav-trigger:checked + label.x .navicon { 558 | background: transparent; 559 | } 560 | 561 | .nav-trigger:checked + label.plus .navicon:before, 562 | .nav-trigger:checked + label.x .navicon:before { 563 | -webkit-transform: rotate(-45deg); 564 | transform: rotate(-45deg); 565 | background: #FFF; 566 | } 567 | 568 | .nav-trigger:checked + label.plus .navicon:after, 569 | .nav-trigger:checked + label.x .navicon:after { 570 | -webkit-transform: rotate(45deg); 571 | transform: rotate(45deg); 572 | background: #FFF; 573 | } 574 | 575 | .nav-trigger:checked + label.plus { 576 | -webkit-transform: scale(0.75) rotate(45deg); 577 | transform: scale(0.75) rotate(45deg); 578 | } 579 | 580 | .nav-trigger:checked ~ nav { 581 | left: 0 !important; 582 | } 583 | 584 | .nav-trigger:checked ~ .overlay { 585 | display: block; 586 | } 587 | 588 | .nav-trigger { 589 | position: fixed; 590 | top: 0; 591 | clip: rect(0, 0, 0, 0); 592 | } 593 | 594 | .overlay { 595 | display: none; 596 | position: fixed; 597 | top: 0; 598 | bottom: 0; 599 | left: 0; 600 | right: 0; 601 | width: 100%; 602 | height: 100%; 603 | background: hsla(0, 0%, 0%, 0.5); 604 | z-index: 1; 605 | } 606 | 607 | /* nav level */ 608 | .level-hide { 609 | display: none; 610 | } 611 | html[data-search-mode] .level-hide { 612 | display: block; 613 | } 614 | 615 | 616 | @media only screen and (min-width: 320px) and (max-width: 680px) { 617 | body { 618 | overflow-x: hidden; 619 | } 620 | 621 | nav { 622 | background: #FFF; 623 | width: 250px; 624 | height: 100%; 625 | position: fixed; 626 | top: 0; 627 | right: 0; 628 | bottom: 0; 629 | left: -250px; 630 | z-index: 3; 631 | padding: 0 10px; 632 | transition: left 0.2s; 633 | } 634 | 635 | .navicon-button { 636 | display: inline-block; 637 | position: fixed; 638 | top: 1.5em; 639 | right: 0; 640 | z-index: 2; 641 | } 642 | 643 | #main { 644 | width: 100%; 645 | min-width: 360px; 646 | } 647 | 648 | #main h1.page-title { 649 | margin: 1em 0; 650 | } 651 | 652 | #main section { 653 | padding: 0; 654 | } 655 | 656 | footer { 657 | margin-left: 0; 658 | } 659 | } 660 | 661 | /** Add a '#' to static members */ 662 | [data-type="member"] a::before { 663 | content: '#'; 664 | display: inline-block; 665 | margin-left: -14px; 666 | margin-right: 5px; 667 | } 668 | 669 | #disqus_thread{ 670 | margin-left: 30px; 671 | } 672 | -------------------------------------------------------------------------------- /docs/styles/prettify.css: -------------------------------------------------------------------------------- 1 | .pln { 2 | color: #ddd; 3 | } 4 | 5 | /* string content */ 6 | .str { 7 | color: #61ce3c; 8 | } 9 | 10 | /* a keyword */ 11 | .kwd { 12 | color: #fbde2d; 13 | } 14 | 15 | /* a comment */ 16 | .com { 17 | color: #aeaeae; 18 | } 19 | 20 | /* a type name */ 21 | .typ { 22 | color: #8da6ce; 23 | } 24 | 25 | /* a literal value */ 26 | .lit { 27 | color: #fbde2d; 28 | } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #ddd; 33 | } 34 | 35 | /* lisp open bracket */ 36 | .opn { 37 | color: #000000; 38 | } 39 | 40 | /* lisp close bracket */ 41 | .clo { 42 | color: #000000; 43 | } 44 | 45 | /* a markup tag name */ 46 | .tag { 47 | color: #8da6ce; 48 | } 49 | 50 | /* a markup attribute name */ 51 | .atn { 52 | color: #fbde2d; 53 | } 54 | 55 | /* a markup attribute value */ 56 | .atv { 57 | color: #ddd; 58 | } 59 | 60 | /* a declaration */ 61 | .dec { 62 | color: #EF5050; 63 | } 64 | 65 | /* a variable name */ 66 | .var { 67 | color: #c82829; 68 | } 69 | 70 | /* a function name */ 71 | .fun { 72 | color: #4271ae; 73 | } 74 | 75 | /* Specify class=linenums on a pre to get line numbering */ 76 | ol.linenums { 77 | margin-top: 0; 78 | margin-bottom: 0; 79 | } 80 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const API = require('./API'); 2 | const data = require('./data'); 3 | const ExtUtil = require('./ExtUtil'); 4 | const proc = require('./proc'); 5 | const types = require('./types'); 6 | const Util = require('./Util'); 7 | 8 | module.exports = { 9 | API: API, 10 | data: data, 11 | ExtUtil: ExtUtil, 12 | proc: proc, 13 | types: types, 14 | Util: Util 15 | }; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after the first failure 9 | // bail: false, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/tmp/jest_rs", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | // clearMocks: false, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | collectCoverage: true, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: null, 25 | 26 | // The directory where Jest should output its coverage files 27 | coverageDirectory: "coverage", 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | coveragePathIgnorePatterns: [ 31 | "/node_modules/", 32 | "/test/", 33 | "/*.js" 34 | ], 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: null, 46 | 47 | // Make calling deprecated APIs throw helpful error messages 48 | // errorOnDeprecated: false, 49 | 50 | // Force coverage collection from ignored files usin a array of glob patterns 51 | // forceCoverageMatch: [], 52 | 53 | // A path to a module which exports an async function that is triggered once before all test suites 54 | // globalSetup: null, 55 | 56 | // A path to a module which exports an async function that is triggered once after all test suites 57 | // globalTeardown: null, 58 | 59 | // A set of global variables that need to be available in all test environments 60 | // globals: {}, 61 | 62 | // An array of directory names to be searched recursively up from the requiring module's location 63 | // moduleDirectories: [ 64 | // "node_modules" 65 | // ], 66 | 67 | // An array of file extensions your modules use 68 | // moduleFileExtensions: [ 69 | // "js", 70 | // "json", 71 | // "jsx", 72 | // "node" 73 | // ], 74 | 75 | // A map from regular expressions to module names that allow to stub out resources with a single module 76 | // moduleNameMapper: {}, 77 | 78 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 79 | // modulePathIgnorePatterns: [], 80 | 81 | // Activates notifications for test results 82 | // notify: false, 83 | 84 | // An enum that specifies notification mode. Requires { notify: true } 85 | // notifyMode: "always", 86 | 87 | // A preset that is used as a base for Jest's configuration 88 | // preset: null, 89 | 90 | // Run tests from one or more projects 91 | // projects: null, 92 | 93 | // Use this configuration option to add custom reporters to Jest 94 | // reporters: undefined, 95 | 96 | // Automatically reset mock state between every test 97 | // resetMocks: false, 98 | 99 | // Reset the module registry before running each individual test 100 | // resetModules: false, 101 | 102 | // A path to a custom resolver 103 | // resolver: null, 104 | 105 | // Automatically restore mock state between every test 106 | // restoreMocks: false, 107 | 108 | // The root directory that Jest should scan for tests and modules within 109 | // rootDir: null, 110 | 111 | // A list of paths to directories that Jest should use to search for files in 112 | // roots: [ 113 | // "" 114 | // ], 115 | 116 | // Allows you to use a custom runner instead of Jest's default test runner 117 | // runner: "jest-runner", 118 | 119 | // The paths to modules that run some code to configure or set up the testing environment before each test 120 | // setupFiles: [], 121 | 122 | // The path to a module that runs some code to configure or set up the testing framework before each test 123 | // setupTestFrameworkScriptFile: null, 124 | 125 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 126 | // snapshotSerializers: [], 127 | 128 | // The test environment that will be used for testing 129 | testEnvironment: "node", 130 | 131 | // Options that will be passed to the testEnvironment 132 | // testEnvironmentOptions: {}, 133 | 134 | // Adds a location field to test results 135 | // testLocationInResults: false, 136 | 137 | // The glob patterns Jest uses to detect test files 138 | testMatch: [ 139 | "**/test/**/execute*.js?(x)" 140 | ], 141 | 142 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 143 | // testPathIgnorePatterns: [ 144 | // "/node_modules/" 145 | // ], 146 | 147 | // The regexp pattern Jest uses to detect test files 148 | // testRegex: "", 149 | 150 | // This option allows the use of a custom results processor 151 | // testResultsProcessor: null, 152 | 153 | // This option allows use of a custom test runner 154 | // testRunner: "jasmine2", 155 | 156 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 157 | // testURL: "http://localhost", 158 | 159 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 160 | // timers: "real", 161 | 162 | // A map from regular expressions to paths to transformers 163 | // transform: null, 164 | 165 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 166 | // transformIgnorePatterns: [ 167 | // "/node_modules/" 168 | // ], 169 | 170 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 171 | // unmockedModulePathPatterns: undefined, 172 | 173 | // Indicates whether each individual test should be reported during the run 174 | // verbose: null, 175 | 176 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 177 | // watchPathIgnorePatterns: [], 178 | 179 | // Whether to use watchman for file crawling 180 | // watchman: true, 181 | }; 182 | -------------------------------------------------------------------------------- /jvm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.JVM = void 0; 7 | 8 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 9 | 10 | /** 11 | * JVM Wrapper 12 | * @private 13 | */ 14 | class JVM { 15 | /** 16 | * Reflection된 Java Class 보관소 17 | * @type {Object.} 18 | * @private 19 | */ 20 | 21 | /** 22 | * 시스템에서 사용하는 JVM Instance 23 | */ 24 | 25 | /** 26 | * 불러온 패키지 목록. 27 | */ 28 | 29 | /** 30 | * Node-java에서 사용하는 JVM interface 31 | * @private 32 | * @type {Object} 33 | */ 34 | static init(java, packages) { 35 | if (typeof java === 'undefined' || java === null || JVM.java) throw TypeError('java는 null일 수 없고, JVM.init()은 한 번만 실행되어야 합니다.'); 36 | JVM.PACKAGES = packages; 37 | JVM.java = java; 38 | } 39 | /** 40 | * Java class 반환 41 | * @param {!string} path Class package path 42 | * @returns {Object} Java class 43 | */ 44 | 45 | 46 | static classOf(...path) { 47 | let clsName = path.join('.'); 48 | if (!JVM._classes.hasOwnProperty(clsName)) JVM._classes[clsName] = JVM.java.import(clsName); 49 | return JVM._classes[clsName]; 50 | } 51 | /** 52 | * Koala Class 반환 53 | * @param {!string} path Class package path 54 | * @returns {Object} Java class 55 | */ 56 | 57 | 58 | static koalaClassOf(...path) { 59 | return JVM.classOf('kr.bydelta.koala', ...path); 60 | } 61 | /** 62 | * Koala Enum 반환 63 | * @param {!string} tagset 표지자 집합 클래스 이름 64 | * @param {?string} tag 표지자 이름 65 | * @returns {?Object} 표지자 이름에 맞는 Java Enum 66 | */ 67 | 68 | 69 | static koalaEnumOf(tagset, tag) { 70 | if (tag !== null && typeof tag !== 'undefined') return JVM.koalaClassOf(tagset).valueOf(tag);else return null; 71 | } 72 | /** 73 | * Java Iterable -> JSON Array 74 | * @param {Object} array 75 | * @param itemConverter 76 | * @returns {Array} 77 | */ 78 | 79 | 80 | static toJsArray(array, itemConverter = x => x, isIterable = false) { 81 | if (typeof array === "undefined" || array === null) return []; 82 | if (Array.isArray(array)) return array.map(itemConverter); 83 | let result = []; 84 | 85 | if (isIterable) { 86 | let it = array.iterator(); 87 | 88 | while (it.hasNext()) { 89 | result.push(itemConverter(it.next())); 90 | } 91 | } else { 92 | let length = array.size(); 93 | 94 | for (let i = 0; i < length; ++i) { 95 | result.push(itemConverter(array.get(i))); 96 | } 97 | } 98 | 99 | return result; 100 | } 101 | /** 102 | * JSON Array -> Java List 103 | * @param {Object[]} array 104 | */ 105 | 106 | 107 | static listOf(array) { 108 | let list = new (JVM.classOf('java.util.ArrayList'))(); 109 | 110 | for (const item of array) list.add(item); 111 | 112 | return list; 113 | } 114 | /** 115 | * Make Java Pair 116 | * @param {Object} a First entry 117 | * @param {Object} b Second entry 118 | */ 119 | 120 | 121 | static pair(a, b) { 122 | return new (JVM.classOf('kotlin.Pair'))(a, b); 123 | } 124 | /** 125 | * Make Java Char 126 | * @param {?string} ch Character 127 | * @returns {Object} Java Character 128 | */ 129 | 130 | 131 | static char(ch) { 132 | if (ch !== null && typeof ch !== 'undefined') return JVM.java.newChar(ch.charCodeAt(0));else return null; 133 | } 134 | /** 135 | * Make Java Set 136 | * @param {Object[]} array Items 137 | * @returns {Object} Java Set 138 | */ 139 | 140 | 141 | static setOf(array) { 142 | let list = new (JVM.classOf('java.util.HashSet'))(); 143 | 144 | for (const item of array) list.add(item); 145 | 146 | return list; 147 | } 148 | 149 | static posFilter(posSet) { 150 | return JVM.java.newProxy('kotlin.jvm.functions.Function1', { 151 | 'invoke': function (tag) { 152 | return posSet.includes(tag.name()); 153 | } 154 | }); 155 | } 156 | /** 157 | * Check whether these packages are compatible. 158 | * @param {Object.} packages 159 | */ 160 | 161 | 162 | static canLoadPackages(packages) { 163 | let result = {}; 164 | 165 | if (JVM.java && JVM.java.isJvmCreated()) { 166 | for (let [pack, ver] of Object.entries(packages)) { 167 | result[pack] = JVM.PACKAGES.hasOwnProperty(pack) && (ver.toUpperCase() === 'LATEST' || JVM.PACKAGES[pack] >= ver); 168 | } 169 | } else { 170 | for (let pack of Object.keys(packages)) { 171 | result[pack] = true; 172 | } 173 | } 174 | 175 | return result; 176 | } 177 | 178 | } 179 | 180 | exports.JVM = JVM; 181 | 182 | _defineProperty(JVM, "_classes", {}); 183 | 184 | _defineProperty(JVM, "INSTANCE", void 0); 185 | 186 | _defineProperty(JVM, "PACKAGES", void 0); 187 | 188 | _defineProperty(JVM, "java", void 0); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koalanlp", 3 | "version": "2.1.3", 4 | "description": "Node.js wrapper for KoalaNLP (An integrated API for processing Korean in with Kotlin/Java/Scala)", 5 | "author": { 6 | "name": "koalanlp" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/koalanlp/nodejs-support.git" 11 | }, 12 | "bugs": { 13 | "url": "http://github.com/koalanlp/nodejs-support/issues" 14 | }, 15 | "main": "./index.js", 16 | "scripts": { 17 | "test": "jest --detectOpenHandles --forceExit", 18 | "compile": "./node_modules/.bin/babel -d ./ src/", 19 | "jsdoc": "rm -rf docs/* && ./node_modules/jsdoc/jsdoc.js -c .jsdoc.json && git add docs/*" 20 | }, 21 | "licenses": [ 22 | { 23 | "type": "MIT", 24 | "url": "http://www.opensource.org/licenses/MIT" 25 | } 26 | ], 27 | "engines": { 28 | "node": ">=8.0.0" 29 | }, 30 | "keywords": [ 31 | "korean", 32 | "koalanlp", 33 | "natural language processing", 34 | "sentence", 35 | "tagger", 36 | "parser", 37 | "한국어", 38 | "형태소", 39 | "품사분석", 40 | "구문분석", 41 | "의존구문", 42 | "의미역", 43 | "개체명" 44 | ], 45 | "dependencies": { 46 | "@babel/polyfill": "^7.7.0", 47 | "core-js": "2", 48 | "global": "^4.4.0", 49 | "java": "^0.12.0", 50 | "node-java-maven": "^0.1.1", 51 | "request": "^2.88.0", 52 | "underscore": "^1.9.1" 53 | }, 54 | "devDependencies": { 55 | "@babel/cli": "^7.7.5", 56 | "@babel/core": "^7.7.5", 57 | "@babel/plugin-proposal-class-properties": "^7.7.4", 58 | "@babel/preset-env": "^7.7.6", 59 | "babel-core": "^6.26.3", 60 | "babel-jest": "^24.9.0", 61 | "codecov": "^3.6.1", 62 | "docdash": "^1.1.1", 63 | "graphlib": "^2.1.8", 64 | "iconv-lite": "^0.5.0", 65 | "jest": "^24.9.0", 66 | "jsdoc": "^3.6.3", 67 | "regenerator-runtime": "^0.13.3", 68 | "should": "^13.2.3" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | extract_version() 4 | { 5 | LIB_VER=$(cat package.json | grep "version\": " | cut -d\" -f4 | cut -d- -f1) 6 | LIB_VER_MAJOR=$(echo $LIB_VER | cut -d. -f1) 7 | LIB_VER_MINOR=$(echo $LIB_VER | cut -d. -f2) 8 | LIB_VER_INCRM=$(echo $LIB_VER | cut -d. -f3) 9 | LIB_VER_CURRENT=$LIB_VER_MAJOR.$LIB_VER_MINOR.$LIB_VER_INCRM 10 | } 11 | 12 | add_incremental_ver() 13 | { 14 | LIB_VER_NEXT=$LIB_VER_MAJOR.$LIB_VER_MINOR.$(($LIB_VER_INCRM + 1)) 15 | } 16 | 17 | add_minor_ver() 18 | { 19 | LIB_VER_NEXT=$LIB_VER_MAJOR.$(( $LIB_VER_MINOR + 1)).0 20 | } 21 | 22 | set_version() 23 | { 24 | cat package.json | sed -e "s/version\":\s*\".*\"/version\": \"$1\"/g" > package.json.new 25 | rm package.json 26 | mv package.json.new package.json 27 | git add package.json 28 | } 29 | 30 | ask_proceed() 31 | { 32 | read -p "Proceed $1 [Y/n/p]? " YN 33 | if [ "${YN,,}" = "n" ]; then 34 | exit 0 35 | fi 36 | } 37 | 38 | extract_version 39 | echo $LIB_VER_CURRENT 40 | 41 | ask_proceed "Set Version" 42 | if [ "${YN,,}" != "p" ]; then 43 | read -p "Incrementally increase version [Y/n]? " YN 44 | if [ "${YN,,}" == "y" ]; then 45 | add_incremental_ver 46 | else 47 | add_minor_ver 48 | fi 49 | set_version $LIB_VER_NEXT 50 | fi 51 | 52 | ask_proceed "Documentation" 53 | if [ "${YN,,}" != "p" ]; then 54 | npm run jsdoc 55 | fi 56 | 57 | ask_proceed "Compile to CommonJS" 58 | if [ "${YN,,}" != "p" ]; then 59 | npm run compile 60 | fi 61 | 62 | ask_proceed "Test" 63 | if [ "${YN,,}" != "p" ]; then 64 | npm run test 65 | fi 66 | 67 | ask_proceed "Commit" 68 | if [ "${YN,,}" != "p" ]; then 69 | git add docs/ 70 | git add -u 71 | git add -i 72 | git commit 73 | fi 74 | 75 | ask_proceed "Publish" 76 | if [ "${YN,,}" != "p" ]; then 77 | npm publish 78 | git tag v$LIB_VER_NEXT 79 | fi 80 | 81 | ask_proceed "Push" 82 | if [ "${YN,,}" != "p" ]; then 83 | git push --all 84 | git push --tags 85 | fi -------------------------------------------------------------------------------- /scripts/khaiii_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $TRAVIS_OS_NAME == 'windows' ] 4 | then 5 | echo "Khaiii does not support Windows architecture." 6 | exit 0 7 | fi 8 | 9 | ### Khaiii 저장소 clone 10 | if [ ! -d "$HOME/khaiii-orig/.git" ] 11 | then 12 | ### Clone to ~/khaiii-orig/ 13 | cd $HOME 14 | git clone https://github.com/kakao/khaiii.git khaiii-orig 15 | cd khaiii-orig 16 | echo "\033[34mClone finished!\033[0m" 17 | else 18 | ### Travis CI에 이미 Caching된 데이터가 있다면, pull 19 | cd $HOME/khaiii-orig 20 | git pull origin master 21 | echo "\033[34mPull finished!\033[0m" 22 | fi 23 | 24 | KHAIII_LATEST=$(git tag -l | tail -n 1) 25 | git checkout -f tags/$KHAIII_LATEST 26 | 27 | ### Make build files 28 | if [ ! -d "build" ] 29 | then 30 | mkdir build 31 | fi 32 | 33 | # OS Release check (khaiii/khaiii#103) 34 | if [ $TRAVIS_OS_NAME == 'linux' ] 35 | then 36 | RELEASE=`lsb_release -cs` 37 | else 38 | RELEASE=none 39 | fi 40 | 41 | cd build 42 | if [ $RELEASE == 'focal' ] 43 | then 44 | cmake -E env CXXFLAGS="-w" cmake .. 45 | else 46 | cmake .. 47 | fi 48 | 49 | ### Make shared object file 50 | if [ ! -f "lib/libkhaiii.so.${KHAIII_LATEST}" ] 51 | then 52 | make clean 53 | make all 54 | echo "\033[34mBuild finished!\033[0m" 55 | 56 | ### Make resources 57 | if [ ! -f "share/khaiii/restore.key" ] 58 | then 59 | ### Build resource 60 | make resource 61 | 62 | echo "\033[34mResource build finished!\033[0m" 63 | else 64 | echo resource files exist. 65 | fi 66 | else 67 | echo libkhaiii.so.${KHAIII_LATEST} already exists. 68 | fi 69 | 70 | 71 | if [ "$TRAVIS_OS_NAME" != "linux" ] 72 | then 73 | make install 74 | fi -------------------------------------------------------------------------------- /scripts/utagger_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ### Architecture 확인 4 | if [ $TRAVIS_OS_NAME == 'osx' ] 5 | then 6 | echo "UTagger does not support OSX architecture." 7 | exit 0 8 | fi 9 | 10 | if [ -f "$HOME/utagger/Hlxcfg.txt" ] 11 | then 12 | echo "UTagger already installed." 13 | exit 0 14 | fi 15 | 16 | ### UTagger 파일 다운로드 17 | cd $HOME 18 | if [ -z "$TRAVIS_OS_NAME" ] 19 | then 20 | echo "Downloading from FTP of Ulsan University" 21 | wget ftp://203.250.77.242/utagger%20delay%202018%2010%2031.zip -O utagger.zip 22 | else 23 | echo "Downloading a light-weighted version for Travis CI. (Only for testing use)" 24 | wget https://bitbucket.org/nearbydelta/koalanlp-test-large/downloads/utagger.zip -O utagger.zip 25 | fi 26 | 27 | ### 압축 풀기 28 | unzip -o utagger.zip -d utagger 29 | -------------------------------------------------------------------------------- /src/API.js: -------------------------------------------------------------------------------- 1 | /** 2 | * KoalaNLP가 지원하는 패키지의 목록을 키값으로 가지고 있습니다. 3 | * 4 | * * **주의** API를 import한다고 하여 자동으로 초기화가 되지 않으니 꼭 {@link module:koalanlp/Util.initialize}를 실행하시기 바랍니다. 5 | * 6 | * @module koalanlp/API 7 | * @example 8 | * import * as API from 'koalanlp/API'; 9 | * **/ 10 | 11 | import {JVM} from './jvm'; 12 | 13 | /** 14 | * @public 15 | * @typedef {string} API 16 | */ 17 | 18 | /** 19 | * 한나눔. 20 | * 현재 버전이 최신입니다. 문장분리, 품사분석, 구문분석, 의존분석이 가능합니다. 21 | * @type API 22 | * @example 23 | * import {HNN} from 'koalanlp/API'; 24 | */ 25 | export const HNN = 'hnn'; 26 | 27 | 28 | /** 29 | * 코모란. 30 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 31 | * @type API 32 | * @example 33 | * import {KMR} from 'koalanlp/API'; 34 | */ 35 | export const KMR = 'kmr'; 36 | 37 | 38 | /** 39 | * 꼬꼬마. 40 | * 현재 버전이 최신입니다. 품사분석, 의존분석만 가능합니다. 41 | * @type API 42 | * @example 43 | * import {KKMA} from 'koalanlp/API'; 44 | */ 45 | export const KKMA = 'kkma'; 46 | 47 | 48 | /** 49 | * 은전한닢. 50 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 51 | * @type API 52 | * @example 53 | * import {EUNJEON} from 'koalanlp/API'; 54 | */ 55 | export const EUNJEON = 'eunjeon'; 56 | 57 | 58 | /** 59 | * 아리랑. 60 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 61 | * @type API 62 | * @example 63 | * import {ARIRANG} from 'koalanlp/API'; 64 | */ 65 | export const ARIRANG = 'arirang'; 66 | 67 | 68 | /** 69 | * 라이노. 70 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 71 | * @type API 72 | * @example 73 | * import {RHINO} from 'koalanlp/API'; 74 | */ 75 | export const RHINO = 'rhino'; 76 | 77 | 78 | /** 79 | * 트위터. 80 | * 현재 버전이 최신입니다. 문장분리, 품사분석만 가능합니다. 81 | * @type API 82 | * @example 83 | * import {OKT} from 'koalanlp/API'; 84 | */ 85 | export const OKT = 'okt'; 86 | 87 | 88 | /** 89 | * 다온. 90 | * 현재 버전이 최신입니다. 품사분석만 가능합니다. 91 | * @type API 92 | * @example 93 | * import {DAON} from 'koalanlp/API'; 94 | */ 95 | export const DAON = 'daon'; 96 | 97 | 98 | /** 99 | * ETRI Open API. 100 | * 현재 버전이 최신입니다. 101 | * @type API 102 | * @example 103 | * import {ETRI} from 'koalanlp/API'; 104 | */ 105 | export const ETRI = 'etri'; 106 | 107 | 108 | /** 109 | * Kakao Khaiii (Experimental) 110 | * 현재 버전이 최신입니다. 111 | * 112 | * **(참고)** 113 | * - 이 기능은 현재 실험적인 기능입니다. 114 | * - Khaiii는 C++로 개발되어 별도 설치가 필요합니다. [Khaiii](https://github.com/kakao/khaiii) 공식 홈페이지에서 설치법을 확인하시고, 설치하시면 됩니다. 115 | * 116 | * @type API 117 | * @example 118 | * import {KHAIII} from 'koalanlp/API'; 119 | */ 120 | export const KHAIII = 'khaiii'; 121 | 122 | 123 | /** 124 | * UTagger (Experimental) 125 | * 현재 버전이 최신입니다. 126 | * 127 | * **(참고)** 128 | * - 이 기능은 현재 실험적인 기능입니다. 129 | * - UTagger는 C로 개발되어 별도 설치가 필요합니다. [UTagger install](https://koalanlp.github.io/usage/Install-UTagger.md)에서 설치법을 확인하시고, 설치하시면 됩니다. 130 | * 131 | * @type API 132 | * @example 133 | * import {UTAGGER} from 'koalanlp/API'; 134 | */ 135 | export const UTAGGER = 'utagger'; 136 | 137 | 138 | /** 139 | * 분석기 Interface 정의 라이브러리. 140 | * 현재 버전이 최신입니다. 편의기능을 제공하며 타 분석기 참조시 함께 참조됩니다. 141 | * @type API 142 | * @example 143 | * import {CORE} from 'koalanlp/API'; 144 | */ 145 | export const CORE = 'core'; 146 | 147 | 148 | /** 149 | * 'assembly' classifier 필요 여부 150 | * @private 151 | */ 152 | export const PACKAGE_REQUIRE_ASSEMBLY = [HNN, KKMA, ARIRANG, RHINO, DAON]; 153 | 154 | 155 | /** 156 | * 해당 API가 분석기를 지원하는지 확인함. 157 | * @param {!API} api 분석기 API 158 | * @param {!string} type 분석기 유형 159 | * @returns {Object} 지원한다면 해당 분석기 Java 클래스. 160 | * @private 161 | */ 162 | export function query(api, type) { 163 | try { 164 | return JVM.koalaClassOf(api, type); 165 | } catch (e) { 166 | throw Error(`API.${api}에서 ${type}을 불러오는 데 실패했습니다! ${type}이 없거나, 다운로드가 완전하지 않았을 수 있습니다. Cause: ${e}`); 167 | } 168 | } 169 | 170 | 171 | /** 172 | * API의 패키지 이름 반환 173 | * @param {string} api 분석기 API 174 | * @return {string} 패키지 이름 접미사 175 | * @private 176 | */ 177 | export function getPackage(api) { 178 | return api.toLowerCase(); 179 | } 180 | -------------------------------------------------------------------------------- /src/ExtUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 여러 편의기능을 모아놓은 Module입니다. 3 | * @module koalanlp/ExtUtil 4 | * @example 5 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 6 | **/ 7 | 8 | import {JVM} from './jvm'; 9 | import _ from 'underscore'; 10 | 11 | 12 | /** 13 | * 초성 조합형 문자열 리스트 (UNICODE 순서) 14 | * 15 | * 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' 16 | * 17 | * @type {ReadonlyArray} 18 | * @example 19 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 20 | * console.log(ExtUtil.HanFirstList[0]); 21 | */ 22 | export const HanFirstList = Object.freeze(_.range(0x1100, 0x1112 + 1).map((x) => String.fromCharCode(x))); 23 | 24 | /** 25 | * 중성 조합형 문자열 리스트 (UNICODE 순서) 26 | * 27 | * 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ' 28 | * 29 | * @type {ReadonlyArray} 30 | * @example 31 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 32 | * console.log(ExtUtil.HanSecondList[0]); 33 | */ 34 | export const HanSecondList = Object.freeze(_.range(0x1161, 0x1175 + 1).map((x) => String.fromCharCode(x))); 35 | 36 | /** 37 | * 종성 조합형 문자열 리스트 (UNICODE 순서). 가장 첫번째는 null (받침 없음) 38 | * 39 | * undefined, 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 40 | * 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' 41 | * 42 | * @type {ReadonlyArray} 43 | * @example 44 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 45 | * console.log(ExtUtil.HanLastList[0]); 46 | */ 47 | export const HanLastList = Object.freeze(_.range(0x11A7, 0x11C2 + 1).map((x) => { 48 | if (x === 0x11A7) return undefined; 49 | else return String.fromCharCode(x); 50 | })); 51 | 52 | /** 53 | * 초성 문자를 종성 조합형 문자로 변경할 수 있는 map 54 | * @type {Readonly>} 55 | * @example 56 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 57 | * console.log(ExtUtil.ChoToJong.get('ㄵ')); 58 | */ 59 | export const ChoToJong = Object.freeze(new Map([ 60 | ['\u1100', '\u11A8'], // ㄱ 61 | ['\u1101', '\u11A9'], // ㄲ 62 | ['\u1102', '\u11AB'], // ㄴ 63 | ['\u1103', '\u11AE'], // ㄷ 64 | ['\u1105', '\u11AF'], // ㄹ 65 | ['\u1106', '\u11B7'], // ㅁ 66 | ['\u1107', '\u11B8'], // ㅂ 67 | ['\u1109', '\u11BA'], // ㅅ 68 | ['\u110A', '\u11BB'], // ㅆ 69 | ['\u110B', '\u11BC'], // ㅇ 70 | ['\u110C', '\u11BD'], // ㅈ 71 | ['\u110E', '\u11BE'], // ㅊ 72 | ['\u110F', '\u11BF'], // ㅋ 73 | ['\u1110', '\u11C0'], // ㅌ 74 | ['\u1111', '\u11C1'], // ㅍ 75 | ['\u1112', '\u11C2'], // ㅎ 76 | // 아래는 완성형 문자 77 | ['ㄱ', '\u11A8'], // ㄱ 78 | ['ㄲ', '\u11A9'], 79 | ['ㄳ', '\u11AA'], 80 | ['ㄴ', '\u11AB'], // ㄴ 81 | ['ㄵ', '\u11AC'], 82 | ['ㄶ', '\u11AD'], 83 | ['ㄷ', '\u11AE'], // ㄷ 84 | ['ㄹ', '\u11AF'], // ㄹ 85 | ['ㄺ', '\u11B0'], 86 | ['ㄻ', '\u11B1'], 87 | ['ㄼ', '\u11B2'], 88 | ['ㄽ', '\u11B3'], 89 | ['ㄾ', '\u11B4'], 90 | ['ㄿ', '\u11B5'], 91 | ['ㅀ', '\u11B6'], 92 | ['ㅁ', '\u11B7'], // ㅁ 93 | ['ㅂ', '\u11B8'], // ㅂ 94 | ['ㅄ', '\u11B9'], 95 | ['ㅅ', '\u11BA'], // ㅅ 96 | ['ㅆ', '\u11BB'], // ㅆ 97 | ['ㅇ', '\u11BC'], // ㅇ 98 | ['ㅈ', '\u11BD'], // ㅈ 99 | ['ㅊ', '\u11BE'], // ㅊ 100 | ['ㅋ', '\u11BF'], // ㅋ 101 | ['ㅌ', '\u11C0'], // ㅌ 102 | ['ㅍ', '\u11C1'], // ㅍ 103 | ['ㅎ', '\u11C2'] // ㅎ 104 | ])); 105 | 106 | 107 | /** 108 | * 주어진 문자열에서 알파벳이 발음되는 대로 국문 문자열로 표기하여 값으로 돌려줍니다. 109 | * 110 | * @param {!string} text 알파벳을 발음할 문자열 111 | * @returns {string} 국문 발음 표기된 문자열 112 | * @example 113 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 114 | * console.log(ExtUtil.alphaToHangul("갤럭시S")); 115 | */ 116 | export function alphaToHangul(text) { 117 | return JVM.koalaClassOf('ExtUtil').alphaToHangul(text).toString(); 118 | } 119 | 120 | 121 | /** 122 | * 주어진 문자열에 적힌 알파벳 발음을 알파벳으로 변환하여 문자열로 반환합니다. 123 | * @param {!string} text 국문 발음 표기된 문자열 124 | * @returns {string} 영문 변환된 문자열 125 | * @example 126 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 127 | * console.log(ExtUtil.hangulToAlpah("갤럭시에스")); 128 | */ 129 | export function hangulToAlpha(text) { 130 | return JVM.koalaClassOf('ExtUtil').hangulToAlpha(text).toString(); 131 | } 132 | 133 | 134 | /** 135 | * 주어진 문자열이 알파벳이 발음되는 대로 표기된 문자열인지 확인합니다. 136 | * @param {!string} text 확인할 문자열 137 | * @returns {boolean} 영문 발음으로만 구성되었다면 true 138 | * @example 139 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 140 | * console.log(ExtUtil.isAlphaPronounced("갤럭시에스")); 141 | */ 142 | export function isAlphaPronounced(text) { 143 | return JVM.koalaClassOf('ExtUtil').isAlphaPronounced(text); 144 | } 145 | 146 | /** 147 | * 문자열을 순회하면서 charFunction을 실행합니다. 148 | * @param {!string} text 순회할 문자열 149 | * @param charFunction 문자별로 실행할 함수 150 | * @returns {Array} 문자별 결과. 151 | * @private 152 | */ 153 | function stringRepeat(text, charFunction) { 154 | let result = []; 155 | for (let ch of text) { 156 | let res = charFunction(JVM.char(ch)); 157 | res = (res === null) ? undefined : res; 158 | 159 | result.push(res); 160 | } 161 | return result; 162 | } 163 | 164 | /** 165 | * 문자열의 각 문자가 한자 범위인지 확인합니다. 166 | * @param {!string} text 확인할 문자열 167 | * @returns {boolean[]} 문자열 문자의 위치마다 한자인지 아닌지를 표기한 리스트. 한자라면 True. 168 | * @example 169 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 170 | * console.log(ExtUtil.isHanja("貝波通水")); 171 | */ 172 | export function isHanja(text) { 173 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isHanja); 174 | } 175 | 176 | 177 | /** 178 | * 현재 문자가 한중일 통합한자, 통합한자 확장 - A, 호환용 한자 범위인지 확인합니다. 179 | * (국사편찬위원회 한자음가사전은 해당 범위에서만 정의되어 있어, 별도 확인합니다.) 180 | * 181 | * @param {!string} text 확인할 문자열 182 | * @returns {boolean[]} 문자열 문자의 위치마다 한자인지 아닌지를 표기한 리스트. 한자라면 True. 183 | * @example 184 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 185 | * console.log(ExtUtil.isCJKHanja("貝波通水")); 186 | */ 187 | export function isCJKHanja(text) { 188 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isCJKHanja); 189 | } 190 | 191 | 192 | /** 193 | * 국사편찬위원회 한자음가사전에 따라 한자 표기된 내용을 국문 표기로 전환합니다. 194 | * 195 | * 참고: 196 | * 197 | * * [headCorrection] 값이 true인 경우, whitespace에 따라오는 문자에 두음법칙을 자동 적용함. (기본값 true) 198 | * * 단, 다음 의존명사는 예외: 냥(兩), 년(年), 리(里), 리(理), 량(輛) 199 | * * 다음 두음법칙은 사전을 조회하지 않기 때문에 적용되지 않음에 유의 200 | * - 한자 파생어나 합성어에서 원 단어의 두음법칙: 예) "신여성"이 옳은 표기이나 "신녀성"으로 표기됨 201 | * - 외자가 아닌 이름: 예) "허난설헌"이 옳은 표기이나 "허란설헌"으로 표기됨 202 | * 203 | * @param {!string} text 국문 표기로 전환할 문자열 204 | * @param {boolean} [headCorrection=true] 두음법칙 적용 여부 205 | * @returns {string} 국문 표기로 전환된 문자열 206 | * @example 207 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 208 | * console.log(ExtUtil.hanjaToHangul("貝波通水")); 209 | */ 210 | export function hanjaToHangul(text, headCorrection = true) { 211 | return JVM.koalaClassOf('ExtUtil').hanjaToHangul(text, headCorrection).toString(); 212 | } 213 | 214 | 215 | /** 216 | * 현재 문자가 초성, 중성, 종성(선택적)을 다 갖춘 문자인지 확인합니다. 217 | * 218 | * @param {!string} text 확인할 문자열 219 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 220 | * @example 221 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 222 | * console.log(ExtUtil.isCompleteHangul("Sing! 노래하라")); 223 | */ 224 | export function isCompleteHangul(text) { 225 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isCompleteHangul); 226 | } 227 | 228 | 229 | /** 230 | * 현재 문자가 불완전한 한글 문자인지 확인합니다. 231 | * 232 | * @param {!string} text 확인할 문자열 233 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 234 | * @example 235 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 236 | * console.log(ExtUtil.isIncompleteHangul("Sing! 노래하라")); 237 | */ 238 | export function isIncompleteHangul(text) { 239 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isIncompleteHangul); 240 | } 241 | 242 | 243 | /** 244 | * 현재 문자가 한글 완성형 또는 조합용 문자인지 확인합니다. 245 | * 246 | * @param {!string} text 확인할 문자열 247 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 248 | * @example 249 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 250 | * console.log(ExtUtil.isHangul("Sing! 노래하라")); 251 | */ 252 | export function isHangul(text) { 253 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isHangul); 254 | } 255 | 256 | 257 | /** 258 | * 현재 문자열이 한글 (완성/조합)로 끝나는지 확인합니다. 259 | * 260 | * @param {!string} text 확인할 문자열 261 | * @returns {boolean} 맞다면 True. 262 | * @example 263 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 264 | * console.log(ExtUtil.isHangulEnding("Sing! 노래하라")); 265 | */ 266 | export function isHangulEnding(text) { 267 | return JVM.koalaClassOf('ExtUtil').isHangulEnding(text); 268 | } 269 | 270 | 271 | /** 272 | * 현재 문자가 현대 한글 초성 자음 문자인지 확인합니다. 273 | * 274 | * @param {!string} text 확인할 문자열 275 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 276 | * @example 277 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 278 | * console.log(ExtUtil.isChosungJamo("\u1100")); 279 | */ 280 | export function isChosungJamo(text) { 281 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isChosungJamo); 282 | } 283 | 284 | 285 | /** 286 | * 현재 문자가 현대 한글 중성 모음 문자인지 확인합니다. 287 | * 288 | * @param {!string} text 확인할 문자열 289 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 290 | * @example 291 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 292 | * console.log(ExtUtil.isJungsungJamo("\u1161")); 293 | */ 294 | export function isJungsungJamo(text) { 295 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isJungsungJamo); 296 | } 297 | 298 | 299 | /** 300 | * 현재 문자가 현대 한글 종성 자음 문자인지 확인합니다. 301 | * 302 | * @param {!string} text 확인할 문자열 303 | * @returns {boolean[]} 문자열 문자의 위치마다 확인여부를 표기한 리스트. 맞다면 True. 304 | * @example 305 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 306 | * console.log(ExtUtil.isJungsungJamo("\u11A8")); 307 | */ 308 | export function isJongsungJamo(text) { 309 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').isJongsungJamo); 310 | } 311 | 312 | 313 | /** 314 | * 현재 문자열이 종성으로 끝인지 확인합니다. 315 | * 316 | * @param {!string} text 확인할 문자열 317 | * @returns {boolean} 맞다면 True. 318 | * @example 319 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 320 | * console.log(ExtUtil.isJongsungEnding("Sing! 노래하라")); 321 | */ 322 | export function isJongsungEnding(text) { 323 | return JVM.koalaClassOf('ExtUtil').isJongsungEnding(text); 324 | } 325 | 326 | 327 | /** 328 | * 현재 문자에서 초성 자음문자를 분리합니다. 초성이 없으면 None. 329 | * @param {!string} text 분리할 문자열 330 | * @returns {string[]} 분리된 각 초성이 들어간 리스트. 331 | * @example 332 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 333 | * console.log(ExtUtil.getChosung("제주도의 푸른 밤")); 334 | */ 335 | export function getChosung(text) { 336 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').getChosung); 337 | } 338 | 339 | 340 | /** 341 | * 현재 문자에서 중성 모음문자를 분리합니다. 중성이 없으면 None. 342 | * @param {!string} text 분리할 문자열 343 | * @returns {string[]} 분리된 각 중성이 들어간 리스트. 344 | * @example 345 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 346 | * console.log(ExtUtil.getJungsung("제주도의 푸른 밤")); 347 | */ 348 | export function getJungsung(text) { 349 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').getJungsung); 350 | } 351 | 352 | 353 | /** 354 | * 현재 문자에서 종성 자음문자를 분리합니다. 종성이 없으면 None. 355 | * @param {!string} text 분리할 문자열 356 | * @returns {string[]} 분리된 각 종성이 들어간 리스트. 357 | * @example 358 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 359 | * console.log(ExtUtil.getJongsung("제주도의 푸른 밤")); 360 | */ 361 | export function getJongsung(text) { 362 | return stringRepeat(text, JVM.koalaClassOf('ExtUtil').getJongsung); 363 | } 364 | 365 | 366 | /** 367 | * 현재 문자열을 초성, 중성, 종성 자음문자로 분리하여 새 문자열을 만듭니다. 종성이 없으면 종성은 쓰지 않습니다. 368 | * @param {string} text 분해할 문자열 369 | * @returns {string} 분해된 문자열 370 | * @example 371 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 372 | * console.log(ExtUtil.dissembleHangul("제주도의 푸른 밤")); 373 | */ 374 | export function dissembleHangul(text) { 375 | return JVM.koalaClassOf('ExtUtil').dissembleHangul(text).toString(); 376 | } 377 | 378 | 379 | /** 380 | * 초성을 [cho] 문자로, 중성을 [jung] 문자로, 종성을 [jong] 문자로 갖는 한글 문자를 재구성합니다. 381 | * 382 | * @param {string} [cho=undefined] 초성 문자. (0x1100-1112) 기본값 ㅇ 자모 383 | * @param {string} [jung=undefined] 중성 문자, (0x1161-1175) 기본값 ㅡ 자모 384 | * @param {string} [jong=undefined] 종성 문자, (0x11a8-11c2) 기본값 종성 없음 385 | * @returns {string} 초성, 중성, 종성을 조합하여 문자를 만듭니다. 386 | * @example 387 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 388 | * console.log(ExtUtil.assembleHangulTriple('\u1100', '\u1161', '\u11A8')); 389 | */ 390 | export function assembleHangulTriple(cho = undefined, jung = undefined, jong = undefined) { 391 | return JVM.koalaClassOf('ExtUtil').assembleHangul(JVM.char(cho), JVM.char(jung), JVM.char(jong)); 392 | } 393 | 394 | 395 | /** 396 | * 주어진 문자열에서 초성, 중성, 종성이 연달아 나오는 경우 이를 조합하여 한글 문자를 재구성합니다. 397 | * 398 | * @param {string} text 조합할 문자열 399 | * @returns {string} 조합형 문자들이 조합된 문자열. 조합이 불가능한 문자는 그대로 남습니다. 400 | * @example 401 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 402 | * console.log(ExtUtil.assembleHangul("제주도의 푸른 밤인 \u1100\u1161\u11A8")); 403 | */ 404 | export function assembleHangul(text) { 405 | return JVM.koalaClassOf('ExtUtil').assembleHangulString(text).toString() 406 | } 407 | 408 | 409 | /** 410 | * 주어진 용언의 원형 [verb]이 뒷 부분 [rest]와 같이 어미가 붙어 활용될 때, 불규칙 활용 용언과 모음조화를 교정합니다. 411 | * 412 | * @param {string} verb 용언 원형인 어근을 표현한 String. '-다.' 와 같은 어미는 없는 어근 상태입니다. 413 | * @param {boolean} isVerb 동사인지 형용사인지 나타내는 지시자. 동사이면 true. 414 | * @param {string} rest 어근에 붙일 어미를 표현한 String. 415 | * @returns {string} 모음조화나 불규칙 활용이 교정된 원형+어미 결합 416 | * @example 417 | * import * as ExtUtil from 'koalanlp/ExtUtil'; 418 | * console.log(ExtUtil.correctVerbApply("밀리",true,"어")); 419 | */ 420 | export function correctVerbApply(verb, isVerb, rest) { 421 | return JVM.koalaClassOf('ExtUtil').correctVerbApply(verb, isVerb, rest); 422 | } 423 | -------------------------------------------------------------------------------- /src/Util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 초기화 및 기타 작업에 필요한 함수를 제공합니다. 3 | * 4 | * @module koalanlp/Util 5 | * @example 6 | * import * as Util from 'koalanlp/Util'; 7 | */ 8 | 9 | import * as API from './API'; 10 | import {JVM} from './jvm'; 11 | import {POS, CoarseEntityType, DependencyTag, PhraseTag, RoleType} from "./types"; 12 | import _ from 'underscore'; 13 | import {assert, isDefined} from './common'; 14 | 15 | /** 16 | * @private 17 | * @param api 18 | */ 19 | async function queryVersion(api){ 20 | const request = require('request'); 21 | 22 | let url = `https://repo1.maven.org/maven2/kr/bydelta/koalanlp-${api}/`; 23 | let result = await new Promise((resolve, reject) => { 24 | request(url, {headers: {'User-Agent': 'curl/7.58.0'}}, // Query as if CURL did. 25 | (error, res, body) => { 26 | if(error) reject(error); 27 | else resolve(body); 28 | }); 29 | }); 30 | 31 | let matches = result.match(new RegExp('href="(\\d+\\.\\d+\\.\\d+(-[A-Za-z]+(\\.\\d+)?)?)/"', 'g')); 32 | matches = matches.map((line) => line.substring(6, line.length - 2)); 33 | 34 | let version = matches.sort().reverse()[0]; 35 | console.info(`[INFO] Latest version of kr.bydelta:koalanlp-${api} (${version}) will be used`); 36 | return version; 37 | } 38 | 39 | /** 40 | * API와 버전을 받아 Artifact 객체 구성 41 | * @param {API} api 분석기 패키지 이름 42 | * @param {!string} version 버전 또는 LATEST 43 | * @return {{groupId: string, artifactId: string, version: string}} Artifact 객체 44 | * @private 45 | */ 46 | async function makeDependencyItem(api, version){ 47 | let isAssembly = API.PACKAGE_REQUIRE_ASSEMBLY.includes(api); 48 | if(typeof version === 'undefined' || version.toUpperCase() === 'LATEST'){ 49 | version = await queryVersion(api) 50 | } 51 | 52 | let obj = { 53 | "groupId": "kr.bydelta", 54 | "artifactId": `koalanlp-${api}`, 55 | "version": version 56 | }; 57 | 58 | if(isAssembly){ 59 | obj.classifier = "assembly" 60 | } 61 | 62 | return obj; 63 | } 64 | 65 | /** 66 | * Remote Maven Repository list 67 | * @type {Object[]} 68 | * @private 69 | */ 70 | let remoteRepos = [ 71 | { 72 | id: 'sonatype', 73 | url: 'https://oss.sonatype.org/content/repositories/public/' 74 | }, 75 | { 76 | id: "jitpack.io", 77 | url: "https://jitpack.io/" 78 | }, 79 | { 80 | id: 'jcenter', 81 | url: 'https://jcenter.bintray.com/' 82 | }, 83 | { 84 | id: 'maven-central-1', 85 | url: 'https://repo1.maven.org/maven2/' 86 | }, 87 | { 88 | id: 'maven-central-2', 89 | url: 'http://insecure.repo1.maven.org/maven2/' 90 | }, 91 | { 92 | id: 'kotlin-dev', 93 | url: 'https://dl.bintray.com/kotlin/kotlin-dev/' 94 | } 95 | ]; 96 | 97 | function versionSplit(ver){ 98 | let dashAt = ver.indexOf('-'); 99 | 100 | if (dashAt !== -1){ 101 | let semver = ver.substr(0, dashAt).split('\\.'); 102 | let tag = ver.substr(dashAt+1); 103 | 104 | semver = semver.map(parseInt); 105 | semver.push(tag); 106 | return semver; 107 | }else{ 108 | let semver = ver.split('\\.'); 109 | return semver.map(parseInt); 110 | } 111 | } 112 | 113 | /** @private */ 114 | function isRightNewer(ver1, ver2){ 115 | let semver1 = versionSplit(ver1); 116 | let semver2 = versionSplit(ver2); 117 | 118 | let length = Math.max(semver1.length, semver2.length); 119 | for(let i of _.range(length)){ 120 | let comp1 = semver1[i]; 121 | let comp2 = semver2[i]; 122 | 123 | if(!isDefined(comp2)) return true; // 왼쪽은 Tag가 있지만 오른쪽은 없는 상태. (오른쪽이 더 최신) 124 | if(!isDefined(comp1)) return false; // 반대: 왼쪽이 더 최신 125 | if(comp1 !== comp2) return comp1 < comp2; // comp2 가 더 높으면 최신. 126 | } 127 | 128 | return false; 129 | } 130 | 131 | /** 132 | * 자바 및 의존패키지를 Maven Repository에서 다운받고, 자바 환경을 실행합니다. 133 | * 134 | * @param {Object} options 135 | * @param {Object.} options.packages 사용할 패키지와 그 버전들. 136 | * @param {string[]} [options.javaOptions=["-Xmx4g", "-Dfile.encoding=utf-8"]] JVM 초기화 조건 137 | * @param {boolean} [options.verbose=true] 더 상세히 초기화 과정을 보여줄 것인지의 여부. 138 | * @param {!string} [options.tempJsonName='koalanlp.json'] Maven 실행을 위해 임시로 작성할 파일의 이름. 139 | * @example 140 | * import {initialize} from 'koalanlp/Util'; 141 | * import {ETRI} from 'koalanlp/API'; 142 | * 143 | * // Promise 방식 144 | * let promise = initialize({'packages': {ETRI: '2.0.4'}}); 145 | * promise.then(...); 146 | * 147 | * // Async/Await 방식 (async function 내부에서) 148 | * await initialize({ETRI: '2.0.4'}); 149 | * ... 150 | */ 151 | export async function initialize(options) { 152 | assert(options.packages, "packages는 설정되어야 하는 값입니다."); 153 | let packages = options.packages; 154 | let verbose = (isDefined(options.verbose)) ? options.verbose : false; 155 | let javaOptions = options.javaOptions || ["-Xmx4g", "-Dfile.encoding=utf-8"]; 156 | let tempJsonName = options.tempJsonName || 'koalanlp.json'; 157 | 158 | /***** 자바 초기화 ******/ 159 | let java = require('java'); 160 | 161 | if (!JVM.canLoadPackages(packages)){ 162 | throw Error(`JVM은 두번 이상 초기화될 수 없습니다. ${packages}를 불러오려고 시도했지만 이미 ${JVM.PACKAGES}를 불러온 상태입니다.`); 163 | } 164 | 165 | java.options.push(...javaOptions); 166 | java.asyncOptions = { 167 | asyncSuffix: undefined, // Async Callback 무력화 168 | syncSuffix: '', // Synchronized call은 접미사 없음 169 | promiseSuffix: 'Promise', // Promise Callback 설정 170 | promisify: require('util').promisify 171 | }; 172 | 173 | /***** Maven 설정 *****/ 174 | const os = require('os'); 175 | const fs = require('fs'); 176 | const path = require('path'); 177 | const mvn = require('node-java-maven'); 178 | 179 | // 의존 패키지 목록을 JSON으로 작성하기 180 | let dependencies = await Promise.all(Object.keys(packages) 181 | .map((pack) => makeDependencyItem(API.getPackage(pack), packages[pack]))); 182 | 183 | // Package 버전 업데이트 (Compatiblity check 위함) 184 | for(const pack of dependencies){ 185 | packages[pack.artifactId.replace('koalanlp-','').toUpperCase()] = pack.version; 186 | } 187 | 188 | // 저장하기 189 | let packPath = path.join(os.tmpdir(), tempJsonName); 190 | fs.writeFileSync(packPath, JSON.stringify({ 191 | java: { 192 | dependencies: dependencies, 193 | exclusions: [ 194 | { 195 | groupId: "com.jsuereth", 196 | artifactId: "sbt-pgp" 197 | } 198 | ] 199 | } 200 | })); 201 | 202 | let threads = require('os').cpus().length; 203 | threads = Math.max(threads - 1, 1); 204 | let promise = new Promise((resolve, reject) => { 205 | mvn({ 206 | packageJsonPath: packPath, 207 | debug: verbose, 208 | repositories: remoteRepos, 209 | concurrency: threads 210 | }, function(err, mvnResults) { 211 | if (err) { 212 | console.error('필요한 의존패키지를 전부 다 가져오지는 못했습니다.'); 213 | reject(err); 214 | }else { 215 | let cleanClasspath = {}; 216 | 217 | for(const dependency of Object.values(mvnResults.dependencies)){ 218 | let group = dependency.groupId; 219 | let artifact = dependency.artifactId; 220 | let version = dependency.version; 221 | let key = `${group}:${artifact}`; 222 | 223 | if(!isDefined(cleanClasspath[key]) || isRightNewer(cleanClasspath[key].version, version)){ 224 | cleanClasspath[key] = { 225 | version: version, 226 | path: dependency.jarPath 227 | }; 228 | } 229 | } 230 | 231 | for(const dependency of Object.values(cleanClasspath)){ 232 | if (!isDefined(dependency.path)) 233 | continue; 234 | if (verbose) 235 | console.debug(`Classpath에 ${dependency.path} 추가`); 236 | java.classpath.push(path.resolve(dependency.path)); 237 | } 238 | 239 | JVM.init(java, packages); 240 | 241 | // Enum 초기화. 242 | POS.values(); 243 | PhraseTag.values(); 244 | DependencyTag.values(); 245 | RoleType.values(); 246 | CoarseEntityType.values(); 247 | 248 | resolve(); 249 | } 250 | }); 251 | }); 252 | 253 | return await promise; 254 | } 255 | 256 | /** 257 | * 주어진 문자열 리스트에 구문분석 표지자/의존구문 표지자/의미역 표지/개체명 분류가 포함되는지 확인합니다. 258 | * @param {string[]} stringList 분류가 포함되는지 확인할 문자열 목록 259 | * @param {(POS|PhraseTag|DependencyTag|CoarseEntityType|RoleType)} tag 포함되는지 확인할 구문분석 표지자/의존구문 표지자/의미역 표지/개체명 분류 260 | * @return {boolean} 포함되면 true. 261 | * @example 262 | * import { contains } from 'koalanlp/Util'; 263 | * contains(['S', 'NP'], PhraseTag.NP); 264 | */ 265 | export function contains(stringList, tag) { 266 | if(tag instanceof POS || tag instanceof PhraseTag || 267 | tag instanceof DependencyTag || tag instanceof RoleType || tag instanceof CoarseEntityType){ 268 | return JVM.koalaClassOf('Util').contains(JVM.listOf(stringList), tag.reference); 269 | }else{ 270 | return false; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | /** @private */ 2 | export function isDefined(value) { 3 | return typeof value !== 'undefined' && value !== null; 4 | } 5 | 6 | /** @private */ 7 | export function getOrUndefined(value) { 8 | return isDefined(value) ? value : undefined; 9 | } 10 | 11 | /** @private */ 12 | export function assert(condition, msg){ 13 | if (!condition) 14 | throw TypeError(msg); 15 | } 16 | 17 | /** @private */ 18 | export function typeCheck(values, ...types){ 19 | types = new Set(types); 20 | 21 | new Set(values.map((x) => { 22 | let t = typeof x; 23 | if (t === 'object') 24 | t = x.constructor.name; 25 | 26 | return t; 27 | })).forEach((t) => { 28 | assert(types.has(t), `[${[...types].toString()}] 중 하나를 기대했지만 ${t} 타입이 들어왔습니다!`); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/jvm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JVM Wrapper 3 | * @private 4 | */ 5 | export class JVM { 6 | /** 7 | * Reflection된 Java Class 보관소 8 | * @type {Object.} 9 | * @private 10 | */ 11 | static _classes = {}; 12 | 13 | /** 14 | * 시스템에서 사용하는 JVM Instance 15 | */ 16 | static INSTANCE; 17 | 18 | /** 19 | * 불러온 패키지 목록. 20 | */ 21 | static PACKAGES; 22 | 23 | /** 24 | * Node-java에서 사용하는 JVM interface 25 | * @private 26 | * @type {Object} 27 | */ 28 | static java; 29 | 30 | static init(java, packages) { 31 | if (typeof java === 'undefined' || java === null || JVM.java) 32 | throw TypeError('java는 null일 수 없고, JVM.init()은 한 번만 실행되어야 합니다.'); 33 | 34 | JVM.PACKAGES = packages; 35 | JVM.java = java; 36 | } 37 | 38 | /** 39 | * Java class 반환 40 | * @param {!string} path Class package path 41 | * @returns {Object} Java class 42 | */ 43 | static classOf(...path) { 44 | let clsName = path.join('.'); 45 | 46 | if (!JVM._classes.hasOwnProperty(clsName)) 47 | JVM._classes[clsName] = JVM.java.import(clsName); 48 | 49 | return JVM._classes[clsName]; 50 | } 51 | 52 | /** 53 | * Koala Class 반환 54 | * @param {!string} path Class package path 55 | * @returns {Object} Java class 56 | */ 57 | static koalaClassOf(...path) { 58 | return JVM.classOf('kr.bydelta.koala', ...path); 59 | } 60 | 61 | /** 62 | * Koala Enum 반환 63 | * @param {!string} tagset 표지자 집합 클래스 이름 64 | * @param {?string} tag 표지자 이름 65 | * @returns {?Object} 표지자 이름에 맞는 Java Enum 66 | */ 67 | static koalaEnumOf(tagset, tag) { 68 | if (tag !== null && typeof tag !== 'undefined') 69 | return JVM.koalaClassOf(tagset).valueOf(tag); 70 | else return null; 71 | } 72 | 73 | /** 74 | * Java Iterable -> JSON Array 75 | * @param {Object} array 76 | * @param itemConverter 77 | * @returns {Array} 78 | */ 79 | static toJsArray(array, itemConverter = (x) => x, isIterable = false) { 80 | if (typeof array === "undefined" || array === null) 81 | return []; 82 | if (Array.isArray(array)) 83 | return array.map(itemConverter); 84 | 85 | let result = []; 86 | if (isIterable) { 87 | let it = array.iterator(); 88 | while (it.hasNext()) { 89 | result.push(itemConverter(it.next())); 90 | } 91 | } else { 92 | let length = array.size(); 93 | for (let i = 0; i < length; ++i) { 94 | result.push(itemConverter(array.get(i))); 95 | } 96 | } 97 | 98 | return result; 99 | } 100 | 101 | /** 102 | * JSON Array -> Java List 103 | * @param {Object[]} array 104 | */ 105 | static listOf(array) { 106 | let list = new (JVM.classOf('java.util.ArrayList'))(); 107 | 108 | for (const item of array) 109 | list.add(item); 110 | 111 | return list; 112 | } 113 | 114 | /** 115 | * Make Java Pair 116 | * @param {Object} a First entry 117 | * @param {Object} b Second entry 118 | */ 119 | static pair(a, b) { 120 | return new (JVM.classOf('kotlin.Pair'))(a, b); 121 | } 122 | 123 | /** 124 | * Make Java Char 125 | * @param {?string} ch Character 126 | * @returns {Object} Java Character 127 | */ 128 | static char(ch) { 129 | if (ch !== null && typeof ch !== 'undefined') 130 | return JVM.java.newChar(ch.charCodeAt(0)); 131 | else return null; 132 | } 133 | 134 | /** 135 | * Make Java Set 136 | * @param {Object[]} array Items 137 | * @returns {Object} Java Set 138 | */ 139 | static setOf(array) { 140 | let list = new (JVM.classOf('java.util.HashSet'))(); 141 | 142 | for (const item of array) 143 | list.add(item); 144 | 145 | return list; 146 | } 147 | 148 | static posFilter(posSet) { 149 | return JVM.java.newProxy('kotlin.jvm.functions.Function1', { 150 | 'invoke': function (tag) { 151 | return posSet.includes(tag.name()); 152 | } 153 | }); 154 | } 155 | 156 | /** 157 | * Check whether these packages are compatible. 158 | * @param {Object.} packages 159 | */ 160 | static canLoadPackages(packages) { 161 | let result = {}; 162 | 163 | if (JVM.java && JVM.java.isJvmCreated()) { 164 | for(let [pack, ver] of Object.entries(packages)){ 165 | result[pack] = JVM.PACKAGES.hasOwnProperty(pack) && 166 | (ver.toUpperCase() === 'LATEST' || JVM.PACKAGES[pack] >= ver) 167 | } 168 | } else { 169 | for(let pack of Object.keys(packages)){ 170 | result[pack] = true; 171 | } 172 | } 173 | return result; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 품사, 구문구조 표지자 등 각종 표지자 3 | * @module koalanlp/types 4 | * @example 5 | * import { POS, PhraseTag, DependencyTag, RoleType, CoarseEntityType } from 'koalanlp/types'; 6 | **/ 7 | 8 | import {JVM} from './jvm'; 9 | import _ from 'underscore'; 10 | 11 | /** 12 | * 자바 Enum 표현 13 | * @private 14 | */ 15 | class JavaEnum{ 16 | /** 17 | * Enum 명칭 18 | * @type {string} 19 | */ 20 | tagname = ''; 21 | /** 22 | * Enum 순서 번호 23 | * @type {number} 24 | */ 25 | ordinal = -1; 26 | /** 27 | * Enum class 이름 28 | * @type {string} 29 | */ 30 | classType = ''; 31 | 32 | constructor(reference){ 33 | this.reference = reference; 34 | this.tagname = reference.name(); 35 | this.ordinal = reference.ordinal(); 36 | this.classType = reference.getClass().getName(); 37 | } 38 | 39 | /** 40 | * 문자열로 변환 41 | * @returns {string} 이 값을 표현하는 문자열 42 | */ 43 | toString(){ 44 | return this.tagname; 45 | } 46 | 47 | /** 48 | * 두 대상이 같은지 확인합니다. 49 | * @param other 다른 대상 50 | * @returns {boolean} 같다면 true. 51 | */ 52 | equals(other){ 53 | if(other instanceof JavaEnum && this.classType === other.classType) 54 | return other.reference.equals(this.reference); 55 | else return false; 56 | } 57 | 58 | /** 59 | * 전체 값의 목록을 불러옵니다. 60 | * @param clsName 불러올 클래스 61 | * @returns {Array} 전체 값 목록 62 | */ 63 | static getAllOf(...clsName){ 64 | return JVM.toJsArray(JVM.koalaClassOf(...clsName).values()); 65 | } 66 | } 67 | 68 | /** 69 | * 세종 품사표기 70 | * @example 71 | * import { POS } from 'koalanlp/types'; 72 | * POS.NNP; 73 | */ 74 | export class POS extends JavaEnum{ 75 | /** 76 | * POS 값 전체 77 | * @type {Object.} 78 | * @private 79 | */ 80 | static _values = {}; 81 | 82 | /** 83 | * POS 값들을 모두 돌려줍니다. 84 | * @returns {POS[]} POS값들의 array 85 | */ 86 | static values(){ 87 | if (_.isEmpty(POS._values)){ 88 | JavaEnum.getAllOf('POS').forEach(it => { 89 | let value = new POS(it); 90 | POS._values[value.tagname] = value; 91 | 92 | Object.defineProperty(POS, value.tagname, { 93 | value: value, 94 | writable: false, 95 | configurable: false 96 | }); 97 | }); 98 | 99 | Object.defineProperty(POS, '_values', { 100 | value: Object.freeze(POS._values), 101 | writable: false, 102 | configurable: false 103 | }); 104 | } 105 | 106 | return _.values(POS._values) 107 | } 108 | 109 | /** 110 | * 이름에 해당하는 값을 찾아줍니다. 111 | * @param {!string} name 해당 이름으로 된 값 112 | * @returns {POS} 113 | */ 114 | static withName(name){ 115 | return POS._values[name]; 116 | } 117 | 118 | /** 119 | * 이 값이 체언인지 확인합니다. 120 | * @returns {boolean} 체언인 경우 true 121 | */ 122 | isNoun(){ 123 | return this.reference.isNoun(); 124 | } 125 | 126 | /** 127 | * 이 값이 용언인지 확인합니다. 128 | * @returns {boolean} 용언인 경우 true 129 | */ 130 | isPredicate(){ 131 | return this.reference.isPredicate(); 132 | } 133 | 134 | /** 135 | * 이 값이 수식언인지 확인합니다. 136 | * @returns {boolean} 수식언인 경우 true 137 | */ 138 | isModifier(){ 139 | return this.reference.isModifier(); 140 | } 141 | 142 | /** 143 | * 이 값이 관계언인지 확인합니다. 144 | * @returns {boolean} 관계언인 경우 true 145 | */ 146 | isPostPosition(){ 147 | return this.reference.isPostPosition(); 148 | } 149 | 150 | /** 151 | * 이 값이 어미인지 확인합니다. 152 | * @returns {boolean} 어미인 경우 true 153 | */ 154 | isEnding(){ 155 | return this.reference.isEnding(); 156 | } 157 | 158 | /** 159 | * 이 값이 접사인지 확인합니다. 160 | * @returns {boolean} 접사인 경우 true 161 | */ 162 | isAffix(){ 163 | return this.reference.isAffix(); 164 | } 165 | 166 | /** 167 | * 이 값이 접미사인지 확인합니다. 168 | * @returns {boolean} 접미사인 경우 true 169 | */ 170 | isSuffix(){ 171 | return this.reference.isSuffix(); 172 | } 173 | 174 | /** 175 | * 이 값이 기호인지 확인합니다. 176 | * @returns {boolean} 기호인 경우 true 177 | */ 178 | isSymbol(){ 179 | return this.reference.isSymbol(); 180 | } 181 | 182 | /** 183 | * 이 값이 미확인 단어인지 확인합니다. 184 | * @returns {boolean} 미확인 단어인 경우 true 185 | */ 186 | isUnknown(){ 187 | return this.reference.isUnknown(); 188 | } 189 | 190 | /** 191 | * 이 값이 주어진 [tag]로 시작하는지 확인합니다. 192 | * @param {!string} tag 시작하는지 확인할 품사 분류 193 | * @returns {boolean} 포함되는 경우(시작하는 경우) True 194 | */ 195 | startsWith(tag){ 196 | return this.reference.startsWith(tag); 197 | } 198 | } 199 | 200 | /** 201 | * 세종 구문구조 표지자 202 | * @example 203 | * import { PhraseTag } from 'koalanlp/types'; 204 | */ 205 | export class PhraseTag extends JavaEnum{ 206 | /** 207 | * 값 전체 208 | * @type {Object.} 209 | * @private 210 | */ 211 | static _values = {}; 212 | 213 | /** 214 | * PhraseTag 값들을 모두 돌려줍니다. 215 | * @returns {PhraseTag[]} PhraseTag값들의 array 216 | */ 217 | static values(){ 218 | if (_.isEmpty(PhraseTag._values)){ 219 | for(const it of JavaEnum.getAllOf('PhraseTag')) { 220 | let value = new PhraseTag(it); 221 | PhraseTag._values[value.tagname] = value; 222 | 223 | Object.defineProperty(PhraseTag, value.tagname, { 224 | value: value, 225 | writable: false, 226 | configurable: false 227 | }); 228 | } 229 | 230 | Object.defineProperty(PhraseTag, '_values', { 231 | value: Object.freeze(PhraseTag._values), 232 | writable: false, 233 | configurable: false 234 | }); 235 | } 236 | 237 | return _.values(PhraseTag._values); 238 | } 239 | 240 | /** 241 | * 이름에 해당하는 값을 찾아줍니다. 242 | * @param {!string} name 해당 이름으로 된 값 243 | * @returns {PhraseTag} 244 | */ 245 | static withName(name){ 246 | return PhraseTag[name]; 247 | } 248 | } 249 | 250 | /** 251 | * ETRI 의존구문구조 기능표지자 252 | * @example 253 | * import { DependencyTag } from 'koalanlp/types'; 254 | */ 255 | export class DependencyTag extends JavaEnum{ 256 | /** 257 | * 값 전체 258 | * @type {Object.} 259 | * @private 260 | */ 261 | static _values = {}; 262 | 263 | /** 264 | * DependencyTag 값들을 모두 돌려줍니다. 265 | * @returns {DependencyTag[]} DependencyTag값들의 array 266 | */ 267 | static values(){ 268 | if (_.isEmpty(DependencyTag._values)){ 269 | JavaEnum.getAllOf('DependencyTag').forEach(it => { 270 | let value = new DependencyTag(it); 271 | DependencyTag._values[value.tagname] = value; 272 | 273 | Object.defineProperty(DependencyTag, value.tagname, { 274 | value: value, 275 | writable: false, 276 | configurable: false 277 | }); 278 | }); 279 | 280 | Object.defineProperty(DependencyTag, '_values', { 281 | value: Object.freeze(DependencyTag._values), 282 | writable: false, 283 | configurable: false 284 | }); 285 | } 286 | 287 | return _.values(DependencyTag._values) 288 | } 289 | 290 | /** 291 | * 이름에 해당하는 값을 찾아줍니다. 292 | * @param {!string} name 해당 이름으로 된 값 293 | * @returns {DependencyTag} 294 | */ 295 | static withName(name){ 296 | return DependencyTag._values[name]; 297 | } 298 | } 299 | 300 | /** 301 | * ETRI 의미역 분석 표지 302 | * @example 303 | * import { RoleType } from 'koalanlp/types'; 304 | */ 305 | export class RoleType extends JavaEnum{ 306 | /** 307 | * 값 전체 308 | * @type {Object.} 309 | * @private 310 | */ 311 | static _values = {}; 312 | 313 | /** 314 | * RoleType 값들을 모두 돌려줍니다. 315 | * @returns {RoleType[]} RoleType값들의 array 316 | */ 317 | static values(){ 318 | if (_.isEmpty(RoleType._values)){ 319 | JavaEnum.getAllOf('RoleType').forEach(it => { 320 | let value = new RoleType(it); 321 | RoleType._values[value.tagname] = value; 322 | 323 | Object.defineProperty(RoleType, value.tagname, { 324 | value: value, 325 | writable: false, 326 | configurable: false 327 | }); 328 | }); 329 | 330 | Object.defineProperty(RoleType, '_values', { 331 | value: Object.freeze(RoleType._values), 332 | writable: false, 333 | configurable: false 334 | }); 335 | } 336 | 337 | return _.values(RoleType._values) 338 | } 339 | 340 | /** 341 | * 이름에 해당하는 값을 찾아줍니다. 342 | * @param {!string} name 해당 이름으로 된 값 343 | * @returns {RoleType} 344 | */ 345 | static withName(name){ 346 | return RoleType._values[name]; 347 | } 348 | } 349 | 350 | /** 351 | * ETRI 개체명 대분류 352 | * @example 353 | * import { CoarseEntityType } from 'koalanlp/types'; 354 | */ 355 | export class CoarseEntityType extends JavaEnum{ 356 | /** 357 | * 값 전체 358 | * @type {Object.} 359 | * @private 360 | */ 361 | static _values = {}; 362 | 363 | /** 364 | * CoarseEntityType 값들을 모두 돌려줍니다. 365 | * @returns {CoarseEntityType[]} CoarseEntityType값들의 array 366 | */ 367 | static values(){ 368 | if (_.isEmpty(CoarseEntityType._values)){ 369 | JavaEnum.getAllOf('CoarseEntityType').forEach(it => { 370 | let value = new CoarseEntityType(it); 371 | CoarseEntityType._values[value.tagname] = value; 372 | 373 | Object.defineProperty(CoarseEntityType, value.tagname, { 374 | value: value, 375 | writable: false, 376 | configurable: false 377 | }); 378 | }); 379 | 380 | Object.defineProperty(CoarseEntityType, '_values', { 381 | value: Object.freeze(CoarseEntityType._values), 382 | writable: false, 383 | configurable: false 384 | }); 385 | } 386 | 387 | return _.values(CoarseEntityType._values) 388 | } 389 | 390 | /** 391 | * 이름에 해당하는 값을 찾아줍니다. 392 | * @param {!string} name 해당 이름으로 된 값 393 | * @returns {CoarseEntityType} 394 | */ 395 | static withName(name){ 396 | return CoarseEntityType._values[name]; 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /test/dictionary.js: -------------------------------------------------------------------------------- 1 | import {KKMA, OKT} from '../src/API'; 2 | import {Dictionary} from '../src/proc'; 3 | import {POS} from '../src/types'; 4 | 5 | export default function () { 6 | describe('Dictionary', () => { 7 | let dict1, dict2; 8 | 9 | beforeAll(async() => { 10 | dict1 = new Dictionary(KKMA); 11 | dict2 = new Dictionary(OKT); 12 | }); 13 | 14 | describe('#addUserDictionary()', () => { 15 | it('correctly adds an entry', async() => { 16 | expect(() => dict1.addUserDictionary({surface: '설빙', tag: POS.NNP})).not.toThrowError(); 17 | expect(dict1.contains('설빙', POS.NNP)).toBe(true); 18 | 19 | expect(() => dict1.addUserDictionary({surface: "설국열차", tag: POS.NNP}, { 20 | surface: "안드로이드", 21 | tag: POS.NNP 22 | })).not.toThrowError(); 23 | 24 | expect(dict1.contains("안드로이드", POS.NNP)).toBe(true); 25 | expect(dict1.contains("설국열차", POS.NNP)).toBe(true); 26 | 27 | dict1.addUserDictionary({surface: "하동균", tag: POS.NNP}); 28 | dict1.addUserDictionary({surface: "나비야", tag: POS.NNP}); 29 | 30 | expect(dict1.contains("하동균", POS.NNP, POS.NNG)).toBe(true); 31 | expect(await dict1.getItems()).toHaveLength(5); 32 | }); 33 | }); 34 | 35 | describe('#getNotExists()', () => { 36 | it('can filter only non-existing entries', async()=> { 37 | let items = await dict2.getNotExists(true, {surface: '쓰국', tag: POS.NNP}, {surface: '일', tag: POS.NNG}); 38 | expect(items).toHaveLength(1); 39 | }) 40 | }); 41 | 42 | describe('#getBaseEntries()', () => { 43 | it('can obtain entries correctly', async() => { 44 | let gen = await dict1.getBaseEntries((t) => t.isNoun()); 45 | expect(gen.next().done).toBe(false); 46 | 47 | let gen2 = await dict1.getBaseEntries((t) => t.isAffix()); 48 | expect(gen2.next().done).toBe(false); 49 | let gen2Array = []; 50 | for (const item of gen2) 51 | gen2Array.push(item); 52 | expect(Object.keys(gen2Array)).not.toHaveLength(0); 53 | 54 | let counter = 0; 55 | for (const entry of gen) { 56 | counter += gen2Array.includes(entry); 57 | } 58 | expect(counter).toBe(0); 59 | }); 60 | }); 61 | 62 | describe('#importFrom()', () => { 63 | it('can import from other dict', async() => { 64 | let itemsPrev = await dict2.getItems(); 65 | let itemSizePrev = itemsPrev.length; 66 | let itemSizeNounPrev = itemsPrev.filter((t) => t.tag.isNoun()).length; 67 | await dict2.importFrom(dict1, true, (t) => t.isNoun()); 68 | let itemsAfter = await dict2.getItems(); 69 | let itemSizeAfter= itemsAfter.length; 70 | let itemSizeNounAfter = itemsAfter.filter((t) => t.tag.isNoun()).length; 71 | 72 | expect(itemSizePrev).toBeLessThan(itemSizeAfter); 73 | expect(itemSizeAfter - itemSizePrev).toEqual(itemSizeNounAfter - itemSizeNounPrev); 74 | }); 75 | }); 76 | }); 77 | } -------------------------------------------------------------------------------- /test/execute.all.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(5000000); 2 | import {platform} from 'process'; 3 | import {initialize} from '../src/Util'; 4 | import dictest from './dictionary'; 5 | import typetest from './type'; 6 | import exttest from './extension'; 7 | import proctest from './proc'; 8 | import datatest from './datacheck'; 9 | import khaiiitest from './khaiiiTest'; 10 | import utaggertest from './utaggerTest'; 11 | 12 | import {JVM} from '../src/jvm'; 13 | 14 | beforeAll(async () => { 15 | await initialize({ 16 | packages: {OKT: 'LATEST', HNN: 'LATEST', ETRI: 'LATEST', KKMA: 'LATEST', KHAIII: 'LATEST', UTAGGER: 'LATEST'}, 17 | javaOptions: ["-Xmx4g", "-Dfile.encoding=utf-8", "-Djna.library.path=" + process.env['KHAIII_LIB']] 18 | }); 19 | }); 20 | 21 | describe('JVM', () => { 22 | it('can check loadable packages', () => { 23 | expect(JVM.canLoadPackages({ARIRANG: 'LATEST'})).toEqual({ARIRANG: false}); 24 | expect(JVM.canLoadPackages({OKT: 'LATEST'})).toEqual({OKT: true}); 25 | expect(JVM.canLoadPackages({OKT: '3.0.0'})).toEqual({OKT: false}); 26 | }); 27 | it('only initialize once', () => { 28 | expect(() => JVM.init()).toThrowError(); 29 | expect(() => JVM.init(null)).toThrowError(); 30 | expect(() => JVM.init({})).toThrowError(); 31 | }); 32 | }); 33 | 34 | dictest(); 35 | typetest(); 36 | exttest(); 37 | proctest(); 38 | datatest(); 39 | 40 | if (platform === 'linux' || platform === 'darwin') khaiiitest(); 41 | if (platform === 'win32') utaggertest(); 42 | -------------------------------------------------------------------------------- /test/khaiiiTest.js: -------------------------------------------------------------------------------- 1 | import {Tagger} from '../src/proc'; 2 | import {KHAIII} from '../src/API'; 3 | import _ from 'underscore'; 4 | import {compareSentence, EXAMPLES} from "./proc_common"; 5 | 6 | 7 | export default function () { 8 | describe('khaiii Module', () => { 9 | let tagger; 10 | 11 | beforeAll((done) => { 12 | tagger = new Tagger(KHAIII, {khaResource: process.env['KHAIII_RSC']}); 13 | done(); 14 | }); 15 | 16 | afterAll((done) => { 17 | tagger = null; 18 | done(); 19 | }); 20 | 21 | describe('Tagger', () => { 22 | it('can handle empty sentence', async() => { 23 | expect(await tagger('')).toHaveLength(0); 24 | expect(tagger.tagSync('')).toHaveLength(0); 25 | }); 26 | 27 | it('can convert Java output correctly', async() => { 28 | for (const [cnt, line] of EXAMPLES) { 29 | process.stdout.write('.'); 30 | let para = await tagger(line); 31 | expect(para).toBeInstanceOf(Array); 32 | for (const sent of para) 33 | compareSentence(sent); 34 | 35 | let paraSync = tagger.tagSync(line); 36 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 37 | 38 | let single = await tagger.tagSentence(line); 39 | expect(single).toBeInstanceOf(Array); 40 | expect(single).toHaveLength(1); 41 | 42 | let singleSync = tagger.tagSentenceSync(line); 43 | expect(_.zip(single, singleSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 44 | 45 | let singles = await tagger.tagSentence(para.map((x) => x.surfaceString())); 46 | expect(para).toHaveLength(singles.length); 47 | 48 | let singlesSync = tagger.tagSentenceSync(para.map((x) => x.surfaceString())); 49 | expect(_.zip(singles, singlesSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 50 | } 51 | }); 52 | }); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /test/proc.js: -------------------------------------------------------------------------------- 1 | import {EntityRecognizer, Parser, RoleLabeler, SentenceSplitter, Tagger} from '../src/proc'; 2 | import {ETRI, HNN, OKT} from '../src/API'; 3 | import _ from 'underscore'; 4 | import {compareSentence, EXAMPLES, snooze} from "./proc_common"; 5 | 6 | 7 | export default function () { 8 | describe('proc Module', () => { 9 | let splitter; 10 | let tagger; 11 | let parser; 12 | let entityRecog; 13 | let roleLabeler; 14 | 15 | beforeAll((done) => { 16 | splitter = new SentenceSplitter(OKT); 17 | tagger = new Tagger(OKT); 18 | parser = new Parser(HNN); 19 | entityRecog = new EntityRecognizer(ETRI, {etriKey: process.env['API_KEY']}); 20 | roleLabeler = new RoleLabeler(ETRI, {etriKey: process.env['API_KEY']}); 21 | done(); 22 | }); 23 | 24 | afterAll((done) => { 25 | splitter = null; 26 | tagger = null; 27 | parser = null; 28 | entityRecog = null; 29 | roleLabeler = null; 30 | done(); 31 | }); 32 | 33 | describe('SentenceSplitter', () => { 34 | it('can handle empty sentence', async() => { 35 | expect(await splitter('')).toHaveLength(0); 36 | expect(splitter.sentencesSync('')).toHaveLength(0); 37 | }); 38 | 39 | it('can convert Java output correctly', async() => { 40 | for (const [dummy, line] of EXAMPLES) { 41 | let res = await splitter(line); 42 | expect(res).toBeInstanceOf(Array); 43 | expect(res[0]).toEqual(expect.arrayContaining([])); 44 | 45 | let resSync = splitter.sentencesSync(line); 46 | expect(res).toEqual(resSync); 47 | 48 | let res2 = await splitter([line]); 49 | expect(res).toEqual(res2); 50 | 51 | let resSync2 = splitter.sentencesSync([line]); 52 | expect(res2).toEqual(resSync2); 53 | } 54 | }); 55 | }); 56 | 57 | describe('Tagger', () => { 58 | it('can handle empty sentence', async() => { 59 | expect(await tagger('')).toHaveLength(0); 60 | expect(tagger.tagSync('')).toHaveLength(0); 61 | }); 62 | 63 | it('can convert Java output correctly', async() => { 64 | for (const [cnt, line] of EXAMPLES) { 65 | process.stdout.write('.'); 66 | let para = await tagger(line); 67 | expect(para).toBeInstanceOf(Array); 68 | for (const sent of para) 69 | compareSentence(sent); 70 | 71 | let paraSync = tagger.tagSync(line); 72 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 73 | 74 | let single = await tagger.tagSentence(line); 75 | expect(single).toBeInstanceOf(Array); 76 | expect(single).toHaveLength(1); 77 | 78 | let singleSync = tagger.tagSentenceSync(line); 79 | expect(_.zip(single, singleSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 80 | 81 | let singles = await tagger.tagSentence(para.map((x) => x.surfaceString())); 82 | expect(para).toHaveLength(singles.length); 83 | 84 | let singlesSync = tagger.tagSentenceSync(para.map((x) => x.surfaceString())); 85 | expect(_.zip(singles, singlesSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 86 | } 87 | }); 88 | }); 89 | 90 | describe('Parser', () => { 91 | it('can handle empty sentence', async() => { 92 | expect(await parser('')).toHaveLength(0); 93 | expect(parser.analyzeSync('')).toHaveLength(0); 94 | }); 95 | 96 | it('can convert Java output correctly', async() => { 97 | for (const [cnt, line] of EXAMPLES) { 98 | process.stdout.write('.'); 99 | let para = await parser(line); 100 | expect(para).toBeInstanceOf(Array); 101 | for (const sent of para) 102 | compareSentence(sent, {SYN: true, DEP: true}); 103 | 104 | let paraSync = parser.analyzeSync(line); 105 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 106 | 107 | let singles = await parser(...para.map((x) => x.surfaceString())); 108 | expect(para).toHaveLength(singles.length); 109 | 110 | let singlesSync = parser.analyzeSync(...para.map((x) => x.surfaceString())); 111 | expect(_.zip(singles, singlesSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 112 | } 113 | }); 114 | 115 | it('can relay outputs', async() => { 116 | for (const [cnt, line] of EXAMPLES) { 117 | process.stdout.write('.'); 118 | let splits = await splitter(line); 119 | let tagged = await tagger.tagSentence(splits); 120 | expect(tagged).toHaveLength(splits.length); 121 | 122 | let taggedSync = tagger.tagSentenceSync(splits); 123 | expect(_.zip(tagged, taggedSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 124 | 125 | let para = await parser(tagged); 126 | expect(para).toHaveLength(tagged.length); 127 | 128 | let paraSync = parser.analyzeSync(tagged); 129 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 130 | 131 | expect(para).toBeInstanceOf(Array); 132 | for (const sent of para) 133 | compareSentence(sent, {SYN: true, DEP: true}); 134 | } 135 | }); 136 | }); 137 | 138 | describe('RoleLabeler', () => { 139 | it('can handle empty sentence', async() => { 140 | expect(await roleLabeler('')).toHaveLength(0); 141 | expect(roleLabeler.analyzeSync('')).toHaveLength(0); 142 | }); 143 | 144 | it('can convert Java output correctly', async() => { 145 | for (const [cnt, line] of _.sample(EXAMPLES, 5)) { 146 | await snooze(_.random(5000, 10000)); 147 | 148 | let para = await roleLabeler(line); 149 | expect(para).toBeInstanceOf(Array); 150 | for (const sent of para) 151 | compareSentence(sent, {SRL: true, DEP: true, NER: true, WSD: true}); 152 | 153 | let paraSync = roleLabeler.analyzeSync(line); 154 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 155 | 156 | let singles = await roleLabeler(...para.map((x) => x.surfaceString())); 157 | expect(para).toHaveLength(singles.length); 158 | 159 | let singlesSync = roleLabeler.analyzeSync(...para.map((x) => x.surfaceString())); 160 | expect(_.zip(singles, singlesSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 161 | } 162 | }); 163 | }); 164 | 165 | describe('EntityRecognizer', () => { 166 | it('can handle empty sentence', async() => { 167 | expect(await entityRecog('')).toHaveLength(0); 168 | }); 169 | 170 | it('can convert Java output correctly', async() => { 171 | for (const [cnt, line] of _.sample(EXAMPLES, 5)) { 172 | await snooze(_.random(5000, 10000)); 173 | 174 | let para = await entityRecog(line); 175 | expect(para).toBeInstanceOf(Array); 176 | for (const sent of para) 177 | compareSentence(sent, {NER: true, WSD: true}); 178 | 179 | let paraSync = entityRecog.analyzeSync(line); 180 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 181 | 182 | let singles = await entityRecog(...para.map((x) => x.surfaceString())); 183 | expect(para).toHaveLength(singles.length); 184 | 185 | let singlesSync = entityRecog.analyzeSync(...para.map((x) => x.surfaceString())); 186 | expect(_.zip(singles, singlesSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 187 | } 188 | }); 189 | }); 190 | }); 191 | } 192 | -------------------------------------------------------------------------------- /test/proc_common.js: -------------------------------------------------------------------------------- 1 | import {DepEdge, Entity, Morpheme, RoleEdge, Sentence, SyntaxTree, Word} from "../src/data"; 2 | import _ from "underscore"; 3 | import {POS} from "../src/types"; 4 | import {getOrUndefined} from "../src/common"; 5 | 6 | export let EXAMPLES = 7 | `01 1+1은 2이고, 3*3은 9이다. 8 | 01 RHINO는 말줄임표를... 확인해야함... ^^ 이것도 확인해야. 9 | 03 식사함은 식사에서부터인지 식사에서부터이었는지 살펴봄. 보기에는 살펴봄이 아리랑을 위한 시험임을 지나쳤음에. 사랑하였음은 사랑해봄은 보고싶기에 써보기에 써보았기에. 10 | 03 먹음이니. 먹음이었으니. 사면되어보았기에. 11 | 01 a/b는 분수이다. 12 | 01 | 기호는 분리기호이다. 13 | 02 ▶ 오늘의 날씨입니다. ◆ 기온 23도는 낮부터임. 14 | 01 【그】가 졸음이다. 사랑스러웠기에. 15 | 01 [Dr.브레인 - 마루투자자문 조인갑 대표] 16 | 01 [결 마감특징주 - 유진투자증권 갤러리아지점 나현진 대리] 17 | 01 진경복 한기대 산학협력단장은 "이번 협약 체결로 우수한 현장 기능 인력에 대한 대기업-중소기업간 수급 불균형 문제와 중소·중견기업들에 대한 취업 기피 현상을 해결할 것"이라며 "특성화 및 마이스터고 학생들을 대학에 진학시켜 자기계발 기회를 제공하는 국내 최초의 상생협력 모델이라는 점에 의미가 있다"고 강조했다. 18 | 01 [결 마감특징주 - 신한금융투자 명품PB강남센터 남경표 대리] 19 | 01 [Dr.브레인 - 마루투자자문 조인갑 대표] 20 | 01 '플라이 아웃'은 타자가 친 공이 땅에 닿기 전에 상대팀 야수(투수와 포수를 제외한 야수들-1·2·3루수, 좌익수, 중견수, 우익수, 유격수)가 잡는 것, '삼진 아웃'은 스트라이크가 세 개가 되어 아웃되는 것을 말한다. 21 | 01 대선 출마를 선언한 민주통합당 손학규 상임고문이 5일 오후 서울 세종문화회관 세종홀에서 열린 '저녁이 있는 삶-손학규의 민생경제론'출판기념회에서 행사장으로 들어서며 손을 들어 인사하고 있다.2012.7.5/뉴스1 22 | 23 | # Example 1: JTBC, 2017.04.22 24 | 03 북한이 도발을 멈추지 않으면 미국이 북핵 시설을 타격해도 군사개입을 하지 않겠다. 중국 관영 환구시보가 밝힌 내용인데요. 중국이 여태껏 제시한 북한에 대한 압박 수단 가운데 가장 이례적이고, 수위가 높은 것으로 보입니다. 25 | 01 이한주 기자입니다. 26 | 02 중국 관영매체 환구시보가 북핵문제에 대해 제시한 중국의 마지노선입니다. 북핵 억제를 위해 외교적 노력이 우선해야 하지만 북한이 도발을 지속하면 핵시설 타격은 용인할 수 있다는 뜻을 내비친 겁니다. 27 | 02 그러나 한국과 미국이 38선을 넘어 북한 정권 전복에 나서면 중국이 즉각 군사개입에 나서야 한다는 점을 분명히 하였습니다. 북한에 대한 압박수위도 한층 높였습니다. 28 | 02 핵실험을 강행하면 트럼프 대통령이 북한의 생명줄로 지칭한 중국의 원유공급을 대폭 축소할 거라고 경고하였습니다. 축소 규모에 대해서도 '인도주의적 재앙이 일어나지 않는 수준'이라는 기준까지 제시하며 안보리 결정을 따르겠다고 못 박았습니다. 29 | 02 중국 관영매체가 그동안 북한에 자제를 요구한 적은 있지만, 군사지원 의무제공 포기 가능성과 함께 유엔 안보리 제재안을 먼저 제시한 것은 이례적입니다. 미·중 빅딜에 따른 대북압박 공조 가능성이 제기되는 가운데 북한이 어떤 반응을 보일지 관심이 쏠립니다. 30 | 31 | # Example 3. 허핑턴포스트, 17.04.22 32 | 01 박근혜 전 대통령이 거주하던 서울 삼성동 자택은 홍성열 마리오아울렛 회장(63)이 67억5000만원에 매입한 것으로 확인되었다. 33 | 01 홍 회장은 21일 뉴스1과의 통화에서 "값이 싸게 나오고 위치가 좋아서 삼성동 자택을 사게 되었다"고 밝혔다. 34 | 01 홍 회장은 "제가 강남에 집이나 땅이 하나도 없어서 알아보던 중에 부동산에 아는 사람을 통해서 삼성동 자택이 매물로 나온 걸 알게 되었다"며 "처음에는 조금 부담되었지만 집사람도 크게 문제가 없다고 해서 매입하였다"고 말하였다. 35 | 01 이어 "조만간 이사를 할 생각이지만 난방이나 이런게 다 망가졌다기에 보고나서 이사를 하려한다"며 "집부터 먼저 봐야될 것 같다"고 하였다. 36 | 01 홍 회장은 한때 자택 앞에서 박 전 대통령 지지자들의 집회로 주민들이 큰 불편을 겪었던 것과 관련 "주인이 바뀌면 그런 일을 할 이유가 없을 것이라 생각한다"고 밝혔다. 37 | 01 박 전 대통령과의 인연 등에 대해선 "정치에 전혀 관심이 없고 그런(인연) 건 전혀 없다"며 "박 전 대통령 측이나 친박계 의원 측과의 접촉도 전혀 없었다"고 전하였다. 38 | 01 홍 회장은 일부 언론보도로 알려진 박지만 EG회장과의 친분설도 "사실과 다르다"며 "박 전 대통령 사돈의 팔촌과도 인연이 없다"고 거듭 강조하였다. 39 | 02 홍 회장에 따르면 자택 매입가는 67억5000만원이다. 홍 회장은 주택을 매입하면서 2억3600만원의 취득세를 납부하였다고 밝혔다. 40 | 01 홍 회장은 1980년 마리오상사를 설립한 뒤 2001년 마리오아울렛을 오픈하며 의류 판매업 등으로 국내 최대급 아울렛으로 성장시켰다. 41 | 01 한편 박 전 대통령은 최근 삼성동 자택을 매각하고 내곡동에 새 집을 장만한 것으로 확인되었으며 이달 중 내곡동으로 이삿짐을 옮길 것으로 알려졌다.` 42 | .trim().split('\n').filter((line) => line.length > 0 && !line.startsWith('#')).map((line) => { 43 | line = line.trim(); 44 | return [parseInt(line.substring(0, 2)), line.substring(3)]; 45 | }); 46 | 47 | function isDefined(obj) { 48 | return typeof obj !== 'undefined' && obj !== null; 49 | } 50 | 51 | function isUndefined(obj) { 52 | return typeof obj === 'undefined'; 53 | } 54 | 55 | function compareMorphemes(jsmorph, opts) { 56 | expect(jsmorph).toBeInstanceOf(Morpheme); 57 | expect(jsmorph.getId()).toBe(jsmorph.reference.getId()); 58 | expect(jsmorph.getTag().tagname).toBe(jsmorph.reference.getTag().name()); 59 | expect(jsmorph.getOriginalTag()).toBe(jsmorph.reference.getOriginalTag()); 60 | expect(jsmorph.getSurface()).toBe(jsmorph.reference.getSurface()); 61 | expect(jsmorph.getWord().reference.equals(jsmorph.reference.getWord())).toBe(true); 62 | 63 | expect(jsmorph.getId()).toBe(jsmorph.id); 64 | expect(jsmorph.getTag()).toBe(jsmorph.tag); 65 | expect(jsmorph.getOriginalTag()).toBe(jsmorph.originalTag); 66 | expect(jsmorph.getSurface()).toBe(jsmorph.surface); 67 | expect(jsmorph.getWord()).toBe(jsmorph.word); 68 | expect(jsmorph.getWord().equals(jsmorph.word)).toBe(true); 69 | 70 | if (opts.NER && isDefined(jsmorph.reference.getEntities())) { 71 | let jsents = jsmorph.getEntities().map((e) => e.reference); 72 | let jents = jsmorph.reference.getEntities(); 73 | expect(jsents.every((e) => jents.contains(e))).toBe(true); 74 | } else { 75 | expect(jsmorph.getEntities()).toHaveLength(0); 76 | } 77 | 78 | expect(jsmorph.getEntities()).toEqual(jsmorph.entities); 79 | 80 | if (opts.WSD) { 81 | expect(jsmorph.getWordSense()).toBe(getOrUndefined(jsmorph.reference.getWordSense())); 82 | expect(jsmorph.getWordSense()).toBe(getOrUndefined(jsmorph.wordSense)); 83 | } else { 84 | expect(isUndefined(jsmorph.getWordSense())).toBe(true); 85 | expect(isUndefined(jsmorph.wordSense)).toBe(true); 86 | } 87 | 88 | expect(jsmorph.isJosa()).toBe(jsmorph.reference.isJosa()); 89 | expect(jsmorph.isModifier()).toBe(jsmorph.reference.isModifier()); 90 | expect(jsmorph.isNoun()).toBe(jsmorph.reference.isNoun()); 91 | expect(jsmorph.isPredicate()).toBe(jsmorph.reference.isPredicate()); 92 | 93 | expect( 94 | POS.values().every((tag) => jsmorph.hasTag(tag.tagname) === jsmorph.reference.hasTag(tag.tagname)) 95 | ).toBe(true); 96 | 97 | let sampled = _.sample(POS.values(), 3).map((x) => x.tagname); 98 | expect(jsmorph.hasTagOneOf(...sampled)).toBe(jsmorph.reference.hasTagOneOf(...sampled)); 99 | 100 | expect(jsmorph.toString()).toBe(jsmorph.reference.toString()); 101 | } 102 | 103 | function compareWords(jsword, opts) { 104 | expect(jsword).toBeInstanceOf(Word); 105 | 106 | for (const morph of jsword) { 107 | expect(jsword.reference.contains(morph.reference)).toBe(true); 108 | compareMorphemes(morph, opts); 109 | } 110 | 111 | expect(jsword.getSurface()).toBe(jsword.reference.getSurface()); 112 | expect(jsword.getId()).toBe(jsword.reference.getId()); 113 | expect(jsword.singleLineString()).toBe(jsword.reference.singleLineString()); 114 | 115 | expect(jsword.getSurface()).toBe(jsword.surface); 116 | expect(jsword.getId()).toBe(jsword.id); 117 | 118 | if (opts.NER && isDefined(jsword.reference.getEntities())) { 119 | let jsents = jsword.getEntities().map((e) => e.reference); 120 | let jents = jsword.reference.getEntities(); 121 | expect(jsents.every((e) => jents.contains(e))).toBe(true); 122 | } else { 123 | expect(jsword.getEntities()).toHaveLength(0); 124 | } 125 | 126 | expect(jsword.getEntities()).toEqual(jsword.entities); 127 | 128 | if (opts.SRL) { 129 | if (isDefined(jsword.reference.getPredicateRoles())) { 130 | let jsents = jsword.getPredicateRoles().map((e) => e.reference); 131 | let jents = jsword.reference.getPredicateRoles(); 132 | expect(jsents.every((e) => jents.contains(e))).toBe(true); 133 | } 134 | 135 | if (isDefined(jsword.reference.getArgumentRoles())) { 136 | let jsents = jsword.getArgumentRoles().map((e) => e.reference); 137 | let jents = jsword.reference.getArgumentRoles(); 138 | expect(jsents.every((e) => jents.contains(e))).toBe(true); 139 | } 140 | } else { 141 | expect(jsword.getPredicateRoles()).toHaveLength(0); 142 | expect(jsword.getArgumentRoles()).toHaveLength(0); 143 | } 144 | 145 | expect(jsword.getPredicateRoles()).toEqual(jsword.predicateRoles); 146 | expect(jsword.getArgumentRoles()).toEqual(jsword.argumentRoles); 147 | 148 | if (opts.DEP) { 149 | if (isDefined(jsword.reference.getGovernorEdge())) { 150 | expect( 151 | jsword.getGovernorEdge().reference.equals(jsword.reference.getGovernorEdge()) 152 | ).toBe(true); 153 | expect(jsword.getGovernorEdge()).toBe(jsword.governorEdge); 154 | } 155 | 156 | if (isDefined(jsword.reference.getDependentEdges())) { 157 | let jsents = jsword.getDependentEdges().map((e) => e.reference); 158 | let jents = jsword.reference.getDependentEdges(); 159 | expect(jsents.every((e) => jents.contains(e))).toBe(true); 160 | } 161 | } else { 162 | expect(isUndefined(jsword.getGovernorEdge())).toBe(true); 163 | expect(jsword.getDependentEdges()).toHaveLength(0); 164 | } 165 | 166 | expect(jsword.getDependentEdges()).toEqual(jsword.dependentEdges); 167 | 168 | if (opts.SYN) { 169 | expect(jsword.getPhrase().reference.equals(jsword.reference.getPhrase())).toBe(true); 170 | expect(jsword.getPhrase()).toEqual(jsword.phrase); 171 | } else { 172 | expect(isUndefined(jsword.getPhrase())).toBe(true); 173 | expect(isUndefined(jsword.phrase)).toBe(true); 174 | } 175 | 176 | expect(jsword.toString()).toBe(jsword.reference.toString()); 177 | } 178 | 179 | function comparePhrase(jstree) { 180 | expect(jstree).toBeInstanceOf(SyntaxTree); 181 | expect(jstree.getLabel().tagname).toBe(jstree.reference.getLabel().name()); 182 | expect(jstree.hasNonTerminals()).toBe(jstree.reference.hasNonTerminals()); 183 | expect(jstree.isRoot()).toBe(jstree.reference.isRoot()); 184 | expect(jstree.getOriginalLabel()).toBe(jstree.reference.getOriginalLabel()); 185 | expect(jstree.getTreeString()).toBe(jstree.reference.getTreeString().toString()); 186 | 187 | expect(jstree.getLabel()).toBe(jstree.label); 188 | expect(jstree.getOriginalLabel()).toBe(jstree.originalLabel); 189 | 190 | let jsterms = jstree.getTerminals().map((t) => t.reference); 191 | let jterms = jstree.reference.getTerminals(); 192 | expect(jsterms.every((e) => jterms.contains(e))).toBe(true); 193 | 194 | let jsnterms = jstree.getNonTerminals().map((t) => t.reference); 195 | let jnterms = jstree.reference.getNonTerminals(); 196 | expect(jsnterms.every((e) => jnterms.contains(e))).toBe(true); 197 | 198 | if (!jstree.reference.isRoot()) { 199 | expect(jstree.getParent().reference.equals(jstree.reference.getParent())).toBe(true); 200 | expect(jstree.getParent()).toEqual(jstree.parent); 201 | } else { 202 | expect(isUndefined(jstree.getParent())).toBe(true); 203 | expect(isUndefined(jstree.parent)).toBe(true); 204 | } 205 | 206 | for (const nonterm of jstree) { 207 | expect(jstree.reference.contains(nonterm.reference)).toBe(true); 208 | comparePhrase(nonterm); 209 | } 210 | 211 | expect(jstree.toString()).toBe(jstree.reference.toString()); 212 | } 213 | 214 | function compareDepEdge(jsedge) { 215 | expect(jsedge).toBeInstanceOf(DepEdge); 216 | expect(jsedge.getOriginalLabel()).toBe(jsedge.reference.getOriginalLabel()); 217 | expect(jsedge.getType().tagname).toBe(jsedge.reference.getType().name()); 218 | 219 | expect(jsedge.getOriginalLabel()).toBe(jsedge.originalLabel); 220 | expect(jsedge.getType()).toBe(jsedge.type); 221 | 222 | let gov = jsedge.getGovernor(); 223 | if (isDefined(jsedge.reference.getGovernor())) { 224 | expect(gov).toBeDefined(); 225 | expect(gov.reference.equals(jsedge.reference.getGovernor())).toBe(true); 226 | expect(jsedge.getSrc().reference.equals(jsedge.reference.getSrc())).toBe(true); 227 | expect(gov).toBe(jsedge.getSrc()); 228 | expect(gov).toBe(jsedge.governor); 229 | expect(jsedge.getSrc()).toBe(jsedge.src); 230 | } else { 231 | expect(isDefined(jsedge.reference.getGovernor())).toBe(false); 232 | expect(isDefined(jsedge.reference.getSrc())).toBe(false); 233 | expect(isUndefined(jsedge.getGovernor())).toBe(true); 234 | expect(isUndefined(jsedge.getSrc())).toBe(true); 235 | expect(isUndefined(jsedge.governor)).toBe(true); 236 | expect(isUndefined(jsedge.src)).toBe(true); 237 | } 238 | 239 | expect(jsedge.getDependent().reference.equals(jsedge.reference.getDependent())).toBe(true) 240 | expect(jsedge.getDest().reference.equals(jsedge.reference.getDest())).toBe(true); 241 | expect(jsedge.getDependent()).toBe(jsedge.getDest()); 242 | expect(jsedge.getDependent()).toBe(jsedge.dependent); 243 | expect(jsedge.getDest()).toBe(jsedge.dest); 244 | 245 | if (isDefined(jsedge.reference.getDepType())) { 246 | expect(jsedge.getDepType().tagname).toBe(jsedge.reference.getDepType().name()); 247 | expect(jsedge.getLabel().tagname).toBe(jsedge.reference.getLabel().name()); 248 | expect(jsedge.getDepType()).toBe(jsedge.getLabel()); 249 | expect(jsedge.getDepType()).toBe(jsedge.depType); 250 | expect(jsedge.getLabel()).toBe(jsedge.label); 251 | } else { 252 | expect(isDefined(jsedge.reference.getDepType())).toBe(false); 253 | expect(isDefined(jsedge.reference.getLabel())).toBe(false); 254 | expect(isUndefined(jsedge.getLabel())).toBe(true); 255 | expect(isUndefined(jsedge.getDepType())).toBe(true); 256 | expect(isUndefined(jsedge.label)).toBe(true); 257 | expect(isUndefined(jsedge.depType)).toBe(true); 258 | } 259 | 260 | expect(jsedge.toString()).toBe(jsedge.reference.toString()); 261 | } 262 | 263 | function compareRoleEdge(jsedge) { 264 | expect(jsedge).toBeInstanceOf(RoleEdge); 265 | expect(jsedge.getOriginalLabel()).toBe(jsedge.reference.getOriginalLabel()); 266 | expect(jsedge.getLabel().tagname).toBe(jsedge.reference.getLabel().name()); 267 | expect(jsedge.getOriginalLabel()).toBe(jsedge.originalLabel); 268 | expect(jsedge.getLabel()).toBe(jsedge.label); 269 | 270 | let gov = jsedge.getPredicate(); 271 | if (isDefined(jsedge.reference.getPredicate())) { 272 | expect(gov.reference.equals(jsedge.reference.getPredicate())).toBe(true); 273 | expect(jsedge.getSrc().reference.equals(jsedge.reference.getSrc())).toBe(true); 274 | expect(gov).toBe(jsedge.getSrc()); 275 | expect(gov).toBe(jsedge.predicate); 276 | expect(jsedge.getSrc()).toBe(jsedge.src); 277 | } else { 278 | expect(isDefined(jsedge.reference.getPredicate())).toBe(false); 279 | expect(isDefined(jsedge.reference.getSrc())).toBe(false); 280 | expect(isUndefined(jsedge.getPredicate())).toBe(true); 281 | expect(isUndefined(jsedge.getSrc())).toBe(true); 282 | expect(isUndefined(jsedge.predicate)).toBe(true); 283 | expect(isUndefined(jsedge.src)).toBe(true); 284 | } 285 | 286 | expect(jsedge.getArgument().reference.equals(jsedge.reference.getArgument())).toBe(true); 287 | expect(jsedge.getDest().reference.equals(jsedge.reference.getDest())).toBe(true); 288 | expect(jsedge.getArgument()).toBe(jsedge.getDest()); 289 | expect(jsedge.getArgument()).toBe(jsedge.argument); 290 | expect(jsedge.getDest()).toBe(jsedge.dest); 291 | 292 | expect(jsedge.toString()).toBe(jsedge.reference.toString()); 293 | } 294 | 295 | function compareEntity(jsentity) { 296 | expect(jsentity).toBeInstanceOf(Entity); 297 | expect(jsentity.getLabel().tagname).toBe(jsentity.reference.getLabel().name()); 298 | expect(jsentity.getOriginalLabel()).toBe(jsentity.reference.getOriginalLabel()); 299 | expect(jsentity.getSurface()).toBe(jsentity.reference.getSurface()); 300 | expect(jsentity.getFineLabel()).toBe(jsentity.reference.getFineLabel()); 301 | expect(jsentity.getLabel()).toBe(jsentity.label); 302 | expect(jsentity.getOriginalLabel()).toBe(jsentity.originalLabel); 303 | expect(jsentity.getSurface()).toBe(jsentity.surface); 304 | expect(jsentity.getFineLabel()).toBe(jsentity.fineLabel); 305 | // jsentity.getCorefGroup().reference.equals(jsentity.referecnce.getCorefGroup()).should.be.true() 306 | 307 | for (const i of _.range(jsentity.length)) { 308 | let morph = jsentity[i]; 309 | expect(morph).toBeInstanceOf(Morpheme); 310 | expect(jsentity.reference.contains(morph.reference)).toBe(true); 311 | expect(jsentity.reference.get(i).equals(morph.reference)).toBe(true); 312 | } 313 | 314 | expect(jsentity.toString()).toBe(jsentity.reference.toString()); 315 | } 316 | 317 | export function compareSentence(jssent, opts = {}) { 318 | expect(jssent).toBeInstanceOf(Sentence); 319 | expect(jssent.toString()).toBe(jssent.reference.toString()); 320 | expect(jssent.singleLineString()).toBe(jssent.reference.singleLineString()); 321 | 322 | expect(jssent.surfaceString()).toBe(jssent.reference.surfaceString()); 323 | expect(jssent.surfaceString('//')).toBe(jssent.reference.surfaceString('//')); 324 | 325 | if (opts.NER) { 326 | for (const e of jssent.getEntities()) { 327 | expect(jssent.reference.getEntities().contains(e.reference)).toBe(true); 328 | compareEntity(e); 329 | } 330 | } else { 331 | expect(jssent.getEntities()).toHaveLength(0); 332 | } 333 | expect(jssent.getEntities()).toEqual(jssent.entities); 334 | 335 | if (opts.DEP) { 336 | for (const e of jssent.getDependencies()) { 337 | expect(jssent.reference.getDependencies().contains(e.reference)).toBe(true); 338 | compareDepEdge(e); 339 | } 340 | } else { 341 | expect(jssent.getDependencies()).toHaveLength(0); 342 | } 343 | expect(jssent.getDependencies()).toEqual(jssent.dependencies); 344 | 345 | if (opts.SRL) { 346 | for (const e of jssent.getRoles()) { 347 | expect(jssent.reference.getRoles().contains(e.reference)).toBe(true); 348 | compareRoleEdge(e); 349 | } 350 | } else { 351 | expect(jssent.getRoles()).toHaveLength(0); 352 | } 353 | expect(jssent.getRoles()).toEqual(jssent.roles); 354 | 355 | if (opts.SYN) { 356 | comparePhrase(jssent.getSyntaxTree()); 357 | expect(jssent.getSyntaxTree()).toEqual(jssent.syntaxTree); 358 | } else { 359 | expect(isUndefined(jssent.getSyntaxTree())).toBe(true); 360 | expect(isUndefined(jssent.syntaxTree)).toBe(true); 361 | } 362 | 363 | for (const word of jssent.getNouns()) { 364 | expect(word).toBeInstanceOf(Word); 365 | expect(jssent.reference.getNouns().contains(word.reference)).toBe(true); 366 | } 367 | 368 | for (const word of jssent.getVerbs()) { 369 | expect(word).toBeInstanceOf(Word); 370 | expect(jssent.reference.getVerbs().contains(word.reference)).toBe(true); 371 | } 372 | 373 | for (const word of jssent.getModifiers()) { 374 | expect(word).toBeInstanceOf(Word); 375 | //jssent.reference.getModifiers().contains(word.reference).should.be.true(); 376 | } 377 | 378 | for (const word of jssent) { 379 | expect(jssent.reference.contains(word.reference)).toBe(true); 380 | compareWords(word, opts); 381 | } 382 | } 383 | 384 | export const snooze = ms => new Promise(resolve => setTimeout(resolve, ms)); -------------------------------------------------------------------------------- /test/type.js: -------------------------------------------------------------------------------- 1 | import * as Util from '../src/Util'; 2 | import {POS, CoarseEntityType, DependencyTag, PhraseTag, RoleType} from '../src/types'; 3 | import _ from 'underscore'; 4 | 5 | export default function () { 6 | describe('Type Tags', () => { 7 | describe('POS', () => { 8 | it('discriminate tags', () => { 9 | const SET_NOUNS = (x) => x.isNoun(); 10 | const SET_PREDICATES = (x) => x.isPredicate(); 11 | const SET_MODIFIERS = (x) => x.isModifier(); 12 | const SET_POSTPOSITIONS = (x) => x.isPostPosition(); 13 | const SET_ENDINGS = (x) => x.isEnding(); 14 | const SET_AFFIXES = (x) => x.isAffix(); 15 | const SET_SUFFIXES = (x) => x.isSuffix(); 16 | const SET_SYMBOLS = (x) => x.isSymbol(); 17 | const SET_UNKNOWNS = (x) => x.isUnknown(); 18 | 19 | let map = { 20 | 'NNG': [SET_NOUNS], 21 | 'NNP': [SET_NOUNS], 22 | 'NNB': [SET_NOUNS], 23 | 'NNM': [SET_NOUNS], 24 | 'NR': [SET_NOUNS], 25 | 'NP': [SET_NOUNS], 26 | 'VV': [SET_PREDICATES], 27 | 'VA': [SET_PREDICATES], 28 | 'VX': [SET_PREDICATES], 29 | 'VCP': [SET_PREDICATES], 30 | 'VCN': [SET_PREDICATES], 31 | 'MM': [SET_MODIFIERS], 32 | 'MAG': [SET_MODIFIERS], 33 | 'MAJ': [SET_MODIFIERS], 34 | 'IC': [], 35 | 'JKS': [SET_POSTPOSITIONS], 36 | 'JKC': [SET_POSTPOSITIONS], 37 | 'JKG': [SET_POSTPOSITIONS], 38 | 'JKO': [SET_POSTPOSITIONS], 39 | 'JKB': [SET_POSTPOSITIONS], 40 | 'JKV': [SET_POSTPOSITIONS], 41 | 'JKQ': [SET_POSTPOSITIONS], 42 | 'JC': [SET_POSTPOSITIONS], 43 | 'JX': [SET_POSTPOSITIONS], 44 | 'EP': [SET_ENDINGS], 45 | 'EF': [SET_ENDINGS], 46 | 'EC': [SET_ENDINGS], 47 | 'ETN': [SET_ENDINGS], 48 | 'ETM': [SET_ENDINGS], 49 | 'XPN': [SET_AFFIXES], 50 | 'XPV': [SET_AFFIXES], 51 | 'XSN': [SET_AFFIXES, SET_SUFFIXES], 52 | 'XSV': [SET_AFFIXES, SET_SUFFIXES], 53 | 'XSA': [SET_AFFIXES, SET_SUFFIXES], 54 | 'XSM': [SET_AFFIXES, SET_SUFFIXES], 55 | 'XSO': [SET_AFFIXES, SET_SUFFIXES], 56 | 'XR': [], 57 | 'SF': [SET_SYMBOLS], 58 | 'SP': [SET_SYMBOLS], 59 | 'SS': [SET_SYMBOLS], 60 | 'SE': [SET_SYMBOLS], 61 | 'SO': [SET_SYMBOLS], 62 | 'SW': [SET_SYMBOLS], 63 | 'NF': [SET_UNKNOWNS], 64 | 'NV': [SET_UNKNOWNS], 65 | 'NA': [SET_UNKNOWNS], 66 | 'SL': [], 67 | 'SH': [], 68 | 'SN': [] 69 | }; 70 | 71 | let tagset = [SET_UNKNOWNS, 72 | SET_SYMBOLS, 73 | SET_SUFFIXES, 74 | SET_AFFIXES, 75 | SET_ENDINGS, 76 | SET_POSTPOSITIONS, 77 | SET_MODIFIERS, 78 | SET_PREDICATES, 79 | SET_NOUNS]; 80 | 81 | expect(Object.keys(map).sort()).toEqual(POS.values() 82 | .filter((x) => x.tagname !== 'TEMP').map((x) => x.tagname).sort()); 83 | 84 | for (const [tag, setup] of Object.entries(map)) { 85 | for (const target of tagset) { 86 | expect(target(POS[tag])).toBe(setup.includes(target)); 87 | expect(POS[tag]).toBe(POS.withName(tag)); 88 | } 89 | } 90 | }); 91 | 92 | it('belongs to some partial tags', () => { 93 | let partialCodes = []; 94 | for (const tag of POS.values()) { 95 | if (tag !== POS.TEMP) { 96 | let name = tag.tagname; 97 | 98 | _.range(1, name.length + 1).forEach((l) => { 99 | let substring = name.substr(0, l); 100 | if (!partialCodes.includes(substring)) { 101 | partialCodes.push(substring); 102 | partialCodes.push(substring.toLowerCase()); 103 | } 104 | }); 105 | } 106 | } 107 | 108 | for (const tag of POS.values()) { 109 | if (tag !== POS.TEMP) { 110 | if (tag.isUnknown()) { 111 | for (const code of partialCodes) { 112 | if (code.toUpperCase() === 'N') { 113 | expect(tag.startsWith(code)).toBe(false); 114 | } else { 115 | expect(tag.startsWith(code)).toBe(tag.tagname.startsWith(code.toUpperCase())); 116 | } 117 | } 118 | } else { 119 | for (const code of partialCodes) { 120 | expect(tag.startsWith(code)).toBe(tag.tagname.startsWith(code.toUpperCase())); 121 | } 122 | } 123 | } 124 | } 125 | }) 126 | }); 127 | 128 | describe('PhraseTag', () => { 129 | it('discriminate tags', () => { 130 | let values = PhraseTag.values(); 131 | let codes = values.map((c) => c.tagname); 132 | 133 | _.range(100).forEach(() => { 134 | let filtered = codes.filter(() => _.random(0, 1) == 1); 135 | for (const tag of values) { 136 | expect(Util.contains(filtered, tag)).toBe(filtered.includes(tag.tagname)); 137 | } 138 | }); 139 | 140 | for (const code of codes) { 141 | expect(PhraseTag.withName(code)).toBe(PhraseTag[code]); 142 | } 143 | }); 144 | }); 145 | 146 | describe('DependencyTag', () => { 147 | it('discriminate tags', () => { 148 | let values = DependencyTag.values(); 149 | let codes = values.map((c) => c.tagname); 150 | 151 | _.range(100).forEach(() => { 152 | let filtered = codes.filter(() => _.random(0, 1) == 1); 153 | for (const tag of values) { 154 | expect(Util.contains(filtered, tag)).toBe(filtered.includes(tag.tagname)); 155 | } 156 | }); 157 | 158 | for (const code of codes) { 159 | expect(DependencyTag.withName(code)).toBe(DependencyTag[code]); 160 | } 161 | }); 162 | }); 163 | 164 | describe('RoleType', () => { 165 | it('discriminate tags', () => { 166 | let values = RoleType.values(); 167 | let codes = values.map((c) => c.tagname); 168 | 169 | _.range(100).forEach(() => { 170 | let filtered = codes.filter(() => _.random(0, 1) == 1); 171 | for (const tag of values) { 172 | expect(Util.contains(filtered, tag)).toBe(filtered.includes(tag.tagname)); 173 | } 174 | }); 175 | 176 | for (const code of codes) { 177 | expect(RoleType.withName(code)).toBe(RoleType[code]); 178 | } 179 | }); 180 | }); 181 | 182 | describe('CoarseEntityType', () => { 183 | it('discriminate tags', () => { 184 | let values = CoarseEntityType.values(); 185 | let codes = values.map((c) => c.tagname); 186 | 187 | _.range(100).forEach(() => { 188 | let filtered = codes.filter(() => _.random(0, 1) == 1); 189 | for (const tag of values) { 190 | expect(Util.contains(filtered, tag)).toBe(filtered.includes(tag.tagname)); 191 | } 192 | }); 193 | 194 | for (const code of codes) { 195 | expect(CoarseEntityType.withName(code)).toBe(CoarseEntityType[code]); 196 | } 197 | }); 198 | }); 199 | }); 200 | } -------------------------------------------------------------------------------- /test/utaggerTest.js: -------------------------------------------------------------------------------- 1 | import {UTagger, Tagger} from '../src/proc'; 2 | import {UTAGGER} from '../src/API'; 3 | import _ from 'underscore'; 4 | import * as os from 'os'; 5 | import * as path from 'path'; 6 | import * as fs from 'fs'; 7 | import {compareSentence, EXAMPLES} from "./proc_common"; 8 | import * as iconv from 'iconv-lite'; 9 | 10 | export default function () { 11 | describe('utagger Module', () => { 12 | let tagger; 13 | 14 | beforeAll((done) => { 15 | let utaggerPath = path.normalize(path.join(process.env['HOME'], 'utagger')); 16 | let binPath = path.join(utaggerPath, 'bin'); 17 | let libPath = path.join(binPath, 'utagger-ubuntu1804.so'); 18 | 19 | let configPath = path.join(utaggerPath, "Hlxcfg.txt"); 20 | UTagger.setPath(libPath, configPath); 21 | 22 | let lines = fs.readFileSync(configPath); 23 | lines = iconv.decode(lines, 'euc-kr').split('\n'); 24 | lines = lines.map(it => it.replace("HLX_DIR ../", `HLX_DIR ${utaggerPath}/`)); 25 | lines = lines.join('\n'); 26 | lines = iconv.encode(lines, 'euc-kr'); 27 | fs.writeFileSync(configPath, lines); 28 | 29 | tagger = new Tagger(UTAGGER); 30 | done(); 31 | }); 32 | 33 | afterAll((done) => { 34 | tagger = null; 35 | done() 36 | }); 37 | 38 | describe('Tagger', () => { 39 | it('can handle empty sentence', async() => { 40 | expect(await tagger('')).toHaveLength(0); 41 | expect(tagger.tagSync('')).toHaveLength(0); 42 | }); 43 | 44 | it('can convert Java output correctly', async() => { 45 | for (const [cnt, line] of EXAMPLES) { 46 | process.stdout.write('.'); 47 | let para = await tagger(line); 48 | expect(para).toBeInstanceOf(Array); 49 | for (const sent of para) 50 | compareSentence(sent, {'WSD': true}); 51 | 52 | let paraSync = tagger.tagSync(line); 53 | expect(_.zip(para, paraSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 54 | 55 | let single = await tagger.tagSentence(line); 56 | expect(single).toBeInstanceOf(Array); 57 | expect(single).toHaveLength(1); 58 | 59 | let singleSync = tagger.tagSentenceSync(line); 60 | expect(_.zip(single, singleSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 61 | 62 | let singles = await tagger.tagSentence(para.map((x) => x.surfaceString())); 63 | expect(para).toHaveLength(singles.length); 64 | 65 | let singlesSync = tagger.tagSentenceSync(para.map((x) => x.surfaceString())); 66 | expect(_.zip(singles, singlesSync).every((tuple) => tuple[0].equals(tuple[1]))).toBe(true); 67 | } 68 | }); 69 | }); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.CoarseEntityType = exports.RoleType = exports.DependencyTag = exports.PhraseTag = exports.POS = void 0; 7 | 8 | var _jvm = require("./jvm"); 9 | 10 | var _underscore = _interopRequireDefault(require("underscore")); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 15 | 16 | /** 17 | * 자바 Enum 표현 18 | * @private 19 | */ 20 | class JavaEnum { 21 | /** 22 | * Enum 명칭 23 | * @type {string} 24 | */ 25 | 26 | /** 27 | * Enum 순서 번호 28 | * @type {number} 29 | */ 30 | 31 | /** 32 | * Enum class 이름 33 | * @type {string} 34 | */ 35 | constructor(reference) { 36 | _defineProperty(this, "tagname", ''); 37 | 38 | _defineProperty(this, "ordinal", -1); 39 | 40 | _defineProperty(this, "classType", ''); 41 | 42 | this.reference = reference; 43 | this.tagname = reference.name(); 44 | this.ordinal = reference.ordinal(); 45 | this.classType = reference.getClass().getName(); 46 | } 47 | /** 48 | * 문자열로 변환 49 | * @returns {string} 이 값을 표현하는 문자열 50 | */ 51 | 52 | 53 | toString() { 54 | return this.tagname; 55 | } 56 | /** 57 | * 두 대상이 같은지 확인합니다. 58 | * @param other 다른 대상 59 | * @returns {boolean} 같다면 true. 60 | */ 61 | 62 | 63 | equals(other) { 64 | if (other instanceof JavaEnum && this.classType === other.classType) return other.reference.equals(this.reference);else return false; 65 | } 66 | /** 67 | * 전체 값의 목록을 불러옵니다. 68 | * @param clsName 불러올 클래스 69 | * @returns {Array} 전체 값 목록 70 | */ 71 | 72 | 73 | static getAllOf(...clsName) { 74 | return _jvm.JVM.toJsArray(_jvm.JVM.koalaClassOf(...clsName).values()); 75 | } 76 | 77 | } 78 | /** 79 | * 세종 품사표기 80 | * @example 81 | * import { POS } from 'koalanlp/types'; 82 | * POS.NNP; 83 | */ 84 | 85 | 86 | class POS extends JavaEnum { 87 | /** 88 | * POS 값 전체 89 | * @type {Object.} 90 | * @private 91 | */ 92 | 93 | /** 94 | * POS 값들을 모두 돌려줍니다. 95 | * @returns {POS[]} POS값들의 array 96 | */ 97 | static values() { 98 | if (_underscore.default.isEmpty(POS._values)) { 99 | JavaEnum.getAllOf('POS').forEach(it => { 100 | let value = new POS(it); 101 | POS._values[value.tagname] = value; 102 | Object.defineProperty(POS, value.tagname, { 103 | value: value, 104 | writable: false, 105 | configurable: false 106 | }); 107 | }); 108 | Object.defineProperty(POS, '_values', { 109 | value: Object.freeze(POS._values), 110 | writable: false, 111 | configurable: false 112 | }); 113 | } 114 | 115 | return _underscore.default.values(POS._values); 116 | } 117 | /** 118 | * 이름에 해당하는 값을 찾아줍니다. 119 | * @param {!string} name 해당 이름으로 된 값 120 | * @returns {POS} 121 | */ 122 | 123 | 124 | static withName(name) { 125 | return POS._values[name]; 126 | } 127 | /** 128 | * 이 값이 체언인지 확인합니다. 129 | * @returns {boolean} 체언인 경우 true 130 | */ 131 | 132 | 133 | isNoun() { 134 | return this.reference.isNoun(); 135 | } 136 | /** 137 | * 이 값이 용언인지 확인합니다. 138 | * @returns {boolean} 용언인 경우 true 139 | */ 140 | 141 | 142 | isPredicate() { 143 | return this.reference.isPredicate(); 144 | } 145 | /** 146 | * 이 값이 수식언인지 확인합니다. 147 | * @returns {boolean} 수식언인 경우 true 148 | */ 149 | 150 | 151 | isModifier() { 152 | return this.reference.isModifier(); 153 | } 154 | /** 155 | * 이 값이 관계언인지 확인합니다. 156 | * @returns {boolean} 관계언인 경우 true 157 | */ 158 | 159 | 160 | isPostPosition() { 161 | return this.reference.isPostPosition(); 162 | } 163 | /** 164 | * 이 값이 어미인지 확인합니다. 165 | * @returns {boolean} 어미인 경우 true 166 | */ 167 | 168 | 169 | isEnding() { 170 | return this.reference.isEnding(); 171 | } 172 | /** 173 | * 이 값이 접사인지 확인합니다. 174 | * @returns {boolean} 접사인 경우 true 175 | */ 176 | 177 | 178 | isAffix() { 179 | return this.reference.isAffix(); 180 | } 181 | /** 182 | * 이 값이 접미사인지 확인합니다. 183 | * @returns {boolean} 접미사인 경우 true 184 | */ 185 | 186 | 187 | isSuffix() { 188 | return this.reference.isSuffix(); 189 | } 190 | /** 191 | * 이 값이 기호인지 확인합니다. 192 | * @returns {boolean} 기호인 경우 true 193 | */ 194 | 195 | 196 | isSymbol() { 197 | return this.reference.isSymbol(); 198 | } 199 | /** 200 | * 이 값이 미확인 단어인지 확인합니다. 201 | * @returns {boolean} 미확인 단어인 경우 true 202 | */ 203 | 204 | 205 | isUnknown() { 206 | return this.reference.isUnknown(); 207 | } 208 | /** 209 | * 이 값이 주어진 [tag]로 시작하는지 확인합니다. 210 | * @param {!string} tag 시작하는지 확인할 품사 분류 211 | * @returns {boolean} 포함되는 경우(시작하는 경우) True 212 | */ 213 | 214 | 215 | startsWith(tag) { 216 | return this.reference.startsWith(tag); 217 | } 218 | 219 | } 220 | /** 221 | * 세종 구문구조 표지자 222 | * @example 223 | * import { PhraseTag } from 'koalanlp/types'; 224 | */ 225 | 226 | 227 | exports.POS = POS; 228 | 229 | _defineProperty(POS, "_values", {}); 230 | 231 | class PhraseTag extends JavaEnum { 232 | /** 233 | * 값 전체 234 | * @type {Object.} 235 | * @private 236 | */ 237 | 238 | /** 239 | * PhraseTag 값들을 모두 돌려줍니다. 240 | * @returns {PhraseTag[]} PhraseTag값들의 array 241 | */ 242 | static values() { 243 | if (_underscore.default.isEmpty(PhraseTag._values)) { 244 | for (const it of JavaEnum.getAllOf('PhraseTag')) { 245 | let value = new PhraseTag(it); 246 | PhraseTag._values[value.tagname] = value; 247 | Object.defineProperty(PhraseTag, value.tagname, { 248 | value: value, 249 | writable: false, 250 | configurable: false 251 | }); 252 | } 253 | 254 | Object.defineProperty(PhraseTag, '_values', { 255 | value: Object.freeze(PhraseTag._values), 256 | writable: false, 257 | configurable: false 258 | }); 259 | } 260 | 261 | return _underscore.default.values(PhraseTag._values); 262 | } 263 | /** 264 | * 이름에 해당하는 값을 찾아줍니다. 265 | * @param {!string} name 해당 이름으로 된 값 266 | * @returns {PhraseTag} 267 | */ 268 | 269 | 270 | static withName(name) { 271 | return PhraseTag[name]; 272 | } 273 | 274 | } 275 | /** 276 | * ETRI 의존구문구조 기능표지자 277 | * @example 278 | * import { DependencyTag } from 'koalanlp/types'; 279 | */ 280 | 281 | 282 | exports.PhraseTag = PhraseTag; 283 | 284 | _defineProperty(PhraseTag, "_values", {}); 285 | 286 | class DependencyTag extends JavaEnum { 287 | /** 288 | * 값 전체 289 | * @type {Object.} 290 | * @private 291 | */ 292 | 293 | /** 294 | * DependencyTag 값들을 모두 돌려줍니다. 295 | * @returns {DependencyTag[]} DependencyTag값들의 array 296 | */ 297 | static values() { 298 | if (_underscore.default.isEmpty(DependencyTag._values)) { 299 | JavaEnum.getAllOf('DependencyTag').forEach(it => { 300 | let value = new DependencyTag(it); 301 | DependencyTag._values[value.tagname] = value; 302 | Object.defineProperty(DependencyTag, value.tagname, { 303 | value: value, 304 | writable: false, 305 | configurable: false 306 | }); 307 | }); 308 | Object.defineProperty(DependencyTag, '_values', { 309 | value: Object.freeze(DependencyTag._values), 310 | writable: false, 311 | configurable: false 312 | }); 313 | } 314 | 315 | return _underscore.default.values(DependencyTag._values); 316 | } 317 | /** 318 | * 이름에 해당하는 값을 찾아줍니다. 319 | * @param {!string} name 해당 이름으로 된 값 320 | * @returns {DependencyTag} 321 | */ 322 | 323 | 324 | static withName(name) { 325 | return DependencyTag._values[name]; 326 | } 327 | 328 | } 329 | /** 330 | * ETRI 의미역 분석 표지 331 | * @example 332 | * import { RoleType } from 'koalanlp/types'; 333 | */ 334 | 335 | 336 | exports.DependencyTag = DependencyTag; 337 | 338 | _defineProperty(DependencyTag, "_values", {}); 339 | 340 | class RoleType extends JavaEnum { 341 | /** 342 | * 값 전체 343 | * @type {Object.} 344 | * @private 345 | */ 346 | 347 | /** 348 | * RoleType 값들을 모두 돌려줍니다. 349 | * @returns {RoleType[]} RoleType값들의 array 350 | */ 351 | static values() { 352 | if (_underscore.default.isEmpty(RoleType._values)) { 353 | JavaEnum.getAllOf('RoleType').forEach(it => { 354 | let value = new RoleType(it); 355 | RoleType._values[value.tagname] = value; 356 | Object.defineProperty(RoleType, value.tagname, { 357 | value: value, 358 | writable: false, 359 | configurable: false 360 | }); 361 | }); 362 | Object.defineProperty(RoleType, '_values', { 363 | value: Object.freeze(RoleType._values), 364 | writable: false, 365 | configurable: false 366 | }); 367 | } 368 | 369 | return _underscore.default.values(RoleType._values); 370 | } 371 | /** 372 | * 이름에 해당하는 값을 찾아줍니다. 373 | * @param {!string} name 해당 이름으로 된 값 374 | * @returns {RoleType} 375 | */ 376 | 377 | 378 | static withName(name) { 379 | return RoleType._values[name]; 380 | } 381 | 382 | } 383 | /** 384 | * ETRI 개체명 대분류 385 | * @example 386 | * import { CoarseEntityType } from 'koalanlp/types'; 387 | */ 388 | 389 | 390 | exports.RoleType = RoleType; 391 | 392 | _defineProperty(RoleType, "_values", {}); 393 | 394 | class CoarseEntityType extends JavaEnum { 395 | /** 396 | * 값 전체 397 | * @type {Object.} 398 | * @private 399 | */ 400 | 401 | /** 402 | * CoarseEntityType 값들을 모두 돌려줍니다. 403 | * @returns {CoarseEntityType[]} CoarseEntityType값들의 array 404 | */ 405 | static values() { 406 | if (_underscore.default.isEmpty(CoarseEntityType._values)) { 407 | JavaEnum.getAllOf('CoarseEntityType').forEach(it => { 408 | let value = new CoarseEntityType(it); 409 | CoarseEntityType._values[value.tagname] = value; 410 | Object.defineProperty(CoarseEntityType, value.tagname, { 411 | value: value, 412 | writable: false, 413 | configurable: false 414 | }); 415 | }); 416 | Object.defineProperty(CoarseEntityType, '_values', { 417 | value: Object.freeze(CoarseEntityType._values), 418 | writable: false, 419 | configurable: false 420 | }); 421 | } 422 | 423 | return _underscore.default.values(CoarseEntityType._values); 424 | } 425 | /** 426 | * 이름에 해당하는 값을 찾아줍니다. 427 | * @param {!string} name 해당 이름으로 된 값 428 | * @returns {CoarseEntityType} 429 | */ 430 | 431 | 432 | static withName(name) { 433 | return CoarseEntityType._values[name]; 434 | } 435 | 436 | } 437 | 438 | exports.CoarseEntityType = CoarseEntityType; 439 | 440 | _defineProperty(CoarseEntityType, "_values", {}); --------------------------------------------------------------------------------