├── .codeclimate.yml ├── .csslintrc ├── .dockerignore ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .project ├── .travis.yml ├── Dockerfile ├── LICENSE.md ├── README.md ├── api.html ├── apiary.apib ├── app.js ├── bin └── www ├── coffeelint.json ├── deb ├── config.json ├── control │ ├── control │ ├── postinst │ ├── postrm │ ├── preinst │ └── prerm └── supervisor.conf ├── docker-compose.yml ├── fixRefsetsCount.js ├── grammars └── apg │ ├── apg-example.js │ ├── apg-iniFileOpcodes.js │ ├── apg-sct-briefSyntax.js │ ├── apgAst.js │ ├── apgLib.js │ ├── apgStats.js │ ├── apgTrace.js │ ├── apgUtilities.js │ └── expressionParser.js ├── lib ├── apiModelUtility.js ├── snomed.js ├── snomedv2.js └── transform.js ├── package.json ├── pom.xml ├── public ├── stylesheets │ └── style.css └── svg2pngTemp │ └── readme.md ├── routes ├── expressions.js ├── expressionsv1.js ├── index.js ├── server.js ├── serverv1.js ├── snomed-old.js ├── snomed.js ├── snomedv1.js └── util.js ├── util.js ├── views ├── error.jade ├── index.jade └── layout.jade └── wait-for-it.sh /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | csslint: 4 | enabled: true 5 | coffeelint: 6 | enabled: true 7 | duplication: 8 | enabled: false 9 | config: 10 | languages: 11 | - ruby 12 | - javascript 13 | - python 14 | - php 15 | eslint: 16 | enabled: true 17 | fixme: 18 | enabled: true 19 | ratings: 20 | paths: 21 | - "**.css" 22 | - "**.coffee" 23 | - "**.inc" 24 | - "**.js" 25 | - "**.jsx" 26 | - "**.module" 27 | - "**.php" 28 | - "**.py" 29 | - "**.rb" 30 | exclude_paths: 31 | - config/ 32 | - test/ 33 | - public/ 34 | - OLD/ 35 | - grammars/ 36 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | --exclude-exts=.min.css 2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .DS_Store 3 | api.html 4 | public/svg2pngTemp/*.png 5 | public/svg2pngTemp/*.svg 6 | target/ 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | jsx: true 4 | 5 | env: 6 | amd: true 7 | browser: true 8 | es6: true 9 | jquery: true 10 | node: true 11 | 12 | # http://eslint.org/docs/rules/ 13 | rules: 14 | # Possible Errors 15 | comma-dangle: [2, never] 16 | no-cond-assign: 2 17 | no-console: 0 18 | no-constant-condition: 2 19 | no-control-regex: 2 20 | no-debugger: 2 21 | no-dupe-args: 2 22 | no-dupe-keys: 2 23 | no-duplicate-case: 2 24 | no-empty: 2 25 | no-empty-character-class: 2 26 | no-ex-assign: 2 27 | no-extra-boolean-cast: 2 28 | no-extra-parens: 0 29 | no-extra-semi: 2 30 | no-func-assign: 2 31 | no-inner-declarations: [2, functions] 32 | no-invalid-regexp: 2 33 | no-irregular-whitespace: 2 34 | no-negated-in-lhs: 2 35 | no-obj-calls: 2 36 | no-regex-spaces: 2 37 | no-sparse-arrays: 2 38 | no-unexpected-multiline: 2 39 | no-unreachable: 2 40 | use-isnan: 2 41 | valid-jsdoc: 0 42 | valid-typeof: 2 43 | 44 | # Best Practices 45 | accessor-pairs: 2 46 | block-scoped-var: 0 47 | complexity: [2, 6] 48 | consistent-return: 0 49 | curly: 0 50 | default-case: 0 51 | dot-location: 0 52 | dot-notation: 0 53 | eqeqeq: 2 54 | guard-for-in: 2 55 | no-alert: 2 56 | no-caller: 2 57 | no-case-declarations: 2 58 | no-div-regex: 2 59 | no-else-return: 0 60 | no-empty-label: 2 61 | no-empty-pattern: 2 62 | no-eq-null: 2 63 | no-eval: 2 64 | no-extend-native: 2 65 | no-extra-bind: 2 66 | no-fallthrough: 2 67 | no-floating-decimal: 0 68 | no-implicit-coercion: 0 69 | no-implied-eval: 2 70 | no-invalid-this: 0 71 | no-iterator: 2 72 | no-labels: 0 73 | no-lone-blocks: 2 74 | no-loop-func: 2 75 | no-magic-number: 0 76 | no-multi-spaces: 0 77 | no-multi-str: 0 78 | no-native-reassign: 2 79 | no-new-func: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-octal-escape: 2 83 | no-octal: 2 84 | no-proto: 2 85 | no-redeclare: 2 86 | no-return-assign: 2 87 | no-script-url: 2 88 | no-self-compare: 2 89 | no-sequences: 0 90 | no-throw-literal: 0 91 | no-unused-expressions: 2 92 | no-useless-call: 2 93 | no-useless-concat: 2 94 | no-void: 2 95 | no-warning-comments: 0 96 | no-with: 2 97 | radix: 2 98 | vars-on-top: 0 99 | wrap-iife: 2 100 | yoda: 0 101 | 102 | # Strict 103 | strict: 0 104 | 105 | # Variables 106 | init-declarations: 0 107 | no-catch-shadow: 2 108 | no-delete-var: 2 109 | no-label-var: 2 110 | no-shadow-restricted-names: 2 111 | no-shadow: 0 112 | no-undef-init: 2 113 | no-undef: 0 114 | no-undefined: 0 115 | no-unused-vars: 0 116 | no-use-before-define: 0 117 | 118 | # Node.js and CommonJS 119 | callback-return: 2 120 | global-require: 2 121 | handle-callback-err: 2 122 | no-mixed-requires: 0 123 | no-new-require: 0 124 | no-path-concat: 2 125 | no-process-exit: 2 126 | no-restricted-modules: 0 127 | no-sync: 0 128 | 129 | # Stylistic Issues 130 | array-bracket-spacing: 0 131 | block-spacing: 0 132 | brace-style: 0 133 | camelcase: 0 134 | comma-spacing: 0 135 | comma-style: 0 136 | computed-property-spacing: 0 137 | consistent-this: 0 138 | eol-last: 0 139 | func-names: 0 140 | func-style: 0 141 | id-length: 0 142 | id-match: 0 143 | indent: 0 144 | jsx-quotes: 0 145 | key-spacing: 0 146 | linebreak-style: 0 147 | lines-around-comment: 0 148 | max-depth: 0 149 | max-len: 0 150 | max-nested-callbacks: 0 151 | max-params: 0 152 | max-statements: [2, 30] 153 | new-cap: 0 154 | new-parens: 0 155 | newline-after-var: 0 156 | no-array-constructor: 0 157 | no-bitwise: 0 158 | no-continue: 0 159 | no-inline-comments: 0 160 | no-lonely-if: 0 161 | no-mixed-spaces-and-tabs: 0 162 | no-multiple-empty-lines: 0 163 | no-negated-condition: 0 164 | no-nested-ternary: 0 165 | no-new-object: 0 166 | no-plusplus: 0 167 | no-restricted-syntax: 0 168 | no-spaced-func: 0 169 | no-ternary: 0 170 | no-trailing-spaces: 0 171 | no-underscore-dangle: 0 172 | no-unneeded-ternary: 0 173 | object-curly-spacing: 0 174 | one-var: 0 175 | operator-assignment: 0 176 | operator-linebreak: 0 177 | padded-blocks: 0 178 | quote-props: 0 179 | quotes: 0 180 | require-jsdoc: 0 181 | semi-spacing: 0 182 | semi: 0 183 | sort-vars: 0 184 | space-after-keywords: 0 185 | space-before-blocks: 0 186 | space-before-function-paren: 0 187 | space-before-keywords: 0 188 | space-in-parens: 0 189 | space-infix-ops: 0 190 | space-return-throw-case: 0 191 | space-unary-ops: 0 192 | spaced-comment: 0 193 | wrap-regex: 0 194 | 195 | # ECMAScript 6 196 | arrow-body-style: 0 197 | arrow-parens: 0 198 | arrow-spacing: 0 199 | constructor-super: 0 200 | generator-star-spacing: 0 201 | no-arrow-condition: 0 202 | no-class-assign: 0 203 | no-const-assign: 0 204 | no-dupe-class-members: 0 205 | no-this-before-super: 0 206 | no-var: 0 207 | object-shorthand: 0 208 | prefer-arrow-callback: 0 209 | prefer-const: 0 210 | prefer-reflect: 0 211 | prefer-spread: 0 212 | prefer-template: 0 213 | require-yield: 0 214 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .idea 17 | .vscode 18 | .DS_Store 19 | api.html 20 | public/svg2pngTemp/*.png 21 | public/svg2pngTemp/*.svg 22 | target/ 23 | OLD/ 24 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | sct-snapshot-rest-api 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.m2e.core.maven2Builder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.m2e.core.maven2Nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | 3 | RUN apk add --update bash && rm -rf /var/cache/apk/* 4 | 5 | # Create a directory where our app will be placed 6 | RUN mkdir -p /app 7 | 8 | # Change directory so that our commands run inside this new directory 9 | WORKDIR /app 10 | 11 | # RUN git clone https://github.com/IHTSDO/sct-snapshot-rest-api.git 12 | COPY . /app/sct-snapshot-rest-api 13 | WORKDIR /app/sct-snapshot-rest-api 14 | 15 | RUN chmod u+x wait-for-it.sh 16 | 17 | RUN npm install 18 | 19 | # Expose the port the app runs in 20 | EXPOSE 9999 21 | 22 | # Serve the app 23 | CMD ["node", "app.js"] 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SNOMED CT Snapshot REST API 2 | [![Build Status](https://travis-ci.org/IHTSDO/sct-snapshot-rest-api.svg?branch=master)](https://travis-ci.org/IHTSDO/sct-snapshot-rest-api) 3 | 4 | ### **This API has now been deprecated and is no longer supported or used by SNOMED International. We recommend using the organization's open source [Snowstorm Terminology Server](https://github.com/IHTSDO/snowstorm)** 5 | 6 | Lightweight mongo server with a rest API for SNOMED CT Snapshot views, powered by the MEAN stack, http://mean.io/, (Node.js, Express & MongoDB). 7 | 8 | This repository also now provides Docker config to make it easier to use and install. 9 | 10 | ## API docs 11 | 12 | The API documentation can be found here: 13 | 14 | 15 | 16 | 17 | ## How to run using Docker 18 | [![](https://images.microbadger.com/badges/image/snomedinternational/snomed-snapshot-api:2.0.svg)](https://microbadger.com/images/snomedinternational/snomed-snapshot-api:2.0 "Get your own image badge on microbadger.com") [![](https://images.microbadger.com/badges/version/snomedinternational/snomed-snapshot-api:2.0.svg)](https://microbadger.com/images/snomedinternational/snomed-snapshot-api:2.0 "Get your own version badge on microbadger.com") 19 | 20 | Clone this project 21 | ``` 22 | git clone https://github.com/IHTSDO/sct-snapshot-rest-api.git 23 | ``` 24 | 25 | and then 26 | ``` 27 | docker-compose up -d 28 | ``` 29 | This will start a few containersone of which is a volume called db-data attached to it. The db-data volume maps to local folders under ~/mongo/, allowing you to re-use the data between different versions of the container 30 | 31 | (Note, you might have to create the paths ```/data/db```, ```/var/lib/mongodb```, and ```/var/log/mongodb``` and ensure that they are writable by the user running your mongo container.) 32 | 33 | Next, you'll need to get SNOMED CT data into your mongo database. To create the necessary JSON files from a SNOMED CT RF2 release, follow the instructions here once you have the SNOMED CT Files in RF2 format (standard release files). You can then create the JSON files for importing into Mongo using this project: 34 | 35 | 36 | 37 | **NOTE** ensure you are using versions 1.3, and above of the conversion tool to create the JSON files. Older versions will not work. 38 | 39 | Instructions on how to then import into the MongoDB are also in that repository. 40 | 41 | Once that's you have it all up and running, you can test out the install by going to 42 | http://localhost:3000/snomed/en-edition/v20180131/descriptions?query=heart%20attack 43 | which should show you some JSON formatted information about heart attacks. 44 | 45 | However, if you're having problems you may be able to get access to download the data already generated ready for the MongoDB from SNOMED International by contacting [techsupport@snomed.org)](mailto:techsupport@snomed.org). 46 | 47 | ## How to install manually 48 | 49 | Clone this project into the server, by using: 50 | 51 | ``` 52 | git clone https://github.com/IHTSDO/sct-snapshot-rest-api.git 53 | ``` 54 | 55 | In the "sct-snapshot-rest-api" folder use Node.js to install all dependencies: 56 | 57 | ``` 58 | sct-snapshot-rest-api: $ npm install 59 | ``` 60 | 61 | And then run the server: 62 | 63 | ``` 64 | sct-snapshot-rest-api: $ node app.js 65 | ``` 66 | 67 | **IMPORTANT:** This API needs to have local access to the MongoDB server where the terminology data has been loaded into. The SNOMED CT data for the mongo instance can be obtained via your local National Resource Center (info in ). 68 | 69 | Once you have the SNOMED CT Files in RF2 format (standard release files) you can create a JSON file for importing into Mongo using this project: 70 | 71 | 72 | 73 | **NOTE** ensure you are using versions 1.3 and above of the conversion tool to create the JSON files. Older versions will not work. 74 | 75 | Instructions on how to then import into the MongoDB are also in that repository. 76 | 77 | ## Access the server 78 | 79 | The server will start listening automatically on port 3000\. You can test a REST call by goint to a Web Browser and navigating to this link: 80 | 81 | 82 | 83 | This call will retrieve the data for the concept Clinical Finding (finding), idenfied by the SCTID 404684003, in the International edition (en-edition) for the January 2016 release (v20160131). 84 | ``` 85 | 86 | ## NOTES: 87 | 88 | The server will attempt to write a pid file at: 89 | /var/sct-snapshot-rest-api.pid 90 | to change this location please set the environment variable 91 | PID_FILE 92 | for example (windows): 93 | set PID_FILE=c:\temp\sct-snapshot-rest-api.pid 94 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | // var favicon = require('static-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var MongoClient = require('mongodb').MongoClient; 8 | var fs = require('fs'); 9 | var pidFile = process.env.PID_FILE || "mongodb-rest.pid"; 10 | 11 | var routes = require('./routes/index'); 12 | var snomed = require('./routes/snomed'); 13 | var snomedv1 = require('./routes/snomedv1'); 14 | var util = require('./routes/util'); 15 | var server = require('./routes/server'); 16 | var serverv1 = require('./routes/serverv1'); 17 | var expressions = require('./routes/expressions'); 18 | var expressionsv1 = require('./routes/expressionsv1'); 19 | 20 | var accessControlConfig = { 21 | "allowOrigin": "*", 22 | "allowMethods": "GET,POST,PUT,DELETE,HEAD,OPTIONS" 23 | }; 24 | 25 | // ************************ 26 | 27 | var app = express(); 28 | // view engine setu 29 | // p 30 | app.set('views', path.join(__dirname, 'views')); 31 | app.set('view engine', 'pug'); 32 | 33 | // app.use(favicon()); 34 | app.use(logger('dev')); 35 | app.use(bodyParser.json()); 36 | app.use(bodyParser.urlencoded({ extended: true })); 37 | app.use(cookieParser()); 38 | app.use(express.static(path.join(__dirname, 'public'))); 39 | 40 | app.use(function(req, res, next) { 41 | var oneof = false; 42 | if (req.headers.origin) { 43 | res.header('Access-Control-Allow-Origin', req.headers.origin); 44 | oneof = true; 45 | } 46 | if (req.headers['access-control-request-method']) { 47 | res.header('Access-Control-Allow-Methods', req.headers['access-control-request-method']); 48 | oneof = true; 49 | } 50 | if (req.headers['access-control-request-headers']) { 51 | res.header('Access-Control-Allow-Headers', req.headers['access-control-request-headers']); 52 | oneof = true; 53 | } 54 | if (oneof) { 55 | res.header('Access-Control-Max-Age', 60 * 60 * 24 * 365); 56 | } 57 | 58 | // intercept OPTIONS method 59 | if (oneof && req.method == 'OPTIONS') { 60 | res.send(200); 61 | } else { 62 | next(); 63 | } 64 | }); 65 | 66 | app.use('/', routes); 67 | app.use('/snomed', snomed); 68 | app.use('/v2/snomed', snomed); 69 | app.use('/v1/snomed', snomedv1); 70 | app.use('/util', util); 71 | app.use('/server', serverv1); 72 | app.use("/expressions", expressionsv1); 73 | app.use('/v2/util', util); 74 | app.use('/v2/server', server); 75 | app.use("/v2/expressions", expressions); 76 | app.use('/v1/util', util); 77 | app.use('/v1/server', serverv1); 78 | app.use("/v1/expressions", expressionsv1); 79 | 80 | /// catch 404 and forward to error handler 81 | app.use(function(req, res, next) { 82 | var err = new Error('Not Found'); 83 | err.status = 404; 84 | next(err); 85 | }); 86 | 87 | /// error handlers 88 | 89 | // development error handler 90 | // will print stacktrace 91 | if (app.get('env') === 'development') { 92 | app.use(function(err, req, res, next) { 93 | res.status(err.status || 500); 94 | res.status(err.status >= 100 && err.status < 600 ? err.code : 500).send(err.message); 95 | }); 96 | } 97 | 98 | // production error handler 99 | // no stacktraces leaked to user 100 | // Adding raw body support 101 | app.use(function(err, req, res, next) { 102 | res.status(err.status >= 100 && err.status < 600 ? err.code : 500).send(err.message); 103 | }); 104 | 105 | var cluster = require('cluster'); 106 | var port = process.env.PORT || 3000; 107 | 108 | if (cluster.isMaster) { 109 | fs.writeFile(pidFile, process.pid); 110 | var numWorkers = require('os').cpus().length; 111 | 112 | console.log('Master cluster setting up ' + numWorkers + ' workers...'); 113 | 114 | for (var i = 0; i < numWorkers; i++) { 115 | cluster.fork(); 116 | } 117 | 118 | cluster.on('online', function(worker) { 119 | console.log('Worker ' + worker.process.pid + ' is online'); 120 | }); 121 | 122 | cluster.on('exit', function(worker, code, signal) { 123 | console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal); 124 | console.log('Starting a new worker'); 125 | cluster.fork(); 126 | }); 127 | } else { 128 | //var app = require('express')(); 129 | // app.all('/*', function(req, res) {res.send('process ' + process.pid + ' says hello!').end();}) 130 | 131 | var server = app.listen(port, function() { 132 | console.log('Process ' + process.pid + ' is listening in port ' + port + ' to all incoming requests'); 133 | }); 134 | } 135 | 136 | // var server = require('http').Server(app); 137 | // 138 | // server.listen(port); 139 | // 140 | // console.log('Express app started on port '+port); 141 | 142 | module.exports = app; 143 | module.exports.accessControlConfig = accessControlConfig; -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('generated-express-app'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrow_spacing": { 3 | "level": "ignore" 4 | }, 5 | "braces_spacing": { 6 | "level": "ignore", 7 | "spaces": 0, 8 | "empty_object_spaces": 0 9 | }, 10 | "camel_case_classes": { 11 | "level": "error" 12 | }, 13 | "coffeescript_error": { 14 | "level": "error" 15 | }, 16 | "colon_assignment_spacing": { 17 | "level": "ignore", 18 | "spacing": { 19 | "left": 0, 20 | "right": 0 21 | } 22 | }, 23 | "cyclomatic_complexity": { 24 | "value": 10, 25 | "level": "ignore" 26 | }, 27 | "duplicate_key": { 28 | "level": "error" 29 | }, 30 | "empty_constructor_needs_parens": { 31 | "level": "ignore" 32 | }, 33 | "ensure_comprehensions": { 34 | "level": "warn" 35 | }, 36 | "eol_last": { 37 | "level": "ignore" 38 | }, 39 | "indentation": { 40 | "value": 2, 41 | "level": "error" 42 | }, 43 | "line_endings": { 44 | "level": "ignore", 45 | "value": "unix" 46 | }, 47 | "max_line_length": { 48 | "value": 80, 49 | "level": "error", 50 | "limitComments": true 51 | }, 52 | "missing_fat_arrows": { 53 | "level": "ignore", 54 | "is_strict": false 55 | }, 56 | "newlines_after_classes": { 57 | "value": 3, 58 | "level": "ignore" 59 | }, 60 | "no_backticks": { 61 | "level": "error" 62 | }, 63 | "no_debugger": { 64 | "level": "warn", 65 | "console": false 66 | }, 67 | "no_empty_functions": { 68 | "level": "ignore" 69 | }, 70 | "no_empty_param_list": { 71 | "level": "ignore" 72 | }, 73 | "no_implicit_braces": { 74 | "level": "ignore", 75 | "strict": true 76 | }, 77 | "no_implicit_parens": { 78 | "strict": true, 79 | "level": "ignore" 80 | }, 81 | "no_interpolation_in_single_quotes": { 82 | "level": "ignore" 83 | }, 84 | "no_plusplus": { 85 | "level": "ignore" 86 | }, 87 | "no_stand_alone_at": { 88 | "level": "ignore" 89 | }, 90 | "no_tabs": { 91 | "level": "error" 92 | }, 93 | "no_this": { 94 | "level": "ignore" 95 | }, 96 | "no_throwing_strings": { 97 | "level": "error" 98 | }, 99 | "no_trailing_semicolons": { 100 | "level": "error" 101 | }, 102 | "no_trailing_whitespace": { 103 | "level": "error", 104 | "allowed_in_comments": false, 105 | "allowed_in_empty_lines": true 106 | }, 107 | "no_unnecessary_double_quotes": { 108 | "level": "ignore" 109 | }, 110 | "no_unnecessary_fat_arrows": { 111 | "level": "warn" 112 | }, 113 | "non_empty_constructor_needs_parens": { 114 | "level": "ignore" 115 | }, 116 | "prefer_english_operator": { 117 | "level": "ignore", 118 | "doubleNotLevel": "ignore" 119 | }, 120 | "space_operators": { 121 | "level": "ignore" 122 | }, 123 | "spacing_after_comma": { 124 | "level": "ignore" 125 | }, 126 | "transform_messes_up_line_numbers": { 127 | "level": "warn" 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /deb/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "port": 27101, 4 | "host": "localhost" 5 | }, 6 | "server": { 7 | "port": 3000, 8 | "address": "0.0.0.0" 9 | }, 10 | "accessControl": { 11 | "allowOrigin": "*", 12 | "allowMethods": "GET,POST,PUT,DELETE,HEAD,OPTIONS" 13 | }, 14 | "flavor": "nounderscore", 15 | "debug": true 16 | } 17 | -------------------------------------------------------------------------------- /deb/control/control: -------------------------------------------------------------------------------- 1 | Package: [[artifactId]] 2 | Version: [[version]] 3 | Priority: optional 4 | Architecture: all 5 | Depends: nodejs (>=0.10.18), supervisor 6 | Maintainer: Delivery Development Team 7 | Description: [[name]] 8 | Distribution: stable 9 | Section: web 10 | -------------------------------------------------------------------------------- /deb/control/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | #For debugging uncomment these two lines 4 | set -x 5 | echo $* 6 | 7 | APP_NAME=[[artifactId]] 8 | 9 | chmod +x /opt/$APP_NAME/bin/* 10 | 11 | supervisorctl update $APP_NAME || true 12 | supervisorctl start $APP_NAME || true 13 | 14 | -------------------------------------------------------------------------------- /deb/control/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | #For debugging uncomment these two lines 4 | set -x 5 | echo $* 6 | 7 | APP_NAME=[[artifactId]] 8 | 9 | case $1 in 10 | purge) 11 | if getent passwd $APP_NAME >/dev/null; then 12 | userdel $APP_NAME 13 | fi 14 | if test -d /var/opt/$APP_NAME; then 15 | rm -r /var/opt/$APP_NAME 16 | fi 17 | ;; 18 | esac 19 | -------------------------------------------------------------------------------- /deb/control/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | #For debugging uncomment these two lines 4 | set -x 5 | echo $* 6 | 7 | APP_NAME=[[artifactId]] 8 | 9 | if ! getent passwd $APP_NAME >/dev/null; then 10 | adduser --quiet --system --no-create-home --group \ 11 | --home /var/opt/$APP_NAME --shell /bin/false $APP_NAME 12 | fi 13 | 14 | supervisorctl stop $APP_NAME || true 15 | -------------------------------------------------------------------------------- /deb/control/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | #For debugging uncomment these two lines 4 | set -x 5 | echo $* 6 | 7 | APP_NAME=[[artifactId]] 8 | 9 | case $1 in 10 | remove|purge) 11 | supervisorctl stop $APP_NAME >/dev/null 12 | supervisorctl remove $APP_NAME 13 | ;; 14 | esac 15 | -------------------------------------------------------------------------------- /deb/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:sct-snapshot-rest-api] 2 | user = sct-snapshot-rest-api 3 | directory = /opt/%(program_name)s 4 | command = /opt/%(program_name)s/bin/mongodb-rest 5 | autostart = true 6 | autorestart = true 7 | stdout_logfile_backups=5 8 | stderr_logfile_backups=5 9 | stdout_logfile_maxbytes=10485760 10 | stderr_logfile_maxbytes=10485760 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | services: 3 | db: 4 | image: mongo:latest 5 | ports: 6 | - "27017:27017" 7 | volumes_from: 8 | - db-data 9 | networks: 10 | snomed: 11 | aliases: 12 | - db 13 | 14 | 15 | db-data: 16 | image: mongo:latest 17 | volumes: 18 | - ~/mongo/data:/data/db 19 | - ~/mongo/lib:/var/lib/mongodb 20 | - ~/mongo/log:/var/log/mongodb 21 | entrypoint: /bin/true 22 | 23 | snomed-api: 24 | image: snomedinternational/snomed-snapshot-api:2.0 25 | links: 26 | - db 27 | ports: 28 | - "3000:3000" 29 | - "35729:35729" 30 | environment: 31 | - NODE_ENV=development 32 | - MONGO_DB_CONN=db:27017 33 | - SCT_VERSION=20180131 34 | networks: 35 | - snomed 36 | command: ["./wait-for-it.sh", "db:27017", "--", "node", "app.js"] 37 | 38 | 39 | networks: 40 | snomed: 41 | driver: bridge 42 | -------------------------------------------------------------------------------- /fixRefsetsCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tbertonatti on 3/9/16. 3 | */ 4 | var MongoClient = require('mongodb').MongoClient; 5 | var databases = {}; 6 | 7 | var performMongoDbRequest = function(databaseName, callback) { 8 | if (databases[databaseName]) { 9 | //console.log("Using cache"); 10 | callback(databases[databaseName]); 11 | } else { 12 | //console.log("Connecting"); 13 | MongoClient.connect("mongodb://localhost:27017/"+databaseName, function(err, db) { 14 | if (err) { 15 | console.warn(getTime() + " - " + err.message); 16 | res.status(500); 17 | res.send(err.message); 18 | return; 19 | } 20 | //console.log("Connection OK") 21 | databases[databaseName] = db; 22 | callback(db); 23 | }); 24 | } 25 | }; 26 | 27 | var params = []; 28 | process.argv.forEach(function (val, index, array) { 29 | params.push(val); 30 | }); 31 | 32 | performMongoDbRequest("server",function(db){ 33 | var endWithMessage = function(err){ 34 | console.log(err); 35 | process.exit(); 36 | }; 37 | console.log("getting all manifests"); 38 | var collection = db.collection("resources"); 39 | if (params[2] && params[3]){ 40 | collection.find({"databaseName" : params[2], "collectionName": params[3].replace("v", "")}, {resourceSetName: 1, databaseName: 1, collectionName: 1, refsets: 1}, function(err, cursor) { 41 | if (err){ 42 | endWithMessage(err); 43 | }else{ 44 | cursor.toArray(function(err, docs) { 45 | if (err){ 46 | endWithMessage(err); 47 | }else if (docs && docs.length){ 48 | console.log(docs.length + " manifests matching the params"); 49 | //docs.forEach(function(manifest, indM){ 50 | var indM = 0; 51 | var updateManifest = function(){ 52 | var manifest = docs[indM]; 53 | console.log("Manifest: " + manifest.resourceSetName + " " + manifest.databaseName + " " + manifest.collectionName); 54 | console.log("getting all the counts of the refsets"); 55 | if (manifest.refsets && manifest.refsets.length){ 56 | performMongoDbRequest(manifest.databaseName, function(db){ 57 | collection = db.collection("v" + manifest.collectionName); 58 | var findsDone = 0, percentage = 0; 59 | manifest.refsets.forEach(function(refset, indR){ 60 | var idParam = refset.conceptId; 61 | var idParamStr = refset.conceptId + ""; 62 | var query = {"memberships": {"$elemMatch": {"$or": [ {"refset.conceptId": idParam }, {"refset.conceptId": idParamStr } ], "active": true}}}; 63 | collection.count(query, function (err, total) { 64 | findsDone++; 65 | if (percentage != parseInt(findsDone * 100 / manifest.refsets.length)){ 66 | percentage = parseInt(findsDone * 100 / manifest.refsets.length); 67 | console.log(percentage + " %"); 68 | } 69 | if (err){ 70 | console.log(err); 71 | }else{ 72 | //console.log("Replace", refset.count, "with", total, idParamStr); 73 | manifest.refsets[indR].count = total; 74 | } 75 | if (findsDone == manifest.refsets.length){ 76 | console.log("Updating the manifest"); 77 | //_id 78 | performMongoDbRequest("server",function(db){ 79 | var collection = db.collection("resources"); 80 | collection.update({_id: manifest._id}, {$set: {refsets: manifest.refsets}}, {safe: true, upsert: false}, function (err, obj) { 81 | indM++; 82 | console.log(indM + " of " + docs.length + " manifests updated"); 83 | if (indM == docs.length){ 84 | if (err){ 85 | endWithMessage(err); 86 | }else{ 87 | endWithMessage("Finish"); 88 | } 89 | }else{ 90 | updateManifest(); 91 | } 92 | }); 93 | }); 94 | } 95 | }); 96 | }); 97 | }); 98 | } 99 | }; 100 | updateManifest(); 101 | }else{ 102 | endWithMessage("No manifests found"); 103 | } 104 | }); 105 | } 106 | }); 107 | }else{ 108 | endWithMessage("Params missing"); 109 | } 110 | }); -------------------------------------------------------------------------------- /grammars/apg/apgAst.js: -------------------------------------------------------------------------------- 1 | /* 2 | JavaScript APG Runtime Library, Version 1.0 3 | 4 | APG - an ABNF Parser Generator 5 | Copyright (C) 2009 Lowell D. Thomas, all rights reserved 6 | 7 | author: Lowell D. Thomas 8 | email: lowell@coasttocoastresearch.com 9 | website: http://www.coasttocoastresearch.com 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 2 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see 23 | 24 | or write to the Free Software Foundation, Inc., 25 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 26 | */ 27 | "use strict"; 28 | /** 29 | * @class 30 | Creates records for a subset of the RNM nodes of a parse tree to be used 31 | to build the Abstract Syntax Tree (AST). 32 | * @version 1.0 33 | * @copyright Copyright © 2009 Lowell D. Thomas, all rights reserved 34 | * @license [GNU General Public License]{@link http://www.gnu.org/licenses/licenses.html} 35 | * Version 2 or higher. 36 | @constructor 37 | @param list - a list of true/false values for each rule name in the grammar, 38 | true if the rule is to be included, false if not 39 | @param rules - the list of rules from the APG-generated opcodes 40 | @param ruleIds - the list of rule IDs from the APG-generated opcodes 41 | @param chars - the array of character codes for the input string 42 | */ 43 | function Ast(list, rules, ruleIds, chars) 44 | { 45 | this.rules = rules; 46 | this.chars = chars; 47 | this.ruleIds = ruleIds; 48 | this.inPRD = 0; 49 | this.astList = []; 50 | this.ast = []; 51 | this.rulePhrases = []; 52 | this.ruleCount = rules.length; 53 | 54 | var i; 55 | for(i=0; i < this.ruleCount; i+=1) 56 | { 57 | // initialize the AST rule name node list 58 | if(list[i] === true){this.astList[i] = [];} 59 | else{this.astList[i] = null;} 60 | 61 | // initialize the rule phrases 62 | this.rulePhrases[i] = []; 63 | } 64 | 65 | /*****************************************************************************/ 66 | /** clears the array of nodes */ 67 | this.clear = function() 68 | { 69 | this.ast.length = 0; 70 | }; 71 | 72 | /*****************************************************************************/ 73 | /** 74 | * Test if a rule is defined. 75 | * @param {Number} ruleIndex - the index of the rule to test 76 | * @returns {boolean} true if the rule is defined, false otherwise. 77 | */ 78 | this.ruleDefined = function(ruleIndex) 79 | { 80 | return (this.astList[ruleIndex] !== null); 81 | }; 82 | 83 | /** 84 | * Creates and saves a record for the rule on the downward traversal of the parse tree. 85 | * @param {Number} ruleIndex - index of the rule name of the RNM node. 86 | * @returns {Number} the array index of the saved record 87 | */ 88 | this.down = function(ruleIndex) 89 | { 90 | var thisIndex = this.ast.length; 91 | if(this.inPRD === 0) 92 | { 93 | // only record this node if not in a PRD opcode branch 94 | this.ast[thisIndex] = []; 95 | this.ast[thisIndex].down = true; 96 | this.ast[thisIndex].ruleIndex = ruleIndex; 97 | this.ast[thisIndex].upIndex = null; 98 | } 99 | return thisIndex; 100 | }; 101 | 102 | /** 103 | * Creates and saves a record to the AST during upward traversal of the parse tree. 104 | * @param {Number} downIndex - index to the corresponding record saved for this RNM node on downward traversal 105 | * @param {Number} phraseIndex - offset into the character code array of the input string 106 | * for the beginning of the matched phrase 107 | * @param {Number} phraseLength - number of characters in the matched phrase 108 | * @returns {Number} the array index of the created record 109 | */ 110 | this.up = function(downIndex, phraseIndex, phraseLength) 111 | { 112 | var thisIndex = this.ast.length; 113 | if(this.inPRD === 0) 114 | { 115 | // only record this node if not in a PRD opcode branch 116 | this.ast[thisIndex] = []; 117 | this.ast[thisIndex].down = false; 118 | this.ast[thisIndex].downIndex = downIndex; 119 | this.ast[thisIndex].phraseIndex = phraseIndex; 120 | this.ast[thisIndex].phraseLength = phraseLength; 121 | this.ast[downIndex].upIndex = thisIndex; 122 | } 123 | return thisIndex; 124 | }; 125 | 126 | /** 127 | * Truncates all saved AST node records above "length" records. 128 | * Used to delete AST node records saved in al branch tha ultimately failed 129 | * and had to be backtracked over. 130 | * @param {Number} length - the length (number of records) to be retained. 131 | */ 132 | this.truncate = function(length) 133 | { 134 | if(this.inPRD === 0){this.ast.length = length;} 135 | }; 136 | 137 | /** 138 | * Find the number of AST records currently saved. 139 | * @returns {Number} the number of AST records saved 140 | */ 141 | this.currentLength = function() 142 | { 143 | return this.ast.length; 144 | }; 145 | 146 | /** 147 | * A specialty function to aid the EventLoop() in Interactive APG 148 | * in constructing a drop-down list of matched phrases. 149 | * Modifies this.rulePhrases. 150 | */ 151 | this.countPhrases = function() 152 | { 153 | for(var i = 0; i < this.ast.length; i+=1) 154 | { 155 | if(this.ast[i].down) 156 | { 157 | // count and index the phrase 158 | this.rulePhrases[this.ast[i].ruleIndex].push(this.ast[i].upIndex); 159 | } 160 | } 161 | }; 162 | 163 | /** 164 | * Specialty function to aid the EventLoop() function in Interactive APG 165 | * in constructing a drop-down list of matched phrases. 166 | * @param {Array} options - a list of options supplied by Interactive APG 167 | */ 168 | // specialty function to aid EventLoop() in constructing the drop down list 169 | // of matched phrases for interactive display 170 | this.getDropDownOptions = function(options) 171 | { 172 | for(var i = 0; i < this.ruleCount; i+=1) 173 | { 174 | var j = this.ruleIds[i]; 175 | options[j] = []; 176 | options[j].rule = this.rules[j].rule; 177 | options[j]['phrase-count'] = this.rulePhrases[j].length; 178 | } 179 | }; 180 | 181 | // returns HTML ASCII-formatted input string with highlighted phrases for the 182 | // ruleIndex rule name 183 | /** 184 | * returns HTML ASCII-formatted input string with highlighted phrases for the given rule. 185 | * @param {number} ruleIndex - index of the rule to highlight 186 | */ 187 | this.displayPhrasesAscii = function(ruleIndex) 188 | { 189 | var html = ''; 190 | var list = []; 191 | var stack = []; 192 | var listIndex = 0; 193 | var node; 194 | var nextNode; 195 | 196 | list = this.rulePhrases[ruleIndex]; 197 | if(list[listIndex] !== undefined) 198 | { 199 | nextNode = this.ast[list[listIndex]]; 200 | } 201 | else{nextNode = undefined;} 202 | for(var i = 0; i < this.chars.length; i+=1) 203 | { 204 | if(nextNode && nextNode.phraseIndex === i) 205 | { 206 | if(nextNode.phraseLength === 0) 207 | { 208 | // empty phrase 209 | html += 'ε'; 210 | while(true) 211 | { 212 | listIndex+=1; 213 | nextNode = (list[listIndex] !== undefined) ? this.ast[list[listIndex]] : undefined; 214 | if(nextNode && nextNode.phraseIndex === i) 215 | { 216 | // empty phrase 217 | html += 'ε'; 218 | } 219 | else{break;} 220 | } 221 | } 222 | else 223 | { 224 | // open the next highlighted phrase 225 | if(stack.length%2 === 0){html += '';} 226 | else{html += '';} 227 | stack.push(nextNode); 228 | listIndex+=1; 229 | nextNode = (list[listIndex] !== undefined) ? this.ast[list[listIndex]] : undefined; 230 | } 231 | } 232 | 233 | if(this.chars[i] === 10){html += 'LF
';} 234 | else if(this.chars[i] === 13){html +='CR';} 235 | else if(this.chars[i] === 9){html += 'TAB';} 236 | else if(this.chars[i] < 32 || this.chars[i] > 126) 237 | {html +='x'+this.chars[i].toString(16).toUpperCase()+'';} 238 | else if(this.chars[i] === 32){html += ' ';} 239 | else{html += '&#'+this.chars[i];} 240 | 241 | // check for end of last opened phrase 242 | if(stack.length > 0) 243 | { 244 | node = stack[stack.length - 1]; 245 | while(node && (node.phraseIndex + node.phraseLength - 1) === i) 246 | { 247 | html += '
'; 248 | stack.pop(); 249 | node = stack[stack.length - 1]; 250 | } 251 | } 252 | } 253 | if(stack.length > 0) 254 | { 255 | apgAssert(stack.length === 1, 'displayPhrasesAscii: stack length: '+stack.length); 256 | html += '
'; 257 | stack.pop(); 258 | } 259 | 260 | return html; 261 | }; 262 | 263 | // returns HTML hexidecimal-formatted input string with highlighted phrases for the 264 | // ruleIndex rule name 265 | /** 266 | * returns HTML hexidecimal-formatted input string with highlighted phrases for the given rule. 267 | * @param {number} ruleIndex - index of the rule to highlight 268 | */ 269 | this.displayPhrasesHex = function(ruleIndex) 270 | { 271 | var html = ''; 272 | var htmlHex = ''; 273 | var htmlAscii = ''; 274 | var list = []; 275 | var stack = []; 276 | var listIndex = 0; 277 | var node; 278 | var nextNode; 279 | var hexChar; 280 | var spanEven = ''; 281 | var spanOdd = ''; 282 | var emptyHex = '00'; 283 | var emptyAscii = 'ε'; 284 | 285 | var count = 0; 286 | var matchLen = 24; 287 | 288 | list = this.rulePhrases[ruleIndex]; 289 | if(list[listIndex] !== undefined) 290 | { 291 | nextNode = this.ast[list[listIndex]]; 292 | } 293 | else{nextNode = undefined;} 294 | for(var i = 0; i < this.chars.length; i+=1) 295 | { 296 | if(nextNode && nextNode.phraseIndex === i) 297 | { 298 | if(nextNode.phraseLength === 0) 299 | { 300 | // empty phrase 301 | htmlAscii += emptyAscii; 302 | htmlHex += emptyHex; 303 | count+=1; 304 | if(count === matchLen) 305 | { 306 | htmlHex += '
'; 307 | htmlAscii += '
'; 308 | count = 0; 309 | } 310 | else 311 | { 312 | if(count%4 === 0){htmlHex += ' ';} 313 | } 314 | while(true) 315 | { 316 | listIndex+=1; 317 | nextNode = (list[listIndex] !== undefined) ? this.ast[list[listIndex]] : undefined; 318 | if(nextNode && nextNode.phraseIndex === i) 319 | { 320 | // empty phrase 321 | htmlAscii += emptyAscii; 322 | htmlHex += emptyHex; 323 | count+=1; 324 | if(count === matchLen) 325 | { 326 | htmlHex += '
'; 327 | htmlAscii += '
'; 328 | count = 0; 329 | } 330 | else 331 | { 332 | if(count%4 === 0){htmlHex += ' ';} 333 | } 334 | } 335 | else{break;} 336 | } 337 | } 338 | else 339 | { 340 | // open the next highlighted phrase 341 | if(stack.length%2 === 0){htmlAscii += spanEven; htmlHex += spanEven;} 342 | else{htmlAscii += spanOdd; htmlHex += spanOdd;} 343 | stack.push(nextNode); 344 | listIndex+=1; 345 | nextNode = (list[listIndex] !== undefined) ? this.ast[list[listIndex]] : undefined; 346 | } 347 | } 348 | 349 | if(this.chars[i] < 32 || this.chars[i] > 126){htmlAscii += '.';} 350 | else if(this.chars[i] === 32){htmlAscii += ' ';} 351 | else{htmlAscii += '&#'+this.chars[i];} 352 | hexChar = this.chars[i].toString(16).toUpperCase(); 353 | if(hexChar.length === 1){htmlHex += '0' + hexChar;} 354 | else{htmlHex += hexChar;} 355 | // check for end of last opened phrase 356 | if(stack.length > 0) 357 | { 358 | node = stack[stack.length - 1]; 359 | if((node.phraseIndex + node.phraseLength - 1) === i) 360 | { 361 | htmlHex += '
'; 362 | htmlAscii += '
'; 363 | stack.pop(); 364 | } 365 | } 366 | 367 | count+=1; 368 | if(count === matchLen) 369 | { 370 | htmlHex += '
'; 371 | htmlAscii += '
'; 372 | count = 0; 373 | } 374 | else 375 | { 376 | if(count%4 === 0){htmlHex += ' ';} 377 | } 378 | 379 | } 380 | if(stack.length > 0) 381 | { 382 | apgAssert(stack.length === 1, 'displayPhrasesHex: stack length: '+stack.length); 383 | html += ''; 384 | stack.pop(); 385 | } 386 | html += '
'+htmlHex+''+htmlAscii+'
'; 387 | return html; 388 | }; 389 | 390 | // helper function for dump() of the AST 391 | /** 392 | * @private 393 | */ 394 | function printLine(indent, up, name, phraseIndex, phraseLength, chars) 395 | { 396 | var out = ""; 397 | var i = 0; 398 | for(; i < indent; i+=1) 399 | { 400 | out += ' '; 401 | } 402 | if(up){out += '↑';} 403 | else{out += '↓';} 404 | out += name+': ['+phraseIndex+']['+phraseLength+']'; 405 | out += '
'; 406 | return out; 407 | } 408 | 409 | // mostly for debugging 410 | // gives a quick HTML formatted dump of the entire AST 411 | /** 412 | * Gives a quick HTML-formatted dump of the entire AST. Just a debugging aid. 413 | * @param {object} the rules from the parser's opcodes object 414 | * @param {array} chars - the array of input string character codes 415 | */ 416 | this.dump = function(rules, chars) 417 | { 418 | var i, indent, downIndex, upIndex, ruleIndex, name, index, count; 419 | var html = ''; 420 | 421 | html += 'AST dump:'; 422 | html += '
'; 423 | indent = 0; 424 | i = 0; 425 | for(; i < this.ast.length; i+=1) 426 | { 427 | if(this.ast[i].down) 428 | { 429 | downIndex = i; 430 | upIndex = this.ast[downIndex].upIndex; 431 | ruleIndex = this.ast[downIndex].ruleIndex; 432 | name = rules[ruleIndex].rule; 433 | index = this.ast[upIndex].phraseIndex; 434 | count = this.ast[upIndex].phraseLength; 435 | html += printLine(indent, false, name, index, count, chars); 436 | indent+=1; 437 | } 438 | else 439 | { 440 | indent-=1; 441 | upIndex = i; 442 | downIndex = this.ast[upIndex].downIndex; 443 | ruleIndex = this.ast[downIndex].ruleIndex; 444 | name = rules[ruleIndex].rule; 445 | index = this.ast[upIndex].phraseIndex; 446 | count = this.ast[upIndex].phraseLength; 447 | html += printLine(indent, true, name, index, count, chars); 448 | } 449 | } 450 | return html; 451 | }; 452 | } 453 | -------------------------------------------------------------------------------- /grammars/apg/apgStats.js: -------------------------------------------------------------------------------- 1 | /* 2 | JavaScript APG Runtime Library, Version 1.0 3 | 4 | APG - an ABNF Parser Generator 5 | Copyright (C) 2009 Lowell D. Thomas, all rights reserved 6 | 7 | author: Lowell D. Thomas 8 | email: lowell@coasttocoastresearch.com 9 | website: http://www.coasttocoastresearch.com 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 2 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see 23 | 24 | or write to the Free Software Foundation, Inc., 25 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 26 | */ 27 | "use strict"; 28 | /** 29 | * @class 30 | * Collects node count and other statistics during parsing. 31 | * @version 1.0 32 | * @copyright Copyright © 2009 Lowell D. Thomas, all rights reserved 33 | * @license [GNU General Public License]{@link http://www.gnu.org/licenses/licenses.html} 34 | * Version 2 or higher. 35 | * @constructor 36 | * @param {Array} rules - the APG-generated rule list 37 | */ 38 | function Stats(rules) 39 | { 40 | /** 41 | * clears the object, makes it ready to start collecting 42 | */ 43 | this.clear = function() 44 | { 45 | this.statsALT.match = 0; 46 | this.statsALT.nomatch = 0; 47 | this.statsALT.empty = 0; 48 | this.statsALT.backtrack = 0; 49 | this.statsCAT.match = 0; 50 | this.statsCAT.nomatch = 0; 51 | this.statsCAT.empty = 0; 52 | this.statsREP.match = 0; 53 | this.statsREP.nomatch = 0; 54 | this.statsREP.empty = 0; 55 | this.statsREP.backtrack = 0; 56 | this.statsPRD.match = 0; 57 | this.statsPRD.nomatch = 0; 58 | this.statsPRD.empty = 0; 59 | this.statsPRD.backtrack = 0; 60 | this.statsRNM.match = 0; 61 | this.statsRNM.nomatch = 0; 62 | this.statsRNM.empty = 0; 63 | this.statsTRG.match = 0; 64 | this.statsTRG.nomatch = 0; 65 | this.statsTRG.empty = 0; 66 | this.statsTLS.match = 0; 67 | this.statsTLS.nomatch = 0; 68 | this.statsTLS.empty = 0; 69 | this.statsTBS.match = 0; 70 | this.statsTBS.nomatch = 0; 71 | this.statsTBS.empty = 0; 72 | if(this.rules) 73 | { 74 | for(var i = 0; i < this.rules.length; i+=1) 75 | { 76 | this.statsRules[i].match = 0; 77 | this.statsRules[i].nomatch = 0; 78 | this.statsRules[i].empty = 0; 79 | this.statsRules[i].rule = this.rules[i].rule; 80 | } 81 | } 82 | }; 83 | 84 | /** 85 | * The primary interface with the parser. Increments node counts for the various parsing states. 86 | * @private 87 | * @param {Object} op - the opcode for this node 88 | * @param state - the final state after the parser has processed this node. 89 | */ 90 | this.collect = function(op, state) 91 | { 92 | var whichState = ''; 93 | switch(state[OP_STATE]) 94 | { 95 | case APG_MATCH: 96 | whichState = 'match'; 97 | break; 98 | 99 | case APG_NOMATCH: 100 | whichState = 'nomatch'; 101 | break; 102 | 103 | case APG_EMPTY: 104 | whichState = 'empty'; 105 | break; 106 | 107 | default: 108 | throw ['Trace.collect: invalid state: ' + state[OP_STATE]]; 109 | 110 | } 111 | switch(op.type) 112 | { 113 | case ALT: 114 | this.statsALT[whichState]+=1; 115 | break; 116 | 117 | case CAT: 118 | this.statsCAT[whichState]+=1; 119 | break; 120 | 121 | case REP: 122 | this.statsREP[whichState]+=1; 123 | break; 124 | 125 | case PRD: 126 | this.statsPRD[whichState]+=1; 127 | break; 128 | 129 | case RNM: 130 | this.statsRNM[whichState]+=1; 131 | if(this.statsRules){this.statsRules[op.ruleIndex][whichState]+=1;} 132 | break; 133 | 134 | case TRG: 135 | this.statsTRG[whichState]+=1; 136 | break; 137 | 138 | case TLS: 139 | this.statsTLS[whichState]+=1; 140 | break; 141 | 142 | case TBS: 143 | this.statsTBS[whichState]+=1; 144 | break; 145 | 146 | default: 147 | throw ['Trace.collect: invalid opcode type: ' + op.type]; 148 | } 149 | }; 150 | 151 | // records back tracking statistics after ALT, REP or PRD back tracks 152 | this.backtrack = function(op) 153 | { 154 | switch(op.type) 155 | { 156 | case ALT: 157 | this.statsALT.backtrack+=1; 158 | break; 159 | 160 | case REP: 161 | this.statsREP.backtrack+=1; 162 | break; 163 | 164 | case PRD: 165 | this.statsPRD.backtrack+=1; 166 | break; 167 | 168 | default: 169 | throw ['Trace.backtrack: invalid opcode type: ' + op.type]; 170 | } 171 | }; 172 | 173 | // helper function for sorting the display of stats 174 | function compareCount(lhs, rhs) 175 | { 176 | var totalLhs, totalRhs; 177 | totalLhs = lhs.match + lhs.empty + lhs.nomatch; 178 | totalRhs = rhs.match + rhs.empty + rhs.nomatch; 179 | if(totalLhs < totalRhs){return 1;} 180 | if(totalLhs > totalRhs){return -1;} 181 | return 0; 182 | } 183 | 184 | // helper function for sorting the display of stats 185 | function compareName(lhs, rhs) 186 | { 187 | var nameLhs, nameRhs, lenLhs, lenRhs, len, i; 188 | nameLhs = lhs.rule.toLowerCase(); 189 | nameRhs = rhs.rule.toLowerCase(); 190 | lenLhs = nameLhs.length; 191 | lenRhs = nameRhs.length; 192 | len = (lenLhs < lenRhs) ? lenLhs : lenRhs; 193 | for(i = 0; i < len; i+=1) 194 | { 195 | if(nameLhs[i] < nameRhs[i]){return -1;} 196 | if(nameLhs[i] > nameRhs[i]){return 1;} 197 | } 198 | if(lenLhs < lenRhs){return -1;} 199 | if(lenLhs > lenRhs){return 1;} 200 | return 0; 201 | } 202 | 203 | // returns HTML tablular display of the statistics 204 | // caption - option table caption 205 | /** 206 | * Returns an HTML table of the statistics. 207 | * @param {string} caption - optional table caption 208 | * @returns {string} the HTML 209 | */ 210 | this.display = function(caption) 211 | { 212 | var i, tot, total, html = ''; 213 | total = []; 214 | total.match = this.statsALT.match + 215 | this.statsCAT.match + 216 | this.statsREP.match + 217 | this.statsPRD.match + 218 | this.statsRNM.match + 219 | this.statsTRG.match + 220 | this.statsTBS.match + 221 | this.statsTLS.match; 222 | total.nomatch = this.statsALT.nomatch + 223 | this.statsCAT.nomatch + 224 | this.statsREP.nomatch + 225 | this.statsPRD.nomatch + 226 | this.statsRNM.nomatch + 227 | this.statsTRG.nomatch + 228 | this.statsTBS.nomatch + 229 | this.statsTLS.nomatch; 230 | total.empty = this.statsALT.empty + 231 | this.statsCAT.empty + 232 | this.statsREP.empty + 233 | this.statsPRD.empty + 234 | this.statsRNM.empty + 235 | this.statsTRG.empty + 236 | this.statsTBS.empty + 237 | this.statsTLS.empty; 238 | total.backtrack = this.statsALT.backtrack + 239 | this.statsREP.backtrack + 240 | this.statsPRD.backtrack; 241 | 242 | if(this.statsRules) 243 | { 244 | this.statsRules.sort(compareName); 245 | this.statsRules.sort(compareCount); 246 | } 247 | 248 | html += ''; 249 | if(typeof(caption) === 'string') 250 | { 251 | html += ''; 252 | } 253 | 254 | html += ''; 255 | html += ''; 258 | html += ''; 261 | html += ''; 264 | html += ''; 267 | html += ''; 270 | html += ''; 273 | html += ''; 274 | 275 | html += ''; 276 | html += ''; 279 | html += ''; 282 | html += ''; 285 | html += ''; 288 | html += ''; 291 | html += ''; 294 | html += ''; 295 | 296 | html += ''; 297 | html += ''; 300 | html += ''; 303 | html += ''; 306 | html += ''; 309 | html += ''; 312 | html += ''; 315 | html += ''; 316 | 317 | html += ''; 318 | html += ''; 321 | html += ''; 324 | html += ''; 327 | html += ''; 330 | html += ''; 333 | html += ''; 336 | html += ''; 337 | 338 | html += ''; 339 | html += ''; 342 | html += ''; 345 | html += ''; 348 | html += ''; 351 | html += ''; 354 | html += ''; 357 | html += ''; 358 | 359 | html += ''; 360 | html += ''; 363 | html += ''; 366 | html += ''; 369 | html += ''; 372 | html += ''; 375 | html += ''; 378 | html += ''; 379 | 380 | html += ''; 381 | html += ''; 384 | html += ''; 387 | html += ''; 390 | html += ''; 393 | html += ''; 396 | html += ''; 399 | html += ''; 400 | 401 | html += ''; 402 | html += ''; 405 | html += ''; 408 | html += ''; 411 | html += ''; 414 | html += ''; 417 | html += ''; 420 | html += ''; 421 | 422 | html += ''; 423 | html += ''; 426 | html += ''; 429 | html += ''; 432 | html += ''; 435 | html += ''; 438 | html += ''; 441 | html += ''; 442 | 443 | html += ''; 444 | html += ''; 447 | html += ''; 450 | html += ''; 453 | html += ''; 456 | html += ''; 459 | html += ''; 462 | html += ''; 463 | 464 | html += ''; 465 | html += ''; 467 | html += ''; 470 | html += ''; 471 | 472 | if(this.statsRules) 473 | { 474 | for(i = 0; i < this.rules.length; i+=1) 475 | { 476 | tot = this.statsRules[i].match + this.statsRules[i].nomatch + this.statsRules[i].empty; 477 | if(tot > 0) 478 | { 479 | html += ''; 480 | html += ''; 482 | html += ''; 485 | html += ''; 488 | html += ''; 491 | html += ''; 494 | html += ''; 497 | html += ''; 498 | } 499 | } 500 | } 501 | 502 | html += '
'+caption+'
'; 256 | html += ''; 257 | html += ''; 259 | html += 'MATCH'; 260 | html += ''; 262 | html += 'NOMATCH'; 263 | html += ''; 265 | html += 'EMPTY'; 266 | html += ''; 268 | html += 'ALL'; 269 | html += ''; 271 | html += 'BACKTRACKS'; 272 | html += '
'; 277 | html += 'ALT'; 278 | html += ''; 280 | html += this.statsALT.match; 281 | html += ''; 283 | html += this.statsALT.nomatch; 284 | html += ''; 286 | html += this.statsALT.empty; 287 | html += ''; 289 | html += (this.statsALT.match + this.statsALT.nomatch + this.statsALT.empty); 290 | html += ''; 292 | html += this.statsALT.backtrack; 293 | html += '
'; 298 | html += 'CAT'; 299 | html += ''; 301 | html += this.statsCAT.match; 302 | html += ''; 304 | html += this.statsCAT.nomatch; 305 | html += ''; 307 | html += this.statsCAT.empty; 308 | html += ''; 310 | html += (this.statsCAT.match + this.statsCAT.nomatch + this.statsCAT.empty); 311 | html += ''; 313 | html += ''; 314 | html += '
'; 319 | html += 'REP'; 320 | html += ''; 322 | html += this.statsREP.match; 323 | html += ''; 325 | html += this.statsREP.nomatch; 326 | html += ''; 328 | html += this.statsREP.empty; 329 | html += ''; 331 | html += (this.statsREP.match + this.statsREP.nomatch + this.statsREP.empty); 332 | html += ''; 334 | html += this.statsREP.backtrack; 335 | html += '
'; 340 | html += 'PRD'; 341 | html += ''; 343 | html += this.statsPRD.match; 344 | html += ''; 346 | html += this.statsPRD.nomatch; 347 | html += ''; 349 | html += this.statsPRD.empty; 350 | html += ''; 352 | html += (this.statsPRD.match + this.statsPRD.nomatch + this.statsPRD.empty); 353 | html += ''; 355 | html += this.statsPRD.backtrack; 356 | html += '
'; 361 | html += 'RNM'; 362 | html += ''; 364 | html += this.statsRNM.match; 365 | html += ''; 367 | html += this.statsRNM.nomatch; 368 | html += ''; 370 | html += this.statsRNM.empty; 371 | html += ''; 373 | html += (this.statsRNM.match + this.statsRNM.nomatch + this.statsRNM.empty); 374 | html += ''; 376 | html += ''; 377 | html += '
'; 382 | html += 'TRG'; 383 | html += ''; 385 | html += this.statsTRG.match; 386 | html += ''; 388 | html += this.statsTRG.nomatch; 389 | html += ''; 391 | html += this.statsTRG.empty; 392 | html += ''; 394 | html += (this.statsTRG.match + this.statsTRG.nomatch + this.statsTRG.empty); 395 | html += ''; 397 | html += ''; 398 | html += '
'; 403 | html += 'TBS'; 404 | html += ''; 406 | html += this.statsTBS.match; 407 | html += ''; 409 | html += this.statsTBS.nomatch; 410 | html += ''; 412 | html += this.statsTBS.empty; 413 | html += ''; 415 | html += (this.statsTBS.match + this.statsTBS.nomatch + this.statsTBS.empty); 416 | html += ''; 418 | html += ''; 419 | html += '
'; 424 | html += 'TLS'; 425 | html += ''; 427 | html += this.statsTLS.match; 428 | html += ''; 430 | html += this.statsTLS.nomatch; 431 | html += ''; 433 | html += this.statsTLS.empty; 434 | html += ''; 436 | html += (this.statsTLS.match + this.statsTLS.nomatch + this.statsTLS.empty); 437 | html += ''; 439 | html += ''; 440 | html += '
'; 445 | html += 'TOTAL'; 446 | html += ''; 448 | html += total.match; 449 | html += ''; 451 | html += total.nomatch; 452 | html += ''; 454 | html += total.empty; 455 | html += ''; 457 | html += (total.match + total.nomatch + total.empty); 458 | html += ''; 460 | html += total.backtrack; 461 | html += '
'; 466 | html += ''; 468 | html += 'RULE NAME'; 469 | html += '
'; 481 | html += ''; 483 | html += this.statsRules[i].match; 484 | html += ''; 486 | html += this.statsRules[i].nomatch; 487 | html += ''; 489 | html += this.statsRules[i].empty; 490 | html += ''; 492 | html += tot; 493 | html += ''; 495 | html += this.statsRules[i].rule; 496 | html += '
'; 503 | return html; 504 | }; 505 | 506 | this.statsALT = []; 507 | this.statsCAT = []; 508 | this.statsREP = []; 509 | this.statsPRD = []; 510 | this.statsRNM = []; 511 | this.statsTRG = []; 512 | this.statsTLS = []; 513 | this.statsTBS = []; 514 | this.statsRules = null; 515 | this.rules = null; 516 | if(isArray(rules)) 517 | { 518 | this.rules = rules; 519 | this.statsRules = []; 520 | for(var i = 0; i < this.rules.length; i+=1) 521 | { 522 | this.statsRules[i] = []; 523 | } 524 | } 525 | this.clear(); 526 | } 527 | -------------------------------------------------------------------------------- /lib/apiModelUtility.js: -------------------------------------------------------------------------------- 1 | var snomedLib = require("./snomed"); 2 | 3 | module.exports.convertToV1 = function(concept) { 4 | if (typeof concept.module == "string") { 5 | var moduleId = concept.module + ""; 6 | concept.module = { 7 | conceptId: moduleId, 8 | preferredTerm: modules[moduleId] 9 | }; 10 | } 11 | if (typeof concept.definitionStatus == "string") { 12 | if (concept.definitionStatus == "Primitive") { 13 | concept.definitionStatus = { 14 | conceptId: "900000000000074008", 15 | preferredTerm: "Primitive" 16 | } 17 | } else { 18 | concept.definitionStatus = { 19 | conceptId: "900000000000073002", 20 | preferredTerm: "Defined" 21 | } 22 | } 23 | } 24 | }; 25 | 26 | var modules = {}; // Load this modules cache on startup 27 | 28 | module.exports.loadModules = function(databaseName, collectionName) { 29 | snomedLib.loadModules(databaseName, collectionName, function(dbModules) { 30 | modules = dbModules; 31 | console.log("Modules cache initialized"); 32 | }); 33 | }; -------------------------------------------------------------------------------- /lib/snomed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tbertonatti on 11/1/16. 3 | */ 4 | var MongoClient = require('mongodb').MongoClient; 5 | var databases = {}; 6 | var util = require('util'); 7 | 8 | var mongoConnection = process.env['MONGO_DB_CONN'] || "localhost:27017"; 9 | 10 | var performMongoDbRequest = function(databaseName, callback) { 11 | if (databases[databaseName]) { 12 | callback(databases[databaseName]); 13 | } else { 14 | //console.log("Connecting"); 15 | MongoClient.connect("mongodb://" + mongoConnection + "/" + databaseName, function(err, db) { 16 | if (err) { 17 | console.warn(err.message); 18 | process.exit(); 19 | } 20 | //console.log("Connection OK") 21 | databases[databaseName] = db; 22 | callback(db); 23 | }); 24 | } 25 | }; 26 | var getObject = function(dbP, collectionP, query, options, callback) { 27 | console.log("DB: " + JSON.stringify(dbP)); 28 | console.log("Collection: " + JSON.stringify(collectionP)); 29 | console.log("Query: " + JSON.stringify(query)); 30 | performMongoDbRequest(dbP, function(db) { 31 | var collection = db.collection(collectionP); 32 | console.log("Collection: " + util.inspect(collection)); 33 | //var query = { '$or': [ {'conceptId': conceptId }, {'conceptId': parseInt(conceptId) } ]}; 34 | //collection.find(query, options).nextObject(function(err, doc) { 35 | collection.find(query, options, function(err, cursor) { 36 | if (err) { 37 | callback(err.message); 38 | } else { 39 | cursor.toArray(function(err, docs) { 40 | if (err) { 41 | callback(err.message); 42 | } else if (docs) { 43 | callback(false, docs); 44 | } else { 45 | callback("Docs not found for = " + JSON.stringify(query)); 46 | } 47 | }); 48 | } 49 | }); 50 | }); 51 | }; 52 | 53 | var getConcept = function(dbP, collectionP, conceptId, options, callback) { 54 | getObject(dbP, collectionP, { '$or': [{ 'conceptId': conceptId }, { 'conceptId': parseInt(conceptId) }] }, options, function(err, docs) { 55 | if (err) callback(err); 56 | else if (docs) callback(false, docs[0]); 57 | }); 58 | }; 59 | 60 | var getDescriptions = function(dbP, collectionP, conceptId, descriptionId, options, callback) { 61 | getConcept(dbP, collectionP, conceptId, options, function(err, doc) { 62 | if (err) callback(err); 63 | else if (doc) { 64 | var result = []; 65 | doc.descriptions.forEach(function(desc) { 66 | if (descriptionId) { 67 | if (parseInt(descriptionId) == desc.descriptionId || descriptionId == desc.descriptionId) { 68 | result.push(desc); 69 | } 70 | } else { 71 | result.push(desc); 72 | } 73 | }); 74 | callback(false, result); 75 | } 76 | }); 77 | }; 78 | 79 | var getRelationShips = function(dbP, collectionP, conceptId, form, options, callback) { 80 | getConcept(dbP, collectionP, conceptId, options, function(err, doc) { 81 | if (err) callback(err); 82 | else if (doc) { 83 | var result = []; 84 | if (form == "all" || form == "inferred") { 85 | doc.relationships.forEach(function(desc) { 86 | result.push(desc); 87 | }); 88 | } 89 | if (form == "all" || form == "stated") { 90 | doc.statedRelationships.forEach(function(desc) { 91 | result.push(desc); 92 | }); 93 | } 94 | callback(false, result); 95 | } 96 | }); 97 | }; 98 | 99 | var getParents = function(dbP, collectionP, conceptId, form, options, callback) { 100 | getConcept(dbP, collectionP, conceptId, options, function(err, doc) { 101 | if (err) callback(err); 102 | else if (doc) { 103 | var result = []; 104 | if (typeof doc.relationships != 'undefined') { 105 | if (form) { 106 | if (form == "inferred" && doc.relationships) { 107 | doc.relationships.forEach(function(rel) { 108 | if (rel.active == true && (rel.type.conceptId == 116680003 || rel.type.conceptId == "116680003")) { 109 | result.push({ conceptId: rel.target.conceptId, defaultTerm: rel.target.defaultTerm, definitionStatus: rel.target.definitionStatus, module: rel.target.module, statedDescendants: rel.target.statedDescendants, inferredDescendants: rel.target.inferredDescendants }); 110 | } 111 | }); 112 | } else if (form == "stated" && doc.statedRelationships) { 113 | doc.statedRelationships.forEach(function(rel) { 114 | if (rel.active == true && (rel.type.conceptId == 116680003 || rel.type.conceptId == "116680003")) { 115 | result.push({ conceptId: rel.target.conceptId, defaultTerm: rel.target.defaultTerm, definitionStatus: rel.target.definitionStatus, module: rel.target.module, statedDescendants: rel.target.statedDescendants, inferredDescendants: rel.target.inferredDescendants }); 116 | } 117 | }); 118 | } 119 | } else if (doc.relationships) { 120 | doc.relationships.forEach(function(rel) { 121 | if (rel.active == true && (rel.type.conceptId == 116680003 || rel.type.conceptId == "116680003")) { 122 | result.push({ conceptId: rel.target.conceptId, defaultTerm: rel.target.defaultTerm, definitionStatus: rel.target.definitionStatus, module: rel.target.module }); 123 | } 124 | }); 125 | } 126 | } 127 | callback(false, result); 128 | } 129 | }); 130 | }; 131 | 132 | var getMembers = function(dbP, collectionP, conceptId, options, callback) { 133 | var query = { "memberships": { "$elemMatch": { "refset.conceptId": conceptId, "active": true } } }; 134 | if (options.filter) { 135 | var searchTerm = "\\b" + regExpEscape(options.filter).toLowerCase(); 136 | query.defaultTerm = { "$regex": searchTerm, "$options": "i" }; 137 | } 138 | if (options.activeOnly == "true") { 139 | query.active = "true"; 140 | } 141 | //console.log(JSON.stringify(query)); 142 | var getTotalOf = function(refsetId, callback) { 143 | getObject("server", "resources", { "databaseName": dbP, "collectionName": collectionP.replace("v", "") }, { refsets: 1 }, function(err, docs) { 144 | if (err) { 145 | callback(err); 146 | } else { 147 | var total = 0, 148 | error = "No refset matching in the manifest"; 149 | docs[0].refsets.forEach(function(refset) { 150 | if (refset.conceptId == refsetId) { 151 | error = false; 152 | total = refset.count; 153 | } 154 | }); 155 | callback(error, total); 156 | } 157 | }); 158 | }; 159 | getTotalOf(conceptId, function(err, totalR) { 160 | var total = totalR; 161 | if (err) total = err; 162 | getObject(dbP, collectionP, query, options, function(err, docs) { 163 | if (err) callback(err); 164 | else { 165 | var result = {}; 166 | result.members = []; 167 | result.details = { 'total': total, 'refsetId': conceptId }; 168 | if (docs && docs.length > 0) 169 | result.members = docs; 170 | callback(false, result); 171 | } 172 | }); 173 | }); 174 | }; 175 | 176 | var searchDescription = function(dbP, collectionP, filters, query, options, callback) { 177 | console.log("Search", JSON.stringify(query)); 178 | var start = Date.now(); 179 | var processMatches = function(docs) { 180 | var dbDuration = Date.now() - start; 181 | var result = {}; 182 | result.matches = []; 183 | result.details = { 'total': 0, 'skipTo': filters.skipTo, 'returnLimit': filters.returnLimit }; 184 | result.filters = {}; 185 | result.filters.lang = {}; 186 | result.filters.semTag = {}; 187 | result.filters.module = {}; 188 | result.filters.refsetId = {}; 189 | if (docs && docs.length > 0) { 190 | result.details = { 'total': docs.length, 'skipTo': filters.skipTo, 'returnLimit': filters.returnLimit }; 191 | if (filters.idParamStr == docs[0].descriptionId) { 192 | result.matches.push({ "term": docs[0].term, "conceptId": docs[0].conceptId, "active": docs[0].active, "conceptActive": docs[0].conceptActive, "fsn": docs[0].fsn, "module": docs[0].module }); 193 | callback(false, result); 194 | } else { 195 | var matchedDescriptions = docs.slice(0); 196 | if (filters.searchMode == "regex" || filters.searchMode == "partialMatching") { 197 | matchedDescriptions.sort(function(a, b) { 198 | if (a.term.length < b.term.length) 199 | return -1; 200 | if (a.term.length > b.term.length) 201 | return 1; 202 | return 0; 203 | }); 204 | } 205 | var count = 0; 206 | var conceptIds = []; 207 | matchedDescriptions.forEach(function(doc) { 208 | var refsetOk = false; 209 | if (doc.refsetIds) { 210 | doc.refsetIds.forEach(function(refset) { 211 | if (refset == filters.refsetFilter) { 212 | refsetOk = true; 213 | } 214 | }); 215 | } 216 | if (filters.semanticFilter == "none" || (filters.semanticFilter == doc.semanticTag)) { 217 | if (filters.langFilter == "none" || (filters.langFilter == doc.lang)) { 218 | if (filters.moduleFilter == "none" || (filters.moduleFilter == doc.module)) { 219 | if (filters.refsetFilter == "none" || refsetOk) { 220 | if (!filters["groupByConcept"] || conceptIds.indexOf(doc.conceptId) == -1) { 221 | conceptIds.push(doc.conceptId); 222 | 223 | if (count >= filters.skipTo && count < (filters.skipTo + filters.returnLimit)) { 224 | result.matches.push({ "term": doc.term, "conceptId": doc.conceptId, "active": doc.active, "conceptActive": doc.conceptActive, "fsn": doc.fsn, "module": doc.module, "definitionStatus": doc.definitionStatus }); 225 | } 226 | if (result.filters.semTag.hasOwnProperty(doc.semanticTag)) { 227 | result.filters.semTag[doc.semanticTag] = result.filters.semTag[doc.semanticTag] + 1; 228 | } else { 229 | result.filters.semTag[doc.semanticTag] = 1; 230 | } 231 | if (result.filters.lang.hasOwnProperty(doc.lang)) { 232 | result.filters.lang[doc.lang] = result.filters.lang[doc.lang] + 1; 233 | } else { 234 | result.filters.lang[doc.lang] = 1; 235 | } 236 | if (result.filters.module.hasOwnProperty(doc.module)) { 237 | result.filters.module[doc.module] = result.filters.module[doc.module] + 1; 238 | } else { 239 | result.filters.module[doc.module] = 1; 240 | } 241 | if (doc.refsetIds) { 242 | doc.refsetIds.forEach(function(refset) { 243 | if (result.filters.refsetId.hasOwnProperty(refset)) { 244 | result.filters.refsetId[refset] = result.filters.refsetId[refset] + 1; 245 | } else { 246 | result.filters.refsetId[refset] = 1; 247 | } 248 | }); 249 | } 250 | count = count + 1; 251 | } 252 | } 253 | } 254 | } 255 | } 256 | }); 257 | result.details.total = count; 258 | callback(false, result); 259 | } 260 | } else { 261 | var duration = Date.now() - start; 262 | result.matches = []; 263 | result.details = { 'total': 0, 'skipTo': filters.skipTo, 'returnLimit': filters.returnLimit }; 264 | callback(false, result); 265 | } 266 | }; 267 | if (filters.searchMode == "regex" || filters.searchMode == "partialMatching") { 268 | console.log("Entering part match search"); 269 | getObject(dbP, collectionP + "tx", query, options, function(err, docs) { 270 | console.log("Result: ", docs.length); 271 | processMatches(docs); 272 | }); 273 | } else { 274 | performMongoDbRequest(dbP, function(db) { 275 | var collection = db.collection(collectionP + 'tx'); 276 | collection.find(query, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" }, length: 1 }, function(err, cursor) { 277 | if (err) processMatches([]); 278 | else { 279 | cursor.toArray(function(err, docs) { 280 | processMatches(docs); 281 | }); 282 | } 283 | }); 284 | }); 285 | } 286 | }; 287 | 288 | var getTime = function() { 289 | var currentdate = new Date(); 290 | var datetime = "Last Sync: " + currentdate.getDate() + "/" + 291 | (currentdate.getMonth() + 1) + "/" + 292 | currentdate.getFullYear() + " @ " + 293 | currentdate.getHours() + ":" + 294 | currentdate.getMinutes() + ":" + 295 | currentdate.getSeconds(); 296 | return datetime; 297 | }; 298 | 299 | module.exports.getObject = getObject; 300 | module.exports.getConcept = getConcept; 301 | module.exports.getDescriptions = getDescriptions; 302 | module.exports.getRelationShips = getRelationShips; 303 | module.exports.getParents = getParents; 304 | module.exports.getMembers = getMembers; 305 | module.exports.searchDescription = searchDescription; 306 | 307 | var regExpEscape = function(s) { 308 | return String(s).replace(/([-()\[\]{}+?*.$\^|,:# 0) { 59 | if (!docs[0].v || docs[0].v != "2") { 60 | callback("The db isnot version 2. It must be created with the new conversion module."); 61 | 62 | } else { 63 | if (v1) { 64 | getDefaultTermType(dbP, collectionP, function(err, defTermType) { 65 | if (err) callback(err); 66 | else { 67 | var result = transform.getV1Concept(docs[0], defTermType); 68 | callback(false, result); 69 | } 70 | }); 71 | } else { 72 | callback(false, docs[0]); 73 | } 74 | } 75 | } else { 76 | callback(true, "There are no data for this conceptId:" + conceptId); 77 | } 78 | }); 79 | }; 80 | 81 | var getDescriptions = function(dbP, collectionP, conceptId, descriptionId, options, callback) { 82 | 83 | var v1; 84 | if (options.v1) { 85 | v1 = true; 86 | delete options.v1; 87 | } 88 | getConcept(dbP, collectionP, conceptId, options, function(err, doc) { 89 | if (err) callback(err); 90 | else if (doc) { 91 | var result = []; 92 | if (!doc.v || doc.v != "2") { 93 | callback("The db isnot version 2. It must be created with the new conversion module."); 94 | 95 | } else { 96 | if (doc.descriptions) { 97 | doc.descriptions.forEach(function(desc) { 98 | if (descriptionId) { 99 | if (descriptionId == desc.descriptionId) { 100 | result.push(desc); 101 | } 102 | } else { 103 | result.push(desc); 104 | } 105 | }); 106 | if (v1) { 107 | result = transform.getV1Descriptions(result); 108 | } 109 | } 110 | callback(false, result); 111 | } 112 | } 113 | }); 114 | }; 115 | 116 | var getRelationShips = function(dbP, collectionP, conceptId, form, options, callback) { 117 | 118 | var v1; 119 | if (options.v1) { 120 | v1 = true; 121 | delete options.v1; 122 | } 123 | getConcept(dbP, collectionP, conceptId, options, function(err, doc) { 124 | if (err) callback(err); 125 | else if (doc) { 126 | var result = []; 127 | if (!doc.v || doc.v != "2") { 128 | callback("The db isnot version 2. It must be created with the new conversion module."); 129 | 130 | } else { 131 | if (doc.relationships) { 132 | doc.relationships.forEach(function(desc) { 133 | if (form == "all") { 134 | result.push(desc); 135 | } else if (form == "inferred" && desc.characteristicType.conceptId == "900000000000011006") { 136 | result.push(desc); 137 | } else if (form == "stated" && desc.characteristicType.conceptId == "900000000000010007") { 138 | result.push(desc); 139 | } else if (form == "additional" && desc.characteristicType.conceptId == "900000000000227009") { 140 | result.push(desc); 141 | } 142 | }); 143 | if (v1) { 144 | 145 | getDefaultTermType(dbP, collectionP, function(err, defTermType) { 146 | if (err) callback(err); 147 | else { 148 | result = transform.getV1Relationships(result, defTermType); 149 | callback(false, result); 150 | } 151 | }); 152 | } else { 153 | callback(false, result); 154 | } 155 | } else { 156 | callback(false, result); 157 | } 158 | } 159 | } 160 | }); 161 | }; 162 | 163 | var getParents = function(dbP, collectionP, conceptId, form, options, callback) { 164 | 165 | var v1; 166 | if (options.v1) { 167 | v1 = true; 168 | delete options.v1; 169 | } 170 | getConcept(dbP, collectionP, conceptId, options, function(err, doc) { 171 | if (err) callback(err); 172 | else if (doc) { 173 | var result = []; 174 | 175 | if (!doc.v || doc.v != "2") { 176 | callback("The db isnot version 2. It must be created with the new conversion module."); 177 | 178 | } else { 179 | if (typeof doc.relationships != 'undefined') { 180 | if (form) { 181 | if (form == "inferred" && doc.relationships) { 182 | doc.relationships.forEach(function(rel) { 183 | if (rel.characteristicType.conceptId == "900000000000011006" && rel.active == true && rel.type.conceptId == "116680003") { 184 | result.push({ 185 | conceptId: rel.destination.conceptId, 186 | preferredTerm: rel.destination.preferredTerm, 187 | fullySpecifiedName: rel.destination.fullySpecifiedName, 188 | definitionStatus: rel.destination.definitionStatus, 189 | module: rel.destination.module, 190 | statedDescendants: rel.destination.statedDescendants, 191 | inferredDescendants: rel.destination.inferredDescendants 192 | }); 193 | } 194 | }); 195 | } else if (form == "stated" && doc.relationships) { 196 | doc.relationships.forEach(function(rel) { 197 | if (rel.characteristicType.conceptId == "900000000000010007" && rel.active == true && rel.type.conceptId == "116680003") { 198 | result.push({ 199 | conceptId: rel.destination.conceptId, 200 | preferredTerm: rel.destination.preferredTerm, 201 | fullySpecifiedName: rel.destination.fullySpecifiedName, 202 | definitionStatus: rel.destination.definitionStatus, 203 | module: rel.destination.module, 204 | statedDescendants: rel.destination.statedDescendants, 205 | inferredDescendants: rel.destination.inferredDescendants 206 | }); 207 | } 208 | }); 209 | } 210 | } else if (doc.relationships) { 211 | doc.relationships.forEach(function(rel) { 212 | if (rel.characteristicType.conceptId == "900000000000011006" && rel.active == true && rel.type.conceptId == "116680003") { 213 | result.push({ 214 | conceptId: rel.destination.conceptId, 215 | preferredTerm: rel.destination.preferredTerm, 216 | fullySpecifiedName: rel.destination.fullySpecifiedName, 217 | definitionStatus: rel.destination.definitionStatus, 218 | module: rel.destination.module, 219 | statedDescendants: rel.destination.statedDescendants, 220 | inferredDescendants: rel.destination.inferredDescendants 221 | }); 222 | } 223 | }); 224 | } 225 | if (v1 && result.length > 0) { 226 | 227 | getDefaultTermType(dbP, collectionP, function(err, defTermType) { 228 | if (err) callback(err); 229 | else { 230 | result = transform.getV1ConceptDescriptors(result, defTermType); 231 | callback(false, result); 232 | } 233 | }); 234 | } else { 235 | callback(false, result); 236 | 237 | } 238 | } else { 239 | callback(false, result); 240 | } 241 | } 242 | } 243 | }); 244 | }; 245 | 246 | var getMembers = function(dbP, collectionP, conceptId, options, callback) { 247 | 248 | var v1; 249 | if (options.v1) { 250 | v1 = true; 251 | delete options.v1; 252 | } 253 | var query = { "memberships": { "$elemMatch": { "refset.conceptId": conceptId, "active": true } } }; 254 | if (options.filter) { 255 | var searchTerm = "\\b" + regExpEscape(options.filter).toLowerCase(); 256 | query.preferredTerm = { "$regex": searchTerm, "$options": "i" }; 257 | } 258 | if (options.activeOnly == "true") { 259 | query.active = "true"; 260 | } 261 | //console.log(JSON.stringify(query)); 262 | var getTotalOf = function(refsetId, callback) { 263 | getObject("server", "resources", { "databaseName": dbP, "collectionName": collectionP.replace("v", "") }, { refsets: 1 }, function(err, docs) { 264 | if (err) { 265 | callback(err); 266 | } else { 267 | var total = 0, 268 | error = "No refset matching in the manifest"; 269 | docs[0].refsets.forEach(function(refset) { 270 | if (refset.conceptId == refsetId) { 271 | error = false; 272 | total = refset.count; 273 | } 274 | }); 275 | callback(error, total); 276 | } 277 | }); 278 | }; 279 | getTotalOf(conceptId, function(err, totalR) { 280 | var total = totalR; 281 | if (err) total = err; 282 | getObject(dbP, collectionP, query, options, function(err, docs) { 283 | if (err) callback(err); 284 | else { 285 | var result = {}; 286 | result.members = []; 287 | result.details = { 'total': total, 'refsetId': conceptId }; 288 | if (docs && docs.length > 0) { 289 | if (!docs[0].v || docs[0].v != "2") { 290 | callback("The db isnot version 2. It must be created with the new conversion module."); 291 | 292 | } else { 293 | result.members = docs; 294 | 295 | if (v1) { 296 | 297 | getDefaultTermType(dbP, collectionP, function(err, defTermType) { 298 | if (err) callback(err); 299 | else { 300 | result.members = transform.getV1ConceptDescriptors(result.members, defTermType); 301 | callback(false, result); 302 | } 303 | }); 304 | 305 | } else { 306 | callback(false, result); 307 | } 308 | } 309 | } else { 310 | callback(false, result); 311 | } 312 | } 313 | }); 314 | }); 315 | }; 316 | 317 | var searchDescription = function(dbP, collectionP, filters, query, options, callback) { 318 | console.log("Search", JSON.stringify(query)); 319 | var processMatches = function(docs) { 320 | var result = {}; 321 | result.matches = []; 322 | result.details = { 'total': 0, 'skipTo': filters.skipTo, 'returnLimit': filters.returnLimit }; 323 | result.filters = {}; 324 | result.filters.lang = {}; 325 | result.filters.semTag = {}; 326 | result.filters.module = {}; 327 | result.filters.refsetId = {}; 328 | if (docs && docs.length > 0) { 329 | 330 | result.details = { 'total': docs.length, 'skipTo': filters.skipTo, 'returnLimit': filters.returnLimit }; 331 | if (filters.idParamStr == docs[0].descriptionId) { 332 | result.matches.push({ 333 | "term": docs[0].term, 334 | "conceptId": docs[0].conceptId, 335 | "active": docs[0].active, 336 | "conceptActive": docs[0].conceptActive, 337 | "fsn": docs[0].fsn, 338 | "module": docs[0].stringModule 339 | }); 340 | callback(false, result); 341 | } else { 342 | var matchedDescriptions = docs.slice(0); 343 | if (filters.searchMode == "regex" || filters.searchMode == "partialMatching") { 344 | matchedDescriptions.sort(function(a, b) { 345 | if (a.term.length < b.term.length) 346 | return -1; 347 | if (a.term.length > b.term.length) 348 | return 1; 349 | return 0; 350 | }); 351 | } else { 352 | matchedDescriptions.sort(function(a, b) { 353 | if (a.score > b.score) 354 | return -1; 355 | if (a.score < b.score) 356 | return 1; 357 | return 0; 358 | }); 359 | } 360 | var count = 0; 361 | var conceptIds = []; 362 | matchedDescriptions.forEach(function(doc) { 363 | var refsetOk = false; 364 | if (doc.refsetIds) { 365 | doc.refsetIds.forEach(function(refset) { 366 | if (refset == filters.refsetFilter) { 367 | refsetOk = true; 368 | } 369 | }); 370 | } 371 | if (filters.semanticFilter == "none" || (filters.semanticFilter == doc.semanticTag)) { 372 | if (filters.langFilter == "none" || (filters.langFilter == doc.languageCode)) { 373 | if (filters.moduleFilter == "none" || (filters.moduleFilter == doc.stringModule)) { 374 | if (filters.refsetFilter == "none" || refsetOk) { 375 | if (!filters["groupByConcept"] || conceptIds.indexOf(doc.conceptId) == -1) { 376 | conceptIds.push(doc.conceptId); 377 | 378 | if (count >= filters.skipTo && count < (filters.skipTo + filters.returnLimit)) { 379 | result.matches.push({ 380 | "term": doc.term, 381 | "conceptId": doc.conceptId, 382 | "active": doc.active, 383 | "conceptActive": doc.conceptActive, 384 | "fsn": doc.fsn, 385 | "module": doc.stringModule, 386 | "definitionStatus": doc.definitionStatus 387 | }); 388 | } 389 | if (result.filters.semTag.hasOwnProperty(doc.semanticTag)) { 390 | result.filters.semTag[doc.semanticTag] = result.filters.semTag[doc.semanticTag] + 1; 391 | } else { 392 | result.filters.semTag[doc.semanticTag] = 1; 393 | } 394 | if (result.filters.lang.hasOwnProperty(doc.languageCode)) { 395 | result.filters.lang[doc.languageCode] = result.filters.lang[doc.languageCode] + 1; 396 | } else { 397 | result.filters.lang[doc.languageCode] = 1; 398 | } 399 | if (result.filters.module.hasOwnProperty(doc.stringModule)) { 400 | result.filters.module[doc.stringModule] = result.filters.module[doc.stringModule] + 1; 401 | } else { 402 | result.filters.module[doc.stringModule] = 1; 403 | } 404 | if (doc.refsetIds) { 405 | doc.refsetIds.forEach(function(refset) { 406 | if (result.filters.refsetId.hasOwnProperty(refset)) { 407 | result.filters.refsetId[refset] = result.filters.refsetId[refset] + 1; 408 | } else { 409 | result.filters.refsetId[refset] = 1; 410 | } 411 | }); 412 | } 413 | count = count + 1; 414 | } 415 | } 416 | } 417 | } 418 | } 419 | }); 420 | result.details.total = count; 421 | callback(false, result); 422 | 423 | } 424 | } else { 425 | result.matches = []; 426 | result.details = { 'total': 0, 'skipTo': filters.skipTo, 'returnLimit': filters.returnLimit }; 427 | callback(false, result); 428 | } 429 | }; 430 | if (filters.searchMode == "regex" || filters.searchMode == "partialMatching") { 431 | //console.log("Entering part match search"); 432 | getObject(dbP, collectionP + "tx", query, options, function(err, docs) { 433 | //console.log("Result: ", docs.length); 434 | processMatches(docs); 435 | }); 436 | } else { 437 | getObject(dbP, collectionP + "tx", query, { score: { $meta: "textScore" } }, function(err, docs) { 438 | //console.log("Result: ", docs.length); 439 | processMatches(docs); 440 | }); 441 | } 442 | // else { 443 | // performMongoDbRequest(dbP, function(db) { 444 | // console.log(collectionP); 445 | // var collection = db.collection(collectionP + 'tx'); 446 | // collection.find(query, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" }}, function (err, cursor) { 447 | // if (err) { 448 | // console.log("Text index search error",err); 449 | // processMatches([]); 450 | // } else{ 451 | // cursor.toArray(function(err, docs) { 452 | // console.log("docs found",docs.length); 453 | // processMatches(docs); 454 | // }); 455 | // } 456 | // }); 457 | // }); 458 | // } 459 | }; 460 | 461 | var getTime = function() { 462 | var currentdate = new Date(); 463 | var datetime = "Last Sync: " + currentdate.getDate() + "/" + 464 | (currentdate.getMonth() + 1) + "/" + 465 | currentdate.getFullYear() + " @ " + 466 | currentdate.getHours() + ":" + 467 | currentdate.getMinutes() + ":" + 468 | currentdate.getSeconds(); 469 | return datetime; 470 | }; 471 | 472 | var getDefaultTermType = function(dbP, collectionP, callback) { 473 | 474 | var typeId = "900000000000003001"; 475 | if (dbP == "en-edition") { 476 | //just return the FSN if it is the international edition 477 | callback(null, typeId); 478 | } else if (defaultTermTypes[dbP + "-" + collectionP]) { 479 | typeId = defaultTermTypes[dbP + "-" + collectionP]; 480 | callback(null, typeId); 481 | } else { 482 | var options = {}; 483 | options["fields"] = { "defaultTermType": 1 }; 484 | var query = { databaseName: dbP, collectionName: collectionP.replace("v", "") }; 485 | 486 | getObject("server", "resources", query, options, function(err, docs) { 487 | //console.log("Result: ", docs.length); 488 | if (err) callback(err); 489 | else { 490 | if (docs && docs.length > 0) { 491 | typeId = docs[0].defaultTermType; 492 | } 493 | defaultTermTypes[dbP + "-" + collectionP] = typeId; 494 | callback(null, typeId); 495 | } 496 | }); 497 | } 498 | }; 499 | 500 | module.exports.getObject = getObject; 501 | module.exports.getConcept = getConcept; 502 | module.exports.getDescriptions = getDescriptions; 503 | module.exports.getRelationShips = getRelationShips; 504 | module.exports.getParents = getParents; 505 | module.exports.getMembers = getMembers; 506 | module.exports.searchDescription = searchDescription; 507 | module.exports.getDefaultTermType = getDefaultTermType; 508 | 509 | var regExpEscape = function(s) { 510 | return String(s).replace(/([-()\[\]{}+?*.$\^|,:#= 0.0.0", 20 | "passport-local": ">= 0.0.0", 21 | "path": "^0.12.7", 22 | "pug": "^2.0.0-rc.1", 23 | "serve-favicon": "^2.4.1", 24 | "socket.io": "~1.7.3", 25 | "svg2png": "^4.1.1", 26 | "winston": "^2.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.ihtsdo.sct 6 | sct-snapshot-rest-api 7 | 1.0 8 | 9 | deb 10 | SCT Snapshot REST API 11 | 12 | 13 | ${project.artifactId} 14 | 15 | 16 | 17 | 18 | 19 | external.atlassian.jgitflow 20 | jgitflow-maven-plugin 21 | 1.0-m1.1 22 | 23 | true 24 | true 25 | true 26 | 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-resource-plugin 31 | 2.3 32 | 33 | 34 | exec-maven-plugin 35 | org.codehaus.mojo 36 | 37 | 38 | Install node.js modules 39 | compile 40 | 41 | exec 42 | 43 | 44 | npm 45 | 46 | install 47 | 48 | ${project.build.outputDirectory} 49 | 50 | 51 | 52 | 53 | 54 | jdeb 55 | org.vafer 56 | 1.2 57 | 58 | 59 | ${basedir}/deb/control 60 | true 61 | BUILD_NUMBER 62 | false 63 | all 64 | 65 | 66 | ${project.build.outputDirectory} 67 | directory 68 | 69 | perm 70 | /opt/${project.artifactId} 71 | 72 | 73 | 74 | ${basedir}/deb/supervisor.conf 75 | /etc/supervisor/conf.d/${packageName}.conf 76 | file 77 | true 78 | 79 | 80 | ${basedir}/deb/config.json 81 | /opt/${packageName}/config.json 82 | file 83 | true 84 | 85 | perm 86 | 0644 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | ${basedir} 96 | 97 | pom.xml 98 | target/** 99 | deb/** 100 | 101 | 102 | 103 | 104 | 105 | org.vafer 106 | jdeb 107 | 1.2 108 | 109 | 110 | 111 | 112 | 113 | 114 | ihtsdo-public-nexus 115 | IHTSDO Public Nexus Releases 116 | https://maven.ihtsdotools.org/content/repositories/releases/ 117 | 118 | 119 | ihtsdo-public-nexus 120 | IHTSDO Public Nexus Snapshots 121 | https://maven.ihtsdotools.org/content/repositories/snapshots/ 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /public/svg2pngTemp/readme.md: -------------------------------------------------------------------------------- 1 | Temporary folder for generating pngs and svgs -------------------------------------------------------------------------------- /routes/expressions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by alo on 7/9/15. 3 | */ 4 | var express = require('express'); 5 | var router = express.Router(); 6 | var expressionsParser = require('../grammars/apg/expressionParser'); 7 | var MongoClient = require('mongodb').MongoClient; 8 | var connectTimeout = require('connect-timeout'); 9 | //var winston = require('winston'); 10 | var path = require('path'); 11 | // find the first module to be loaded 12 | var topModule = module; 13 | while(topModule.parent) 14 | topModule = topModule.parent; 15 | var appDir = path.dirname(topModule.filename); 16 | 17 | //var logger = new (winston.Logger)({ 18 | // transports: [ 19 | // new (winston.transports.Console)(), 20 | // new (winston.transports.File)({ filename: appDir +'/constraints.log' }) 21 | // ] 22 | //}); 23 | 24 | 25 | var databases = {}; 26 | 27 | var mongoConnection = process.env['MONGO_DB_CONN'] || "localhost:27017"; 28 | 29 | var performMongoDbRequest = function(databaseName, callback) { 30 | if (databases[databaseName]) { 31 | //console.log("Using cache"); 32 | callback(databases[databaseName]); 33 | } else { 34 | //console.log("Connecting"); 35 | MongoClient.connect("mongodb://" + mongoConnection + "/" + databaseName, function(err, db) { 36 | if (err) { 37 | console.warn(err.message); 38 | process.exit(); 39 | } 40 | //console.log("Connection OK") 41 | databases[databaseName] = db; 42 | callback(db); 43 | }); 44 | } 45 | }; 46 | 47 | /** 48 | * Parses the expression in the body 49 | */ 50 | router.post('/parse/:language', function (req, res) { 51 | var expression = req.body.expression.replace(/[^\x00-\x7F]/g, ""); 52 | var language = req.params.language; 53 | var results = expressionsParser.parse(expression, language); 54 | res.send(results); 55 | }); 56 | 57 | router.post('/:db/:collection/execute/:language', connectTimeout('120s'), function (req, res) { 58 | var request = req.body; 59 | var expression = request.expression.replace(/[^\x00-\x7F]/g, ""); 60 | var language = req.params.language; 61 | var collectionName = req.params.collection; 62 | var results = expressionsParser.parse(expression, language); 63 | var responseData = { 64 | paserResponse: results, 65 | computeResponse: {} 66 | }; 67 | if (!results.validation) { 68 | if (expression.charAt(0) == "(" && expression.charAt(expression.length-1) == ")") { 69 | expression = expression.substr(1, expression.length - 2); 70 | results = expressionsParser.parse(expression, language); 71 | responseData.paserResponse = results; 72 | } 73 | } 74 | if (results.validation) { 75 | // Execute query 76 | //logger.log('info', 'Query execution started', { 77 | // expression: expression, 78 | // language: language 79 | //}); 80 | var start = process.hrtime(); 81 | computeGrammarQuery3(results, request.form, req.params.db, collectionName, request.skip, request.limit, function(err, results) { 82 | if (err) { 83 | responseData.computeResponse = err; 84 | res.status(500); 85 | res.send(responseData); 86 | } else { 87 | var elapsed_time = function(note){ 88 | var precision = 0; // 3 decimal places 89 | var elapsed = process.hrtime(start)[1] / 1000000; // divide by a million to get nano to milli 90 | //console.log(process.hrtime(start)[0] + " s, " + elapsed.toFixed(precision) + " ms - " + note); // print message + time 91 | start = process.hrtime(); // reset the timer 92 | return elapsed.toFixed(precision) + " ms."; 93 | }; 94 | //logger.log('info', 'Query execution finished', { 95 | // expression: expression, 96 | // language: language, 97 | // matches: results.total, 98 | // elapsed: elapsed_time() 99 | //}); 100 | responseData.computeResponse = results; 101 | res.send(responseData); 102 | } 103 | }); 104 | } else { 105 | res.status(400); 106 | res.send(responseData); 107 | } 108 | }); 109 | 110 | module.exports = router; 111 | 112 | var readConceptReference = function(node, ast) { 113 | var conceptId; 114 | ast.getChildren(node, ast.nodes).forEach(function(referenceChild) { 115 | if (referenceChild.rule == "conceptId") { 116 | conceptId = referenceChild.content; 117 | } 118 | }); 119 | return conceptId; 120 | } 121 | 122 | var readSimpleExpressionConstraint = function(node, ast) { 123 | var condition = { 124 | condition: node.rule, 125 | criteria: false, 126 | memberOf: false, 127 | conceptId: false 128 | }; 129 | if (node.rule == "simpleExpressionConstraint") { 130 | ast.getChildren(node, ast.nodes).forEach(function(child) { 131 | if (child.rule == "constraintOperator") { 132 | var constraintOperator = child; 133 | var operatorChildren = ast.getChildren(constraintOperator, ast.nodes); 134 | if (operatorChildren.length) { 135 | condition.criteria = operatorChildren[0].rule; 136 | } 137 | } else if (child.rule == "focusConcept") { 138 | var focusConcept = child; 139 | var focusChildren = ast.getChildren(focusConcept, ast.nodes); 140 | if (focusChildren.length) { 141 | focusChildren.forEach(function(loopFocusChild) { 142 | if (loopFocusChild.rule == "conceptReference") { 143 | condition.conceptId = readConceptReference(loopFocusChild, ast); 144 | } else if (loopFocusChild.rule == "memberOf") { 145 | condition.memberOf = true; 146 | } else if (loopFocusChild.rule == "wildCard") { 147 | condition.criteria = loopFocusChild.rule; 148 | } 149 | }); 150 | } 151 | } 152 | }); 153 | if (!condition.criteria) { 154 | condition.criteria = "self"; 155 | } 156 | } 157 | return condition; 158 | }; 159 | 160 | var readAttribute = function(node, ast) { 161 | var condition = { 162 | criteria: node.rule, 163 | cardinality: false, 164 | reverseFlag: false, 165 | attributeOperator: false, 166 | typeId: false, 167 | expressionComparisonOperator: false, 168 | targetNode: false 169 | }; 170 | if (node.rule == "attribute") { 171 | ast.getChildren(node, ast.nodes).forEach(function(attrChild) { 172 | if (attrChild.rule == "attributeOperator") { 173 | ast.getChildren(attrChild, ast.nodes).forEach(function(operator) { 174 | condition.attributeOperator = operator.rule; 175 | }); 176 | } else if (attrChild.rule == "attributeName") { 177 | ast.getChildren(attrChild, ast.nodes).forEach(function(nameChild) { 178 | if (nameChild.rule == "wildCard") { 179 | condition.typeId = "*"; 180 | } else if (nameChild.rule == "conceptReference") { 181 | condition.typeId = readConceptReference(nameChild, ast); 182 | } 183 | }); 184 | } else if (attrChild.rule == "expressionComparisonOperator") { 185 | condition.expressionComparisonOperator = attrChild.content; 186 | } else if (attrChild.rule == "expressionConstraintValue") { 187 | ast.getChildren(attrChild, ast.nodes).forEach(function(valueChild) { 188 | //if (valueChild.rule == "simpleExpressionConstraint") { 189 | condition.targetNode = valueChild; 190 | //} 191 | }); 192 | } else if (attrChild.rule == "cardinality") { 193 | condition.cardinality = true; 194 | } else if (attrChild.rule == "reverseFlag") { 195 | condition.reverseFlag = true; 196 | } 197 | }); 198 | } 199 | return condition; 200 | }; 201 | 202 | var rulesThatRequireAnd = [ 203 | "refinedExpressionConstraint" 204 | ]; 205 | 206 | var rulesThatShouldNotBeFollowedIn = [ 207 | "conjunction" 208 | ]; 209 | 210 | var computeGrammarQuery3 = function(parserResults, form, databaseName, collectionName, skip, limit, callback) { 211 | var ast = parserResults.simpleTree; 212 | var root = ast.getRootNode(ast.nodes); 213 | var computer = {}; 214 | var queryOptions = { 215 | limit: limit, 216 | skip: skip 217 | }; 218 | var exitWithError = function(message) { 219 | console.log("Error:", message); 220 | callback({message: message}, {}); 221 | }; 222 | computer.resolve = function(node, ast, queryPart) { 223 | //console.log(node.rule); 224 | if (typeof computer[node.rule] == "undefined") { 225 | exitWithError("Unsupported rule: " + node.rule); 226 | } else { 227 | computer[node.rule](node, ast, queryPart); 228 | } 229 | }; 230 | computer.expressionConstraint = function(node, ast, queryPart) { 231 | ast.getChildren(node, ast.nodes).forEach(function(child) { 232 | computer.resolve(child, ast, queryPart); 233 | }); 234 | }; 235 | computer.simpleExpressionConstraint = function(node, ast, queryPart) { 236 | node.condition = readSimpleExpressionConstraint(node, ast); 237 | if (node.condition.memberOf) { 238 | queryPart.push({"memberships.refset.conceptId": node.condition.conceptId}); 239 | if (node.condition.criteria && node.condition.criteria != "self") { 240 | exitWithError("Unsupported condition: combined memberOf and hierarchy criteria"); 241 | } 242 | } else if (node.condition.criteria == "self") { 243 | queryPart.push({"conceptId": node.condition.conceptId}); 244 | } else if (node.condition.criteria == "descendantOf") { 245 | if (form == "stated") { 246 | queryPart.push({"statedAncestors": node.condition.conceptId}); 247 | } else { 248 | queryPart.push({"inferredAncestors": node.condition.conceptId}); 249 | } 250 | } else if (node.condition.criteria == "descendantOrSelfOf") { 251 | var or = {$or: []}; 252 | or["$or"].push({"conceptId": node.condition.conceptId}); 253 | if (form == "stated") { 254 | or["$or"].push({"statedAncestors": node.condition.conceptId}); 255 | } else { 256 | or["$or"].push({"inferredAncestors": node.condition.conceptId}); 257 | } 258 | queryPart.push(or); 259 | } else if (node.condition.criteria == "ancestorOf") { 260 | // Not supported right now 261 | exitWithError("Unsupported condition: " +node. condition.criteria); 262 | } else if (node.condition.criteria == "ancestorOrSelfOf") { 263 | queryPart.push({"conceptId": node.condition.conceptId}); 264 | // Not supported right now 265 | exitWithError("Unsupported condition: " + node.condition.criteria); 266 | } 267 | }; 268 | computer.compoundExpressionConstraint = function(node, ast, queryPart) { 269 | ast.getChildren(node, ast.nodes).forEach(function(child) { 270 | computer.resolve(child, ast, queryPart); 271 | }); 272 | }; 273 | computer.subExpressionConstraint = function(node, ast, queryPart) { 274 | ast.getChildren(node, ast.nodes).forEach(function(child) { 275 | result = computer.resolve(child, ast, queryPart); 276 | }); 277 | }; 278 | computer.exclusionExpressionConstraint = function(node, ast, queryPart) { 279 | var children = ast.getChildren(node, ast.nodes); 280 | if (children.length != 3) { 281 | exitWithError("Problem with exclusionExpressionConstraint: " + node.content); 282 | } 283 | //var excl = {$and:[]}; 284 | var excl = queryPart; 285 | computer.resolve(children[0], ast, excl); 286 | var nor = []; 287 | computer.resolve(children[2], ast, nor); 288 | var not = {$nor: nor}; 289 | queryPart.push(not); 290 | }; 291 | computer.disjunctionExpressionConstraint = function(node, ast, queryPart) { 292 | var or = {$or:[]}; 293 | ast.getChildren(node, ast.nodes).forEach(function(child) { 294 | if (child.rule == "subExpressionConstraint") { 295 | computer.resolve(child, ast, or["$or"]); 296 | } 297 | }); 298 | queryPart.push(or); 299 | }; 300 | computer.conjunctionExpressionConstraint = function(node, ast, queryPart) { 301 | var and = {$and:[]}; 302 | ast.getChildren(node, ast.nodes).forEach(function(child) { 303 | if (child.rule == "subExpressionConstraint") { 304 | computer.resolve(child, ast, and["$and"]); 305 | } 306 | }); 307 | queryPart.push(and); 308 | }; 309 | computer.refinedExpressionConstraint = function(node, ast, queryPart) { 310 | var children = ast.getChildren(node, ast.nodes); 311 | if (children.length != 2) { 312 | exitWithError("Problem with refinedExpressionConstraint: " + node.content); 313 | } 314 | //var and = {$and:[]}; 315 | computer.resolve(children[0], ast, queryPart); 316 | computer.resolve(children[1], ast, queryPart); 317 | //queryPart.push(and); 318 | }; 319 | computer.refinement = function(node, ast, queryPart) { 320 | var children = ast.getChildren(node, ast.nodes); 321 | if (children.length == 1) { 322 | computer.resolve(children[0], ast, queryPart); 323 | } else { 324 | if (children[1].rule == "conjunctionRefinementSet") { 325 | var and = {$and:[]}; 326 | computer.resolve(children[0], ast, and["$and"]); 327 | computer.resolve(children[1], ast, and["$and"]); 328 | queryPart.push(and); 329 | } else if (children[1].rule == "disjunctionRefinementSet") { 330 | var or = {$or:[]}; 331 | computer.resolve(children[0], ast, or["$or"]); 332 | computer.resolve(children[1], ast, or["$or"]); 333 | queryPart.push(or); 334 | } 335 | } 336 | }; 337 | computer.disjunctionRefinementSet = function(node, ast, queryPart) { 338 | var or = {$or:[]}; 339 | ast.getChildren(node, ast.nodes).forEach(function(child) { 340 | if (child.rule == "subRefinement") { 341 | computer.resolve(child, ast, or["$or"]); 342 | } 343 | }); 344 | queryPart.push(or); 345 | }; 346 | computer.conjunctionRefinementSet = function(node, ast, queryPart) { 347 | var and = {$and:[]}; 348 | ast.getChildren(node, ast.nodes).forEach(function(child) { 349 | if (child.rule == "subRefinement") { 350 | computer.resolve(child, ast, and["$and"]); 351 | } 352 | }); 353 | queryPart.push(and); 354 | }; 355 | computer.subRefinement = function(node, ast, queryPart) { 356 | //var or = {$or:[]}; 357 | ast.getChildren(node, ast.nodes).forEach(function(child) { 358 | computer.resolve(child, ast, queryPart); 359 | }); 360 | //queryPart.push(or); 361 | }; 362 | computer.attributeSet = function(node, ast, queryPart) { 363 | var children = ast.getChildren(node, ast.nodes); 364 | if (children.length == 1) { 365 | computer.resolve(children[0], ast, queryPart); 366 | } else { 367 | if (children[1].rule == "conjunctionAttributeSet") { 368 | var and = {$and:[]}; 369 | computer.resolve(children[0], ast, and["$and"]); 370 | computer.resolve(children[1], ast, and["$and"]); 371 | queryPart.push(and); 372 | } else if (children[1].rule == "disjunctionAttributeSet") { 373 | var or = {$or:[]}; 374 | computer.resolve(children[0], ast, or["$or"]); 375 | computer.resolve(children[1], ast, or["$or"]); 376 | queryPart.push(or); 377 | } 378 | } 379 | }; 380 | computer.conjunctionAttributeSet = function(node, ast, queryPart) { 381 | var and = {$and:[]}; 382 | ast.getChildren(node, ast.nodes).forEach(function(child) { 383 | if (child.rule == "subAttributeSet") { 384 | computer.resolve(child, ast, and["$and"]); 385 | } 386 | }); 387 | queryPart.push(and); 388 | }; 389 | computer.disjunctionAttributeSet = function(node, ast, queryPart) { 390 | var or = {$or:[]}; 391 | ast.getChildren(node, ast.nodes).forEach(function(child) { 392 | if (child.rule == "subAttributeSet") { 393 | computer.resolve(child, ast, or["$or"]); 394 | } 395 | }); 396 | queryPart.push(or); 397 | }; 398 | computer.subAttributeSet = function(node, ast, queryPart) { 399 | //var or = {$or:[]}; 400 | ast.getChildren(node, ast.nodes).forEach(function(child) { 401 | if (child.rule == "attribute" || child.rule == "attributeSet") { 402 | computer.resolve(child, ast, queryPart); 403 | } 404 | }); 405 | //queryPart.push(or); 406 | }; 407 | computer.attributeGroup = function(node, ast, queryPart) { 408 | //TODO: Implement cardinality 409 | var or = {$or:[]}; 410 | ast.getChildren(node, ast.nodes).forEach(function(child) { 411 | if (child.rule == "attributeSet") { 412 | computer.resolve(child, ast, or["$or"]); 413 | } 414 | }); 415 | queryPart.push(or); 416 | }; 417 | computer.attribute = function(node, ast, queryPart) { 418 | var elemMatch = {}; 419 | var condition = readAttribute(node, ast); 420 | // Process attribute name 421 | var attributeNameResults = false; 422 | if (condition.cardinality) { 423 | exitWithError("Unsupported condition: cardinality"); 424 | } 425 | if (condition.reverseFlag) { 426 | exitWithError("Unsupported condition: reverseFlag"); 427 | } 428 | if (condition.typeId != "*") { 429 | if (condition.attributeOperator) { 430 | if (condition.attributeOperator == "descendantOrSelfOf") { 431 | elemMatch["$or"] = []; 432 | elemMatch["$or"].push({"type.conceptId" : condition.conceptId}); 433 | elemMatch["$or"].push({"typeInferredAncestors" : condition.conceptId}); 434 | } else if (condition.attributeOperator == "descendantOf") { 435 | elemMatch["typeInferredAncestors"] = condition.conceptId; 436 | } else { 437 | elemMatch["type.conceptId"] = condition.typeId; 438 | } 439 | } else { 440 | elemMatch["type.conceptId"] = condition.typeId; 441 | } 442 | } 443 | // Process attribute value 444 | //if (condition.targetNode.content != "*") { 445 | // var temp = []; 446 | // computer.resolve(condition.targetNode, ast, temp; 447 | //} 448 | //queryPart.push({relationships: {"$elemMatch": elemMatch}}); 449 | //TODO: update for nested definitions in attributes 450 | if (condition.targetNode) { 451 | if (condition.targetNode.rule == "simpleExpressionConstraint") { 452 | var targetExp = readSimpleExpressionConstraint(condition.targetNode, ast); 453 | //console.log(JSON.stringify(targetExp)); 454 | if (targetExp.memberOf) { 455 | elemMatch["targetMemberships"] = targetExp.conceptId; 456 | } else if (targetExp.criteria == "descendantOrSelfOf") { 457 | elemMatch["$or"] = []; 458 | elemMatch["$or"].push({"destination.conceptId" : targetExp.conceptId}); 459 | elemMatch["$or"].push({"targetInferredAncestors" : targetExp.conceptId}); 460 | } else if (targetExp.criteria == "descendantOf") { 461 | elemMatch["targetInferredAncestors"] = targetExp.conceptId; 462 | } else { 463 | elemMatch["destination.conceptId"] = targetExp.conceptId; 464 | } 465 | } else { 466 | exitWithError("Unsupported condition: Nested definitions"); 467 | } 468 | } 469 | if (Object.keys(elemMatch).length > 0) { 470 | elemMatch["active"] = true; 471 | queryPart.push({relationships: {"$elemMatch": elemMatch}}); 472 | } 473 | }; 474 | 475 | var mongoQuery = {$and:[]}; 476 | computer.resolve(root, ast,mongoQuery["$and"]); 477 | //console.log(JSON.stringify(mongoQuery)); 478 | var returnData = {}; 479 | performMongoDbRequest(databaseName, function(db) { 480 | var collection = db.collection(collectionName); 481 | collection.count(mongoQuery, function(err, count) { 482 | returnData.total = count; 483 | collection.find(mongoQuery,{conceptId:1, fullySpecifiedName:1}, queryOptions, function(err, cursor) { 484 | returnData.limit = queryOptions.limit; 485 | returnData.skip = queryOptions.skip; 486 | returnData.matches = []; 487 | returnData.query = mongoQuery; 488 | cursor.toArray(function (err, docs) { 489 | if (docs) { 490 | //var page = docs.slice(parseInt(skip), parseInt(skip) + parseInt(limit)); 491 | returnData.matches = docs; 492 | callback(null, returnData); 493 | } else { 494 | returnData.total = 0; 495 | returnData.matches = []; 496 | callback(null, returnData); 497 | } 498 | }); 499 | }); 500 | }); 501 | }); 502 | }; 503 | -------------------------------------------------------------------------------- /routes/expressionsv1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by alo on 7/9/15. 3 | */ 4 | var express = require('express'); 5 | var router = express.Router(); 6 | var expressionsParser = require('../grammars/apg/expressionParser'); 7 | var MongoClient = require('mongodb').MongoClient; 8 | var connectTimeout = require('connect-timeout'); 9 | //var winston = require('winston'); 10 | var path = require('path'); 11 | var transform = require("../lib/transform"); 12 | // find the first module to be loaded 13 | var topModule = module; 14 | while(topModule.parent) 15 | topModule = topModule.parent; 16 | var appDir = path.dirname(topModule.filename); 17 | 18 | //var logger = new (winston.Logger)({ 19 | // transports: [ 20 | // new (winston.transports.Console)(), 21 | // new (winston.transports.File)({ filename: appDir +'/constraints.log' }) 22 | // ] 23 | //}); 24 | 25 | 26 | var databases = {}; 27 | 28 | var mongoConnection = process.env['MONGO_DB_CONN'] || "localhost:27017"; 29 | 30 | var performMongoDbRequest = function(databaseName, callback) { 31 | if (databases[databaseName]) { 32 | //console.log("Using cache"); 33 | callback(databases[databaseName]); 34 | } else { 35 | //console.log("Connecting"); 36 | MongoClient.connect("mongodb://" + mongoConnection + "/" + databaseName, function(err, db) { 37 | if (err) { 38 | console.warn(err.message); 39 | process.exit(); 40 | } 41 | //console.log("Connection OK") 42 | databases[databaseName] = db; 43 | callback(db); 44 | }); 45 | } 46 | }; 47 | 48 | /** 49 | * Parses the expression in the body 50 | */ 51 | router.post('/parse/:language', function (req, res) { 52 | var expression = req.body.expression.replace(/[^\x00-\x7F]/g, ""); 53 | var language = req.params.language; 54 | var results = expressionsParser.parse(expression, language); 55 | res.send(results); 56 | }); 57 | 58 | router.post('/:db/:collection/execute/:language', connectTimeout('120s'), function (req, res) { 59 | var request = req.body; 60 | var expression = request.expression.replace(/[^\x00-\x7F]/g, ""); 61 | var language = req.params.language; 62 | var collectionName = req.params.collection; 63 | var results = expressionsParser.parse(expression, language); 64 | var responseData = { 65 | paserResponse: results, 66 | computeResponse: {} 67 | }; 68 | if (!results.validation) { 69 | if (expression.charAt(0) == "(" && expression.charAt(expression.length-1) == ")") { 70 | expression = expression.substr(1, expression.length - 2); 71 | results = expressionsParser.parse(expression, language); 72 | responseData.paserResponse = results; 73 | } 74 | } 75 | if (results.validation) { 76 | // Execute query 77 | //logger.log('info', 'Query execution started', { 78 | // expression: expression, 79 | // language: language 80 | //}); 81 | var start = process.hrtime(); 82 | computeGrammarQuery3(results, request.form, req.params.db, collectionName, request.skip, request.limit, function(err, results) { 83 | if (err) { 84 | responseData.computeResponse = err; 85 | res.status(500); 86 | res.send(responseData); 87 | } else { 88 | var elapsed_time = function(note){ 89 | var precision = 0; // 3 decimal places 90 | var elapsed = process.hrtime(start)[1] / 1000000; // divide by a million to get nano to milli 91 | //console.log(process.hrtime(start)[0] + " s, " + elapsed.toFixed(precision) + " ms - " + note); // print message + time 92 | start = process.hrtime(); // reset the timer 93 | return elapsed.toFixed(precision) + " ms."; 94 | }; 95 | //logger.log('info', 'Query execution finished', { 96 | // expression: expression, 97 | // language: language, 98 | // matches: results.total, 99 | // elapsed: elapsed_time() 100 | //}); 101 | responseData.computeResponse = results; 102 | res.send(responseData); 103 | } 104 | }); 105 | } else { 106 | res.status(400); 107 | res.send(responseData); 108 | } 109 | }); 110 | 111 | module.exports = router; 112 | 113 | var readConceptReference = function(node, ast) { 114 | var conceptId; 115 | ast.getChildren(node, ast.nodes).forEach(function(referenceChild) { 116 | if (referenceChild.rule == "conceptId") { 117 | conceptId = referenceChild.content; 118 | } 119 | }); 120 | return conceptId; 121 | } 122 | 123 | var readSimpleExpressionConstraint = function(node, ast) { 124 | var condition = { 125 | condition: node.rule, 126 | criteria: false, 127 | memberOf: false, 128 | conceptId: false 129 | }; 130 | if (node.rule == "simpleExpressionConstraint") { 131 | ast.getChildren(node, ast.nodes).forEach(function(child) { 132 | if (child.rule == "constraintOperator") { 133 | var constraintOperator = child; 134 | var operatorChildren = ast.getChildren(constraintOperator, ast.nodes); 135 | if (operatorChildren.length) { 136 | condition.criteria = operatorChildren[0].rule; 137 | } 138 | } else if (child.rule == "focusConcept") { 139 | var focusConcept = child; 140 | var focusChildren = ast.getChildren(focusConcept, ast.nodes); 141 | if (focusChildren.length) { 142 | focusChildren.forEach(function(loopFocusChild) { 143 | if (loopFocusChild.rule == "conceptReference") { 144 | condition.conceptId = readConceptReference(loopFocusChild, ast); 145 | } else if (loopFocusChild.rule == "memberOf") { 146 | condition.memberOf = true; 147 | } else if (loopFocusChild.rule == "wildCard") { 148 | condition.criteria = loopFocusChild.rule; 149 | } 150 | }); 151 | } 152 | } 153 | }); 154 | if (!condition.criteria) { 155 | condition.criteria = "self"; 156 | } 157 | } 158 | return condition; 159 | }; 160 | 161 | var readAttribute = function(node, ast) { 162 | var condition = { 163 | criteria: node.rule, 164 | cardinality: false, 165 | reverseFlag: false, 166 | attributeOperator: false, 167 | typeId: false, 168 | expressionComparisonOperator: false, 169 | targetNode: false 170 | }; 171 | if (node.rule == "attribute") { 172 | ast.getChildren(node, ast.nodes).forEach(function(attrChild) { 173 | if (attrChild.rule == "attributeOperator") { 174 | ast.getChildren(attrChild, ast.nodes).forEach(function(operator) { 175 | condition.attributeOperator = operator.rule; 176 | }); 177 | } else if (attrChild.rule == "attributeName") { 178 | ast.getChildren(attrChild, ast.nodes).forEach(function(nameChild) { 179 | if (nameChild.rule == "wildCard") { 180 | condition.typeId = "*"; 181 | } else if (nameChild.rule == "conceptReference") { 182 | condition.typeId = readConceptReference(nameChild, ast); 183 | } 184 | }); 185 | } else if (attrChild.rule == "expressionComparisonOperator") { 186 | condition.expressionComparisonOperator = attrChild.content; 187 | } else if (attrChild.rule == "expressionConstraintValue") { 188 | ast.getChildren(attrChild, ast.nodes).forEach(function(valueChild) { 189 | //if (valueChild.rule == "simpleExpressionConstraint") { 190 | condition.targetNode = valueChild; 191 | //} 192 | }); 193 | } else if (attrChild.rule == "cardinality") { 194 | condition.cardinality = true; 195 | } else if (attrChild.rule == "reverseFlag") { 196 | condition.reverseFlag = true; 197 | } 198 | }); 199 | } 200 | return condition; 201 | }; 202 | 203 | var rulesThatRequireAnd = [ 204 | "refinedExpressionConstraint" 205 | ]; 206 | 207 | var rulesThatShouldNotBeFollowedIn = [ 208 | "conjunction" 209 | ]; 210 | 211 | var computeGrammarQuery3 = function(parserResults, form, databaseName, collectionName, skip, limit, callback) { 212 | var ast = parserResults.simpleTree; 213 | var root = ast.getRootNode(ast.nodes); 214 | var computer = {}; 215 | var queryOptions = { 216 | limit: limit, 217 | skip: skip 218 | }; 219 | var exitWithError = function(message) { 220 | console.log("Error:", message); 221 | callback({message: message}, {}); 222 | }; 223 | computer.resolve = function(node, ast, queryPart) { 224 | //console.log(node.rule); 225 | if (typeof computer[node.rule] == "undefined") { 226 | exitWithError("Unsupported rule: " + node.rule); 227 | } else { 228 | computer[node.rule](node, ast, queryPart); 229 | } 230 | }; 231 | computer.expressionConstraint = function(node, ast, queryPart) { 232 | ast.getChildren(node, ast.nodes).forEach(function(child) { 233 | computer.resolve(child, ast, queryPart); 234 | }); 235 | }; 236 | computer.simpleExpressionConstraint = function(node, ast, queryPart) { 237 | node.condition = readSimpleExpressionConstraint(node, ast); 238 | if (node.condition.memberOf) { 239 | queryPart.push({"memberships.refset.conceptId": node.condition.conceptId}); 240 | if (node.condition.criteria && node.condition.criteria != "self") { 241 | exitWithError("Unsupported condition: combined memberOf and hierarchy criteria"); 242 | } 243 | } else if (node.condition.criteria == "self") { 244 | queryPart.push({"conceptId": node.condition.conceptId}); 245 | } else if (node.condition.criteria == "descendantOf") { 246 | if (form == "stated") { 247 | queryPart.push({"statedAncestors": node.condition.conceptId}); 248 | } else { 249 | queryPart.push({"inferredAncestors": node.condition.conceptId}); 250 | } 251 | } else if (node.condition.criteria == "descendantOrSelfOf") { 252 | var or = {$or: []}; 253 | or["$or"].push({"conceptId": node.condition.conceptId}); 254 | if (form == "stated") { 255 | or["$or"].push({"statedAncestors": node.condition.conceptId}); 256 | } else { 257 | or["$or"].push({"inferredAncestors": node.condition.conceptId}); 258 | } 259 | queryPart.push(or); 260 | } else if (node.condition.criteria == "ancestorOf") { 261 | // Not supported right now 262 | exitWithError("Unsupported condition: " +node. condition.criteria); 263 | } else if (node.condition.criteria == "ancestorOrSelfOf") { 264 | queryPart.push({"conceptId": node.condition.conceptId}); 265 | // Not supported right now 266 | exitWithError("Unsupported condition: " + node.condition.criteria); 267 | } 268 | }; 269 | computer.compoundExpressionConstraint = function(node, ast, queryPart) { 270 | ast.getChildren(node, ast.nodes).forEach(function(child) { 271 | computer.resolve(child, ast, queryPart); 272 | }); 273 | }; 274 | computer.subExpressionConstraint = function(node, ast, queryPart) { 275 | ast.getChildren(node, ast.nodes).forEach(function(child) { 276 | result = computer.resolve(child, ast, queryPart); 277 | }); 278 | }; 279 | computer.exclusionExpressionConstraint = function(node, ast, queryPart) { 280 | var children = ast.getChildren(node, ast.nodes); 281 | if (children.length != 3) { 282 | exitWithError("Problem with exclusionExpressionConstraint: " + node.content); 283 | } 284 | //var excl = {$and:[]}; 285 | var excl = queryPart; 286 | computer.resolve(children[0], ast, excl); 287 | var nor = []; 288 | computer.resolve(children[2], ast, nor); 289 | var not = {$nor: nor}; 290 | queryPart.push(not); 291 | }; 292 | computer.disjunctionExpressionConstraint = function(node, ast, queryPart) { 293 | var or = {$or:[]}; 294 | ast.getChildren(node, ast.nodes).forEach(function(child) { 295 | if (child.rule == "subExpressionConstraint") { 296 | computer.resolve(child, ast, or["$or"]); 297 | } 298 | }); 299 | queryPart.push(or); 300 | }; 301 | computer.conjunctionExpressionConstraint = function(node, ast, queryPart) { 302 | var and = {$and:[]}; 303 | ast.getChildren(node, ast.nodes).forEach(function(child) { 304 | if (child.rule == "subExpressionConstraint") { 305 | computer.resolve(child, ast, and["$and"]); 306 | } 307 | }); 308 | queryPart.push(and); 309 | }; 310 | computer.refinedExpressionConstraint = function(node, ast, queryPart) { 311 | var children = ast.getChildren(node, ast.nodes); 312 | if (children.length != 2) { 313 | exitWithError("Problem with refinedExpressionConstraint: " + node.content); 314 | } 315 | //var and = {$and:[]}; 316 | computer.resolve(children[0], ast, queryPart); 317 | computer.resolve(children[1], ast, queryPart); 318 | //queryPart.push(and); 319 | }; 320 | computer.refinement = function(node, ast, queryPart) { 321 | var children = ast.getChildren(node, ast.nodes); 322 | if (children.length == 1) { 323 | computer.resolve(children[0], ast, queryPart); 324 | } else { 325 | if (children[1].rule == "conjunctionRefinementSet") { 326 | var and = {$and:[]}; 327 | computer.resolve(children[0], ast, and["$and"]); 328 | computer.resolve(children[1], ast, and["$and"]); 329 | queryPart.push(and); 330 | } else if (children[1].rule == "disjunctionRefinementSet") { 331 | var or = {$or:[]}; 332 | computer.resolve(children[0], ast, or["$or"]); 333 | computer.resolve(children[1], ast, or["$or"]); 334 | queryPart.push(or); 335 | } 336 | } 337 | }; 338 | computer.disjunctionRefinementSet = function(node, ast, queryPart) { 339 | var or = {$or:[]}; 340 | ast.getChildren(node, ast.nodes).forEach(function(child) { 341 | if (child.rule == "subRefinement") { 342 | computer.resolve(child, ast, or["$or"]); 343 | } 344 | }); 345 | queryPart.push(or); 346 | }; 347 | computer.conjunctionRefinementSet = function(node, ast, queryPart) { 348 | var and = {$and:[]}; 349 | ast.getChildren(node, ast.nodes).forEach(function(child) { 350 | if (child.rule == "subRefinement") { 351 | computer.resolve(child, ast, and["$and"]); 352 | } 353 | }); 354 | queryPart.push(and); 355 | }; 356 | computer.subRefinement = function(node, ast, queryPart) { 357 | //var or = {$or:[]}; 358 | ast.getChildren(node, ast.nodes).forEach(function(child) { 359 | computer.resolve(child, ast, queryPart); 360 | }); 361 | //queryPart.push(or); 362 | }; 363 | computer.attributeSet = function(node, ast, queryPart) { 364 | var children = ast.getChildren(node, ast.nodes); 365 | if (children.length == 1) { 366 | computer.resolve(children[0], ast, queryPart); 367 | } else { 368 | if (children[1].rule == "conjunctionAttributeSet") { 369 | var and = {$and:[]}; 370 | computer.resolve(children[0], ast, and["$and"]); 371 | computer.resolve(children[1], ast, and["$and"]); 372 | queryPart.push(and); 373 | } else if (children[1].rule == "disjunctionAttributeSet") { 374 | var or = {$or:[]}; 375 | computer.resolve(children[0], ast, or["$or"]); 376 | computer.resolve(children[1], ast, or["$or"]); 377 | queryPart.push(or); 378 | } 379 | } 380 | }; 381 | computer.conjunctionAttributeSet = function(node, ast, queryPart) { 382 | var and = {$and:[]}; 383 | ast.getChildren(node, ast.nodes).forEach(function(child) { 384 | if (child.rule == "subAttributeSet") { 385 | computer.resolve(child, ast, and["$and"]); 386 | } 387 | }); 388 | queryPart.push(and); 389 | }; 390 | computer.disjunctionAttributeSet = function(node, ast, queryPart) { 391 | var or = {$or:[]}; 392 | ast.getChildren(node, ast.nodes).forEach(function(child) { 393 | if (child.rule == "subAttributeSet") { 394 | computer.resolve(child, ast, or["$or"]); 395 | } 396 | }); 397 | queryPart.push(or); 398 | }; 399 | computer.subAttributeSet = function(node, ast, queryPart) { 400 | //var or = {$or:[]}; 401 | ast.getChildren(node, ast.nodes).forEach(function(child) { 402 | if (child.rule == "attribute" || child.rule == "attributeSet") { 403 | computer.resolve(child, ast, queryPart); 404 | } 405 | }); 406 | //queryPart.push(or); 407 | }; 408 | computer.attributeGroup = function(node, ast, queryPart) { 409 | //TODO: Implement cardinality 410 | var or = {$or:[]}; 411 | ast.getChildren(node, ast.nodes).forEach(function(child) { 412 | if (child.rule == "attributeSet") { 413 | computer.resolve(child, ast, or["$or"]); 414 | } 415 | }); 416 | queryPart.push(or); 417 | }; 418 | computer.attribute = function(node, ast, queryPart) { 419 | var elemMatch = {}; 420 | var condition = readAttribute(node, ast); 421 | // Process attribute name 422 | var attributeNameResults = false; 423 | if (condition.cardinality) { 424 | exitWithError("Unsupported condition: cardinality"); 425 | } 426 | if (condition.reverseFlag) { 427 | exitWithError("Unsupported condition: reverseFlag"); 428 | } 429 | if (condition.typeId != "*") { 430 | if (condition.attributeOperator) { 431 | if (condition.attributeOperator == "descendantOrSelfOf") { 432 | elemMatch["$or"] = []; 433 | elemMatch["$or"].push({"type.conceptId" : condition.conceptId}); 434 | elemMatch["$or"].push({"typeInferredAncestors" : condition.conceptId}); 435 | } else if (condition.attributeOperator == "descendantOf") { 436 | elemMatch["typeInferredAncestors"] = condition.conceptId; 437 | } else { 438 | elemMatch["type.conceptId"] = condition.typeId; 439 | } 440 | } else { 441 | elemMatch["type.conceptId"] = condition.typeId; 442 | } 443 | } 444 | // Process attribute value 445 | //if (condition.targetNode.content != "*") { 446 | // var temp = []; 447 | // computer.resolve(condition.targetNode, ast, temp; 448 | //} 449 | //queryPart.push({relationships: {"$elemMatch": elemMatch}}); 450 | //TODO: update for nested definitions in attributes 451 | if (condition.targetNode) { 452 | if (condition.targetNode.rule == "simpleExpressionConstraint") { 453 | var targetExp = readSimpleExpressionConstraint(condition.targetNode, ast); 454 | //console.log(JSON.stringify(targetExp)); 455 | if (targetExp.memberOf) { 456 | elemMatch["targetMemberships"] = targetExp.conceptId; 457 | } else if (targetExp.criteria == "descendantOrSelfOf") { 458 | elemMatch["$or"] = []; 459 | elemMatch["$or"].push({"destination.conceptId" : targetExp.conceptId}); 460 | elemMatch["$or"].push({"targetInferredAncestors" : targetExp.conceptId}); 461 | } else if (targetExp.criteria == "descendantOf") { 462 | elemMatch["targetInferredAncestors"] = targetExp.conceptId; 463 | } else { 464 | elemMatch["destination.conceptId"] = targetExp.conceptId; 465 | } 466 | } else { 467 | exitWithError("Unsupported condition: Nested definitions"); 468 | } 469 | } 470 | if (Object.keys(elemMatch).length > 0) { 471 | elemMatch["active"] = true; 472 | queryPart.push({relationships: {"$elemMatch": elemMatch}}); 473 | } 474 | }; 475 | 476 | var mongoQuery = {$and:[]}; 477 | computer.resolve(root, ast,mongoQuery["$and"]); 478 | //console.log(JSON.stringify(mongoQuery)); 479 | var returnData = {}; 480 | performMongoDbRequest(databaseName, function(db) { 481 | var collection = db.collection(collectionName); 482 | collection.count(mongoQuery, function(err, count) { 483 | returnData.total = count; 484 | collection.find(mongoQuery,{conceptId:1, fullySpecifiedName:1}, queryOptions, function(err, cursor) { 485 | returnData.limit = queryOptions.limit; 486 | returnData.skip = queryOptions.skip; 487 | returnData.matches = []; 488 | returnData.query = mongoQuery; 489 | cursor.toArray(function (err, docs) { 490 | if (docs) { 491 | //var page = docs.slice(parseInt(skip), parseInt(skip) + parseInt(limit)); 492 | var results=transform.getExpressionResultsV1(docs); 493 | returnData.matches = results; 494 | callback(null, returnData); 495 | } else { 496 | returnData.total = 0; 497 | returnData.matches = []; 498 | callback(null, returnData); 499 | } 500 | }); 501 | }); 502 | }); 503 | }); 504 | }; 505 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var fs = require('fs'); 4 | var MongoClient = require('mongodb').MongoClient; 5 | var ObjectID = require('mongodb').ObjectID; 6 | 7 | var serverDb; 8 | var resourcesCol; 9 | 10 | var mongoConnection = process.env['MONGO_DB_CONN'] || "localhost:27017"; 11 | 12 | MongoClient.connect("mongodb://" + mongoConnection + "/server", function(err, db) { 13 | if (err) { 14 | console.warn(err.message); 15 | process.exit(); 16 | } 17 | serverDb = db; 18 | resourcesCol = db.collection("resources"); 19 | console.log("Connected to server/resources db.") 20 | }); 21 | 22 | router.get('/releases', function(req, res) { 23 | resourcesCol.find().toArray(function(err, doc) { 24 | if (err) { 25 | console.log(err.message); 26 | } 27 | if (doc) { 28 | res.status(200); 29 | res.header('Content-Type', 'application/json'); 30 | res.send(doc); 31 | } else { 32 | res.status(200); 33 | res.send("Manifest not found for id = " + idParam); 34 | } 35 | }); 36 | }); 37 | 38 | router.get('/releases/:id', function(req, res) { 39 | var idParam = ObjectID.createFromHexString(req.params.id); 40 | if (idParam) { 41 | resourcesCol.find({_id: idParam}).nextObject(function(err, doc) { 42 | if (err) { 43 | console.log(err.message); 44 | } 45 | if (doc) { 46 | res.status(200); 47 | res.header('Content-Type', 'application/json'); 48 | res.send(doc); 49 | } else { 50 | res.status(200); 51 | res.send("Manifest not found for id = " + idParam); 52 | } 53 | 54 | }); 55 | } else { 56 | res.status(200); 57 | res.send("Not a valid id"); 58 | } 59 | 60 | }); 61 | 62 | 63 | module.exports = router; 64 | 65 | // Manifest model 66 | var sampleManifest = { 67 | id: "4b7865c0-18e0-11e4-8c21-0800200c9a66", 68 | resourceSetName: "International Edition", 69 | effectiveTime: process.env['SCT_VERSION'] || "20180131", 70 | databaseName: "en-edition", 71 | collectionName: process.env['SCT_VERSION'] || "20180131", 72 | expirationDate: "20200131", 73 | modules: [ 74 | {sctid: 900000000000207008, defaultTerm: "SNOMED CT core module"}, 75 | {sctid: 900000000000012004, defaultTerm: "SNOMED CT model component module"} 76 | ], 77 | languageRefsets: [ 78 | {sctid: 900000000000509007, defaultTerm: "US English"}, 79 | {sctid: 900000000000508004, defaultTerm: "GB English"} 80 | ], 81 | refsets: [ 82 | {sctid: 900000000000497000, defaultTerm: "CTV3 simple map"}, 83 | {sctid: 446608001, defaultTerm: "ICD-O simple map reference set"}, 84 | {sctid: 447566000, defaultTerm: "Virtual medicinal product simple reference set"} 85 | ], 86 | defaultTermLangCode: "en", 87 | defaultTermType: 900000000000003001, 88 | textIndexNormalized: true 89 | } 90 | -------------------------------------------------------------------------------- /routes/serverv1.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var fs = require('fs'); 4 | var transform = require("../lib/transform"); 5 | var MongoClient = require('mongodb').MongoClient; 6 | var ObjectID = require('mongodb').ObjectID; 7 | 8 | var serverDb; 9 | var resourcesCol; 10 | 11 | var mongoConnection = process.env['MONGO_DB_CONN'] || "localhost:27017"; 12 | 13 | MongoClient.connect("mongodb://" + mongoConnection + "/server", function(err, db) { 14 | if (err) { 15 | console.warn(err.message); 16 | process.exit(); 17 | } 18 | serverDb = db; 19 | resourcesCol = db.collection("resources"); 20 | console.log("Connected to server/resources db.") 21 | }); 22 | 23 | router.get('/releases', function(req, res) { 24 | resourcesCol.find().toArray(function(err, doc) { 25 | if (err) { 26 | console.log(err.message); 27 | } 28 | if (doc) { 29 | res.status(200); 30 | res.header('Content-Type', 'application/json'); 31 | var result=transform.getManifestsV1(doc); 32 | res.send(result); 33 | } else { 34 | res.status(200); 35 | res.send("Manifest not found for id = " + idParam); 36 | } 37 | }); 38 | }); 39 | 40 | router.get('/releases/:id', function(req, res) { 41 | var idParam = ObjectID.createFromHexString(req.params.id); 42 | if (idParam) { 43 | resourcesCol.find({_id: idParam}).nextObject(function(err, doc) { 44 | if (err) { 45 | console.log(err.message); 46 | } 47 | if (doc) { 48 | res.status(200); 49 | res.header('Content-Type', 'application/json'); 50 | var result=transform.getManifestV1(doc); 51 | res.send(result); 52 | } else { 53 | res.status(200); 54 | res.send("Manifest not found for id = " + idParam); 55 | } 56 | 57 | }); 58 | } else { 59 | res.status(200); 60 | res.send("Not a valid id"); 61 | } 62 | 63 | }); 64 | 65 | 66 | module.exports = router; 67 | 68 | // Manifest model 69 | var sampleManifest = { 70 | id: "4b7865c0-18e0-11e4-8c21-0800200c9a66", 71 | resourceSetName: "International Edition", 72 | effectiveTime: "20140731", 73 | databaseName: "en-edition", 74 | collectionName: "20140731", 75 | expirationDate: "20150201", 76 | modules: [ 77 | {sctid: 900000000000207008, defaultTerm: "SNOMED CT core module"}, 78 | {sctid: 900000000000012004, defaultTerm: "SNOMED CT model component module"} 79 | ], 80 | languageRefsets: [ 81 | {sctid: 900000000000509007, defaultTerm: "US English"}, 82 | {sctid: 900000000000508004, defaultTerm: "GB English"} 83 | ], 84 | refsets: [ 85 | {sctid: 900000000000497000, defaultTerm: "CTV3 simple map"}, 86 | {sctid: 446608001, defaultTerm: "ICD-O simple map reference set"}, 87 | {sctid: 447566000, defaultTerm: "Virtual medicinal product simple reference set"} 88 | ], 89 | defaultTermLangCode: "en", 90 | defaultTermType: 900000000000003001, 91 | textIndexNormalized: true 92 | } 93 | -------------------------------------------------------------------------------- /routes/snomed.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var winston = require('winston'); 4 | var MongoClient = require('mongodb').MongoClient; 5 | var snomedLib = require("../lib/snomedv2"); 6 | var apiModelUtility = require("../lib/apiModelUtility"); 7 | 8 | var logger = new(winston.Logger)({ 9 | transports: [ 10 | new(winston.transports.File)({ filename: '/root/concepts-json/node_modules/sct-snapshot-rest-api/search.log' }) 11 | ] 12 | }); 13 | 14 | //console.log("ÁáéÉ\u03A8 --> " + util.removeDiacritics("ÁáéÉ\u03A8")); 15 | //var regextxt = "^186^1$1/1"; 16 | //console.log(regextxt + " --> " + util.regExpEscape(regextxt)); 17 | 18 | var databases = {}; 19 | 20 | var mongoConnection = process.env['MONGO_DB_CONN'] || "localhost:27017"; 21 | 22 | var performMongoDbRequest = function(databaseName, callback) { 23 | if (databases[databaseName]) { 24 | //console.log("Using cache"); 25 | callback(databases[databaseName]); 26 | } else { 27 | //console.log("Connecting"); 28 | MongoClient.connect("mongodb://" + mongoConnection + "/" + databaseName, function(err, db) { 29 | if (err) { 30 | console.warn(err.message); 31 | process.exit(); 32 | } 33 | //console.log("Connection OK") 34 | databases[databaseName] = db; 35 | callback(db); 36 | }); 37 | } 38 | } 39 | 40 | router.get('/:db/:collection/concepts/:sctid', function(req, res) { 41 | var options = req.params.options || {}; 42 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 43 | for (o in req.query) { 44 | if (test.indexOf(o) >= 0) { 45 | options[o] = JSON.parse(req.query[o]); 46 | } 47 | } 48 | snomedLib.getConcept(req.params.db, req.params.collection, req.params.sctid, options, function(err, doc) { 49 | if (doc) { 50 | res.status(200); 51 | res.header('Content-Type', 'application/json'); 52 | res.send(doc); 53 | } else { 54 | res.status(200); 55 | res.send(err); 56 | } 57 | }); 58 | }); 59 | 60 | router.get('/:db/:collection/concepts/:sctid/descriptions/:descriptionId?', function(req, res) { 61 | var descId = false; 62 | if (req.params.descriptionId) descId = req.params.descriptionId; 63 | var options = req.params.options || {}; 64 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 65 | for (o in req.query) { 66 | if (test.indexOf(o) >= 0) { 67 | if (o == "limit" || o == "skip") { 68 | options[o] = parseInt(req.query[o]); 69 | } else { 70 | options[o] = JSON.parse(req.query[o]); 71 | } 72 | } 73 | } 74 | snomedLib.getDescriptions(req.params.db, req.params.collection, req.params.sctid, descId, options, function(err, docs) { 75 | res.status(200); 76 | res.send(docs); 77 | }); 78 | }); 79 | 80 | router.get('/:db/:collection/concepts/:sctid/relationships?', function(req, res) { 81 | var form = "all"; 82 | if (req.query["form"]) { 83 | form = req.query["form"]; 84 | } 85 | var options = req.params.options || {}; 86 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 87 | for (o in req.query) { 88 | if (test.indexOf(o) >= 0) { 89 | options[o] = JSON.parse(req.query[o]); 90 | } 91 | } 92 | 93 | snomedLib.getRelationShips(req.params.db, req.params.collection, req.params.sctid, form, options, function(err, docs) { 94 | res.status(200); 95 | res.send(docs); 96 | }); 97 | }); 98 | 99 | router.get('/:db/:collection/concepts/:sctid/children?', function(req, res) { 100 | var idParamStr = req.params.sctid; 101 | var query = { "relationships": { "$elemMatch": { "destination.conceptId": idParamStr, "type.conceptId": "116680003", "active": true } } }; 102 | if (req.query["form"]) { 103 | if (req.query["form"] == "inferred") { 104 | query = { 105 | "relationships": { 106 | "$elemMatch": { 107 | "destination.conceptId": idParamStr, 108 | "type.conceptId": "116680003", 109 | "characteristicType.conceptId": "900000000000011006", 110 | "active": true 111 | } 112 | } 113 | }; 114 | } 115 | if (req.query["form"] == "stated") { 116 | query = { 117 | "relationships": { 118 | "$elemMatch": { 119 | "destination.conceptId": idParamStr, 120 | "type.conceptId": "116680003", 121 | "characteristicType.conceptId": "900000000000010007", 122 | "active": true 123 | } 124 | } 125 | }; 126 | } 127 | } 128 | 129 | var options = req.params.options || {}; 130 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 131 | for (o in req.query) { 132 | if (test.indexOf(o) >= 0) { 133 | options[o] = JSON.parse(req.query[o]); 134 | } 135 | } 136 | options["fields"] = { "preferredTerm": 1, "conceptId": 1, "active": 1, "definitionStatus": 1, "module": 1, "isLeafInferred": 1, "isLeafStated": 1, "statedDescendants": 1, "inferredDescendants": 1, "v": 1 }; 137 | snomedLib.getObject(req.params.db, req.params.collection, query, options, function(err, docs) { 138 | res.status(200); 139 | if (!docs) docs = []; 140 | res.send(docs); 141 | }); 142 | }); 143 | 144 | router.get('/:db/:collection/concepts/:sctid/references?', function(req, res) { 145 | var idParamStr = req.params.sctid; 146 | var query = { "relationships": { "$elemMatch": { "destination.conceptId": idParamStr, "active": true } } }; 147 | var options = req.params.options || {}; 148 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 149 | for (o in req.query) { 150 | if (test.indexOf(o) >= 0) { 151 | options[o] = JSON.parse(req.query[o]); 152 | } 153 | } 154 | var typeId = "900000000000011006"; 155 | 156 | if (req.query["form"]) { 157 | if (req.query["form"] == "stated") { 158 | typeId = "900000000000010007"; 159 | 160 | } else if (req.query["form"] == "additional") { 161 | typeId = "900000000000227009"; 162 | 163 | } 164 | } 165 | query = { "relationships": { "$elemMatch": { "destination.conceptId": idParamStr, "characteristicType.conceptId": typeId, "active": true } } }; 166 | options["fields"] = { "relationships": { "$elemMatch": { "destination.conceptId": idParamStr, "characteristicType.conceptId": typeId, "active": true } }, "preferredTerm": 1, "conceptId": 1, "active": 1, "definitionStatus": 1, "effectiveTime": 1, "module": 1, "isLeafInferred": 1, "isLeafStated": 1, "statedDescendants": 1, "inferredDescendants": 1, "v": 1 }; 167 | 168 | snomedLib.getObject(req.params.db, req.params.collection, query, options, function(err, docs) { 169 | if (!docs) docs = []; 170 | res.status(200); 171 | res.send(docs); 172 | }); 173 | }); 174 | 175 | router.get('/:db/:collection/concepts/:sctid/parents?', function(req, res) { 176 | var options = req.params.options || {}; 177 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 178 | for (o in req.query) { 179 | if (test.indexOf(o) >= 0) { 180 | options[o] = JSON.parse(req.query[o]); 181 | } 182 | } 183 | options["fields"] = { "relationships": 1, "v": 1 }; 184 | snomedLib.getParents(req.params.db, req.params.collection, req.params.sctid, req.query["form"], options, function(err, docs) { 185 | if (!docs) docs = []; 186 | res.status(200); 187 | res.send(docs); 188 | }); 189 | }); 190 | 191 | router.get('/:db/:collection/concepts/:sctid/members?', function(req, res) { 192 | var options = req.params.options || {}; 193 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 194 | for (o in req.query) { 195 | if (test.indexOf(o) >= 0) { 196 | options[o] = JSON.parse(req.query[o]); 197 | } 198 | } 199 | options["fields"] = { "preferredTerm": 1, "conceptId": 1, "active": 1, "definitionStatus": 1, "module": 1, "isLeafInferred": 1, "isLeafStated": 1, "statedDescendants": 1, "inferredDescendants": 1, "v": 1 }; 200 | if (!options.limit) { 201 | options.limit = 100; 202 | } 203 | if (!options.skip) { 204 | options.skip = 0; 205 | } 206 | snomedLib.getMembers(req.params.db, req.params.collection, req.params.sctid, options, function(err, docs) { 207 | if (err) { 208 | res.status(400); 209 | if (typeof err == 'boolean') { 210 | res.send("Error: " + docs); 211 | } else { 212 | res.send("Error: " + err); 213 | } 214 | } else { 215 | res.status(200); 216 | res.send(docs); 217 | } 218 | }); 219 | }); 220 | 221 | router.get('/:db/:collection/descriptions/:sctid?', function(req, res) { 222 | var idParamStr = null; 223 | var query = { 'descriptions.descriptionId': 0 }; 224 | var searchMode = "regex"; 225 | var searchTerm = null; 226 | var lang = "english"; 227 | 228 | if (req.query["lang"]) { 229 | lang = req.query["lang"]; 230 | } 231 | var semanticFilter = "none"; 232 | var moduleFilter = "none"; 233 | var langFilter = "none"; 234 | var refsetFilter = "none"; 235 | var statusFilter; 236 | var returnLimit = 100; 237 | var skipTo = 0; 238 | if (req.params.sctid) { 239 | idParamStr = req.params.sctid; 240 | query = { "descriptionId": idParamStr }; 241 | } else { 242 | if (req.query["query"]) { 243 | if (!req.query["statusFilter"]) { 244 | statusFilter = 'activeOnly'; 245 | } else { 246 | statusFilter = req.query["statusFilter"]; 247 | } 248 | //console.log("statusFilter " + statusFilter); 249 | if (!req.query["searchMode"] || req.query["searchMode"] == "partialMatching") { 250 | searchMode = "partialMatching"; 251 | searchTerm = req.query["query"]; 252 | var words = searchTerm.split(" "); 253 | 254 | if (statusFilter == 'inactiveOnly') { 255 | query = { "$and": [], "$or": [{ "active": false }, { "conceptActive": false }] }; 256 | } else if (statusFilter == 'activeAndInactive') { 257 | query = { "$and": [] }; 258 | } else { 259 | query = { "$and": [], "active": true, "conceptActive": true }; 260 | } 261 | 262 | words.forEach(function(word) { 263 | if (req.query["normalize"] && req.query["normalize"] == "true") { 264 | var expWord = "^" + removeDiacritics(regExpEscape(word).toLowerCase()) + ".*"; 265 | //console.log("Normalizing"); 266 | } else { 267 | //console.log("Not normalizing"); 268 | var expWord = "^" + regExpEscape(word).toLowerCase() + ".*"; 269 | } 270 | query.$and.push({ "words": { "$regex": expWord } }); 271 | }); 272 | } else if (req.query["searchMode"] == "fullText") { 273 | //{ $text: { $search: , $language: } } 274 | searchMode = req.query["searchMode"]; 275 | searchTerm = req.query["query"]; 276 | if (statusFilter == 'inactiveOnly') { 277 | query = { "$text": { "$search": searchTerm, "$language": lang }, "$or": [{ "active": false }, { "conceptActive": false }] }; 278 | } else if (statusFilter == 'activeAndInactive') { 279 | query = { "$text": { "$search": searchTerm, "$language": lang } }; 280 | } else { 281 | query = { "$text": { "$search": searchTerm, "$language": lang }, "$and": [{ "active": true }, { "conceptActive": true }] }; 282 | } 283 | } else if (req.query["searchMode"] == "regex") { 284 | searchMode = req.query["searchMode"]; 285 | searchTerm = req.query["query"]; 286 | if (statusFilter == 'inactiveOnly') { 287 | query = { "term": { "$regex": searchTerm }, "$or": [{ "active": false }, { "conceptActive": false }] }; 288 | } else if (statusFilter == 'activeAndInactive') { 289 | query = { "term": { "$regex": searchTerm } }; 290 | } else { 291 | query = { "term": { "$regex": searchTerm }, "$and": [{ "active": true }, { "conceptActive": true }] }; 292 | } 293 | } else { 294 | res.status(400); 295 | res.send("Error: Search mode not supported (" + req.query["searchMode"] + ")"); 296 | } 297 | } else { 298 | res.status(400); 299 | res.send("Error: Missing Query and missing SCTID, no search parameters."); 300 | } 301 | } 302 | 303 | if (req.query["semanticFilter"]) { 304 | semanticFilter = req.query["semanticFilter"]; 305 | } 306 | if (req.query["moduleFilter"]) { 307 | moduleFilter = req.query["moduleFilter"]; 308 | } 309 | if (req.query["langFilter"]) { 310 | langFilter = req.query["langFilter"]; 311 | } 312 | if (req.query["refsetFilter"]) { 313 | refsetFilter = req.query["refsetFilter"]; 314 | } 315 | if (req.query["returnLimit"]) { 316 | returnLimit = parseInt(req.query["returnLimit"]); 317 | } 318 | if (req.query["skipTo"]) { 319 | skipTo = parseInt(req.query["skipTo"]); 320 | } 321 | 322 | var groupByConcept = false; 323 | if (req.query["groupByConcept"]) 324 | groupByConcept = req.query["groupByConcept"]; 325 | 326 | var filters = { 327 | idParamStr: idParamStr, 328 | groupByConcept: groupByConcept, 329 | searchMode: searchMode, 330 | lang: lang, 331 | semanticFilter: semanticFilter, 332 | moduleFilter: moduleFilter, 333 | langFilter: langFilter, 334 | refsetFilter: refsetFilter, 335 | skipTo: skipTo, 336 | returnLimit: returnLimit 337 | }; 338 | 339 | var options = req.params.options || {}; 340 | var test = ['limit', 'sort', 'fields', 'skip', 'hint', 'explain', 'snapshot', 'timeout']; 341 | for (o in req.query) { 342 | if (test.indexOf(o) >= 0) { 343 | options[o] = JSON.parse(req.query[o]); 344 | } 345 | } 346 | options["limit"] = 10000000; 347 | 348 | if (searchMode == "regex" || searchMode == "partialMatching" || searchMode == "fullText") { 349 | snomedLib.searchDescription(req.params.db, req.params.collection, filters, query, options, function(err, docs) { 350 | res.status(200); 351 | res.send(docs); 352 | }); 353 | } else { 354 | res.status(400); 355 | res.send("Error: Search mode not supported (" + req.query["searchMode"] + ")"); 356 | } 357 | }); 358 | 359 | var levDist = function(s, t) { 360 | var d = []; //2d matrix 361 | 362 | // Step 1 363 | var n = s.length; 364 | var m = t.length; 365 | 366 | if (n == 0) 367 | return m; 368 | if (m == 0) 369 | return n; 370 | 371 | //Create an array of arrays in javascript (a descending loop is quicker) 372 | for (var i = n; i >= 0; i--) 373 | d[i] = []; 374 | 375 | // Step 2 376 | for (var i = n; i >= 0; i--) 377 | d[i][0] = i; 378 | for (var j = m; j >= 0; j--) 379 | d[0][j] = j; 380 | 381 | // Step 3 382 | for (var i = 1; i <= n; i++) { 383 | var s_i = s.charAt(i - 1); 384 | 385 | // Step 4 386 | for (var j = 1; j <= m; j++) { 387 | 388 | //Check the jagged ld total so far 389 | if (i == j && d[i][j] > 4) 390 | return n; 391 | 392 | var t_j = t.charAt(j - 1); 393 | var cost = (s_i == t_j) ? 0 : 1; // Step 5 394 | 395 | //Calculate the minimum 396 | var mi = d[i - 1][j] + 1; 397 | var b = d[i][j - 1] + 1; 398 | var c = d[i - 1][j - 1] + cost; 399 | 400 | if (b < mi) 401 | mi = b; 402 | if (c < mi) 403 | mi = c; 404 | 405 | d[i][j] = mi; // Step 6 406 | 407 | //Damerau transposition 408 | if (i > 1 && j > 1 && s_i == t.charAt(j - 2) && s.charAt(i - 2) == t_j) { 409 | d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost); 410 | } 411 | } 412 | } 413 | 414 | // Step 7 415 | return d[n][m]; 416 | } 417 | 418 | var defaultDiacriticsRemovalMap = [ 419 | { 'base': 'a', 'letters': /[\u00E1\u00E2\u00E3\u00E4\u00E5\u0101\u0103\u0105\u01CE\u01FB\u00C0\u00C4]/g }, 420 | { 'base': 'ae', 'letters': /[\u00E6\u01FD]/g }, 421 | { 'base': 'c', 'letters': /[\u00E7\u0107\u0109\u010B\u010D]/g }, 422 | { 'base': 'd', 'letters': /[\u010F\u0111\u00F0]/g }, 423 | { 'base': 'e', 'letters': /[\u00E8\u00E9\u00EA\u00EB\u0113\u0115\u0117\u0119\u011B]/g }, 424 | { 'base': 'f', 'letters': /[\u0192]/g }, 425 | { 'base': 'g', 'letters': /[\u011D\u011F\u0121\u0123]/g }, 426 | { 'base': 'h', 'letters': /[\u0125\u0127]/g }, 427 | { 'base': 'i', 'letters': /[\u00ED\u00EC\u00EE\u00EF\u0129\u012B\u012D\u012F\u0131]/g }, 428 | { 'base': 'ij', 'letters': /[\u0133]/g }, 429 | { 'base': 'j', 'letters': /[\u0135]/g }, 430 | { 'base': 'k', 'letters': /[\u0137\u0138]/g }, 431 | { 'base': 'l', 'letters': /[\u013A\u013C\u013E\u0140\u0142]/g }, 432 | { 'base': 'n', 'letters': /[\u00F1\u0144\u0146\u0148\u0149\u014B]/g }, 433 | { 'base': 'o', 'letters': /[\u00F2\u00F3\u00F4\u00F5\u00F6\u014D\u014F\u0151\u01A1\u01D2\u01FF]/g }, 434 | { 'base': 'oe', 'letters': /[\u0153]/g }, 435 | { 'base': 'r', 'letters': /[\u0155\u0157\u0159]/g }, 436 | { 'base': 's', 'letters': /[\u015B\u015D\u015F\u0161]/g }, 437 | { 'base': 't', 'letters': /[\u0163\u0165\u0167]/g }, 438 | { 'base': 'u', 'letters': /[\u00F9\u00FA\u00FB\u00FC\u0169\u016B\u016B\u016D\u016F\u0171\u0173\u01B0\u01D4\u01D6\u01D8\u01DA\u01DC]/g }, 439 | { 'base': 'w', 'letters': /[\u0175]/g }, 440 | { 'base': 'y', 'letters': /[\u00FD\u00FF\u0177]/g }, 441 | { 'base': 'z', 'letters': /[\u017A\u017C\u017E]/g }, 442 | { 'base': 'A', 'letters': /[\u00C1\u00C2\u00C3\uCC04\u00C5\u00E0\u0100\u0102\u0104\u01CD\u01FB]/g }, 443 | { 'base': 'AE', 'letters': /[\u00C6]/g }, 444 | { 'base': 'C', 'letters': /[\u00C7\u0106\u0108\u010A\u010C]/g }, 445 | { 'base': 'D', 'letters': /[\u010E\u0110\u00D0]/g }, 446 | { 'base': 'E', 'letters': /[\u00C8\u00C9\u00CA\u00CB\u0112\u0114\u0116\u0118\u011A]/g }, 447 | { 'base': 'G', 'letters': /[\u011C\u011E\u0120\u0122]/g }, 448 | { 'base': 'H', 'letters': /[\u0124\u0126]/g }, 449 | { 'base': 'I', 'letters': /[\u00CD\u00CC\u00CE\u00CF\u0128\u012A\u012C\u012E\u0049]/g }, 450 | { 'base': 'IJ', 'letters': /[\u0132]/g }, 451 | { 'base': 'J', 'letters': /[\u0134]/g }, 452 | { 'base': 'K', 'letters': /[\u0136]/g }, 453 | { 'base': 'L', 'letters': /[\u0139\u013B\u013D\u013F\u0141]/g }, 454 | { 'base': 'N', 'letters': /[\u00D1\u0143\u0145\u0147\u0149\u014A]/g }, 455 | { 'base': 'O', 'letters': /[\u00D2\u00D3\u00D4\u00D5\u00D6\u014C\u014E\u0150\u01A0\u01D1]/g }, 456 | { 'base': 'OE', 'letters': /[\u0152]/g }, 457 | { 'base': 'R', 'letters': /[\u0154\u0156\u0158]/g }, 458 | { 'base': 'S', 'letters': /[\u015A\u015C\u015E\u0160]/g }, 459 | { 'base': 'T', 'letters': /[\u0162\u0164\u0166]/g }, 460 | { 'base': 'U', 'letters': /[\u00D9\u00DA\u00DB\u00DC\u0168\u016A\u016C\u016E\u0170\u0172\u01AF\u01D3\u01D5\u01D7\u01D9\u01DB]/g }, 461 | { 'base': 'W', 'letters': /[\u0174]/g }, 462 | { 'base': 'Y', 'letters': /[\u0178\u0176]/g }, 463 | { 'base': 'Z', 'letters': /[\u0179\u017B\u017D]/g }, 464 | // Greek letters 465 | { 'base': 'ALPHA', 'letters': /[\u0391\u03B1]/g }, 466 | { 'base': 'BETA', 'letters': /[\u0392\u03B2]/g }, 467 | { 'base': 'GAMMA', 'letters': /[\u0393\u03B3]/g }, 468 | { 'base': 'DELTA', 'letters': /[\u0394\u03B4]/g }, 469 | { 'base': 'EPSILON', 'letters': /[\u0395\u03B5]/g }, 470 | { 'base': 'ZETA', 'letters': /[\u0396\u03B6]/g }, 471 | { 'base': 'ETA', 'letters': /[\u0397\u03B7]/g }, 472 | { 'base': 'THETA', 'letters': /[\u0398\u03B8]/g }, 473 | { 'base': 'IOTA', 'letters': /[\u0399\u03B9]/g }, 474 | { 'base': 'KAPPA', 'letters': /[\u039A\u03BA]/g }, 475 | { 'base': 'LAMBDA', 'letters': /[\u039B\u03BB]/g }, 476 | { 'base': 'MU', 'letters': /[\u039C\u03BC]/g }, 477 | { 'base': 'NU', 'letters': /[\u039D\u03BD]/g }, 478 | { 'base': 'XI', 'letters': /[\u039E\u03BE]/g }, 479 | { 'base': 'OMICRON', 'letters': /[\u039F\u03BF]/g }, 480 | { 'base': 'PI', 'letters': /[\u03A0\u03C0]/g }, 481 | { 'base': 'RHO', 'letters': /[\u03A1\u03C1]/g }, 482 | { 'base': 'SIGMA', 'letters': /[\u03A3\u03C3]/g }, 483 | { 'base': 'TAU', 'letters': /[\u03A4\u03C4]/g }, 484 | { 'base': 'UPSILON', 'letters': /[\u03A5\u03C5]/g }, 485 | { 'base': 'PHI', 'letters': /[\u03A6\u03C6]/g }, 486 | { 'base': 'CHI', 'letters': /[\u03A7\u03C7]/g }, 487 | { 'base': 'PSI', 'letters': /[\u03A8\u03C8]/g }, 488 | { 'base': 'OMEGA', 'letters': /[\u03A9\u03C9]/g } 489 | 490 | 491 | ]; 492 | var changes; 493 | 494 | var removeDiacritics = function(str) { 495 | if (!changes) { 496 | changes = defaultDiacriticsRemovalMap; 497 | } 498 | for (var i = 0; i < changes.length; i++) { 499 | str = str.replace(changes[i].letters, changes[i].base); 500 | } 501 | return str; 502 | }; 503 | 504 | var regExpEscape = function(s) { 505 | return String(s).replace(/([-()\[\]{}+?*.$\^|,:#&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $TIMEOUT -gt 0 ]]; then 28 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 29 | else 30 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 31 | fi 32 | start_ts=$(date +%s) 33 | while : 34 | do 35 | if [[ $ISBUSY -eq 1 ]]; then 36 | nc -z $HOST $PORT 37 | result=$? 38 | else 39 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 40 | result=$? 41 | fi 42 | if [[ $result -eq 0 ]]; then 43 | end_ts=$(date +%s) 44 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 45 | break 46 | fi 47 | sleep 1 48 | done 49 | return $result 50 | } 51 | 52 | wait_for_wrapper() 53 | { 54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 55 | if [[ $QUIET -eq 1 ]]; then 56 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 57 | else 58 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 59 | fi 60 | PID=$! 61 | trap "kill -INT -$PID" INT 62 | wait $PID 63 | RESULT=$? 64 | if [[ $RESULT -ne 0 ]]; then 65 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 66 | fi 67 | return $RESULT 68 | } 69 | 70 | # process arguments 71 | while [[ $# -gt 0 ]] 72 | do 73 | case "$1" in 74 | *:* ) 75 | hostport=(${1//:/ }) 76 | HOST=${hostport[0]} 77 | PORT=${hostport[1]} 78 | shift 1 79 | ;; 80 | --child) 81 | CHILD=1 82 | shift 1 83 | ;; 84 | -q | --quiet) 85 | QUIET=1 86 | shift 1 87 | ;; 88 | -s | --strict) 89 | STRICT=1 90 | shift 1 91 | ;; 92 | -h) 93 | HOST="$2" 94 | if [[ $HOST == "" ]]; then break; fi 95 | shift 2 96 | ;; 97 | --host=*) 98 | HOST="${1#*=}" 99 | shift 1 100 | ;; 101 | -p) 102 | PORT="$2" 103 | if [[ $PORT == "" ]]; then break; fi 104 | shift 2 105 | ;; 106 | --port=*) 107 | PORT="${1#*=}" 108 | shift 1 109 | ;; 110 | -t) 111 | TIMEOUT="$2" 112 | if [[ $TIMEOUT == "" ]]; then break; fi 113 | shift 2 114 | ;; 115 | --timeout=*) 116 | TIMEOUT="${1#*=}" 117 | shift 1 118 | ;; 119 | --) 120 | shift 121 | CLI=("$@") 122 | break 123 | ;; 124 | --help) 125 | usage 126 | ;; 127 | *) 128 | echoerr "Unknown argument: $1" 129 | usage 130 | ;; 131 | esac 132 | done 133 | 134 | if [[ "$HOST" == "" || "$PORT" == "" ]]; then 135 | echoerr "Error: you need to provide a host and port to test." 136 | usage 137 | fi 138 | 139 | TIMEOUT=${TIMEOUT:-15} 140 | STRICT=${STRICT:-0} 141 | CHILD=${CHILD:-0} 142 | QUIET=${QUIET:-0} 143 | 144 | # check to see if timeout is from busybox? 145 | # check to see if timeout is from busybox? 146 | TIMEOUT_PATH=$(realpath $(which timeout)) 147 | if [[ $TIMEOUT_PATH =~ "busybox" ]]; then 148 | ISBUSY=1 149 | BUSYTIMEFLAG="-t" 150 | else 151 | ISBUSY=0 152 | BUSYTIMEFLAG="" 153 | fi 154 | 155 | if [[ $CHILD -gt 0 ]]; then 156 | wait_for 157 | RESULT=$? 158 | exit $RESULT 159 | else 160 | if [[ $TIMEOUT -gt 0 ]]; then 161 | wait_for_wrapper 162 | RESULT=$? 163 | else 164 | wait_for 165 | RESULT=$? 166 | fi 167 | fi 168 | 169 | if [[ $CLI != "" ]]; then 170 | if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then 171 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 172 | exit $RESULT 173 | fi 174 | exec "${CLI[@]}" 175 | else 176 | exit $RESULT 177 | fi 178 |   179 | --------------------------------------------------------------------------------