├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── bin ├── .eslintrc └── hdt ├── binding.gyp ├── lib ├── HdtDocument.cc ├── HdtDocument.h ├── hdt.cc ├── hdt.d.ts └── hdt.js ├── package-lock.json ├── package.json ├── perf └── run.js ├── replace-in-file.json └── test ├── .eslintrc ├── .gitignore ├── hdt-test.js ├── literals.hdt ├── literals.ttl ├── mocha.opts ├── test.hdt ├── test.ttl ├── test2.hdt ├── test2.ttl └── testexport.nt /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | env: { 3 | node: true, 4 | }, 5 | parserOptions: { 6 | ecmaVersion: 6 7 | }, 8 | globals: { 9 | Promise: true 10 | }, 11 | rules: { 12 | // Possible Errors 13 | comma-dangle: [2, "always-multiline"], 14 | no-cond-assign: 0, 15 | no-console: 2, 16 | no-constant-condition: 0, 17 | no-debugger: 2, 18 | no-dupe-args: 2, 19 | no-dupe-keys: 2, 20 | no-duplicate-case: 2, 21 | no-empty: 2, 22 | no-empty-character-class: 2, 23 | no-ex-assign: 0, 24 | no-extra-boolean-cast: 2, 25 | no-extra-parens: 0, 26 | no-extra-semi: 2, 27 | no-func-assign: 2, 28 | no-inner-declarations: 0, 29 | no-invalid-regexp: 2, 30 | no-irregular-whitespace: 2, 31 | no-negated-in-lhs: 2, 32 | no-obj-calls: 2, 33 | no-regex-spaces: 2, 34 | no-sparse-arrays: 2, 35 | no-unreachable: 2, 36 | use-isnan: 2, 37 | valid-jsdoc: 0, 38 | valid-typeof: 2, 39 | no-unexpected-multiline: 2, 40 | 41 | // Best Practices 42 | accessor-pairs: 2, 43 | block-scoped-var: 2, 44 | complexity: 0, 45 | consistent-return: 0, 46 | curly: [2, "multi-or-nest"], 47 | default-case: 0, 48 | dot-notation: 2, 49 | dot-location: [2, "property"], 50 | eqeqeq: 2, 51 | guard-for-in: 0, 52 | no-alert: 2, 53 | no-caller: 2, 54 | no-div-regex: 2, 55 | no-else-return: 0, 56 | no-labels: 2, 57 | no-eq-null: 2, 58 | no-eval: 2, 59 | no-extend-native: 2, 60 | no-extra-bind: 2, 61 | no-fallthrough: 2, 62 | no-floating-decimal: 2, 63 | no-implicit-coercion: 0, 64 | no-implied-eval: 2, 65 | no-invalid-this: 2, 66 | no-iterator: 2, 67 | no-lone-blocks: 2, 68 | no-loop-func: 0, 69 | no-multi-spaces: 0, 70 | no-multi-str: 2, 71 | no-native-reassign: 2, 72 | no-new-func: 2, 73 | no-new-wrappers: 2, 74 | no-new: 2, 75 | no-octal-escape: 2, 76 | no-octal: 2, 77 | no-param-reassign: 0, 78 | no-process-env: 2, 79 | no-proto: 2, 80 | no-redeclare: 2, 81 | no-return-assign: 0, 82 | no-script-url: 2, 83 | no-self-compare: 2, 84 | no-sequences: 0, // allow the comma operator 85 | no-throw-literal: 2, 86 | no-unused-expressions: 0, 87 | no-useless-call: 2, 88 | no-void: 2, 89 | no-warning-comments: 0, 90 | no-with: 2, 91 | radix: 2, 92 | vars-on-top: 0, 93 | wrap-iife: [2, "inside"], 94 | yoda: 2, 95 | 96 | // Strict Mode 97 | strict: [2, "never"], 98 | 99 | // Variables 100 | init-declarations: 0, 101 | no-catch-shadow: 0, 102 | no-delete-var: 2, 103 | no-label-var: 2, 104 | no-shadow-restricted-names: 2, 105 | no-shadow: 0, 106 | no-undef-init: 2, 107 | no-undef: 2, 108 | no-undefined: 0, 109 | no-unused-vars: [ 2, { args: "none" }], 110 | no-use-before-define: [2, "nofunc"], 111 | 112 | // Node.js 113 | callback-return: 0, 114 | handle-callback-err: 2, 115 | no-mixed-requires: 0, 116 | no-new-require: 2, 117 | no-path-concat: 2, 118 | no-process-exit: 0, 119 | no-restricted-modules: 2, 120 | no-sync: 0, 121 | 122 | // Stylistic Issues 123 | array-bracket-spacing: 2, 124 | block-spacing: 2, 125 | brace-style: [2, "stroustrup", { allowSingleLine: true }], 126 | camelcase: 2, 127 | comma-spacing: 2, 128 | comma-style: 2, 129 | computed-property-spacing: 2, 130 | consistent-this: 0, 131 | eol-last: 2, 132 | func-names: 0, 133 | func-style: [2, "declaration"], 134 | id-length: 0, 135 | id-match: 2, 136 | indent-legacy: [2, 2, { VariableDeclarator: 2 }], 137 | key-spacing: 0, 138 | lines-around-comment: 2, 139 | linebreak-style: 2, 140 | max-nested-callbacks: [2, 3], 141 | new-cap: 2, 142 | new-parens: 2, 143 | newline-after-var: 0, 144 | no-array-constructor: 2, 145 | no-continue: 2, 146 | no-inline-comments: 0, 147 | no-lonely-if: 2, 148 | no-mixed-spaces-and-tabs: 2, 149 | no-multiple-empty-lines: 0, 150 | no-nested-ternary: 0, 151 | no-new-object: 2, 152 | no-spaced-func: 2, 153 | no-ternary: 0, 154 | no-trailing-spaces: 2, 155 | no-underscore-dangle: 0, 156 | no-unneeded-ternary: 2, 157 | object-curly-spacing: [2, "always"], 158 | object-curly-newline: 0, 159 | object-property-newline: 0, 160 | one-var: 0, 161 | operator-assignment: 2, 162 | operator-linebreak: [2, "after", { overrides: { ":": "ignore" } }], 163 | padded-blocks: [2, "never"], 164 | quote-props: [2, "consistent-as-needed"], 165 | quotes: [2, "single", "avoid-escape"], 166 | semi-spacing: 2, 167 | semi: 2, 168 | sort-vars: 0, 169 | keyword-spacing: 2, 170 | space-before-blocks: 2, 171 | space-before-function-paren: [2, {"anonymous": "always", "named": "never"}], 172 | space-in-parens: 2, 173 | space-infix-ops: 2, 174 | space-unary-ops: 2, 175 | spaced-comment: [2, "always", { block: { markers: ["!"] } }], 176 | wrap-regex: 0, 177 | }, 178 | } 179 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.ref }} 6 | cancel-in-progress: true 7 | 8 | jobs: 9 | 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - ubuntu-20.04 17 | - ubuntu-22.04 18 | - ubuntu-24.04 19 | - ubuntu-latest 20 | - macos-13 21 | - macos-14 22 | - macos-15 23 | - macos-latest 24 | node-version: 25 | - 18.x 26 | - 20.x 27 | - 22.x 28 | steps: 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | - name: Ensure line endings are consistent 34 | run: git config --global core.autocrlf input 35 | - name: Check out repository 36 | uses: actions/checkout@v3 37 | with: 38 | submodules: 'recursive' 39 | - name: Load cache 40 | uses: actions/cache@v3 41 | with: 42 | path: | 43 | **/node_modules 44 | .rdf-test-suite-cache 45 | .rdf-test-suite-ldf-cache 46 | key: ${{ runner.os }}-${{ runner.node-version }}-test-modules-${{ hashFiles('**/package-lock.json') }} 47 | - name: Install dependencies 48 | run: npm install 49 | - name: Run tests 50 | run: npm run test 51 | - name: Run bin 52 | run: bin/hdt test/test.hdt --format turtle --query 'http://example.org/s1 ?p ?o' | grep 'http://example.org/p1' 53 | 54 | lint: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - name: Use Node.js 58 | uses: actions/setup-node@v3 59 | with: 60 | node-version: 18.x 61 | - name: Check out repository 62 | uses: actions/checkout@v3 63 | with: 64 | submodules: 'recursive' 65 | - name: Load cache 66 | uses: actions/cache@v3 67 | with: 68 | path: '**/node_modules' 69 | key: ${{ runner.os }}-lint-modules-${{ hashFiles('**/package-lock.json') }} 70 | - name: Install dependencies 71 | run: npm install 72 | - name: Run linter 73 | run: npm run lint 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.cproject 2 | *.o 3 | *.o.d 4 | *.log 5 | *.hdt.index* 6 | build 7 | node_modules 8 | test 9 | deps/hdt-it/**/* 10 | **/Doxyfile 11 | deps/libcds/tutorial/* 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps"] 2 | path = deps 3 | url = https://github.com/rdfhdt/hdt-cpp.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright © 2007 Free Software Foundation, Inc. 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 8 | 9 | This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 10 | 11 | 0. Additional Definitions. 12 | As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. 13 | 14 | “The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. 15 | 16 | An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. 17 | 18 | A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. 19 | 20 | The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. 21 | 22 | The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 23 | 24 | 1. Exception to Section 3 of the GNU GPL. 25 | You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 26 | 27 | 2. Conveying Modified Versions. 28 | If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: 29 | 30 | a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or 31 | b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 32 | 3. Object Code Incorporating Material from Library Header Files. 33 | The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: 34 | 35 | a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. 36 | b) Accompany the object code with a copy of the GNU GPL and this license document. 37 | 4. Combined Works. 38 | You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: 39 | 40 | a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. 41 | b) Accompany the Combined Work with a copy of the GNU GPL and this license document. 42 | c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. 43 | d) Do one of the following: 44 | 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 45 | 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. 46 | e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 47 | 5. Combined Libraries. 48 | You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: 49 | 50 | a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. 51 | b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 52 | 6. Revised Versions of the GNU Lesser General Public License. 53 | The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 54 | 55 | Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. 56 | 57 | If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HDT for Node.js 2 | [![npm version](https://badge.fury.io/js/hdt.svg)](https://www.npmjs.com/package/hdt) 3 | [![Build status](https://github.com/LinkedDataFragments/HDT-Node/workflows/CI/badge.svg)](https://github.com/LinkedDataFragments/HDT-Node/actions?query=workflow%3ACI) 4 | [![Dependency Status](https://david-dm.org/RubenVerborgh/HDT-Node.svg)](https://david-dm.org/RubenVerborgh/HDT-Node) 5 | [![devDependency Status](https://david-dm.org/RubenVerborgh/HDT-Node/dev-status.svg)](https://david-dm.org/RubenVerborgh/HDT-Node#info=devDependencies) 6 | 7 | [HDT (Header Dictionary Triples)](http://www.rdfhdt.org/) is a compressed format 8 | for [RDF triples](http://www.w3.org/TR/2014/REC-rdf11-concepts-20140225/#data-model). 9 |
10 | The `hdt` npm package for Node.js brings fast access to HDT files through C bindings. 11 | 12 | 13 | ## Usage 14 | 15 | ### Importing the library 16 | Install the library by adding `hdt` to your `package.json` or executing 17 | 18 | ```bash 19 | $ npm install hdt 20 | ``` 21 | 22 | Then require the library. 23 | 24 | ```JavaScript 25 | const hdt = require('hdt'); 26 | ``` 27 | 28 | ### Opening and closing an HDT document 29 | Open an HDT document with `hdt.fromFile`, 30 | which takes a filename as argument and returns the HDT document in a promise. 31 | Close the document with `close`. 32 | 33 | ```JavaScript 34 | hdt.fromFile('./test/test.hdt').then(function(hdtDocument) { 35 | // Don't forget to close the document when you're done 36 | return hdtDocument.close(); 37 | }); 38 | ``` 39 | 40 | ### Searching for triples matching a pattern 41 | Search for triples with `search`, 42 | which takes subject, predicate, object, and options arguments. 43 | Subject, predicate, and object can be IRIs or literals, 44 | [represented as simple strings](https://github.com/RubenVerborgh/N3.js#triple-representation). 45 | If any of these parameters is `null` or a variable, it is considered a wildcard. 46 | Optionally, an offset and limit can be passed in an options object, 47 | selecting only the specified subset. 48 | 49 | The promise returns an object with an array of triples, the total number of expected triples for the pattern, 50 | and whether the total count is an estimate or exact. 51 | 52 | ```JavaScript 53 | var doc; 54 | hdt.fromFile('./test/test.hdt') 55 | .then(function(hdtDocument) { 56 | doc = hdtDocument; 57 | return doc.searchTriples('http://example.org/s1', null, null, { offset: 0, limit: 10 }) 58 | }) 59 | .then(function(result) { 60 | console.log('Approximately ' + result.totalCount + ' triples match the pattern.'); 61 | result.triples.forEach(function (triple) { console.log(triple); }); 62 | return doc.close(); 63 | }); 64 | ``` 65 | 66 | ### Counting triples matching a pattern 67 | Retrieve an estimate of the total number of triples matching a pattern with `count`, 68 | which takes subject, predicate, and object arguments. 69 | 70 | ```JavaScript 71 | var doc; 72 | hdt.fromFile('./test/test.hdt') 73 | .then(function(hdtDocument) { 74 | doc = hdtDocument; 75 | return doc.countTriples('http://example.org/s1', null, null); 76 | }) 77 | .then(function(result) { 78 | console.log('Approximately ' + result.totalCount + ' triples match the pattern.'); 79 | return doc.close() 80 | }); 81 | ``` 82 | 83 | ### Searching for bindings matching a pattern 84 | Search for [bindings](https://rdf.js.org/query-spec/#bindings-interface) with `searchBindings`, 85 | which takes [bindingsFactory](https://rdf.js.org/query-spec/#bindingsfactory-interface), subject, predicate, object, and options arguments. 86 | Subject, predicate, and object can be IRIs, literals, or variables, 87 | [represented as RDF/JS terms](https://rdf.js.org/data-model-spec/#term-interface). 88 | If any of these parameters is a variable, it is considered a wildcard. 89 | Optionally, an offset and limit can be passed in an options object, 90 | selecting only the specified subset. 91 | 92 | The promise returns an object with an array of bindings, the total number of expected bindings for the pattern, 93 | and whether the total count is an estimate or exact. 94 | 95 | If variables are reused across terms, this library will make sure to only return bindings when matches for those variables are equal. 96 | 97 | ```JavaScript 98 | const DF = new (require('rdf-data-factory').DataFactory)(); 99 | const BF = new (require('@comunica/utils-bindings-factory').BindingsFactory)(DF); 100 | 101 | var doc; 102 | hdt.fromFile('./test/test.hdt') 103 | .then(function(hdtDocument) { 104 | doc = hdtDocument; 105 | return doc.searchBindings(DF.namedNode('http://example.org/s1'), DF.variable('p'), DF.variable('o'), { offset: 0, limit: 10 }) 106 | }) 107 | .then(function(result) { 108 | console.log('Approximately ' + result.totalCount + ' bindings match the pattern.'); 109 | result.bindings.forEach(function (binding) { console.log(binding.toString()); }); 110 | return doc.close(); 111 | }); 112 | ``` 113 | 114 | ### Search terms starting with a prefix 115 | Find terms (literals and IRIs) that start with a given prefix. 116 | 117 | ```JavaScript 118 | hdtDocument.searchTerms({ prefix: 'http://example.org/', limit: 100, position: 'object' }) 119 | .then(function(suggestions) { 120 | console.log('Found ' + suggestions.length + ' suggestions'); 121 | return hdtDocument.close(); 122 | }); 123 | ``` 124 | 125 | ### Fetching unique predicates for a subject and/or an object 126 | 127 | Find all unique predicates for a given subject argument. 128 | 129 | ```JavaScript 130 | hdtDocument.searchTerms({ subject: 'http://example.org/s1' limit: 10, position: 'predicate' }) 131 | .then(function(terms) { 132 | console.log('Found ' + terms.length + ' unique predicates'); 133 | return hdtDocument.close(); 134 | }); 135 | ``` 136 | 137 | Find all unique predicates for a given object argument. 138 | 139 | ```JavaScript 140 | hdtDocument.searchTerms({ object: 'http://example.org/o1', limit: 10, position: 'predicate' }) 141 | .then(function(terms) { 142 | console.log('Found ' + terms.length + ' unique predicates'); 143 | return hdtDocument.close(); 144 | }); 145 | ``` 146 | 147 | Find all unique predicates for given subject and object arguments. 148 | 149 | ```JavaScript 150 | hdtDocument.searchTerms({ subject: 'http://example.org/s1', object: 'http://example.org/o1', limit: 10, position: 'predicate' }) 151 | .then(function(terms) { 152 | console.log('Found ' + terms.length + ' unique predicates'); 153 | return hdtDocument.close(); 154 | }); 155 | ``` 156 | 157 | ### Searching literals containing a substring 158 | In an HDT file that was [generated with an FM index](https://github.com/LinkedDataFragments/hdt-cpp/blob/master/hdt-lib/presets/fmindex.hdtcfg), 159 | you can search for literals that contain a certain substring. 160 | 161 | ```JavaScript 162 | var doc; 163 | hdt.fromFile('./test/test.hdt') 164 | .then(function(hdtDocument) { 165 | doc = hdtDocument; 166 | return doc.searchLiterals('b', { offset: 0, limit: 5 }); 167 | }) 168 | .then(function(result) { 169 | console.log('Approximately ' + result.totalCount + ' literals contain the pattern.'); 170 | result.literals.forEach(function (literal) { console.log(literal); }); 171 | return doc.close(); 172 | }); 173 | ``` 174 | 175 | ### Reading the header 176 | HDT supports reading the header as string using `document.readHeader()`. 177 | The example below reads the header as string, and parses the header using the [N3.js](https://github.com/RubenVerborgh/N3.js/) library. 178 | 179 | ```JavaScript 180 | var N3 = require('n3'); 181 | var doc; 182 | var parser = N3.Parser(); 183 | hdt.fromFile('./test/test.hdt') 184 | .then(function(hdtDocument) { 185 | doc = hdtDocument; 186 | return doc.readHeader(); 187 | }) 188 | .then(function(header) { 189 | var triples = []; 190 | return new Promise(function(resolve, reject) { 191 | parser.parse(header, function(error, triple) { 192 | if (error) return reject(error); 193 | if (triple) return triples.push(triple); 194 | resolve(triples); 195 | }); 196 | }); 197 | }) 198 | .then(function(triples) { 199 | console.log('Read triples from header:\n', triples); 200 | }) 201 | .catch(function(e) { 202 | console.error(e); 203 | }) 204 | ``` 205 | ### Changing the header 206 | To replace header information of an HDT, use `document.changeHeader(header, toFile)`, that returns an HDT document of the output file. 207 | The example below serializes an [N3](https://github.com/RubenVerborgh/N3.js/) triples object into an N-Triples string, and stores it in the header. 208 | 209 | ```JavaScript 210 | var N3 = require('n3'); 211 | var doc; 212 | var outputFile = './out.hdt'; 213 | 214 | hdt.fromFile('./test/test.hdt') 215 | .then(function(hdtDocument) { 216 | doc = hdtDocument; 217 | return new Promise(function(resolve, reject) { 218 | var writer = N3.Writer({format: 'N-Triples'}); 219 | writer.addTriple('http://example.org/cartoons#Tom', 220 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 221 | 'http://example.org/cartoons#Cat'); 222 | writer.end(function(error, triples) { 223 | if (error) return reject(error); 224 | resolve(triples); 225 | }); 226 | }); 227 | }) 228 | .then(function(triples) { 229 | return doc.changeHeader(triples, outputFile); 230 | }) 231 | .then(function(createdDocument) { 232 | return createdDocument.readHeader(); 233 | }) 234 | .then(function(result) { 235 | console.log('Wrote ' + result + ' to ' + outputFile); 236 | }) 237 | .catch(function(e) { 238 | console.error(e); 239 | }); 240 | ``` 241 | 242 | ## Standalone utility 243 | The standalone utility `hdt` allows you to query HDT files from the command line. 244 |
245 | To install system-wide, execute: 246 | ```bash 247 | sudo npm install -g hdt 248 | ``` 249 | 250 | Specify queries as follows: 251 | ``` 252 | hdt dataset.hdt --query '?s ?p ?o' --offset 200 --limit 100 --format turtle 253 | ``` 254 | Replace any of the query variables by an [IRI or literal](https://github.com/RubenVerborgh/N3.js#triple-representation) to match specific patterns. 255 | 256 | ## Build manually 257 | To build the module from source, follow these instructions: 258 | ```Shell 259 | git clone https://github.com/RubenVerborgh/HDT-Node.git hdt 260 | cd hdt 261 | git submodule init 262 | git submodule update 263 | npm install 264 | npm test 265 | ``` 266 | 267 | If you make changes to the source, do the following to rebuild: 268 | ```bash 269 | node-gyp build && npm test 270 | ``` 271 | 272 | 273 | ## License 274 | 275 | The Node.js bindings for HDT are written by [Ruben Verborgh](http://ruben.verborgh.org). 276 | 277 | This code is copyrighted by Ruben Verborgh and released under the [GNU Lesser General Public License](http://opensource.org/licenses/LGPL-3.0). 278 | It uses the [HDT C++ Library](https://github.com/rdfhdt/hdt-cpp), released under the same license. 279 | -------------------------------------------------------------------------------- /bin/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | rules: { 3 | no-console: 0, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /bin/hdt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Parse command-line arguments 3 | var args = require('minimist')(process.argv.slice(2), { alias: 4 | { query: 'q', offset: 'o', limit: 'l', format: 'f' }, 5 | }), 6 | hdtFile = args._[0], 7 | query = typeof args.query === 'string' ? args.query : '', 8 | format = typeof args.format === 'string' ? args.format : 'text/turtle', 9 | offset = /^\d+$/.test(args.offset) ? args.offset : 0, 10 | limit = /^\d+$/.test(args.limit) ? args.limit : Infinity; 11 | 12 | // Verify the arguments 13 | if (args._.length !== 1 || args.h || args.help) { 14 | console.error("usage: hdt dataset.hdt --query '?s ?p ?o' --offset 200 --limit 100 --format turtle"); 15 | process.exit(1); 16 | } 17 | 18 | var hdt = require('../lib/hdt'), 19 | N3 = require('n3'), 20 | { stringToTerm } = require('rdf-string'); 21 | 22 | // Prepare the query and the result writer 23 | var parts = /^\s*]*)>?\s*]*)>?\s*?\s*$/.exec(query), 24 | subject = parts[1][0] !== '?' && parts[1] || null, 25 | predicate = parts[2][0] !== '?' && parts[2] || null, 26 | object = parts[3][0] !== '?' && parts[3] || null; 27 | var writer = new N3.Writer(process.stdout, { format: format, end: false }); 28 | 29 | // Load the HDT file 30 | hdt.fromFile(hdtFile) 31 | .then(hdtDocument => hdtDocument.searchTriples( 32 | stringToTerm(subject), stringToTerm(predicate), stringToTerm(object), 33 | { offset: offset, limit: limit })) 34 | .then(results => { 35 | process.stdout.write('# Total matches: ' + results.totalCount + 36 | (results.exactCount ? '' : ' (estimated)') + '\n'); 37 | writer.addQuads(results.triples); 38 | writer.end(); 39 | }) 40 | .catch(error => { 41 | console.error(error.message); 42 | process.exit(1); 43 | }); 44 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "hdt", 5 | "sources": [ 6 | "lib/hdt.cc", 7 | "lib/HdtDocument.cc", 8 | " 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "HdtDocument.h" 11 | #include "../deps/libhdt/src/util/fileUtil.hpp" 12 | 13 | using namespace v8; 14 | using namespace hdt; 15 | 16 | const uint32_t SELF = 0; 17 | 18 | 19 | 20 | /******** Construction and destruction ********/ 21 | 22 | 23 | // Creates a new HDT document. 24 | HdtDocument::HdtDocument(const Local& handle, HDT* hdt) : hdt(hdt), features(0) { 25 | this->Wrap(handle); 26 | // Determine supported features 27 | if (hdt->getDictionary()->getType() == HDTVocabulary::DICTIONARY_TYPE_LITERAL) 28 | features |= LiteralSearch; 29 | } 30 | 31 | // Deletes the HDT document. 32 | HdtDocument::~HdtDocument() { Destroy(); } 33 | 34 | // Destroys the document, disabling all further operations. 35 | void HdtDocument::Destroy() { 36 | if (hdt) { 37 | delete hdt; 38 | hdt = NULL; 39 | } 40 | } 41 | 42 | // Constructs a JavaScript wrapper for an HDT document. 43 | NAN_METHOD(HdtDocument::New) { 44 | assert(info.IsConstructCall()); 45 | info.GetReturnValue().Set(info.This()); 46 | } 47 | 48 | // Returns the constructor of HdtDocument. 49 | Nan::Persistent constructor; 50 | const Nan::Persistent& HdtDocument::GetConstructor() { 51 | if (constructor.IsEmpty()) { 52 | // Create constructor template 53 | Local constructorTemplate = Nan::New(New); 54 | constructorTemplate->SetClassName(Nan::New("HdtDocument").ToLocalChecked()); 55 | constructorTemplate->InstanceTemplate()->SetInternalFieldCount(1); 56 | // Create prototype 57 | Nan::SetPrototypeMethod(constructorTemplate, "_searchTriples", SearchTriples); 58 | Nan::SetPrototypeMethod(constructorTemplate, "_searchBindings", SearchBindings); 59 | Nan::SetPrototypeMethod(constructorTemplate, "_searchLiterals", SearchLiterals); 60 | Nan::SetPrototypeMethod(constructorTemplate, "_searchTerms", SearchTerms); 61 | Nan::SetPrototypeMethod(constructorTemplate, "_fetchDistinctTerms", FetchDistinctTerms); 62 | Nan::SetPrototypeMethod(constructorTemplate, "_readHeader", ReadHeader); 63 | Nan::SetPrototypeMethod(constructorTemplate, "_changeHeader", ChangeHeader); 64 | Nan::SetPrototypeMethod(constructorTemplate, "_close", Close); 65 | Nan::SetAccessor(constructorTemplate->PrototypeTemplate(), 66 | Nan::New("_features").ToLocalChecked(), Features); 67 | Nan::SetAccessor(constructorTemplate->PrototypeTemplate(), 68 | Nan::New("closed").ToLocalChecked(), Closed); 69 | // Set constructor 70 | constructor.Reset(Nan::GetFunction(constructorTemplate).ToLocalChecked()); 71 | } 72 | return constructor; 73 | } 74 | 75 | 76 | 77 | /******** createHdtDocument ********/ 78 | 79 | class CreateWorker : public Nan::AsyncWorker { 80 | string filename; 81 | HDT* hdt; 82 | 83 | public: 84 | CreateWorker(const char* filename, Nan::Callback *callback) 85 | : Nan::AsyncWorker(callback), filename(filename), hdt(NULL) { }; 86 | 87 | void Execute() { 88 | try { hdt = HDTManager::mapIndexedHDT(filename.c_str()); } 89 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 90 | } 91 | 92 | void HandleOKCallback() { 93 | Nan::HandleScope scope; 94 | // Create a new HdtDocument 95 | Local newDocument = Nan::NewInstance(Nan::New(HdtDocument::GetConstructor())).ToLocalChecked(); 96 | new HdtDocument(newDocument, hdt); 97 | // Send the new HdtDocument through the callback 98 | const unsigned argc = 2; 99 | Local argv[argc] = { Nan::Null(), newDocument }; 100 | callback->Call(argc, argv, async_resource); 101 | } 102 | }; 103 | 104 | // Creates a new instance of HdtDocument. 105 | // JavaScript signature: createHdtDocument(filename, callback) 106 | NAN_METHOD(HdtDocument::Create) { 107 | assert(info.Length() == 2); 108 | Nan::AsyncQueueWorker(new CreateWorker(*Nan::Utf8String(info[0]), 109 | new Nan::Callback(info[1].As()))); 110 | } 111 | 112 | 113 | 114 | /******** HdtDocument#_searchTriples ********/ 115 | 116 | class SearchTriplesWorker : public Nan::AsyncWorker { 117 | HdtDocument* document; 118 | // JavaScript function arguments 119 | string subject, predicate, object; 120 | uint32_t offset, limit; 121 | // Callback return values 122 | vector triples; 123 | map subjects, predicates, objects; 124 | uint32_t totalCount; 125 | bool hasExactCount; 126 | 127 | public: 128 | SearchTriplesWorker(HdtDocument* document, char* subject, char* predicate, char* object, 129 | uint32_t offset, uint32_t limit, Nan::Callback* callback, Local self) 130 | : Nan::AsyncWorker(callback), 131 | document(document), subject(subject), predicate(predicate), object(object), 132 | offset(offset), limit(limit), totalCount(0) { 133 | SaveToPersistent(SELF, self); 134 | }; 135 | 136 | void Execute() { 137 | IteratorTripleID* it = NULL; 138 | try { 139 | // Prepare the triple pattern 140 | Dictionary* dict = document->GetHDT()->getDictionary(); 141 | TripleString triple(subject, predicate, toHdtLiteral(object)); 142 | TripleID tripleId; 143 | dict->tripleStringtoTripleID(triple, tripleId); 144 | // If any of the components does not exist, there are no matches 145 | if ((subject[0] && !tripleId.getSubject()) || 146 | (predicate[0] && !tripleId.getPredicate()) || 147 | (object[0] && !tripleId.getObject())) { 148 | hasExactCount = true; 149 | return; 150 | } 151 | 152 | // Estimate the total number of triples 153 | it = document->GetHDT()->getTriples()->search(tripleId); 154 | totalCount = it->estimatedNumResults(); 155 | hasExactCount = it->numResultEstimation() == EXACT; 156 | 157 | // Go to the right offset 158 | if (it->canGoTo()) 159 | try { it->skip(offset), offset = 0; } 160 | catch (const runtime_error error) { /* invalid offset */ } 161 | else 162 | while (offset && it->hasNext()) it->next(), offset--; 163 | 164 | // Add matching triples to the result vector 165 | if (!offset) { 166 | while (it->hasNext() && triples.size() < limit) { 167 | TripleID& triple = *it->next(); 168 | triples.push_back(triple); 169 | if (!subjects.count(triple.getSubject())) { 170 | subjects[triple.getSubject()] = dict->idToString(triple.getSubject(), SUBJECT); 171 | } 172 | if (!predicates.count(triple.getPredicate())) { 173 | predicates[triple.getPredicate()] = dict->idToString(triple.getPredicate(), PREDICATE); 174 | } 175 | if (!objects.count(triple.getObject())) { 176 | string object(dict->idToString(triple.getObject(), OBJECT)); 177 | objects[triple.getObject()] = fromHdtLiteral(object); 178 | } 179 | } 180 | } 181 | } 182 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 183 | if (it) 184 | delete it; 185 | } 186 | 187 | void HandleOKCallback() { 188 | Nan::HandleScope scope; 189 | // Convert the triple components into strings 190 | map::const_iterator it; 191 | map > subjectStrings, predicateStrings, objectStrings; 192 | for (it = subjects.begin(); it != subjects.end(); it++) 193 | subjectStrings[it->first] = Nan::New(it->second.c_str()).ToLocalChecked(); 194 | for (it = predicates.begin(); it != predicates.end(); it++) 195 | predicateStrings[it->first] = Nan::New(it->second.c_str()).ToLocalChecked(); 196 | for (it = objects.begin(); it != objects.end(); it++) 197 | objectStrings[it->first] = Nan::New(it->second.c_str()).ToLocalChecked(); 198 | 199 | // Convert the triples into a JavaScript object array 200 | uint32_t count = 0; 201 | Local triplesArray = Nan::New(triples.size()); 202 | const Local SUBJECT = Nan::New("subject").ToLocalChecked(); 203 | const Local PREDICATE = Nan::New("predicate").ToLocalChecked(); 204 | const Local OBJECT = Nan::New("object").ToLocalChecked(); 205 | for (vector::const_iterator it = triples.begin(); it != triples.end(); it++) { 206 | Local tripleObject = Nan::New(); 207 | Nan::Set(tripleObject, SUBJECT, subjectStrings[it->getSubject()]); 208 | Nan::Set(tripleObject, PREDICATE, predicateStrings[it->getPredicate()]); 209 | Nan::Set(tripleObject, OBJECT, objectStrings[it->getObject()]); 210 | Nan::Set(triplesArray, count++, tripleObject); 211 | } 212 | 213 | // Send the JavaScript array and estimated total count through the callback 214 | const unsigned argc = 4; 215 | Local argv[argc] = { Nan::Null(), triplesArray, 216 | Nan::New((uint32_t)totalCount), 217 | Nan::New((bool)hasExactCount) }; 218 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), argc, argv, async_resource); 219 | } 220 | 221 | void HandleErrorCallback() { 222 | Nan::HandleScope scope; 223 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 224 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 225 | } 226 | }; 227 | 228 | // Searches for a triple pattern in the document. 229 | // JavaScript signature: HdtDocument#_searchTriples(subject, predicate, object, offset, limit, callback) 230 | NAN_METHOD(HdtDocument::SearchTriples) { 231 | assert(info.Length() == 6); 232 | Nan::AsyncQueueWorker(new SearchTriplesWorker(Unwrap(info.This()), 233 | *Nan::Utf8String(info[0]), *Nan::Utf8String(info[1]), *Nan::Utf8String(info[2]), 234 | Nan::To(info[3]).FromJust(), Nan::To(info[4]).FromJust(), 235 | new Nan::Callback(info[5].As()), info.This())); 236 | } 237 | 238 | /******** HdtDocument#_searchBindings ********/ 239 | 240 | class SearchBindingsWorker : public Nan::AsyncWorker { 241 | HdtDocument* document; 242 | // JavaScript function arguments 243 | string subject, predicate, object; 244 | uint32_t offset, limit; 245 | // Callback return values 246 | vector triples; 247 | map subjects, predicates, objects; 248 | uint32_t totalCount; 249 | bool hasExactCount; 250 | bool varS, varP, varO; 251 | 252 | public: 253 | SearchBindingsWorker(HdtDocument* document, char* subject, char* predicate, char* object, 254 | uint32_t offset, uint32_t limit, Nan::Callback* callback, Local self) 255 | : Nan::AsyncWorker(callback), 256 | document(document), subject(subject), predicate(predicate), object(object), 257 | offset(offset), limit(limit), totalCount(0) { 258 | SaveToPersistent(SELF, self); 259 | }; 260 | 261 | void Execute() { 262 | IteratorTripleID* it = NULL; 263 | try { 264 | // Determine which terms are variables 265 | varS = isVariable(subject); 266 | varP = isVariable(predicate); 267 | varO = isVariable(object); 268 | 269 | // Prepare the triple pattern 270 | Dictionary* dict = document->GetHDT()->getDictionary(); 271 | TripleString triple(varS ? "" : subject, varP ? "" : predicate, varO ? "" : toHdtLiteral(object)); 272 | TripleID tripleId; 273 | dict->tripleStringtoTripleID(triple, tripleId); 274 | // If any of the components does not exist, there are no matches 275 | if ((!varS && subject[0] && !tripleId.getSubject()) || 276 | (!varP && predicate[0] && !tripleId.getPredicate()) || 277 | (!varO && object[0] && !tripleId.getObject())) { 278 | hasExactCount = true; 279 | return; 280 | } 281 | 282 | // Estimate the total number of triples 283 | it = document->GetHDT()->getTriples()->search(tripleId); 284 | totalCount = it->estimatedNumResults(); 285 | hasExactCount = it->numResultEstimation() == EXACT; 286 | 287 | // Go to the right offset 288 | if (it->canGoTo()) 289 | try { it->skip(offset), offset = 0; } 290 | catch (const runtime_error error) { /* invalid offset */ } 291 | else 292 | while (offset && it->hasNext()) it->next(), offset--; 293 | 294 | // Add matching triples to the result vector 295 | if (!offset) { 296 | while (it->hasNext() && triples.size() < limit) { 297 | TripleID& triple = *it->next(); 298 | triples.push_back(triple); 299 | if (varS && !subjects.count(triple.getSubject())) { 300 | subjects[triple.getSubject()] = dict->idToString(triple.getSubject(), SUBJECT); 301 | } 302 | if (varP && !predicates.count(triple.getPredicate())) { 303 | predicates[triple.getPredicate()] = dict->idToString(triple.getPredicate(), PREDICATE); 304 | } 305 | if (varO && !objects.count(triple.getObject())) { 306 | string object(dict->idToString(triple.getObject(), OBJECT)); 307 | objects[triple.getObject()] = fromHdtLiteral(object); 308 | } 309 | } 310 | } 311 | } 312 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 313 | if (it) 314 | delete it; 315 | } 316 | 317 | void HandleOKCallback() { 318 | Nan::HandleScope scope; 319 | // Convert the triple components into strings 320 | map::const_iterator it; 321 | map > subjectStrings, predicateStrings, objectStrings; 322 | for (it = subjects.begin(); it != subjects.end(); it++) 323 | subjectStrings[it->first] = Nan::New(it->second.c_str()).ToLocalChecked(); 324 | for (it = predicates.begin(); it != predicates.end(); it++) 325 | predicateStrings[it->first] = Nan::New(it->second.c_str()).ToLocalChecked(); 326 | for (it = objects.begin(); it != objects.end(); it++) 327 | objectStrings[it->first] = Nan::New(it->second.c_str()).ToLocalChecked(); 328 | 329 | // Convert the triples into a double JavaScript array 330 | uint32_t count = 0; 331 | Local bindingsArray = Nan::New(triples.size()); 332 | uint32_t variables = (varS ? 1 : 0) + (varP ? 1 : 0) + (varO ? 1 : 0); 333 | for (vector::const_iterator it = triples.begin(); it != triples.end(); it++) { 334 | uint32_t countInner = 0; 335 | Local bindingsArrayInner = Nan::New(variables); 336 | if (varS) { 337 | Nan::Set(bindingsArrayInner, countInner++, subjectStrings[it->getSubject()]); 338 | } 339 | if (varP) { 340 | Nan::Set(bindingsArrayInner, countInner++, predicateStrings[it->getPredicate()]); 341 | } 342 | if (varO) { 343 | Nan::Set(bindingsArrayInner, countInner++, objectStrings[it->getObject()]); 344 | } 345 | Nan::Set(bindingsArray, count++, bindingsArrayInner); 346 | } 347 | 348 | // Send the JavaScript array through the callback 349 | const unsigned argc = 4; 350 | Local argv[argc] = { Nan::Null(), bindingsArray, 351 | Nan::New((uint32_t)totalCount), 352 | Nan::New((bool)hasExactCount) }; 353 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), argc, argv, async_resource); 354 | } 355 | 356 | void HandleErrorCallback() { 357 | Nan::HandleScope scope; 358 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 359 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 360 | } 361 | }; 362 | 363 | // Searches for a triple pattern in the document and return bindings. 364 | // JavaScript signature: HdtDocument#_searchBindings(subject, predicate, object, offset, limit, callback) 365 | NAN_METHOD(HdtDocument::SearchBindings) { 366 | assert(info.Length() == 6); 367 | Nan::AsyncQueueWorker(new SearchBindingsWorker(Unwrap(info.This()), 368 | *Nan::Utf8String(info[0]), *Nan::Utf8String(info[1]), *Nan::Utf8String(info[2]), 369 | Nan::To(info[3]).FromJust(), Nan::To(info[4]).FromJust(), 370 | new Nan::Callback(info[5].As()), info.This())); 371 | } 372 | 373 | 374 | 375 | /******** HdtDocument#_searchLiterals ********/ 376 | 377 | class SearchLiteralsWorker : public Nan::AsyncWorker { 378 | HdtDocument* document; 379 | // JavaScript function arguments 380 | string substring; 381 | uint32_t offset, limit; 382 | // Callback return values 383 | vector literals; 384 | uint32_t totalCount; 385 | 386 | public: 387 | SearchLiteralsWorker(HdtDocument* document, char* substring, uint32_t offset, uint32_t limit, 388 | Nan::Callback* callback, Local self) 389 | : Nan::AsyncWorker(callback), document(document), 390 | substring(substring), offset(offset), limit(limit), totalCount(0) { 391 | SaveToPersistent(SELF, self); 392 | }; 393 | 394 | void Execute() { 395 | if (!document->Supports(LiteralSearch)) { 396 | SetErrorMessage("The HDT document does not support literal search"); 397 | return; 398 | } 399 | 400 | uint32_t* literalIds = NULL; 401 | try { 402 | // Find matching literal IDs 403 | LiteralDictionary *dict = (LiteralDictionary*)(document->GetHDT()->getDictionary()); 404 | uint32_t literalCount = 0; 405 | totalCount = dict->substringToId((unsigned char*)substring.c_str(), substring.length(), 406 | offset, limit, false, &literalIds, &literalCount); 407 | 408 | // Convert the literal IDs to strings 409 | for (uint32_t *id = literalIds, *end = literalIds + literalCount; id != end; id++) { 410 | string literal(dict->idToString(*id, OBJECT)); 411 | literals.push_back(fromHdtLiteral(literal)); 412 | } 413 | } 414 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 415 | if (literalIds) 416 | delete[] literalIds; 417 | } 418 | 419 | void HandleOKCallback() { 420 | Nan::HandleScope scope; 421 | // Convert the literals into a JavaScript array 422 | uint32_t count = 0; 423 | Local literalsArray = Nan::New(literals.size()); 424 | for (vector::const_iterator it = literals.begin(); it != literals.end(); it++) 425 | Nan::Set(literalsArray, count++, Nan::New(*it).ToLocalChecked()); 426 | 427 | // Send the JavaScript array and estimated total count through the callback 428 | const unsigned argc = 4; 429 | Local argv[argc] = { Nan::Null(), literalsArray, 430 | Nan::New((uint32_t)totalCount), 431 | Nan::New(true) }; 432 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), argc, argv, async_resource); 433 | } 434 | 435 | void HandleErrorCallback() { 436 | Nan::HandleScope scope; 437 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 438 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 439 | } 440 | }; 441 | 442 | // Searches for a triple pattern in the document. 443 | // JavaScript signature: HdtDocument#_searchLiterals(substring, offset, limit, callback) 444 | NAN_METHOD(HdtDocument::SearchLiterals) { 445 | assert(info.Length() == 4); 446 | Nan::AsyncQueueWorker(new SearchLiteralsWorker(Unwrap(info.This()), 447 | *Nan::Utf8String(info[0]), 448 | Nan::To(info[1]).FromJust(), Nan::To(info[2]).FromJust(), 449 | new Nan::Callback(info[3].As()), info.This())); 450 | } 451 | 452 | /******** HdtDocument#_searchTerms ********/ 453 | 454 | class SearchTermsWorker : public Nan::AsyncWorker { 455 | HdtDocument* document; 456 | // JavaScript function arguments 457 | string base; 458 | uint32_t limit; 459 | hdt::TripleComponentRole position; 460 | // Callback return values 461 | vector suggestions; 462 | public: 463 | SearchTermsWorker(HdtDocument* document, char* base, uint32_t limit, uint32_t posId, 464 | Nan::Callback* callback, Local self) 465 | : Nan::AsyncWorker(callback), 466 | document(document), base(base), limit(limit), position((TripleComponentRole) posId) { 467 | SaveToPersistent(SELF, self); 468 | }; 469 | 470 | void Execute() { 471 | try { 472 | Dictionary* dict = document->GetHDT()->getDictionary(); 473 | dict->getSuggestions(base.c_str(), position, suggestions, limit); 474 | } 475 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 476 | } 477 | 478 | void HandleOKCallback() { 479 | Nan::HandleScope scope; 480 | // Convert the suggestions into a JavaScript array 481 | uint32_t count = 0; 482 | Local suggestionsArray = Nan::New(suggestions.size()); 483 | for (vector::const_iterator it = suggestions.begin(); it != suggestions.end(); it++) 484 | Nan::Set(suggestionsArray, count++, Nan::New(*it).ToLocalChecked()); 485 | 486 | // Send the JavaScript array and estimated total count through the callback 487 | const unsigned argc = 2; 488 | Local argv[argc] = { Nan::Null(), suggestionsArray}; 489 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), argc, argv, async_resource); 490 | } 491 | 492 | void HandleErrorCallback() { 493 | Nan::HandleScope scope; 494 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 495 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 496 | } 497 | }; 498 | 499 | // Searches terms based on a given string over a specific position. 500 | // JavaScript signature: HdtDocument#_searchTerms(prefix, limit, position, callback) 501 | NAN_METHOD(HdtDocument::SearchTerms) { 502 | assert(info.Length() == 4); 503 | Nan::AsyncQueueWorker(new SearchTermsWorker(Unwrap(info.This()), 504 | *Nan::Utf8String(info[0]), Nan::To(info[1]).FromJust(), Nan::To(info[2]).FromJust(), 505 | new Nan::Callback(info[3].As()), info.This())); 506 | } 507 | 508 | /******** HdtDocument#_readHeader ********/ 509 | 510 | class ReadHeaderWorker : public Nan::AsyncWorker { 511 | HdtDocument* document; 512 | // Callback return values 513 | string headerString; 514 | 515 | public: 516 | ReadHeaderWorker(HdtDocument* document, Nan::Callback* callback, Local self) 517 | : Nan::AsyncWorker(callback), document(document), headerString("") { 518 | SaveToPersistent(SELF, self); 519 | }; 520 | 521 | void Execute() { 522 | IteratorTripleString *it = NULL; 523 | try { 524 | Header *header = document->GetHDT()->getHeader(); 525 | IteratorTripleString *it = header->search("","",""); 526 | 527 | // Create header string. 528 | while (it->hasNext()) { 529 | TripleString *ts = it->next(); 530 | headerString += ts->getSubject(); 531 | headerString += " "; 532 | headerString += ts->getPredicate(); 533 | headerString += " "; 534 | headerString += ts->getObject(); 535 | headerString += " .\n"; 536 | } 537 | } 538 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 539 | if (it) 540 | delete it; 541 | } 542 | 543 | void HandleOKCallback() { 544 | Nan::HandleScope scope; 545 | 546 | // Convert header string to Local. 547 | Local nanHeader = Nan::New(headerString).ToLocalChecked(); 548 | 549 | // Send the header string through the callback. 550 | const unsigned argc = 2; 551 | Local argv[argc] = { Nan::Null(), nanHeader }; 552 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), argc, argv, async_resource); 553 | } 554 | 555 | void HandleErrorCallback() { 556 | Nan::HandleScope scope; 557 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 558 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 559 | } 560 | }; 561 | 562 | // Returns the header of the hdt document as a string. 563 | // JavaScript signature: HdtDocument#_readHeader(callback) 564 | NAN_METHOD(HdtDocument::ReadHeader) { 565 | assert(info.Length() == 1); 566 | Nan::AsyncQueueWorker(new ReadHeaderWorker(Unwrap(info.This()), 567 | new Nan::Callback(info[0].As()), info.This())); 568 | } 569 | 570 | /******** HdtDocument#_changeHeader ********/ 571 | 572 | class ChangeHeaderWorker : public Nan::AsyncWorker { 573 | HdtDocument* document; 574 | // JavaScript function arguments 575 | string headerString; 576 | string outputFile; 577 | 578 | public: 579 | ChangeHeaderWorker(HdtDocument* document, string headerString, string outputFile, 580 | Nan::Callback* callback, Local self) 581 | : Nan::AsyncWorker(callback), document(document), 582 | headerString(headerString), outputFile(outputFile) { 583 | SaveToPersistent(SELF, self); 584 | }; 585 | 586 | void Execute() { 587 | try { 588 | // Get and clear current header. 589 | Header *header = document->GetHDT()->getHeader(); 590 | header->clear(); 591 | 592 | // Replace header. 593 | istringstream in(headerString, ios::binary); 594 | ControlInformation ci; 595 | ci.setFormat(HDTVocabulary::HEADER_NTRIPLES); 596 | ci.setUint("length", fileUtil::getSize(in)); 597 | header->load(in, ci); 598 | 599 | // Save 600 | document->GetHDT()->saveToHDT(outputFile.c_str()); 601 | } 602 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 603 | } 604 | 605 | void HandleOKCallback() { 606 | Nan::HandleScope scope; 607 | const unsigned argc = 1; 608 | Local argv[argc] = { Nan::Null() }; 609 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 610 | } 611 | 612 | void HandleErrorCallback() { 613 | Nan::HandleScope scope; 614 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 615 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 616 | } 617 | }; 618 | 619 | // Replaces the current header with a new one and saves result to a new file. 620 | // JavaScript signature: HdtDocument#_changeHeader(header, outputFile, callback) 621 | NAN_METHOD(HdtDocument::ChangeHeader) { 622 | assert(info.Length() == 3); 623 | 624 | Nan::AsyncQueueWorker(new ChangeHeaderWorker(Unwrap(info.This()), 625 | *Nan::Utf8String(info[0]), *Nan::Utf8String(info[1]), 626 | new Nan::Callback(info[2].As()), info.This())); 627 | } 628 | 629 | /******** HdtDocument#_fetchDistinctTerms ********/ 630 | 631 | class FetchDistinctTermsWorker : public Nan::AsyncWorker { 632 | HdtDocument* document; 633 | // JavaScript function arguments 634 | string subject; 635 | string object; 636 | uint32_t limit; 637 | // Callback return values 638 | vector distinctTerms; 639 | public: 640 | FetchDistinctTermsWorker(HdtDocument* document, char* subject, char* object, uint32_t limit, 641 | uint32_t posId, Nan::Callback* callback, Local self) 642 | : Nan::AsyncWorker(callback), document(document), subject(subject), object(object), limit(limit) { 643 | assert(posId == hdt::PREDICATE); // only predicate is supported currently 644 | SaveToPersistent(SELF, self); 645 | }; 646 | 647 | void Execute() { 648 | hdt::IteratorUCharString *terms = NULL; 649 | try { 650 | Dictionary* dict = document->GetHDT()->getDictionary(); 651 | terms = dict->getPredicates(); 652 | 653 | // Iterate over all predicates 654 | while (distinctTerms.size() < limit && terms->hasNext()) { 655 | const char* predicate = reinterpret_cast(terms->next()); 656 | 657 | // Check whether a triple with this predicate and subject or object exists 658 | hdt::IteratorTripleString *it = document->GetHDT()->search(subject.c_str(), predicate, object.c_str()); 659 | if (it->hasNext()) 660 | distinctTerms.push_back(predicate); 661 | delete it; 662 | delete[] predicate; 663 | } 664 | } 665 | catch (const runtime_error error) { SetErrorMessage(error.what()); } 666 | if (terms) 667 | delete terms; 668 | } 669 | 670 | void HandleOKCallback() { 671 | Nan::HandleScope scope; 672 | // Convert the distinctTerms into a JavaScript array 673 | uint32_t count = 0; 674 | Local distinctTermsArray = Nan::New(distinctTerms.size()); 675 | for (vector::const_iterator it = distinctTerms.begin(); it != distinctTerms.end(); it++) 676 | Nan::Set(distinctTermsArray, count++, Nan::New(*it).ToLocalChecked()); 677 | 678 | // Send the JavaScript array through the callback 679 | const unsigned argc = 2; 680 | Local argv[argc] = { Nan::Null(), distinctTermsArray}; 681 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), argc, argv, async_resource); 682 | } 683 | 684 | void HandleErrorCallback() { 685 | Nan::HandleScope scope; 686 | Local argv[] = { Exception::Error(Nan::New(ErrorMessage()).ToLocalChecked()) }; 687 | callback->Call(Nan::To(GetFromPersistent(SELF)).ToLocalChecked(), 1, argv, async_resource); 688 | } 689 | }; 690 | 691 | // Fetches distinct list of predicates given an object. 692 | // JavaScript signature: HdtDocument#_fetchDistinctTerms(object, limit, position, callback) 693 | NAN_METHOD(HdtDocument::FetchDistinctTerms) { 694 | assert(info.Length() == 5); 695 | Nan::AsyncQueueWorker(new FetchDistinctTermsWorker(Unwrap(info.This()), 696 | *Nan::Utf8String(info[0]), *Nan::Utf8String(info[1]), Nan::To(info[2]).FromJust(), Nan::To(info[3]).FromJust(), 697 | new Nan::Callback(info[4].As()), info.This())); 698 | } 699 | 700 | /******** HdtDocument#features ********/ 701 | 702 | 703 | // Gets a bitvector indicating the supported features. 704 | NAN_PROPERTY_GETTER(HdtDocument::Features) { 705 | HdtDocument* hdtDocument = Unwrap(info.This()); 706 | info.GetReturnValue().Set(Nan::New(hdtDocument->features)); 707 | } 708 | 709 | 710 | 711 | /******** HdtDocument#close ********/ 712 | 713 | // Closes the document, disabling all further operations. 714 | // JavaScript signature: HdtDocument#close(callback) 715 | NAN_METHOD(HdtDocument::Close) { 716 | assert(info.Length() == 1); 717 | 718 | // Destroy the current document 719 | HdtDocument* hdtDocument = Unwrap(info.This()); 720 | hdtDocument->Destroy(); 721 | 722 | // Call the callback 723 | const Local callback = info[0].As(); 724 | const unsigned argc = 1; 725 | Local argv[argc] = { Nan::Null() }; 726 | Nan::Call(callback, Nan::GetCurrentContext()->Global(), argc, argv); 727 | } 728 | 729 | 730 | 731 | /******** HdtDocument#closed ********/ 732 | 733 | 734 | // Gets a boolean indicating whether the document is closed. 735 | NAN_PROPERTY_GETTER(HdtDocument::Closed) { 736 | HdtDocument* hdtDocument = Unwrap(info.This()); 737 | info.GetReturnValue().Set(Nan::New(!hdtDocument->hdt)); 738 | } 739 | 740 | 741 | 742 | /******** Utility functions ********/ 743 | 744 | 745 | // The JavaScript representation for a literal with a datatype is 746 | // "literal"^^http://example.org/datatype 747 | // whereas the HDT representation is 748 | // "literal"^^ 749 | // The functions below convert when needed. 750 | 751 | // Check if a term is a variable 752 | bool isVariable(string& term) { 753 | return term[0] == '?'; 754 | } 755 | 756 | // Converts a JavaScript literal to an HDT literal 757 | string& toHdtLiteral(string& literal) { 758 | // Check if the object is a literal with a datatype, which needs conversion 759 | string::const_iterator obj; 760 | string::iterator objLast; 761 | if (*(obj = literal.begin()) == '"' && *(objLast = literal.end() - 1) != '"') { 762 | // If the start of a datatype was found, surround it with angular brackets 763 | string::const_iterator datatype = objLast; 764 | while (obj != --datatype && *datatype != '"' && *datatype != '^'); 765 | if (*datatype == '^') { 766 | // Allocate space for brackets, and update iterators 767 | literal.resize(literal.length() + 2); 768 | datatype += (literal.begin() - obj) + 1; 769 | objLast = literal.end() - 1; 770 | // Add brackets 771 | *objLast = '>'; 772 | while (--objLast != datatype) 773 | *objLast = *(objLast - 1); 774 | *objLast = '<'; 775 | } 776 | } 777 | return literal; 778 | } 779 | 780 | // Converts an HDT literal to a JavaScript literal 781 | string& fromHdtLiteral(string& literal) { 782 | // Check if the literal has a datatype, which needs conversion 783 | string::const_iterator obj; 784 | string::iterator objLast; 785 | if (*(obj = literal.begin()) == '"' && *(objLast = literal.end() - 1) == '>') { 786 | // Find the start of the datatype 787 | string::iterator datatype = objLast; 788 | while (obj != --datatype && *datatype != '<'); 789 | // Change the datatype representation by removing angular brackets 790 | if (*datatype == '<') 791 | literal.erase(datatype), literal.erase(objLast - 1); 792 | } 793 | return literal; 794 | } 795 | -------------------------------------------------------------------------------- /lib/HdtDocument.h: -------------------------------------------------------------------------------- 1 | #ifndef HDTDOCUMENT_H 2 | #define HDTDOCUMENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum HdtDocumentFeatures { 9 | LiteralSearch = 1, // The document supports substring search for literals 10 | }; 11 | 12 | class HdtDocument : public node::ObjectWrap { 13 | public: 14 | HdtDocument(const v8::Local& handle, hdt::HDT* hdt); 15 | 16 | // createHdtDocument(filename, callback) 17 | static NAN_METHOD(Create); 18 | static const Nan::Persistent& GetConstructor(); 19 | 20 | // Accessors 21 | hdt::HDT* GetHDT() { return hdt; } 22 | bool Supports(HdtDocumentFeatures feature) { return features & (int)feature; } 23 | 24 | private: 25 | hdt::HDT* hdt; 26 | int features; 27 | 28 | // Construction and destruction 29 | ~HdtDocument(); 30 | void Destroy(); 31 | static NAN_METHOD(New); 32 | 33 | // HdtDocument#_searchTriples(subject, predicate, object, offset, limit, callback, self) 34 | static NAN_METHOD(SearchTriples); 35 | // HdtDocument#_searchBindings(subject, predicate, object, offset, limit, callback, self) 36 | static NAN_METHOD(SearchBindings); 37 | // HdtDocument#_searchLiterals(substring, offset, limit, callback, self) 38 | static NAN_METHOD(SearchLiterals); 39 | // HdtDocument#_searchTerms(prefix, limit, position, callback) 40 | static NAN_METHOD(SearchTerms); 41 | // HdtDocument#_fetchDistinctTerms(subject, object, limit, position, callback) 42 | static NAN_METHOD(FetchDistinctTerms); 43 | // HdtDocument#_readHeader(callback, self) 44 | static NAN_METHOD(ReadHeader); 45 | // HdtDocument#_changeHeader(headerString, outputFile, callback, self) 46 | static NAN_METHOD(ChangeHeader); 47 | // HdtDocument#_features 48 | static NAN_PROPERTY_GETTER(Features); 49 | // HdtDocument#close([callback], [self]) 50 | static NAN_METHOD(Close); 51 | // HdtDocument#closed 52 | static NAN_PROPERTY_GETTER(Closed); 53 | }; 54 | 55 | // Check if a term is a variable 56 | bool isVariable(string& term); 57 | // Converts a JavaScript literal to an HDT literal 58 | std::string& toHdtLiteral(std::string& literal); 59 | // Converts an HDT literal to a JavaScript literal 60 | std::string& fromHdtLiteral(std::string& literal); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /lib/hdt.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "HdtDocument.h" 4 | 5 | using namespace v8; 6 | 7 | NAN_MODULE_INIT(InitHdtModule) { 8 | Nan::Set(target, Nan::New("HdtDocument").ToLocalChecked(), 9 | Nan::New(HdtDocument::GetConstructor())); 10 | Nan::Set(target, Nan::New("createHdtDocument").ToLocalChecked(), 11 | Nan::GetFunction(Nan::New(HdtDocument::Create)).ToLocalChecked()); 12 | } 13 | 14 | NODE_MODULE(hdt, InitHdtModule) 15 | -------------------------------------------------------------------------------- /lib/hdt.d.ts: -------------------------------------------------------------------------------- 1 | import * as RDF from "@rdfjs/types"; 2 | 3 | export interface SearchTermsOpts { 4 | limit?: number; 5 | position?: "subject" | "predicate" | "object"; 6 | prefix?: string; 7 | subject?: string; // mutually exclusive with prefix and prioritized 8 | object?: string, // mutually exclusive with prefix and prioritized 9 | } 10 | 11 | export interface SearchLiteralsOpts { 12 | limit?: number; 13 | offset?: number; 14 | } 15 | 16 | export interface SearchLiteralsResult { 17 | literals: RDF.Literal[]; 18 | totalCount: number; 19 | } 20 | 21 | export interface SearchTriplesOpts { 22 | limit?: number; 23 | offset?: number; 24 | } 25 | 26 | export interface SearchResult { 27 | triples: RDF.Quad[]; 28 | totalCount: number; 29 | hasExactCount: boolean; 30 | } 31 | 32 | export interface BindingsResult { 33 | bindings: RDF.Bindings[]; 34 | totalCount: number; 35 | hasExactCount: boolean; 36 | } 37 | 38 | export interface Document { 39 | searchTriples(sub?: RDF.Term, pred?: RDF.Term, obj?: RDF.Term, opts?: SearchTriplesOpts): Promise; 40 | 41 | searchBindings(bf: RDF.BindingsFactory, sub: RDF.Term, pred: RDF.Term, obj: RDF.Term, opts?: SearchTriplesOpts): Promise; 42 | 43 | countTriples(sub?: RDF.Term, pred?: RDF.Term, obj?: RDF.Term): Promise; 44 | 45 | searchLiterals(substring: string, opts?: SearchLiteralsOpts): Promise; 46 | 47 | searchTerms(opts?: SearchTermsOpts): Promise; 48 | 49 | close(): Promise; 50 | 51 | readHeader(): Promise; 52 | 53 | changeHeader(triples: string, outputFile: string): Promise; 54 | } 55 | 56 | export function fromFile(filename: string, opts?: { dataFactory?: RDF.DataFactory }): Promise; 57 | -------------------------------------------------------------------------------- /lib/hdt.js: -------------------------------------------------------------------------------- 1 | const N3 = require('n3'); 2 | const { stringQuadToQuad, stringToTerm, termToString } = require('rdf-string'); 3 | 4 | /* Auxiliary methods for HdtDocument */ 5 | const hdtNative = require('../build/Release/hdt'); 6 | const HdtDocumentPrototype = hdtNative.HdtDocument.prototype; 7 | const MAX = Math.pow(2, 31) - 1; 8 | 9 | const closedError = Promise.reject(new Error('The HDT document cannot be accessed because it is closed')); 10 | closedError.catch(e => {}); 11 | 12 | function isValidHdtTerm(term) { 13 | return term && term.termType && (term.termType === 'Literal' || term.termType === 'BlankNode' || term.termType === 'NamedNode'); 14 | } 15 | 16 | function isValidHdtTermForSearchBindings(term) { 17 | return isValidHdtTerm(term) || (term && term.termType === 'Variable'); 18 | } 19 | 20 | // Searches the document for triples with the given subject, predicate, and object. 21 | HdtDocumentPrototype.searchTriples = function (subject, predicate, object, options) { 22 | if (this.closed) return closedError; 23 | if (!isValidHdtTerm(subject)) subject = null; 24 | if (!isValidHdtTerm(predicate)) predicate = null; 25 | if (!isValidHdtTerm(object)) object = null; 26 | options = options || {}; 27 | const dataFactory = this.dataFactory; 28 | return new Promise((resolve, reject) => { 29 | this._searchTriples(termToString(subject) || '', termToString(predicate) || '', termToString(object) || '', 30 | parseOffset(options), parseLimit(options), 31 | (err, triples, totalCount, hasExactCount) => 32 | err ? reject(err) : resolve({ triples: triples.map((t) => stringQuadToQuad(t, dataFactory)), totalCount, hasExactCount })); 33 | }); 34 | }; 35 | 36 | // Searches the document for triples with the given subject, predicate, and object, and return them as bindings 37 | HdtDocumentPrototype.searchBindings = function (bindingsFactory, subject, predicate, object, options) { 38 | if (this.closed) return closedError; 39 | if (!isValidHdtTermForSearchBindings(subject) || !isValidHdtTermForSearchBindings(predicate) || !isValidHdtTermForSearchBindings(object)) throw new Error('Passed invalid subject term'); 40 | options = options || {}; 41 | return new Promise((resolve, reject) => { 42 | // Collect variable terms 43 | const variables = []; 44 | if (subject.termType === 'Variable') variables.push(subject); 45 | if (predicate.termType === 'Variable') variables.push(predicate); 46 | if (object.termType === 'Variable') variables.push(object); 47 | 48 | // Check if we need to do post-filtering for overlapping variables 49 | let shouldFilterIndexes = false; 50 | const filterIndexes = variables.map((variable, i) => { 51 | const equalVariables = []; 52 | for (let j = i + 1; j < variables.length; j++) { 53 | if (variable.equals(variables[j])) { 54 | equalVariables.push(j); 55 | shouldFilterIndexes = true; 56 | } 57 | } 58 | return equalVariables; 59 | }); 60 | 61 | // If we have offset and limit with overlapping variables, they must be handled in JS-land. 62 | const offset = parseOffset(options); 63 | const limit = parseLimit(options); 64 | 65 | this._searchBindings(termToString(subject), termToString(predicate), termToString(object), 66 | shouldFilterIndexes ? 0 : offset, shouldFilterIndexes ? MAX : limit, 67 | (err, bindingsRaw, totalCount, hasExactCount) => { 68 | if (err) 69 | return reject(err); 70 | 71 | // If we had overlapping variables, potentially filter bindings 72 | if (shouldFilterIndexes) { 73 | // Filter bindings 74 | bindingsRaw = bindingsRaw.filter(binding => { 75 | for (let i = 0; i < binding.length; i++) { 76 | const bindingEntry = binding[i]; 77 | const filterI = filterIndexes[i]; 78 | if (filterI) { 79 | for (const j of filterI) { 80 | if (j !== undefined && bindingEntry !== binding[j]) { 81 | totalCount--; 82 | return false; 83 | } 84 | } 85 | } 86 | } 87 | return true; 88 | }); 89 | 90 | // Apply offsets and limits in JS-land 91 | if (offset > 0 || limit < MAX) { 92 | hasExactCount = true; 93 | totalCount = bindingsRaw.length; 94 | bindingsRaw = bindingsRaw.slice(offset, offset + limit); 95 | } 96 | } 97 | 98 | // Create bindings objects 99 | const bindings = bindingsRaw.map(b => bindingsFactory.bindings(b.map((value, i) => [variables[i], stringToTerm(value)]))); 100 | 101 | return resolve({ 102 | bindings, 103 | totalCount, 104 | hasExactCount, 105 | }); 106 | }); 107 | }); 108 | }; 109 | 110 | // Gives an approximate number of matches of triples with the given subject, predicate, and object. 111 | HdtDocumentPrototype.countTriples = function (subject, predicate, object) { 112 | return this.search(subject, predicate, object, { offset: 0, limit: 0 }); 113 | }; 114 | 115 | // Searches the document for literals that contain the given string 116 | HdtDocumentPrototype.searchLiterals = function (substring, options) { 117 | if (this.closed) return closedError; 118 | options = options || {}; 119 | const dataFactory = this.dataFactory; 120 | return new Promise((resolve, reject) => { 121 | this._searchLiterals(substring, 122 | parseOffset(options), parseLimit(options), 123 | (err, literals, totalCount) => 124 | err ? reject(err) : resolve({ literals: literals.map(l => stringToTerm(l, dataFactory)), totalCount })); 125 | }); 126 | }; 127 | 128 | // Searches terms based on a given prefix string. 129 | const POSITIONS = { 130 | subject: 0, 131 | predicate: 1, 132 | object: 2, 133 | }; 134 | HdtDocumentPrototype.searchTerms = function (options) { 135 | if (this.closed) return closedError; 136 | options = options || {}; 137 | const limit = parseLimit(options), 138 | position = options.position, 139 | posId = POSITIONS[position], 140 | prefix = options.prefix || '', 141 | subject = isValidHdtTerm(options.subject) ? options.subject : null, 142 | object = isValidHdtTerm(options.object) ? options.object : null; 143 | 144 | // Validate parameters 145 | if (!(position in POSITIONS)) 146 | return Promise.reject(new Error('Invalid position argument. Expected subject, predicate or object.')); 147 | if ((subject || object) && posId !== POSITIONS.predicate) 148 | return Promise.reject(new Error('Unsupported position argument. Expected predicate.')); 149 | 150 | // Return predicates that connect subject and object 151 | if (subject && object) { 152 | return this.searchTriples(subject, null, object, { limit: limit }) 153 | .then(result => result.triples.map(statement => statement.predicate)); 154 | } 155 | const dataFactory = this.dataFactory; 156 | // Return distinct terms 157 | return new Promise((resolve, reject) => { 158 | if ('subject' in options || 'object' in options) { 159 | if (!subject && !object) return resolve([]); 160 | this._fetchDistinctTerms(termToString(subject) || '', termToString(object) || '', limit, posId, 161 | (error, results) => error ? reject(error) : resolve(results.map(t => stringToTerm(t, dataFactory)))); 162 | } 163 | // No subject or object values specified, so assuming we're autocompleting a term 164 | else { 165 | this._searchTerms(prefix, limit, posId, 166 | (error, results) => error ? reject(error) : resolve(results.map(t => stringToTerm(t, dataFactory)))); 167 | } 168 | }); 169 | }; 170 | 171 | // Returns the header of the HDT document as a string. 172 | HdtDocumentPrototype.readHeader = function () { 173 | if (this.closed) return closedError; 174 | return new Promise((resolve, reject) => 175 | this._readHeader((e, header) => e ? reject(e) : resolve(header))); 176 | }; 177 | 178 | // Replaces the current header with a new one and saves result to a new file. 179 | HdtDocumentPrototype.changeHeader = function (header, outputFile) { 180 | if (this.closed) return closedError; 181 | return new Promise((resolve, reject) => { 182 | this._changeHeader(header, outputFile, 183 | e => e ? reject(e) : resolve(module.exports.fromFile(outputFile))); 184 | }); 185 | }; 186 | 187 | HdtDocumentPrototype.close = function () { 188 | return new Promise((resolve, reject) => 189 | this._close(e => e ? reject(e) : resolve())); 190 | }; 191 | 192 | function parseOffset({ offset }) { 193 | if (isNaN(offset)) return 0; 194 | if (offset === Infinity) return MAX; 195 | return Math.max(0, parseInt(offset, 10)); 196 | } 197 | 198 | function parseLimit({ limit }) { 199 | if (isNaN(limit) || limit === Infinity) return MAX; 200 | return Math.max(0, parseInt(limit, 10)); 201 | } 202 | 203 | // Deprecated method names 204 | HdtDocumentPrototype.count = HdtDocumentPrototype.countTriples; 205 | HdtDocumentPrototype.search = HdtDocumentPrototype.searchTriples; 206 | 207 | 208 | 209 | /* Module exports */ 210 | 211 | module.exports = { 212 | // Creates an HDT document for the given file. 213 | fromFile: (filename, opts) => { 214 | if (typeof filename !== 'string' || filename.length === 0) 215 | return Promise.reject(Error('Invalid filename: ' + filename)); 216 | return new Promise((resolve, reject) => { 217 | hdtNative.createHdtDocument(filename, (error, document) => { 218 | // Abort the creation if any error occurred 219 | if (error) { 220 | switch (error.message) { 221 | case 'Error opening HDT file for mapping.': 222 | return reject(new Error('Could not open HDT file "' + filename + '"')); 223 | case 'Non-HDT Section': 224 | return reject(new Error('The file "' + filename + '" is not a valid HDT file')); 225 | default: 226 | return reject(error); 227 | } 228 | } 229 | document.dataFactory = opts && opts.dataFactory ? opts.dataFactory : N3.DataFactory; 230 | // Document the features of the HDT file 231 | document.features = Object.freeze({ 232 | searchTriples: true, // supported by default 233 | searchBindings: true, // supported by default 234 | countTriples: true, // supported by default 235 | searchLiterals: !!(document._features & 1), 236 | readHeader: true, // supported by default 237 | changeHeader: true, // supported by default 238 | }); 239 | resolve(document); 240 | }); 241 | }); 242 | }, 243 | }; 244 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hdt", 3 | "version": "3.2.2", 4 | "description": "Native bindings to access HDT compressed triple files.", 5 | "author": "Ruben Verborgh ", 6 | "keywords": [ 7 | "turtle", 8 | "rdf", 9 | "hdt", 10 | "triples", 11 | "linkeddata", 12 | "semanticweb" 13 | ], 14 | "license": "LGPL-3.0", 15 | "main": "./lib/hdt", 16 | "bin": "./bin/hdt", 17 | "types": "./lib/hdt.d.ts", 18 | "files": [ 19 | "bin", 20 | "binding.gyp", 21 | "deps", 22 | "lib" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/RubenVerborgh/HDT-Node.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/RubenVerborgh/HDT-Node/issues" 30 | }, 31 | "engines": { 32 | "node": ">=6.0" 33 | }, 34 | "scripts": { 35 | "test": "rm test/*.hdt.index.* 2> /dev/null; mocha", 36 | "lint": "eslint --fix lib/*.js test/*.js bin/*", 37 | "validate": "npm ls", 38 | "preinstall": "if [ -e replace-in-file.json ]; then replace-in-file --configFile=replace-in-file.json; fi && node-gyp rebuild" 39 | }, 40 | "dependencies": { 41 | "minimist": "^1.1.0", 42 | "n3": "^1.17.3", 43 | "nan": "^2.19.0", 44 | "rdf-string": "^1.6.3" 45 | }, 46 | "devDependencies": { 47 | "eslint": "^5.3.0", 48 | "mocha": "^6.2.2", 49 | "precommit-hook": "^3.0.0", 50 | "replace-in-file": "^8.3.0", 51 | "should": "^13.1.0", 52 | "@comunica/utils-bindings-factory": "^4.0.2" 53 | }, 54 | "pre-commit": [ 55 | "lint", 56 | "test" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /perf/run.js: -------------------------------------------------------------------------------- 1 | const hdt = require('../lib/hdt'); 2 | const DF = require('n3').DataFactory; 3 | const BF = new (require('@comunica/utils-bindings-factory').BindingsFactory)(DF); 4 | 5 | const REPLICATION = 100000; 6 | hdt.fromFile('../test/test.hdt').then(runForDocument); 7 | 8 | async function runForDocument(document) { 9 | console.time('warmup'); 10 | await iterate(document, 10000, searchTriples); 11 | await iterate(document, 10000, searchBindings); 12 | console.timeEnd('warmup'); 13 | 14 | console.time('searchTriples'); 15 | await iterate(document, REPLICATION, searchTriples); 16 | console.timeEnd('searchTriples'); 17 | 18 | console.time('searchBindings'); 19 | await iterate(document, REPLICATION, searchBindings); 20 | console.timeEnd('searchBindings'); 21 | } 22 | 23 | async function iterate(document, replication, fun) { 24 | for (let i = 0; i < replication; i++) { 25 | await fun(document); 26 | } 27 | } 28 | 29 | async function searchTriples(document) { 30 | const { triples } = await document.searchTriples(DF.namedNode('http://example.org/s2'), null, null); 31 | assert(triples.length == 10); 32 | } 33 | 34 | async function searchBindings(document) { 35 | const { bindings } = await document.searchBindings(BF, DF.namedNode('http://example.org/s2'), DF.variable('p'), DF.variable('o')); 36 | assert(bindings.length == 10); 37 | } 38 | 39 | function assert(condition, message) { 40 | if (!condition) { 41 | throw message || "Assertion failed"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /replace-in-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": "deps/libhdt/src/util/StopWatch.cpp", 3 | "from": "uint64_t", 4 | "to": "unsigned long long" 5 | } 6 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | globals: { 3 | describe: true, 4 | it: true, 5 | before: true, 6 | after: true, 7 | }, 8 | 9 | rules: { 10 | max-nested-callbacks: 0, // Mocha works with deeply nested callbacks 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | testOutput.hdt* 2 | -------------------------------------------------------------------------------- /test/hdt-test.js: -------------------------------------------------------------------------------- 1 | require('should'); 2 | const { literal, variable, namedNode, quad, defaultGraph } = require('n3').DataFactory; 3 | 4 | const hdt = require('../lib/hdt'); 5 | const BF = new (require('@comunica/utils-bindings-factory').BindingsFactory)(require('n3').DataFactory); 6 | 7 | describe('hdt', function () { 8 | describe('The hdt module', function () { 9 | it('should be an object', function () { 10 | hdt.should.be.an.Object(); 11 | }); 12 | }); 13 | 14 | describe('creating a new HDT document with fromFile', function () { 15 | describe('with a non-string argument', function () { 16 | it('should throw an error', function () { 17 | return hdt.fromFile(null).then(() => Promise.reject(new Error('Expected an error')), error => { 18 | error.should.be.an.Error(); 19 | error.message.should.equal('Invalid filename: null'); 20 | }); 21 | }); 22 | }); 23 | 24 | describe('with a non-existing file as argument', function () { 25 | it('should throw an error', function () { 26 | return hdt.fromFile('abc').then(() => Promise.reject(new Error('Expected an error')), error => { 27 | error.should.be.an.Error(); 28 | error.message.should.equal('Could not open HDT file "abc"'); 29 | }); 30 | }); 31 | }); 32 | 33 | describe('with a non-HDT file as argument', function () { 34 | it('should throw an error', function () { 35 | return hdt.fromFile('./test/hdt-test.js').then(() => Promise.reject(new Error('Expected an error')), error => { 36 | error.should.be.an.Error(); 37 | error.message.should.equal('The file "./test/hdt-test.js" is not a valid HDT file'); 38 | }); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('An HDT document for an example HDT file', function () { 44 | var document; 45 | before(function () { 46 | return hdt.fromFile('./test/test.hdt').then(hdtDocument => { 47 | document = hdtDocument; 48 | }); 49 | }); 50 | after(function () { 51 | return document.close(); 52 | }); 53 | 54 | describe('asked for supported features', function () { 55 | it('should return an object', function () { 56 | document.features.should.be.an.instanceof(Object); 57 | }); 58 | 59 | it('should support searchTriples', function () { 60 | document.features.searchTriples.should.be.true(); 61 | }); 62 | 63 | it('should support searchBindings', function () { 64 | document.features.searchBindings.should.be.true(); 65 | }); 66 | 67 | it('should support countTriples', function () { 68 | document.features.countTriples.should.be.true(); 69 | }); 70 | 71 | it('should not support searchLiterals', function () { 72 | document.features.searchLiterals.should.be.false(); 73 | }); 74 | 75 | it('should support readHeader', function () { 76 | document.features.readHeader.should.be.true(); 77 | }); 78 | 79 | it('should support changeHeader', function () { 80 | document.features.changeHeader.should.be.true(); 81 | }); 82 | }); 83 | 84 | describe('reading the header', function () { 85 | var header; 86 | before(function () { 87 | return document.readHeader().then(result => { 88 | header = result; 89 | }); 90 | }); 91 | it('should return a string with matches', function () { 92 | header.should.be.a.String(); 93 | header.split('\n').should.have.length(23); 94 | header.indexOf(' ' + 95 | ' ' + 96 | '').should.be.above(-1); 97 | header.indexOf('_:publicationInformation ' + 98 | ' ' + 99 | '"2019-01-18T13:09:56+01:00"').should.be.above(-1); 100 | }); 101 | }); 102 | 103 | describe('writing a header and saving to a new file', function () { 104 | var newhdt; 105 | var header = '_:dictionary "1" .\n' + 106 | '_:dictionary "825" .'; 107 | var outputFile = './test/testOutput.hdt'; 108 | before(function () { 109 | return document.changeHeader(header, outputFile) 110 | .then(hdtDocument => { 111 | newhdt = hdtDocument; 112 | }); 113 | }); 114 | after(function () { 115 | return newhdt.close(); 116 | }); 117 | 118 | describe('reading the new header', function () { 119 | var header; 120 | before(function () { 121 | return newhdt.readHeader().then(result => { 122 | header = result; 123 | }); 124 | }); 125 | it('should return a string with matches', function () { 126 | header.should.be.a.String(); 127 | header.split('\n').should.have.length(3); 128 | header.indexOf('_:dictionary ' + 129 | ' ' + 130 | '"1"').should.be.above(-1); 131 | header.indexOf('_:dictionary ' + 132 | ' ' + 133 | '"825"').should.be.above(-1); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('getting suggestions', function () { 139 | it('should have correct results for predicate position', function () { 140 | return document.searchTerms({ prefix: 'http://example.org/', limit:100, position : 'predicate' }).then(suggestions => { 141 | suggestions.should.have.lengthOf(3); 142 | suggestions[0].should.eql(namedNode('http://example.org/p1')); 143 | }); 144 | }); 145 | 146 | it('should have correct results for object position', function () { 147 | return document.searchTerms({ prefix: 'http://example.org/', limit: 2, position : 'object' }).then(suggestions => { 148 | suggestions[0].should.eql(namedNode('http://example.org/o001')); 149 | suggestions.should.have.lengthOf(2); 150 | }); 151 | }); 152 | 153 | it('should get suggestions for literals', function () { 154 | return document.searchTerms({ prefix: '"a', position : 'object' }).then(suggestions => { 155 | suggestions.should.have.lengthOf(9); 156 | }); 157 | }); 158 | 159 | it('should get suggestions for all literals', function () { 160 | return document.searchTerms({ prefix: '"', position : 'object' }).then(suggestions => { 161 | suggestions.should.have.lengthOf(14); 162 | }); 163 | }); 164 | 165 | it('should get all results on empty match', function () { 166 | return document.searchTerms({ prefix: '', position: 'object' }).then(suggestions => { 167 | suggestions.should.have.lengthOf(114); 168 | }); 169 | }); 170 | 171 | it('should get all results when prefix is not defined', function () { 172 | return document.searchTerms({ position: 'object' }).then(suggestions => { 173 | suggestions.should.have.lengthOf(114); 174 | }); 175 | }); 176 | 177 | it('should return 0 results on negative limit', function () { 178 | return document.searchTerms({ prefix: 'http://example.org/', limit: -1, position: 'object' }).then(suggestions => { 179 | suggestions.should.have.lengthOf(0); 180 | }); 181 | }); 182 | 183 | it('should return all results when passed an invalid limit value', function () { 184 | return document.searchTerms({ prefix: 'http://example.org/', limit: 'sdf', position: 'object' }).then(suggestions => { 185 | suggestions.should.have.lengthOf(100); 186 | }); 187 | }); 188 | 189 | it('should throw on invalid position', function () { 190 | return document.searchTerms({ prefix: 'http://example.org/', limit: 'sdf', position: 'bla' }).then( 191 | () => Promise.reject(new Error('Expected an error')), 192 | error => { 193 | error.should.be.an.instanceOf(Error); 194 | error.message.should.equal('Invalid position argument. Expected subject, predicate or object.'); 195 | } 196 | ); 197 | }); 198 | }); 199 | 200 | describe('fetching distinct terms', function () { 201 | it('should have correct results for given subject', function () { 202 | return document.searchTerms({ subject: namedNode('http://example.org/s1'), position: 'predicate' }).then(terms => { 203 | terms.should.have.lengthOf(1); 204 | terms[0].should.eql(namedNode('http://example.org/p1')); 205 | }); 206 | }); 207 | 208 | it('should limit results for given subject', function () { 209 | return document.searchTerms({ subject: namedNode('http://example.org/s2'), limit: 1, position: 'predicate' }).then(terms => { 210 | terms.should.have.lengthOf(1); 211 | terms[0].should.eql(namedNode('http://example.org/p1')); 212 | }); 213 | }); 214 | 215 | it('should throw on unsupported position (subject-subject)', function () { 216 | return document.searchTerms({ subject: namedNode('http://example.org/s1'), position: 'subject' }).then( 217 | () => Promise.reject(new Error('Expected an error')), 218 | error => { 219 | error.should.be.an.instanceOf(Error); 220 | error.message.should.equal('Unsupported position argument. Expected predicate.'); 221 | } 222 | ); 223 | }); 224 | 225 | it('should throw on unsupported position (subject-object)', function () { 226 | return document.searchTerms({ subject: namedNode('http://example.org/s1'), position: 'object' }).then( 227 | () => Promise.reject(new Error('Expected an error')), 228 | error => { 229 | error.should.be.an.instanceOf(Error); 230 | error.message.should.equal('Unsupported position argument. Expected predicate.'); 231 | } 232 | ); 233 | }); 234 | 235 | it('should return 0 results on unspecified subject value', function () { 236 | return document.searchTerms({ subject: undefined, position: 'predicate' }).then(terms => { 237 | terms.should.have.lengthOf(0); 238 | } 239 | ); 240 | }); 241 | 242 | it('should have correct results for given object', function () { 243 | return document.searchTerms({ object: namedNode('http://example.org/o001'), position: 'predicate' }).then(terms => { 244 | terms.should.have.lengthOf(2); 245 | terms[0].should.eql(namedNode('http://example.org/p1')); 246 | terms[1].should.eql(namedNode('http://example.org/p2')); 247 | }); 248 | }); 249 | 250 | it('should limit results for given object', function () { 251 | return document.searchTerms({ object: namedNode('http://example.org/o001'), limit: 1, position: 'predicate' }).then(terms => { 252 | terms.should.have.lengthOf(1); 253 | terms[0].should.eql(namedNode('http://example.org/p1')); 254 | }); 255 | }); 256 | 257 | it('should throw on unsupported position (object-object)', function () { 258 | return document.searchTerms({ object: namedNode('http://example.org/o001'), position: 'object' }).then( 259 | () => Promise.reject(new Error('Expected an error')), 260 | error => { 261 | error.should.be.an.instanceOf(Error); 262 | error.message.should.equal('Unsupported position argument. Expected predicate.'); 263 | } 264 | ); 265 | }); 266 | 267 | it('should throw on unsupported position (object-subject)', function () { 268 | return document.searchTerms({ object: namedNode('http://example.org/o001'), position: 'subject' }).then( 269 | () => Promise.reject(new Error('Expected an error')), 270 | error => { 271 | error.should.be.an.instanceOf(Error); 272 | error.message.should.equal('Unsupported position argument. Expected predicate.'); 273 | } 274 | ); 275 | }); 276 | 277 | it('should throw on invalid position', function () { 278 | return document.searchTerms({ object: namedNode('http://example.org/o001'), position: 'obj' }).then( 279 | () => Promise.reject(new Error('Expected an error')), 280 | error => { 281 | error.should.be.an.instanceOf(Error); 282 | error.message.should.equal('Invalid position argument. Expected subject, predicate or object.'); 283 | } 284 | ); 285 | }); 286 | 287 | it('should return 0 results on unspecified object value', function () { 288 | return document.searchTerms({ object: namedNode(''), limit: 0, position: 'predicate' }).then(terms => { 289 | terms.should.have.lengthOf(0); 290 | } 291 | ); 292 | }); 293 | 294 | it('should return 0 results on negative limit', function () { 295 | return document.searchTerms({ object: namedNode('http://example.org/o001'), limit: -1, position: 'predicate' }).then(terms => { 296 | terms.should.have.lengthOf(0); 297 | }); 298 | }); 299 | 300 | it('should return all results on invalid limit', function () { 301 | return document.searchTerms({ object: namedNode('http://example.org/o001'), limit: 'sdf', position: 'predicate' }).then(terms => { 302 | terms.should.have.lengthOf(2); 303 | }); 304 | }); 305 | 306 | it('should have correct results for given subject and object', function () { 307 | return document.searchTerms({ subject: namedNode('http://example.org/s1'), object: namedNode('http://example.org/o001'), position: 'predicate' }).then(terms => { 308 | terms.should.have.lengthOf(1); 309 | terms[0].should.eql(namedNode('http://example.org/p1')); 310 | }); 311 | }); 312 | 313 | it('should limit results for given subject and object', function () { 314 | return document.searchTerms({ subject: namedNode('http://example.org/s2'), object: namedNode('http://example.org/o001'), limit: 1, position: 'predicate' }).then(terms => { 315 | terms.should.have.lengthOf(1); 316 | terms[0].should.eql(namedNode('http://example.org/p1')); 317 | }); 318 | }); 319 | 320 | it('should throw on unsupported position (subject and object - subject)', function () { 321 | return document.searchTerms({ subject: namedNode('http://example.org/s1'), object: namedNode('http://example.org/o001'), position: 'subject' }).then( 322 | () => Promise.reject(new Error('Expected an error')), 323 | error => { 324 | error.should.be.an.instanceOf(Error); 325 | error.message.should.equal('Unsupported position argument. Expected predicate.'); 326 | } 327 | ); 328 | }); 329 | 330 | it('should throw on unsupported position (subject and object - object)', function () { 331 | return document.searchTerms({ subject: namedNode('http://example.org/s1'), object: namedNode('http://example.org/o001'), position: 'object' }).then( 332 | () => Promise.reject(new Error('Expected an error')), 333 | error => { 334 | error.should.be.an.instanceOf(Error); 335 | error.message.should.equal('Unsupported position argument. Expected predicate.'); 336 | } 337 | ); 338 | }); 339 | }); 340 | 341 | describe('being searched for triples', function () { 342 | describe('with a non-existing pattern', function () { 343 | var triples, totalCount; 344 | before(function () { 345 | return document.searchTriples(namedNode('a'), null, null).then(result => { 346 | triples = result.triples; 347 | totalCount = result.totalCount; 348 | }); 349 | }); 350 | 351 | it('should return an array with matches', function () { 352 | triples.should.be.an.Array(); 353 | triples.should.be.empty(); 354 | }); 355 | 356 | it('should estimate the total count as 0', function () { 357 | totalCount.should.equal(0); 358 | }); 359 | }); 360 | 361 | describe('with pattern null null null', function () { 362 | var triples, totalCount, hasExactCount; 363 | before(function () { 364 | return document.searchTriples(null, null, null).then(result => { 365 | triples = result.triples; 366 | totalCount = result.totalCount; 367 | hasExactCount = result.hasExactCount; 368 | }); 369 | }); 370 | 371 | it('should return an array with matches', function () { 372 | triples.should.be.an.Array(); 373 | triples.should.have.length(134); 374 | triples[0].should.eql(quad( 375 | namedNode('http://example.org/s1'), 376 | namedNode('http://example.org/p1'), 377 | namedNode('http://example.org/o001'), 378 | defaultGraph() 379 | )); 380 | }); 381 | 382 | it('should estimate the total count as 134', function () { 383 | totalCount.should.equal(134); 384 | }); 385 | 386 | it('should be an exact count', function () { 387 | hasExactCount.should.equal(true); 388 | }); 389 | }); 390 | 391 | describe('with pattern null null null, offset 0 and limit 10', function () { 392 | var triples, totalCount, hasExactCount; 393 | before(function () { 394 | return document.searchTriples(null, null, null, { offset: 0, limit: 10 }).then(result => { 395 | triples = result.triples; 396 | totalCount = result.totalCount; 397 | hasExactCount = result.hasExactCount; 398 | }); 399 | }); 400 | 401 | it('should return an array with all matches', function () { 402 | triples.should.be.an.Array(); 403 | triples.should.have.length(10); 404 | triples[0].should.eql(quad( 405 | namedNode('http://example.org/s1'), 406 | namedNode('http://example.org/p1'), 407 | namedNode('http://example.org/o001'), 408 | defaultGraph() 409 | )); 410 | }); 411 | 412 | it('should estimate the total count as 134', function () { 413 | totalCount.should.equal(134); 414 | }); 415 | 416 | it('should be an exact count', function () { 417 | hasExactCount.should.equal(true); 418 | }); 419 | }); 420 | 421 | describe('with pattern null null null, offset 0 and limit 0', function () { 422 | var triples, totalCount, hasExactCount; 423 | before(function () { 424 | return document.searchTriples(null, null, null, { offset: 0, limit: 0 }).then(result => { 425 | triples = result.triples; 426 | totalCount = result.totalCount; 427 | hasExactCount = result.hasExactCount; 428 | }); 429 | }); 430 | 431 | it('should return an empty array', function () { 432 | triples.should.be.an.Array(); 433 | triples.should.have.length(0); 434 | }); 435 | 436 | it('should estimate the total count as 134', function () { 437 | totalCount.should.equal(134); 438 | }); 439 | 440 | it('should be an exact count', function () { 441 | hasExactCount.should.equal(true); 442 | }); 443 | }); 444 | 445 | describe('with pattern null null null, offset 0 and limit Infinity', function () { 446 | var triples, totalCount, hasExactCount; 447 | before(function () { 448 | return document.searchTriples(null, null, null, { offset: 0, limit: Infinity }).then(result => { 449 | triples = result.triples; 450 | totalCount = result.totalCount; 451 | hasExactCount = result.hasExactCount; 452 | }); 453 | }); 454 | 455 | it('should return an array with all matches', function () { 456 | triples.should.be.an.Array(); 457 | triples.should.have.length(134); 458 | triples[0].should.eql(quad( 459 | namedNode('http://example.org/s1'), 460 | namedNode('http://example.org/p1'), 461 | namedNode('http://example.org/o001'), 462 | defaultGraph() 463 | )); 464 | }); 465 | 466 | it('should estimate the total count as 134', function () { 467 | totalCount.should.equal(134); 468 | }); 469 | 470 | it('should be an exact count', function () { 471 | hasExactCount.should.equal(true); 472 | }); 473 | }); 474 | 475 | describe('with pattern null null null, offset 0 and limit -Infinity', function () { 476 | var triples, totalCount, hasExactCount; 477 | before(function () { 478 | return document.searchTriples(null, null, null, { offset: 0, limit: -Infinity }).then(result => { 479 | triples = result.triples; 480 | totalCount = result.totalCount; 481 | hasExactCount = result.hasExactCount; 482 | }); 483 | }); 484 | 485 | it('should return an empty array', function () { 486 | triples.should.be.an.Array(); 487 | triples.should.have.length(0); 488 | }); 489 | 490 | it('should estimate the total count as 134', function () { 491 | totalCount.should.equal(134); 492 | }); 493 | 494 | it('should be an exact count', function () { 495 | hasExactCount.should.equal(true); 496 | }); 497 | }); 498 | 499 | describe('with pattern null null null, offset Infinity and limit Infinity', function () { 500 | var triples, totalCount, hasExactCount; 501 | before(function () { 502 | return document.searchTriples(null, null, null, { offset: Infinity, limit: Infinity }).then(result => { 503 | triples = result.triples; 504 | totalCount = result.totalCount; 505 | hasExactCount = result.hasExactCount; 506 | }); 507 | }); 508 | 509 | it('should return an empty array', function () { 510 | triples.should.be.an.Array(); 511 | triples.should.have.length(0); 512 | }); 513 | 514 | it('should estimate the total count as 134', function () { 515 | totalCount.should.equal(134); 516 | }); 517 | 518 | it('should be an exact count', function () { 519 | hasExactCount.should.equal(true); 520 | }); 521 | }); 522 | 523 | describe('with pattern null null null, offset -Infinity and limit Infinity', function () { 524 | var triples, totalCount, hasExactCount; 525 | before(function () { 526 | return document.searchTriples(null, null, null, { offset: -Infinity, limit: Infinity }).then(result => { 527 | triples = result.triples; 528 | totalCount = result.totalCount; 529 | hasExactCount = result.hasExactCount; 530 | }); 531 | }); 532 | 533 | it('should return an array with all matches', function () { 534 | triples.should.be.an.Array(); 535 | triples.should.have.length(134); 536 | triples[0].should.eql(quad( 537 | namedNode('http://example.org/s1'), 538 | namedNode('http://example.org/p1'), 539 | namedNode('http://example.org/o001'), 540 | defaultGraph() 541 | )); 542 | }); 543 | 544 | it('should estimate the total count as 134', function () { 545 | totalCount.should.equal(134); 546 | }); 547 | 548 | it('should be an exact count', function () { 549 | hasExactCount.should.equal(true); 550 | }); 551 | }); 552 | 553 | describe('with pattern null null null, offset 10 and limit 5', function () { 554 | var triples, totalCount, hasExactCount; 555 | before(function () { 556 | return document.searchTriples(null, null, null, { offset: 10, limit: 5 }).then(result => { 557 | triples = result.triples; 558 | totalCount = result.totalCount; 559 | hasExactCount = result.hasExactCount; 560 | }); 561 | }); 562 | 563 | it('should return an array with matches', function () { 564 | triples.should.be.an.Array(); 565 | triples.should.have.length(5); 566 | triples[0].should.eql(quad( 567 | namedNode('http://example.org/s1'), 568 | namedNode('http://example.org/p1'), 569 | namedNode('http://example.org/o011'), 570 | defaultGraph() 571 | )); 572 | }); 573 | 574 | it('should estimate the total count as 134', function () { 575 | totalCount.should.equal(134); 576 | }); 577 | 578 | it('should be an exact count', function () { 579 | hasExactCount.should.equal(true); 580 | }); 581 | }); 582 | 583 | describe('with pattern null null null, offset 200 and limit 5', function () { 584 | var triples, totalCount, hasExactCount; 585 | before(function () { 586 | return document.searchTriples(null, null, null, { offset: 200, limit: 5 }).then(result => { 587 | triples = result.triples; 588 | totalCount = result.totalCount; 589 | hasExactCount = result.hasExactCount; 590 | }); 591 | }); 592 | 593 | it('should return an array with matches', function () { 594 | triples.should.be.an.Array(); 595 | triples.should.be.empty(); 596 | }); 597 | 598 | it('should estimate the total count as 134', function () { 599 | totalCount.should.equal(134); 600 | }); 601 | 602 | it('should be an exact count', function () { 603 | hasExactCount.should.equal(true); 604 | }); 605 | }); 606 | 607 | describe('with pattern ex:s2 null null', function () { 608 | var triples, totalCount, hasExactCount; 609 | before(function () { 610 | return document.searchTriples(namedNode('http://example.org/s2'), null, null).then(result => { 611 | triples = result.triples; 612 | totalCount = result.totalCount; 613 | hasExactCount = result.hasExactCount; 614 | }); 615 | }); 616 | 617 | it('should return an array with matches', function () { 618 | triples.should.be.an.Array(); 619 | triples.should.have.length(10); 620 | triples[0].should.eql(quad( 621 | namedNode('http://example.org/s2'), 622 | namedNode('http://example.org/p1'), 623 | namedNode('http://example.org/o001'), 624 | defaultGraph() 625 | )); 626 | triples[1].should.eql(quad( 627 | namedNode('http://example.org/s2'), 628 | namedNode('http://example.org/p1'), 629 | namedNode('http://example.org/o002'), 630 | defaultGraph() 631 | )); 632 | }); 633 | 634 | it('should estimate the total count as 10', function () { 635 | totalCount.should.equal(10); 636 | }); 637 | 638 | it('should be an exact count', function () { 639 | hasExactCount.should.equal(true); 640 | }); 641 | }); 642 | 643 | describe('with pattern ex:s2 ex:p1 null', function () { 644 | var triples; 645 | before(function () { 646 | return document.searchTriples(namedNode('http://example.org/s2'), namedNode('http://example.org/p1'), null).then(result => { 647 | triples = result.triples; 648 | }); 649 | }); 650 | 651 | it('should return an array with matches', function () { 652 | triples.should.be.an.Array(); 653 | triples.should.have.length(10); 654 | triples[0].should.eql(quad( 655 | namedNode('http://example.org/s2'), 656 | namedNode('http://example.org/p1'), 657 | namedNode('http://example.org/o001'), 658 | defaultGraph() 659 | )); 660 | triples[1].should.eql(quad( 661 | namedNode('http://example.org/s2'), 662 | namedNode('http://example.org/p1'), 663 | namedNode('http://example.org/o002'), 664 | defaultGraph() 665 | )); 666 | }); 667 | }); 668 | 669 | describe('with pattern ex:s2 ex:p1 ex:o010', function () { 670 | var triples; 671 | before(function () { 672 | return document.searchTriples(namedNode('http://example.org/s2'), namedNode('http://example.org/p1'), namedNode('http://example.org/o010')).then(result => { 673 | triples = result.triples; 674 | }); 675 | }); 676 | 677 | it('should return an array with matches', function () { 678 | triples.should.be.an.Array(); 679 | triples.should.have.length(1); 680 | triples[0].should.eql(quad( 681 | namedNode('http://example.org/s2'), 682 | namedNode('http://example.org/p1'), 683 | namedNode('http://example.org/o010'), 684 | defaultGraph() 685 | )); 686 | }); 687 | }); 688 | 689 | // Use this pattern to check whether the ObjectIndexIterator implementation 690 | // in hdt-cpp works. 691 | // Link to issue -> https://github.com/rdfhdt/hdt-cpp/issues/84 692 | describe('with pattern null null ex:o010', function () { 693 | var triples; 694 | before(function () { 695 | return document.searchTriples(null, null, namedNode('http://example.org/o010')).then(result => { 696 | triples = result.triples; 697 | }); 698 | }); 699 | 700 | it('should return an array with matches', function () { 701 | triples.should.be.an.Array(); 702 | triples.should.have.length(3); 703 | triples[0].should.eql(quad( 704 | namedNode('http://example.org/s1'), 705 | namedNode('http://example.org/p1'), 706 | namedNode('http://example.org/o010'), 707 | defaultGraph() 708 | )); 709 | }); 710 | }); 711 | 712 | describe('with pattern ex:s2 null null, offset 2 and limit 1', function () { 713 | var triples, totalCount, hasExactCount; 714 | before(function () { 715 | return document.searchTriples(namedNode('http://example.org/s2'), null, null, { offset: 2, limit: 1 }).then(result => { 716 | triples = result.triples; 717 | totalCount = result.totalCount; 718 | hasExactCount = result.hasExactCount; 719 | }); 720 | }); 721 | 722 | it('should return an array with matches', function () { 723 | triples.should.be.an.Array(); 724 | triples.should.have.length(1); 725 | triples[0].should.eql(quad( 726 | namedNode('http://example.org/s2'), 727 | namedNode('http://example.org/p1'), 728 | namedNode('http://example.org/o003'), 729 | defaultGraph() 730 | )); 731 | }); 732 | 733 | it('should estimate the total count as 10', function () { 734 | totalCount.should.equal(10); 735 | }); 736 | 737 | it('should be an exact count', function () { 738 | hasExactCount.should.equal(true); 739 | }); 740 | }); 741 | 742 | describe('with pattern ex:s2 null null, offset 200 and limit 1', function () { 743 | var triples, totalCount, hasExactCount; 744 | before(function () { 745 | return document.searchTriples(namedNode('http://example.org/s2'), null, null, { offset: 200, limit: 1 }).then(result => { 746 | triples = result.triples; 747 | totalCount = result.totalCount; 748 | hasExactCount = result.hasExactCount; 749 | }); 750 | }); 751 | 752 | it('should return an array with matches', function () { 753 | triples.should.be.an.Array(); 754 | triples.should.be.empty(); 755 | }); 756 | 757 | it('should estimate the total count as 10', function () { 758 | totalCount.should.equal(10); 759 | }); 760 | 761 | it('should be an exact count', function () { 762 | hasExactCount.should.equal(true); 763 | }); 764 | }); 765 | 766 | describe('with pattern ex:s2 ?p ?o', function () { 767 | var triples, totalCount, hasExactCount; 768 | before(function () { 769 | return document.searchTriples(namedNode('http://example.org/s2'), '?p', variable('?p')).then(result => { 770 | triples = result.triples; 771 | totalCount = result.totalCount; 772 | hasExactCount = result.hasExactCount; 773 | }); 774 | }); 775 | 776 | it('should return an array with matches', function () { 777 | triples.should.be.an.Array(); 778 | triples.should.have.length(10); 779 | triples[0].should.eql(quad( 780 | namedNode('http://example.org/s2'), 781 | namedNode('http://example.org/p1'), 782 | namedNode('http://example.org/o001'), 783 | defaultGraph() 784 | )); 785 | triples[1].should.eql(quad( 786 | namedNode('http://example.org/s2'), 787 | namedNode('http://example.org/p1'), 788 | namedNode('http://example.org/o002'), 789 | defaultGraph() 790 | )); 791 | }); 792 | 793 | it('should estimate the total count as 10', function () { 794 | totalCount.should.equal(10); 795 | }); 796 | 797 | it('should be an exact count', function () { 798 | hasExactCount.should.equal(true); 799 | }); 800 | }); 801 | 802 | describe('with pattern null ex:p2 null, offset 2, limit 2', function () { 803 | var triples, totalCount, hasExactCount; 804 | before(function () { 805 | return document.searchTriples(null, namedNode('http://example.org/p2'), null, { offset: 2, limit: 2 }).then(result => { 806 | triples = result.triples; 807 | totalCount = result.totalCount; 808 | hasExactCount = result.hasExactCount; 809 | }); 810 | }); 811 | 812 | it('should return an array with matches', function () { 813 | triples.should.be.an.Array(); 814 | triples.should.have.length(2); 815 | triples[0].should.eql(quad( 816 | namedNode('http://example.org/s3'), 817 | namedNode('http://example.org/p2'), 818 | namedNode('http://example.org/o003'), 819 | defaultGraph() 820 | )); 821 | triples[1].should.eql(quad( 822 | namedNode('http://example.org/s3'), 823 | namedNode('http://example.org/p2'), 824 | namedNode('http://example.org/o004'), 825 | defaultGraph() 826 | )); 827 | }); 828 | 829 | it('should estimate the total count as 10', function () { 830 | totalCount.should.equal(10); 831 | }); 832 | 833 | it('should be an exact count', function () { 834 | hasExactCount.should.equal(true); 835 | }); 836 | }); 837 | 838 | describe('with pattern null ex:p2 null, offset 200', function () { 839 | var triples, totalCount, hasExactCount; 840 | before(function () { 841 | return document.searchTriples(null, namedNode('http://example.org/p2'), null, { offset: 200 }).then(result => { 842 | triples = result.triples; 843 | totalCount = result.totalCount; 844 | hasExactCount = result.hasExactCount; 845 | }); 846 | }); 847 | 848 | it('should return an empty array', function () { 849 | triples.should.be.an.Array(); 850 | triples.should.be.empty(); 851 | triples.should.have.length(0); 852 | }); 853 | 854 | it('should estimate the total count as 10', function () { 855 | totalCount.should.equal(10); 856 | }); 857 | 858 | it('should be an exact count', function () { 859 | hasExactCount.should.equal(true); 860 | }); 861 | }); 862 | 863 | describe('with pattern null ex:p2 null', function () { 864 | var triples, totalCount, hasExactCount; 865 | before(function () { 866 | return document.searchTriples(null, namedNode('http://example.org/p2'), null).then(result => { 867 | triples = result.triples; 868 | totalCount = result.totalCount; 869 | hasExactCount = result.hasExactCount; 870 | }); 871 | }); 872 | 873 | it('should return an array with matches', function () { 874 | triples.should.be.an.Array(); 875 | triples.should.have.length(10); 876 | triples[0].should.eql(quad( 877 | namedNode('http://example.org/s3'), 878 | namedNode('http://example.org/p2'), 879 | namedNode('http://example.org/o001'), 880 | defaultGraph() 881 | )); 882 | triples[1].should.eql(quad( 883 | namedNode('http://example.org/s3'), 884 | namedNode('http://example.org/p2'), 885 | namedNode('http://example.org/o002'), 886 | defaultGraph() 887 | )); 888 | }); 889 | 890 | it('should estimate the total count as 10', function () { 891 | totalCount.should.equal(10); 892 | }); 893 | 894 | it('should be an exact count', function () { 895 | hasExactCount.should.equal(true); 896 | }); 897 | }); 898 | 899 | describe('with pattern null ex:p1 ""', function () { 900 | var triples, totalCount, hasExactCount; 901 | before(function () { 902 | return document.searchTriples(null, namedNode('http://example.org/p1'), literal('')).then(result => { 903 | triples = result.triples; 904 | totalCount = result.totalCount; 905 | hasExactCount = result.hasExactCount; 906 | }); 907 | }); 908 | 909 | it('should return an array with matches', function () { 910 | triples.should.be.an.Array(); 911 | triples.should.have.length(0); 912 | }); 913 | 914 | it('should estimate the total count as 0', function () { 915 | totalCount.should.equal(0); 916 | }); 917 | 918 | it('should be an exact count', function () { 919 | hasExactCount.should.equal(true); 920 | }); 921 | }); 922 | 923 | describe('with pattern null ex:p3 null', function () { 924 | var triples, totalCount, hasExactCount; 925 | before(function () { 926 | return document.searchTriples(null, namedNode('http://example.org/p3'), null).then(result => { 927 | triples = result.triples; 928 | totalCount = result.totalCount; 929 | hasExactCount = result.hasExactCount; 930 | }); 931 | }); 932 | 933 | it('should return an array with matches', function () { 934 | triples.should.be.an.Array(); 935 | triples.should.have.length(14); 936 | 937 | triples[0].should.eql(quad( 938 | namedNode('http://example.org/s4'), 939 | namedNode('http://example.org/p3'), 940 | literal('', namedNode('http://www.w3.org/2001/XMLSchema#string')), 941 | defaultGraph() 942 | )); 943 | triples[1].should.eql(quad( 944 | namedNode('http://example.org/s4'), 945 | namedNode('http://example.org/p3'), 946 | literal('', 'en'), 947 | defaultGraph() 948 | )); 949 | triples[2].should.eql(quad( 950 | namedNode('http://example.org/s4'), 951 | namedNode('http://example.org/p3'), 952 | literal('', namedNode('http://example.org/literal')), 953 | defaultGraph() 954 | )); 955 | triples[3].should.eql(quad( 956 | namedNode('http://example.org/s4'), 957 | namedNode('http://example.org/p3'), 958 | literal('', namedNode('http://www.w3.org/2001/XMLSchema#string')), 959 | defaultGraph() 960 | )); 961 | triples[4].should.eql(quad( 962 | namedNode('http://example.org/s4'), 963 | namedNode('http://example.org/p3'), 964 | literal('"a"^^xsd:string', 'en'), 965 | defaultGraph() 966 | )); 967 | triples[5].should.eql(quad( 968 | namedNode('http://example.org/s4'), 969 | namedNode('http://example.org/p3'), 970 | literal('a', namedNode('http://www.w3.org/2001/XMLSchema#string')), 971 | defaultGraph() 972 | )); 973 | triples[6].should.eql(quad( 974 | namedNode('http://example.org/s4'), 975 | namedNode('http://example.org/p3'), 976 | literal('a', 'en'), 977 | defaultGraph() 978 | )); 979 | triples[7].should.eql(quad( 980 | namedNode('http://example.org/s4'), 981 | namedNode('http://example.org/p3'), 982 | literal('a', namedNode('abc@def')), 983 | defaultGraph() 984 | )); 985 | triples[8].should.eql(quad( 986 | namedNode('http://example.org/s4'), 987 | namedNode('http://example.org/p3'), 988 | literal('a', namedNode('http://example.org/literal')), 989 | defaultGraph() 990 | )); 991 | triples[9].should.eql(quad( 992 | namedNode('http://example.org/s4'), 993 | namedNode('http://example.org/p3'), 994 | literal('a', namedNode('http://www.w3.org/2001/XMLSchema#string')), 995 | defaultGraph() 996 | )); 997 | triples[10].should.eql(quad( 998 | namedNode('http://example.org/s4'), 999 | namedNode('http://example.org/p3'), 1000 | literal('a"b\'c\\\r\n\\', namedNode('http://www.w3.org/2001/XMLSchema#string')), 1001 | defaultGraph() 1002 | )); 1003 | triples[11].should.eql(quad( 1004 | namedNode('http://example.org/s4'), 1005 | namedNode('http://example.org/p3'), 1006 | literal('a"b\'c\\\r\n\\', 'en'), 1007 | defaultGraph() 1008 | )); 1009 | triples[12].should.eql(quad( 1010 | namedNode('http://example.org/s4'), 1011 | namedNode('http://example.org/p3'), 1012 | literal('a"b\'c\\\r\n\\', namedNode('http://example.org/literal')), 1013 | defaultGraph() 1014 | )); 1015 | triples[13].should.eql(quad( 1016 | namedNode('http://example.org/s4'), 1017 | namedNode('http://example.org/p3'), 1018 | literal('a"b\'c\\\r\n\\', namedNode('http://www.w3.org/2001/XMLSchema#string')), 1019 | defaultGraph() 1020 | )); 1021 | }); 1022 | 1023 | it('should estimate the total count as 14', function () { 1024 | totalCount.should.equal(14); 1025 | }); 1026 | 1027 | it('should be an exact count', function () { 1028 | hasExactCount.should.equal(true); 1029 | }); 1030 | }); 1031 | 1032 | describe('with pattern null null "a"^^http://example.org/literal', function () { 1033 | var triples, totalCount, hasExactCount; 1034 | before(function () { 1035 | return document.searchTriples(null, null, literal('a', namedNode('http://example.org/literal'))).then(result => { 1036 | triples = result.triples; 1037 | totalCount = result.totalCount; 1038 | hasExactCount = result.hasExactCount; 1039 | }); 1040 | }); 1041 | 1042 | it('should return an array with matches', function () { 1043 | triples.should.be.an.Array(); 1044 | triples.should.have.length(1); 1045 | triples[0].should.eql(quad( 1046 | namedNode('http://example.org/s4'), 1047 | namedNode('http://example.org/p3'), 1048 | literal('a', namedNode('http://example.org/literal')), 1049 | defaultGraph() 1050 | )); 1051 | }); 1052 | 1053 | it('should estimate the total count as 1', function () { 1054 | totalCount.should.equal(1); 1055 | }); 1056 | 1057 | it('should be an exact count', function () { 1058 | hasExactCount.should.equal(true); 1059 | }); 1060 | }); 1061 | 1062 | describe('with pattern null null "\"a\"^^xsd:string"@en', function () { 1063 | var triples, totalCount, hasExactCount; 1064 | before(function () { 1065 | return document.searchTriples(null, null, literal('\"a\"^^xsd:string', 'en')).then(result => { 1066 | triples = result.triples; 1067 | totalCount = result.totalCount; 1068 | hasExactCount = result.hasExactCount; 1069 | }); 1070 | }); 1071 | 1072 | it('should return an array with matches', function () { 1073 | triples.should.be.an.Array(); 1074 | triples.should.have.length(1); 1075 | triples[0].should.eql(quad( 1076 | namedNode('http://example.org/s4'), 1077 | namedNode('http://example.org/p3'), 1078 | literal('"a"^^xsd:string', 'en'), 1079 | defaultGraph() 1080 | )); 1081 | }); 1082 | 1083 | it('should estimate the total count as 1', function () { 1084 | totalCount.should.equal(1); 1085 | }); 1086 | 1087 | it('should be an exact count', function () { 1088 | hasExactCount.should.equal(true); 1089 | }); 1090 | }); 1091 | 1092 | describe('with pattern null null ""a"^^abc@def', function () { 1093 | var triples, totalCount, hasExactCount; 1094 | before(function () { 1095 | return document.searchTriples(null, null, literal('a', namedNode('abc@def'))).then(result => { 1096 | triples = result.triples; 1097 | totalCount = result.totalCount; 1098 | hasExactCount = result.hasExactCount; 1099 | }); 1100 | }); 1101 | 1102 | it('should return an array with matches', function () { 1103 | triples.should.be.an.Array(); 1104 | triples.should.have.length(1); 1105 | triples[0].should.eql(quad( 1106 | namedNode('http://example.org/s4'), 1107 | namedNode('http://example.org/p3'), 1108 | literal('a', namedNode('abc@def')), 1109 | defaultGraph() 1110 | )); 1111 | }); 1112 | 1113 | it('should estimate the total count as 1', function () { 1114 | totalCount.should.equal(1); 1115 | }); 1116 | 1117 | it('should be an exact count', function () { 1118 | hasExactCount.should.equal(true); 1119 | }); 1120 | }); 1121 | 1122 | describe('with pattern null null ex:o012', function () { 1123 | var triples, totalCount, hasExactCount; 1124 | before(function () { 1125 | return document.searchTriples(null, null, namedNode('http://example.org/o012')).then(result => { 1126 | triples = result.triples; 1127 | totalCount = result.totalCount; 1128 | hasExactCount = result.hasExactCount; 1129 | }); 1130 | }); 1131 | 1132 | it('should return an array with matches', function () { 1133 | triples.should.be.an.Array(); 1134 | triples.should.have.length(1); 1135 | triples[0].should.eql(quad( 1136 | namedNode('http://example.org/s1'), 1137 | namedNode('http://example.org/p1'), 1138 | namedNode('http://example.org/o012'), 1139 | defaultGraph() 1140 | )); 1141 | }); 1142 | 1143 | it('should estimate the total count as 1', function () { 1144 | totalCount.should.equal(1); 1145 | }); 1146 | 1147 | it('should be an exact count', function () { 1148 | hasExactCount.should.equal(true); 1149 | }); 1150 | }); 1151 | 1152 | describe('with pattern ex:s3 null ex:o001', function () { 1153 | var triples, totalCount, hasExactCount; 1154 | before(function () { 1155 | return document.searchTriples(namedNode('http://example.org/s3'), null, namedNode('http://example.org/o001')).then(result => { 1156 | triples = result.triples; 1157 | totalCount = result.totalCount; 1158 | hasExactCount = result.hasExactCount; 1159 | }); 1160 | }); 1161 | 1162 | it('should return an array with matches', function () { 1163 | triples.should.be.an.Array(); 1164 | triples.should.have.length(1); 1165 | triples[0].should.eql(quad( 1166 | namedNode('http://example.org/s3'), 1167 | namedNode('http://example.org/p2'), 1168 | namedNode('http://example.org/o001'), 1169 | defaultGraph() 1170 | )); 1171 | }); 1172 | 1173 | it('should estimate the total count as 1', function () { 1174 | totalCount.should.equal(10); 1175 | }); 1176 | 1177 | it('should be an exact count', function () { 1178 | hasExactCount.should.equal(false); 1179 | }); 1180 | }); 1181 | 1182 | describe('with pattern ex:s3 null ex:o001 and offset 1', function () { 1183 | var triples, totalCount, hasExactCount; 1184 | before(function () { 1185 | return document.searchTriples(namedNode('http://example.org/s3'), null, namedNode('http://example.org/o001'), { offset : 1 }).then(result => { 1186 | triples = result.triples; 1187 | totalCount = result.totalCount; 1188 | hasExactCount = result.hasExactCount; 1189 | }); 1190 | }); 1191 | 1192 | it('should return an array with matches', function () { 1193 | triples.should.be.an.Array(); 1194 | triples.should.have.length(0); 1195 | triples.should.be.empty(); 1196 | }); 1197 | 1198 | it('should estimate the total count as 1', function () { 1199 | totalCount.should.equal(10); 1200 | }); 1201 | 1202 | it('should be an exact count', function () { 1203 | hasExactCount.should.equal(false); 1204 | }); 1205 | }); 1206 | }); 1207 | 1208 | describe('being searched for bindings', function () { 1209 | describe('with a non-existing pattern', function () { 1210 | var bindings, totalCount; 1211 | before(function () { 1212 | return document.searchBindings(BF, namedNode('a'), variable('p'), variable('o')).then(result => { 1213 | bindings = result.bindings; 1214 | totalCount = result.totalCount; 1215 | }); 1216 | }); 1217 | 1218 | it('should return an array with matches', function () { 1219 | bindings.should.be.an.Array(); 1220 | bindings.should.be.empty(); 1221 | }); 1222 | 1223 | it('should estimate the total count as 0', function () { 1224 | totalCount.should.equal(0); 1225 | }); 1226 | }); 1227 | 1228 | describe('with pattern ?s ?p ?o', function () { 1229 | var bindings, totalCount, hasExactCount; 1230 | before(function () { 1231 | return document.searchBindings(BF, variable('s'), variable('p'), variable('o')).then(result => { 1232 | bindings = result.bindings; 1233 | totalCount = result.totalCount; 1234 | hasExactCount = result.hasExactCount; 1235 | }); 1236 | }); 1237 | 1238 | it('should return an array with matches', function () { 1239 | bindings.should.be.an.Array(); 1240 | bindings.should.have.length(134); 1241 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1242 | s: namedNode('http://example.org/s1'), 1243 | p: namedNode('http://example.org/p1'), 1244 | o: namedNode('http://example.org/o001'), 1245 | })); 1246 | }); 1247 | 1248 | it('should estimate the total count as 134', function () { 1249 | totalCount.should.equal(134); 1250 | }); 1251 | 1252 | it('should not be an exact count', function () { 1253 | hasExactCount.should.equal(true); 1254 | }); 1255 | }); 1256 | 1257 | describe('with pattern ?s ?s ?s', function () { 1258 | var bindings, totalCount, hasExactCount; 1259 | before(function () { 1260 | return document.searchBindings(BF, variable('s'), variable('s'), variable('s')).then(result => { 1261 | bindings = result.bindings; 1262 | totalCount = result.totalCount; 1263 | hasExactCount = result.hasExactCount; 1264 | }); 1265 | }); 1266 | 1267 | it('should return an array with matches', function () { 1268 | bindings.should.be.an.Array(); 1269 | bindings.should.have.length(0); 1270 | }); 1271 | 1272 | it('should estimate the total count as 134', function () { 1273 | totalCount.should.equal(0); 1274 | }); 1275 | 1276 | it('should be an exact count', function () { 1277 | hasExactCount.should.equal(true); 1278 | }); 1279 | }); 1280 | 1281 | describe('with pattern ex:s2 ?p ?o', function () { 1282 | var bindings, totalCount, hasExactCount; 1283 | before(function () { 1284 | return document.searchBindings(BF, namedNode('http://example.org/s2'), variable('p'), variable('o')).then(result => { 1285 | bindings = result.bindings; 1286 | totalCount = result.totalCount; 1287 | hasExactCount = result.hasExactCount; 1288 | }); 1289 | }); 1290 | 1291 | it('should return an array with matches', function () { 1292 | bindings.should.be.an.Array(); 1293 | bindings.should.have.length(10); 1294 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1295 | p: namedNode('http://example.org/p1'), 1296 | o: namedNode('http://example.org/o001'), 1297 | })); 1298 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1299 | p: namedNode('http://example.org/p1'), 1300 | o: namedNode('http://example.org/o002'), 1301 | })); 1302 | }); 1303 | 1304 | it('should estimate the total count as 10', function () { 1305 | totalCount.should.equal(10); 1306 | }); 1307 | 1308 | it('should be an exact count', function () { 1309 | hasExactCount.should.equal(true); 1310 | }); 1311 | }); 1312 | 1313 | describe('with pattern ex:s2 ?p ?o, offset 2 and limit 1', function () { 1314 | var bindings, totalCount, hasExactCount; 1315 | before(function () { 1316 | return document.searchBindings(BF, namedNode('http://example.org/s2'), variable('p'), variable('o'), { offset: 2, limit: 1 }).then(result => { 1317 | bindings = result.bindings; 1318 | totalCount = result.totalCount; 1319 | hasExactCount = result.hasExactCount; 1320 | }); 1321 | }); 1322 | 1323 | it('should return an array with matches', function () { 1324 | bindings.should.be.an.Array(); 1325 | bindings.should.have.length(1); 1326 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1327 | p: namedNode('http://example.org/p1'), 1328 | o: namedNode('http://example.org/o003'), 1329 | })); 1330 | }); 1331 | 1332 | it('should estimate the total count as 10', function () { 1333 | totalCount.should.equal(10); 1334 | }); 1335 | 1336 | it('should be an exact count', function () { 1337 | hasExactCount.should.equal(true); 1338 | }); 1339 | }); 1340 | 1341 | describe('with pattern ex:s2 ?p ?o, offset 200 and limit 1', function () { 1342 | var bindings, totalCount, hasExactCount; 1343 | before(function () { 1344 | return document.searchBindings(BF, namedNode('http://example.org/s2'), variable('p'), variable('o'), { offset: 200, limit: 1 }).then(result => { 1345 | bindings = result.bindings; 1346 | totalCount = result.totalCount; 1347 | hasExactCount = result.hasExactCount; 1348 | }); 1349 | }); 1350 | 1351 | it('should return an array with matches', function () { 1352 | bindings.should.be.an.Array(); 1353 | bindings.should.have.length(0); 1354 | }); 1355 | 1356 | it('should estimate the total count as 10', function () { 1357 | totalCount.should.equal(10); 1358 | }); 1359 | 1360 | it('should be an exact count', function () { 1361 | hasExactCount.should.equal(true); 1362 | }); 1363 | }); 1364 | 1365 | describe('with pattern ?s ?p "a"^^http://example.org/literal', function () { 1366 | var bindings, totalCount, hasExactCount; 1367 | before(function () { 1368 | return document.searchBindings(BF, variable('s'), variable('p'), literal('a', namedNode('http://example.org/literal'))).then(result => { 1369 | bindings = result.bindings; 1370 | totalCount = result.totalCount; 1371 | hasExactCount = result.hasExactCount; 1372 | }); 1373 | }); 1374 | 1375 | it('should return an array with matches', function () { 1376 | bindings.should.be.an.Array(); 1377 | bindings.should.have.length(1); 1378 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1379 | s: namedNode('http://example.org/s4'), 1380 | p: namedNode('http://example.org/p3'), 1381 | })); 1382 | }); 1383 | 1384 | it('should estimate the total count as 1', function () { 1385 | totalCount.should.equal(1); 1386 | }); 1387 | 1388 | it('should be an exact count', function () { 1389 | hasExactCount.should.equal(true); 1390 | }); 1391 | }); 1392 | 1393 | describe('with pattern ex:s3 ?p ex:o001 and offset 1', function () { 1394 | var bindings, totalCount, hasExactCount; 1395 | before(function () { 1396 | return document.searchBindings(BF, namedNode('http://example.org/s3'), variable('p'), namedNode('http://example.org/o001'), { offset : 1 }).then(result => { 1397 | bindings = result.bindings; 1398 | totalCount = result.totalCount; 1399 | hasExactCount = result.hasExactCount; 1400 | }); 1401 | }); 1402 | 1403 | it('should return an array with matches', function () { 1404 | bindings.should.be.an.Array(); 1405 | bindings.should.have.length(0); 1406 | bindings.should.be.empty(); 1407 | }); 1408 | 1409 | it('should estimate the total count as 1', function () { 1410 | totalCount.should.equal(10); 1411 | }); 1412 | 1413 | it('should be an exact count', function () { 1414 | hasExactCount.should.equal(false); 1415 | }); 1416 | }); 1417 | }); 1418 | 1419 | describe('being counted', function () { 1420 | describe('with a non-existing pattern', function () { 1421 | var totalCount, hasExactCount; 1422 | before(function () { 1423 | return document.countTriples(namedNode('a'), null, null).then(result => { 1424 | totalCount = result.totalCount; 1425 | hasExactCount = result.hasExactCount; 1426 | }); 1427 | }); 1428 | 1429 | it('should return 0', function () { 1430 | totalCount.should.equal(0); 1431 | }); 1432 | 1433 | it('should be an exact count', function () { 1434 | hasExactCount.should.equal(true); 1435 | }); 1436 | }); 1437 | 1438 | describe('with pattern null null null', function () { 1439 | var totalCount, hasExactCount; 1440 | before(function () { 1441 | return document.countTriples(null, null, null).then(result => { 1442 | totalCount = result.totalCount; 1443 | hasExactCount = result.hasExactCount; 1444 | }); 1445 | }); 1446 | 1447 | it('should return 134', function () { 1448 | totalCount.should.equal(134); 1449 | }); 1450 | 1451 | it('should be an exact count', function () { 1452 | hasExactCount.should.equal(true); 1453 | }); 1454 | }); 1455 | 1456 | describe('with pattern ex:s2 null null', function () { 1457 | var totalCount, hasExactCount; 1458 | before(function () { 1459 | return document.countTriples(namedNode('http://example.org/s2'), null, null).then(result => { 1460 | totalCount = result.totalCount; 1461 | hasExactCount = result.hasExactCount; 1462 | }); 1463 | }); 1464 | 1465 | it('should return 10', function () { 1466 | totalCount.should.equal(10); 1467 | }); 1468 | 1469 | it('should be an exact count', function () { 1470 | hasExactCount.should.equal(true); 1471 | }); 1472 | }); 1473 | 1474 | describe('with pattern null ex:p2 null', function () { 1475 | var totalCount, hasExactCount; 1476 | before(function () { 1477 | return document.countTriples(null, namedNode('http://example.org/p2'), null).then(result => { 1478 | totalCount = result.totalCount; 1479 | hasExactCount = result.hasExactCount; 1480 | }); 1481 | }); 1482 | 1483 | it('should return 10', function () { 1484 | totalCount.should.equal(10); 1485 | }); 1486 | 1487 | it('should be an exact count', function () { 1488 | hasExactCount.should.equal(true); 1489 | }); 1490 | }); 1491 | 1492 | describe('with pattern null ex:p3 null', function () { 1493 | var totalCount, hasExactCount; 1494 | before(function () { 1495 | return document.countTriples(null, namedNode('http://example.org/p3'), null).then(result => { 1496 | totalCount = result.totalCount; 1497 | hasExactCount = result.hasExactCount; 1498 | }); 1499 | }); 1500 | 1501 | it('should return 14', function () { 1502 | totalCount.should.equal(14); 1503 | }); 1504 | 1505 | it('should be an exact count', function () { 1506 | hasExactCount.should.equal(true); 1507 | }); 1508 | }); 1509 | 1510 | describe('with pattern null null ex:o012', function () { 1511 | var totalCount, hasExactCount; 1512 | before(function () { 1513 | return document.countTriples(null, null, namedNode('http://example.org/o012')).then(result => { 1514 | totalCount = result.totalCount; 1515 | hasExactCount = result.hasExactCount; 1516 | }); 1517 | }); 1518 | 1519 | it('should return 1', function () { 1520 | totalCount.should.equal(1); 1521 | }); 1522 | 1523 | it('should be an exact count', function () { 1524 | hasExactCount.should.equal(true); 1525 | }); 1526 | }); 1527 | 1528 | describe('with pattern null null "a"^^http://example.org/literal', function () { 1529 | var totalCount, hasExactCount; 1530 | before(function () { 1531 | return document.countTriples(null, null, literal('a', namedNode('http://example.org/literal'))).then(result => { 1532 | totalCount = result.totalCount; 1533 | hasExactCount = result.hasExactCount; 1534 | }); 1535 | }); 1536 | 1537 | it('should return 1', function () { 1538 | totalCount.should.equal(1); 1539 | }); 1540 | 1541 | it('should be an exact count', function () { 1542 | hasExactCount.should.equal(true); 1543 | }); 1544 | }); 1545 | }); 1546 | }); 1547 | 1548 | describe('An HDT document for an example HDT file with reused terms', function () { 1549 | var document; 1550 | before(function () { 1551 | return hdt.fromFile('./test/test2.hdt').then(hdtDocument => { 1552 | document = hdtDocument; 1553 | }); 1554 | }); 1555 | after(function () { 1556 | return document.close(); 1557 | }); 1558 | 1559 | describe('being searched for bindings', function () { 1560 | describe('with pattern ?s ?p ?o', function () { 1561 | var bindings, totalCount, hasExactCount; 1562 | before(function () { 1563 | return document.searchBindings(BF, variable('s'), variable('p'), variable('o')).then(result => { 1564 | bindings = result.bindings; 1565 | totalCount = result.totalCount; 1566 | hasExactCount = result.hasExactCount; 1567 | }); 1568 | }); 1569 | 1570 | it('should return an array with matches', function () { 1571 | bindings.should.be.an.Array(); 1572 | bindings.should.have.length(27); 1573 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1574 | s: namedNode('http://example.org/t1'), 1575 | p: namedNode('http://example.org/t1'), 1576 | o: namedNode('http://example.org/t1'), 1577 | })); 1578 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1579 | s: namedNode('http://example.org/t1'), 1580 | p: namedNode('http://example.org/t1'), 1581 | o: namedNode('http://example.org/t2'), 1582 | })); 1583 | bindingsShouldEqual(bindings[2], BF.fromRecord({ 1584 | s: namedNode('http://example.org/t1'), 1585 | p: namedNode('http://example.org/t1'), 1586 | o: namedNode('http://example.org/t3'), 1587 | })); 1588 | }); 1589 | 1590 | it('should estimate the total count as 27', function () { 1591 | totalCount.should.equal(27); 1592 | }); 1593 | 1594 | it('should be an exact count', function () { 1595 | hasExactCount.should.equal(true); 1596 | }); 1597 | }); 1598 | 1599 | describe('with pattern ?s ?s ?s', function () { 1600 | var bindings, totalCount, hasExactCount; 1601 | before(function () { 1602 | return document.searchBindings(BF, variable('s'), variable('s'), variable('s')).then(result => { 1603 | bindings = result.bindings; 1604 | totalCount = result.totalCount; 1605 | hasExactCount = result.hasExactCount; 1606 | }); 1607 | }); 1608 | 1609 | it('should return an array with matches', function () { 1610 | bindings.should.be.an.Array(); 1611 | bindings.should.have.length(3); 1612 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1613 | s: namedNode('http://example.org/t1'), 1614 | })); 1615 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1616 | s: namedNode('http://example.org/t2'), 1617 | })); 1618 | bindingsShouldEqual(bindings[2], BF.fromRecord({ 1619 | s: namedNode('http://example.org/t3'), 1620 | })); 1621 | }); 1622 | 1623 | it('should estimate the total count as 3', function () { 1624 | totalCount.should.equal(3); 1625 | }); 1626 | 1627 | it('should be an exact count', function () { 1628 | hasExactCount.should.equal(true); 1629 | }); 1630 | }); 1631 | 1632 | describe('with pattern ?s ?s ?s, offset 1, limit 2', function () { 1633 | var bindings, totalCount, hasExactCount; 1634 | before(function () { 1635 | return document.searchBindings(BF, variable('s'), variable('s'), variable('s'), { offset: 1, limit: 2 }).then(result => { 1636 | bindings = result.bindings; 1637 | totalCount = result.totalCount; 1638 | hasExactCount = result.hasExactCount; 1639 | }); 1640 | }); 1641 | 1642 | it('should return an array with matches', function () { 1643 | bindings.should.be.an.Array(); 1644 | bindings.should.have.length(2); 1645 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1646 | s: namedNode('http://example.org/t2'), 1647 | })); 1648 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1649 | s: namedNode('http://example.org/t3'), 1650 | })); 1651 | }); 1652 | 1653 | it('should estimate the total count as 3', function () { 1654 | totalCount.should.equal(3); 1655 | }); 1656 | 1657 | it('should be an exact count', function () { 1658 | hasExactCount.should.equal(true); 1659 | }); 1660 | }); 1661 | 1662 | describe('with pattern ?s ?s ?o', function () { 1663 | var bindings, totalCount, hasExactCount; 1664 | before(function () { 1665 | return document.searchBindings(BF, variable('s'), variable('s'), variable('o')).then(result => { 1666 | bindings = result.bindings; 1667 | totalCount = result.totalCount; 1668 | hasExactCount = result.hasExactCount; 1669 | }); 1670 | }); 1671 | 1672 | it('should return an array with matches', function () { 1673 | bindings.should.be.an.Array(); 1674 | bindings.should.have.length(9); 1675 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1676 | s: namedNode('http://example.org/t1'), 1677 | o: namedNode('http://example.org/t1'), 1678 | })); 1679 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1680 | s: namedNode('http://example.org/t1'), 1681 | o: namedNode('http://example.org/t2'), 1682 | })); 1683 | bindingsShouldEqual(bindings[2], BF.fromRecord({ 1684 | s: namedNode('http://example.org/t1'), 1685 | o: namedNode('http://example.org/t3'), 1686 | })); 1687 | bindingsShouldEqual(bindings[3], BF.fromRecord({ 1688 | s: namedNode('http://example.org/t2'), 1689 | o: namedNode('http://example.org/t1'), 1690 | })); 1691 | bindingsShouldEqual(bindings[4], BF.fromRecord({ 1692 | s: namedNode('http://example.org/t2'), 1693 | o: namedNode('http://example.org/t2'), 1694 | })); 1695 | bindingsShouldEqual(bindings[5], BF.fromRecord({ 1696 | s: namedNode('http://example.org/t2'), 1697 | o: namedNode('http://example.org/t3'), 1698 | })); 1699 | bindingsShouldEqual(bindings[6], BF.fromRecord({ 1700 | s: namedNode('http://example.org/t3'), 1701 | o: namedNode('http://example.org/t1'), 1702 | })); 1703 | bindingsShouldEqual(bindings[7], BF.fromRecord({ 1704 | s: namedNode('http://example.org/t3'), 1705 | o: namedNode('http://example.org/t2'), 1706 | })); 1707 | bindingsShouldEqual(bindings[8], BF.fromRecord({ 1708 | s: namedNode('http://example.org/t3'), 1709 | o: namedNode('http://example.org/t3'), 1710 | })); 1711 | }); 1712 | 1713 | it('should estimate the total count as 9', function () { 1714 | totalCount.should.equal(9); 1715 | }); 1716 | 1717 | it('should be an exact count', function () { 1718 | hasExactCount.should.equal(true); 1719 | }); 1720 | }); 1721 | 1722 | describe('with pattern ?s ?p ?p', function () { 1723 | var bindings, totalCount, hasExactCount; 1724 | before(function () { 1725 | return document.searchBindings(BF, variable('s'), variable('p'), variable('p')).then(result => { 1726 | bindings = result.bindings; 1727 | totalCount = result.totalCount; 1728 | hasExactCount = result.hasExactCount; 1729 | }); 1730 | }); 1731 | 1732 | it('should return an array with matches', function () { 1733 | bindings.should.be.an.Array(); 1734 | bindings.should.have.length(9); 1735 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1736 | s: namedNode('http://example.org/t1'), 1737 | p: namedNode('http://example.org/t1'), 1738 | })); 1739 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1740 | s: namedNode('http://example.org/t1'), 1741 | p: namedNode('http://example.org/t2'), 1742 | })); 1743 | bindingsShouldEqual(bindings[2], BF.fromRecord({ 1744 | s: namedNode('http://example.org/t1'), 1745 | p: namedNode('http://example.org/t3'), 1746 | })); 1747 | bindingsShouldEqual(bindings[3], BF.fromRecord({ 1748 | s: namedNode('http://example.org/t2'), 1749 | p: namedNode('http://example.org/t1'), 1750 | })); 1751 | bindingsShouldEqual(bindings[4], BF.fromRecord({ 1752 | s: namedNode('http://example.org/t2'), 1753 | p: namedNode('http://example.org/t2'), 1754 | })); 1755 | bindingsShouldEqual(bindings[5], BF.fromRecord({ 1756 | s: namedNode('http://example.org/t2'), 1757 | p: namedNode('http://example.org/t3'), 1758 | })); 1759 | bindingsShouldEqual(bindings[6], BF.fromRecord({ 1760 | s: namedNode('http://example.org/t3'), 1761 | p: namedNode('http://example.org/t1'), 1762 | })); 1763 | bindingsShouldEqual(bindings[7], BF.fromRecord({ 1764 | s: namedNode('http://example.org/t3'), 1765 | p: namedNode('http://example.org/t2'), 1766 | })); 1767 | bindingsShouldEqual(bindings[8], BF.fromRecord({ 1768 | s: namedNode('http://example.org/t3'), 1769 | p: namedNode('http://example.org/t3'), 1770 | })); 1771 | }); 1772 | 1773 | it('should estimate the total count as 9', function () { 1774 | totalCount.should.equal(9); 1775 | }); 1776 | 1777 | it('should be an exact count', function () { 1778 | hasExactCount.should.equal(true); 1779 | }); 1780 | }); 1781 | 1782 | describe('with pattern ?s ?p ?p, offset 3, limit 3', function () { 1783 | var bindings, totalCount, hasExactCount; 1784 | before(function () { 1785 | return document.searchBindings(BF, variable('s'), variable('p'), variable('p'), { offset: 3, limit: 3 }).then(result => { 1786 | bindings = result.bindings; 1787 | totalCount = result.totalCount; 1788 | hasExactCount = result.hasExactCount; 1789 | }); 1790 | }); 1791 | 1792 | it('should return an array with matches', function () { 1793 | bindings.should.be.an.Array(); 1794 | bindings.should.have.length(3); 1795 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1796 | s: namedNode('http://example.org/t2'), 1797 | p: namedNode('http://example.org/t1'), 1798 | })); 1799 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1800 | s: namedNode('http://example.org/t2'), 1801 | p: namedNode('http://example.org/t2'), 1802 | })); 1803 | bindingsShouldEqual(bindings[2], BF.fromRecord({ 1804 | s: namedNode('http://example.org/t2'), 1805 | p: namedNode('http://example.org/t3'), 1806 | })); 1807 | }); 1808 | 1809 | it('should estimate the total count as 9', function () { 1810 | totalCount.should.equal(9); 1811 | }); 1812 | 1813 | it('should be an exact count', function () { 1814 | hasExactCount.should.equal(true); 1815 | }); 1816 | }); 1817 | 1818 | describe('with pattern ex:t2 ?p ?p', function () { 1819 | var bindings, totalCount, hasExactCount; 1820 | before(function () { 1821 | return document.searchBindings(BF, namedNode('http://example.org/t2'), variable('p'), variable('p')).then(result => { 1822 | bindings = result.bindings; 1823 | totalCount = result.totalCount; 1824 | hasExactCount = result.hasExactCount; 1825 | }); 1826 | }); 1827 | 1828 | it('should return an array with matches', function () { 1829 | bindings.should.be.an.Array(); 1830 | bindings.should.have.length(3); 1831 | bindingsShouldEqual(bindings[0], BF.fromRecord({ 1832 | p: namedNode('http://example.org/t1'), 1833 | })); 1834 | bindingsShouldEqual(bindings[1], BF.fromRecord({ 1835 | p: namedNode('http://example.org/t2'), 1836 | })); 1837 | bindingsShouldEqual(bindings[2], BF.fromRecord({ 1838 | p: namedNode('http://example.org/t3'), 1839 | })); 1840 | }); 1841 | 1842 | it('should estimate the total count as 3', function () { 1843 | totalCount.should.equal(3); 1844 | }); 1845 | 1846 | it('should be an exact count', function () { 1847 | hasExactCount.should.equal(true); 1848 | }); 1849 | }); 1850 | }); 1851 | }); 1852 | 1853 | describe('An HDT document without a literal dictionary', function () { 1854 | var document; 1855 | before(function () { 1856 | return hdt.fromFile('./test/test.hdt').then(hdtDocument => { 1857 | document = hdtDocument; 1858 | }); 1859 | }); 1860 | after(function () { 1861 | return document.close(); 1862 | }); 1863 | 1864 | describe('being searched for literals', function () { 1865 | it('should throw an error', function () { 1866 | return document.searchLiterals('abc').then( 1867 | () => Promise.reject(new Error('expected an error')), 1868 | error => { 1869 | error.should.be.an.instanceOf(Error); 1870 | error.message.should.equal('The HDT document does not support literal search'); 1871 | } 1872 | ); 1873 | }); 1874 | }); 1875 | }); 1876 | 1877 | describe('An HDT document with a literal dictionary', function () { 1878 | var document; 1879 | before(function () { 1880 | return hdt.fromFile('./test/literals.hdt').then(hdtDocument => { 1881 | document = hdtDocument; 1882 | }); 1883 | }); 1884 | after(function () { 1885 | return document.close(); 1886 | }); 1887 | 1888 | describe('asked for supported features', function () { 1889 | it('should return an object', function () { 1890 | document.features.should.be.an.instanceof(Object); 1891 | }); 1892 | 1893 | it('should support searchTriples', function () { 1894 | document.features.searchTriples.should.be.true(); 1895 | }); 1896 | 1897 | it('should support countTriples', function () { 1898 | document.features.countTriples.should.be.true(); 1899 | }); 1900 | 1901 | it('should support searchLiterals', function () { 1902 | document.features.searchLiterals.should.be.true(); 1903 | }); 1904 | 1905 | it('should support readHeader', function () { 1906 | document.features.readHeader.should.be.true(); 1907 | }); 1908 | 1909 | it('should support changeHeader', function () { 1910 | document.features.changeHeader.should.be.true(); 1911 | }); 1912 | }); 1913 | 1914 | describe('reading the header', function () { 1915 | var header; 1916 | before(function () { 1917 | return document.readHeader().then(result => { 1918 | header = result; 1919 | }); 1920 | }); 1921 | it('should return a string with matches', function () { 1922 | header.should.be.a.String(); 1923 | header.split('\n').should.have.length(29); 1924 | header.indexOf(' ' + 1925 | ' ' + 1926 | '').should.be.above(-1); 1927 | header.indexOf('_:publicationInformation ' + 1928 | ' ' + 1929 | '"2019-01-18T11:37:23+01:00"').should.be.above(-1); 1930 | }); 1931 | }); 1932 | 1933 | describe('being searched', function () { 1934 | describe('for an existing subject', function () { 1935 | var triples, totalCount; 1936 | before(function () { 1937 | return document.searchTriples(namedNode('s'), null, null).then(result => { 1938 | triples = result.triples; 1939 | totalCount = result.totalCount; 1940 | }); 1941 | }); 1942 | 1943 | it('should return an array with matches', function () { 1944 | triples.should.be.an.Array(); 1945 | triples.should.have.length(12); 1946 | }); 1947 | 1948 | it('should estimate the total count', function () { 1949 | totalCount.should.equal(12); 1950 | }); 1951 | }); 1952 | 1953 | describe('for a non-existing subject', function () { 1954 | var triples, totalCount; 1955 | before(function () { 1956 | return document.searchTriples(namedNode('x'), null, null).then(result => { 1957 | triples = result.triples; 1958 | totalCount = result.totalCount; 1959 | }); 1960 | }); 1961 | 1962 | it('should return an array without matches', function () { 1963 | triples.should.be.an.Array(); 1964 | triples.should.have.length(0); 1965 | }); 1966 | 1967 | it('should estimate the total count as 0', function () { 1968 | totalCount.should.equal(0); 1969 | }); 1970 | }); 1971 | 1972 | describe('for the empty literal', function () { 1973 | var literals, totalCount; 1974 | before(function () { 1975 | return document.searchLiterals('').then(result => { 1976 | literals = result.literals; 1977 | totalCount = result.totalCount; 1978 | }); 1979 | }); 1980 | 1981 | it('should return the empty array', function () { 1982 | literals.should.eql([]); 1983 | }); 1984 | 1985 | it('should estimate the total count', function () { 1986 | totalCount.should.equal(0); 1987 | }); 1988 | }); 1989 | 1990 | describe('for the literal "a"', function () { 1991 | var literals, totalCount; 1992 | before(function () { 1993 | return document.searchLiterals('a').then(result => { 1994 | literals = result.literals; 1995 | totalCount = result.totalCount; 1996 | }); 1997 | }); 1998 | 1999 | it('should return literals containing "a"', function () { 2000 | literals.should.eql([ 2001 | literal('a', namedNode('http://www.w3.org/2001/XMLSchema#string')), 2002 | literal('a', 'en'), 2003 | literal('a', namedNode('bcd')), 2004 | literal('cd', namedNode('ab@cd')), 2005 | literal('ab^^cd', 'en'), 2006 | literal('abc', namedNode('http://www.w3.org/2001/XMLSchema#string')), 2007 | literal('abc', 'en'), 2008 | literal('abc', namedNode('bcd')), 2009 | literal('', namedNode('abc@d')), 2010 | ]); 2011 | }); 2012 | 2013 | it('should estimate the total count', function () { 2014 | totalCount.should.equal(9); 2015 | }); 2016 | }); 2017 | 2018 | describe('for the literal "b"', function () { 2019 | var literals, totalCount; 2020 | before(function () { 2021 | return document.searchLiterals('b').then(result => { 2022 | literals = result.literals; 2023 | totalCount = result.totalCount; 2024 | }); 2025 | }); 2026 | 2027 | 2028 | it('should return literals containing "b" (with duplicates for multiple matches)', function () { 2029 | literals.should.eql([ 2030 | literal('cd', namedNode('ab@cd')), 2031 | literal('ab^^cd', 'en'), 2032 | literal('abc', namedNode('http://www.w3.org/2001/XMLSchema#string')), 2033 | literal('bc', namedNode('http://www.w3.org/2001/XMLSchema#string')), 2034 | literal('abc', 'en'), 2035 | literal('bc', 'en'), 2036 | literal('abc', namedNode('bcd')), 2037 | literal('bc', namedNode('bcd')), 2038 | literal('', namedNode('abc@d')), 2039 | literal('a', namedNode('bcd')), 2040 | literal('abc', namedNode('bcd')), 2041 | literal('bc', namedNode('bcd')), 2042 | ]); 2043 | }); 2044 | 2045 | it('should estimate the total count', function () { 2046 | totalCount.should.equal(12); 2047 | }); 2048 | }); 2049 | 2050 | describe('for the literal "b" with a limit', function () { 2051 | var literals, totalCount; 2052 | before(function () { 2053 | return document.searchLiterals('b', { limit : 2 }).then(result => { 2054 | literals = result.literals; 2055 | totalCount = result.totalCount; 2056 | }); 2057 | }); 2058 | 2059 | it('should return literals containing "b"', function () { 2060 | literals.should.eql([ 2061 | literal('cd', namedNode('ab@cd')), 2062 | literal('ab^^cd', 'en'), 2063 | ]); 2064 | }); 2065 | 2066 | it('should estimate the total count', function () { 2067 | totalCount.should.equal(12); 2068 | }); 2069 | }); 2070 | 2071 | describe('for the literal "b" with an offset', function () { 2072 | var literals, totalCount; 2073 | before(function () { 2074 | return document.searchLiterals('b', { offset: 4 }).then(result => { 2075 | literals = result.literals; 2076 | totalCount = result.totalCount; 2077 | }); 2078 | }); 2079 | 2080 | 2081 | it('should return literals containing "b"', function () { 2082 | literals.should.eql([ 2083 | literal('abc', 'en'), 2084 | literal('bc', 'en'), 2085 | literal('abc', namedNode('bcd')), 2086 | literal('bc', namedNode('bcd')), 2087 | literal('', namedNode('abc@d')), 2088 | literal('a', namedNode('bcd')), 2089 | literal('abc', namedNode('bcd')), 2090 | literal('bc', namedNode('bcd')), 2091 | ]); 2092 | }); 2093 | 2094 | it('should estimate the total count', function () { 2095 | totalCount.should.equal(12); 2096 | }); 2097 | }); 2098 | 2099 | describe('for the literal "b" with a very large offset', function () { 2100 | var literals, totalCount; 2101 | before(function () { 2102 | return document.searchLiterals('b', { offset: 5000 }).then(result => { 2103 | literals = result.literals; 2104 | totalCount = result.totalCount; 2105 | }); 2106 | }); 2107 | 2108 | it('should return the empty array', function () { 2109 | literals.should.eql([]); 2110 | }); 2111 | 2112 | it('should estimate the total count', function () { 2113 | totalCount.should.equal(12); 2114 | }); 2115 | }); 2116 | }); 2117 | 2118 | describe('for the literal "b" with an offset and a limit', function () { 2119 | var literals, totalCount; 2120 | before(function () { 2121 | return document.searchLiterals('b', { offset: 4, limit: 2 }).then(result => { 2122 | literals = result.literals; 2123 | totalCount = result.totalCount; 2124 | }); 2125 | }); 2126 | 2127 | 2128 | it('should return literals containing "b"', function () { 2129 | literals.should.eql([ 2130 | literal('abc', 'en'), 2131 | literal('bc', 'en'), 2132 | ]); 2133 | }); 2134 | 2135 | it('should estimate the total count', function () { 2136 | totalCount.should.equal(12); 2137 | }); 2138 | }); 2139 | }); 2140 | describe('A closed HDT document', function () { 2141 | var document; 2142 | before(function () { 2143 | return hdt.fromFile('./test/test.hdt').then(hdtDocument => { 2144 | document = hdtDocument; 2145 | return document.close(); 2146 | }); 2147 | }); 2148 | 2149 | describe('reading the header', function () { 2150 | it('should throw an error', function () { 2151 | return document.readHeader().then(() => 2152 | Promise.reject(new Error('Expected an error')), 2153 | error => { 2154 | error.should.be.an.instanceOf(Error); 2155 | error.message.should.equal('The HDT document cannot be accessed because it is closed'); 2156 | } 2157 | ); 2158 | }); 2159 | }); 2160 | 2161 | describe('being searched for triples', function () { 2162 | it('should throw an error', function () { 2163 | return document.searchTriples(null, null, null).then(() => 2164 | Promise.reject(new Error('Expected an error')), 2165 | error => { 2166 | error.should.be.an.instanceOf(Error); 2167 | error.message.should.equal('The HDT document cannot be accessed because it is closed'); 2168 | } 2169 | ); 2170 | }); 2171 | }); 2172 | 2173 | describe('being searched for literals', function () { 2174 | it('should throw an error', function () { 2175 | return document.searchLiterals('abc').then(() => 2176 | Promise.reject(new Error('Expected an error')), 2177 | error => { 2178 | error.should.be.an.instanceOf(Error); 2179 | error.message.should.equal('The HDT document cannot be accessed because it is closed'); 2180 | } 2181 | ); 2182 | }); 2183 | }); 2184 | }); 2185 | }); 2186 | 2187 | function bindingsShouldEqual(left, right) { 2188 | return left.equals(right).should.be.true(`Expected ${left.toString()} to equal ${right.toString()}`); 2189 | } 2190 | -------------------------------------------------------------------------------- /test/literals.hdt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinkedDataFragments/HDT-Node/b5434afb957be19ae1460b44793a8c60f84c270e/test/literals.hdt -------------------------------------------------------------------------------- /test/literals.ttl: -------------------------------------------------------------------------------- 1 |

"a". 2 |

"a"@en. 3 |

"a"^^. 4 |

"abc". 5 |

"abc"@en. 6 |

"abc"^^. 7 |

"bc". 8 |

"bc"@en. 9 |

"bc"^^. 10 |

"cd"^^. 11 |

"ab^^cd"@en. 12 |

""^^. 13 |

""@en. 14 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -R dot 2 | -t 500 3 | -------------------------------------------------------------------------------- /test/test.hdt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinkedDataFragments/HDT-Node/b5434afb957be19ae1460b44793a8c60f84c270e/test/test.hdt -------------------------------------------------------------------------------- /test/test.ttl: -------------------------------------------------------------------------------- 1 | @prefix ex: . 2 | @prefix xsd: . 3 | 4 | # 100 triples: ex:s1 ex:p1 ex:o001–ex:o100 5 | ex:s1 ex:p1 ex:o001. 6 | ex:s1 ex:p1 ex:o002. 7 | ex:s1 ex:p1 ex:o003. 8 | ex:s1 ex:p1 ex:o004. 9 | ex:s1 ex:p1 ex:o005. 10 | ex:s1 ex:p1 ex:o006. 11 | ex:s1 ex:p1 ex:o007. 12 | ex:s1 ex:p1 ex:o008. 13 | ex:s1 ex:p1 ex:o009. 14 | ex:s1 ex:p1 ex:o010. 15 | ex:s1 ex:p1 ex:o011. 16 | ex:s1 ex:p1 ex:o012. 17 | ex:s1 ex:p1 ex:o013. 18 | ex:s1 ex:p1 ex:o014. 19 | ex:s1 ex:p1 ex:o015. 20 | ex:s1 ex:p1 ex:o016. 21 | ex:s1 ex:p1 ex:o017. 22 | ex:s1 ex:p1 ex:o018. 23 | ex:s1 ex:p1 ex:o019. 24 | ex:s1 ex:p1 ex:o020. 25 | ex:s1 ex:p1 ex:o021. 26 | ex:s1 ex:p1 ex:o022. 27 | ex:s1 ex:p1 ex:o023. 28 | ex:s1 ex:p1 ex:o024. 29 | ex:s1 ex:p1 ex:o025. 30 | ex:s1 ex:p1 ex:o026. 31 | ex:s1 ex:p1 ex:o027. 32 | ex:s1 ex:p1 ex:o028. 33 | ex:s1 ex:p1 ex:o029. 34 | ex:s1 ex:p1 ex:o030. 35 | ex:s1 ex:p1 ex:o031. 36 | ex:s1 ex:p1 ex:o032. 37 | ex:s1 ex:p1 ex:o033. 38 | ex:s1 ex:p1 ex:o034. 39 | ex:s1 ex:p1 ex:o035. 40 | ex:s1 ex:p1 ex:o036. 41 | ex:s1 ex:p1 ex:o037. 42 | ex:s1 ex:p1 ex:o038. 43 | ex:s1 ex:p1 ex:o039. 44 | ex:s1 ex:p1 ex:o040. 45 | ex:s1 ex:p1 ex:o041. 46 | ex:s1 ex:p1 ex:o042. 47 | ex:s1 ex:p1 ex:o043. 48 | ex:s1 ex:p1 ex:o044. 49 | ex:s1 ex:p1 ex:o045. 50 | ex:s1 ex:p1 ex:o046. 51 | ex:s1 ex:p1 ex:o047. 52 | ex:s1 ex:p1 ex:o048. 53 | ex:s1 ex:p1 ex:o049. 54 | ex:s1 ex:p1 ex:o050. 55 | ex:s1 ex:p1 ex:o051. 56 | ex:s1 ex:p1 ex:o052. 57 | ex:s1 ex:p1 ex:o053. 58 | ex:s1 ex:p1 ex:o054. 59 | ex:s1 ex:p1 ex:o055. 60 | ex:s1 ex:p1 ex:o056. 61 | ex:s1 ex:p1 ex:o057. 62 | ex:s1 ex:p1 ex:o058. 63 | ex:s1 ex:p1 ex:o059. 64 | ex:s1 ex:p1 ex:o060. 65 | ex:s1 ex:p1 ex:o061. 66 | ex:s1 ex:p1 ex:o062. 67 | ex:s1 ex:p1 ex:o063. 68 | ex:s1 ex:p1 ex:o064. 69 | ex:s1 ex:p1 ex:o065. 70 | ex:s1 ex:p1 ex:o066. 71 | ex:s1 ex:p1 ex:o067. 72 | ex:s1 ex:p1 ex:o068. 73 | ex:s1 ex:p1 ex:o069. 74 | ex:s1 ex:p1 ex:o070. 75 | ex:s1 ex:p1 ex:o071. 76 | ex:s1 ex:p1 ex:o072. 77 | ex:s1 ex:p1 ex:o073. 78 | ex:s1 ex:p1 ex:o074. 79 | ex:s1 ex:p1 ex:o075. 80 | ex:s1 ex:p1 ex:o076. 81 | ex:s1 ex:p1 ex:o077. 82 | ex:s1 ex:p1 ex:o078. 83 | ex:s1 ex:p1 ex:o079. 84 | ex:s1 ex:p1 ex:o080. 85 | ex:s1 ex:p1 ex:o081. 86 | ex:s1 ex:p1 ex:o082. 87 | ex:s1 ex:p1 ex:o083. 88 | ex:s1 ex:p1 ex:o084. 89 | ex:s1 ex:p1 ex:o085. 90 | ex:s1 ex:p1 ex:o086. 91 | ex:s1 ex:p1 ex:o087. 92 | ex:s1 ex:p1 ex:o088. 93 | ex:s1 ex:p1 ex:o089. 94 | ex:s1 ex:p1 ex:o090. 95 | ex:s1 ex:p1 ex:o091. 96 | ex:s1 ex:p1 ex:o092. 97 | ex:s1 ex:p1 ex:o093. 98 | ex:s1 ex:p1 ex:o094. 99 | ex:s1 ex:p1 ex:o095. 100 | ex:s1 ex:p1 ex:o096. 101 | ex:s1 ex:p1 ex:o097. 102 | ex:s1 ex:p1 ex:o098. 103 | ex:s1 ex:p1 ex:o099. 104 | ex:s1 ex:p1 ex:o100. 105 | 106 | # 10 triples: ex:s2 ex:p1 ex:o001–ex:o010 107 | ex:s2 ex:p1 ex:o001. 108 | ex:s2 ex:p1 ex:o002. 109 | ex:s2 ex:p1 ex:o003. 110 | ex:s2 ex:p1 ex:o004. 111 | ex:s2 ex:p1 ex:o005. 112 | ex:s2 ex:p1 ex:o006. 113 | ex:s2 ex:p1 ex:o007. 114 | ex:s2 ex:p1 ex:o008. 115 | ex:s2 ex:p1 ex:o009. 116 | ex:s2 ex:p1 ex:o010. 117 | 118 | # 10 triples: ex:s3 ex:p2 ex:o001–ex:o010 119 | ex:s3 ex:p2 ex:o001. 120 | ex:s3 ex:p2 ex:o002. 121 | ex:s3 ex:p2 ex:o003. 122 | ex:s3 ex:p2 ex:o004. 123 | ex:s3 ex:p2 ex:o005. 124 | ex:s3 ex:p2 ex:o006. 125 | ex:s3 ex:p2 ex:o007. 126 | ex:s3 ex:p2 ex:o008. 127 | ex:s3 ex:p2 ex:o009. 128 | ex:s3 ex:p2 ex:o010. 129 | 130 | # 14 triples with literals 131 | ex:s4 ex:p3 "". 132 | ex:s4 ex:p3 ""@en. 133 | ex:s4 ex:p3 ""^^xsd:string. 134 | ex:s4 ex:p3 ""^^ex:literal. 135 | ex:s4 ex:p3 "a". 136 | ex:s4 ex:p3 "a"@en. 137 | ex:s4 ex:p3 "a"^^xsd:string. 138 | ex:s4 ex:p3 "a"^^ex:literal. 139 | ex:s4 ex:p3 "a\"b\'c\\\r\n\\". 140 | ex:s4 ex:p3 "a\"b\'c\\\r\n\\"@en. 141 | ex:s4 ex:p3 "a\"b\'c\\\r\n\\"^^xsd:string. 142 | ex:s4 ex:p3 "a\"b\'c\\\r\n\\"^^ex:literal. 143 | ex:s4 ex:p3 "\"a\"^^xsd:string"@en. 144 | ex:s4 ex:p3 "a"^^. 145 | -------------------------------------------------------------------------------- /test/test2.hdt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinkedDataFragments/HDT-Node/b5434afb957be19ae1460b44793a8c60f84c270e/test/test2.hdt -------------------------------------------------------------------------------- /test/test2.ttl: -------------------------------------------------------------------------------- 1 | @prefix ex: . 2 | @prefix xsd: . 3 | 4 | # Triples with terms that are reused across triple term positions 5 | # All combinations of ex:t1-t3 6 | 7 | ex:t1 ex:t1 ex:t1. 8 | ex:t1 ex:t1 ex:t2. 9 | ex:t1 ex:t1 ex:t3. 10 | ex:t1 ex:t2 ex:t1. 11 | ex:t1 ex:t2 ex:t2. 12 | ex:t1 ex:t2 ex:t3. 13 | ex:t1 ex:t3 ex:t1. 14 | ex:t1 ex:t3 ex:t2. 15 | ex:t1 ex:t3 ex:t3. 16 | 17 | ex:t2 ex:t1 ex:t1. 18 | ex:t2 ex:t1 ex:t2. 19 | ex:t2 ex:t1 ex:t3. 20 | ex:t2 ex:t2 ex:t1. 21 | ex:t2 ex:t2 ex:t2. 22 | ex:t2 ex:t2 ex:t3. 23 | ex:t2 ex:t3 ex:t1. 24 | ex:t2 ex:t3 ex:t2. 25 | ex:t2 ex:t3 ex:t3. 26 | 27 | ex:t3 ex:t1 ex:t1. 28 | ex:t3 ex:t1 ex:t2. 29 | ex:t3 ex:t1 ex:t3. 30 | ex:t3 ex:t2 ex:t1. 31 | ex:t3 ex:t2 ex:t2. 32 | ex:t3 ex:t2 ex:t3. 33 | ex:t3 ex:t3 ex:t1. 34 | ex:t3 ex:t3 ex:t2. 35 | ex:t3 ex:t3 ex:t3. 36 | -------------------------------------------------------------------------------- /test/testexport.nt: -------------------------------------------------------------------------------- 1 | . 2 | . 3 | . 4 | . 5 | . 6 | . 7 | . 8 | . 9 | . 10 | . 11 | . 12 | . 13 | . 14 | . 15 | . 16 | . 17 | . 18 | . 19 | . 20 | . 21 | . 22 | . 23 | . 24 | . 25 | . 26 | . 27 | . 28 | . 29 | . 30 | . 31 | . 32 | . 33 | . 34 | . 35 | . 36 | . 37 | . 38 | . 39 | . 40 | . 41 | . 42 | . 43 | . 44 | . 45 | . 46 | . 47 | . 48 | . 49 | . 50 | . 51 | . 52 | . 53 | . 54 | . 55 | . 56 | . 57 | . 58 | . 59 | . 60 | . 61 | . 62 | . 63 | . 64 | . 65 | . 66 | . 67 | . 68 | . 69 | . 70 | . 71 | . 72 | . 73 | . 74 | . 75 | . 76 | . 77 | . 78 | . 79 | . 80 | . 81 | . 82 | . 83 | . 84 | . 85 | . 86 | . 87 | . 88 | . 89 | . 90 | . 91 | . 92 | . 93 | . 94 | . 95 | . 96 | . 97 | . 98 | . 99 | . 100 | . 101 | . 102 | . 103 | . 104 | . 105 | . 106 | . 107 | . 108 | . 109 | . 110 | . 111 | . 112 | . 113 | . 114 | . 115 | . 116 | . 117 | . 118 | . 119 | . 120 | . 121 | "" . 122 | ""@en . 123 | ""^^ . 124 | ""^^ . 125 | "a" . 126 | "a"@en . 127 | "a"^^ . 128 | "a"^^ . 129 | "a\"b'c\\\r\n\\" . 130 | "a\"b'c\\\r\n\\"@en . 131 | "a\"b'c\\\r\n\\"^^ . 132 | "a\"b'c\\\r\n\\"^^ . 133 | --------------------------------------------------------------------------------