├── .editorconfig ├── .gitignore ├── .gitmodules ├── .hgignore ├── .jshintrc ├── .nvmrc ├── LICENSE ├── Makefile ├── README.md ├── extension ├── bootstrap.js ├── chrome.manifest ├── core.js ├── manifest.json └── resource │ └── translators │ └── EasyKeyExporter.js ├── misc └── parser.pegjs ├── package-lock.json ├── package.json ├── scripts └── extractcites.py ├── test ├── Gemfile ├── Gemfile.lock ├── test-data │ ├── files │ │ └── 7438 │ │ │ └── doe │ └── zotxt test.rdf ├── test.js └── test.rb └── update.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.js] 4 | indent_size = 4 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | pandoc-zotxt/build/ 3 | pandoc-zotxt/dist/ 4 | pandoc-zotxt/pandoc_zotxt.egg-info/ 5 | pandoc-zotxt/venv/ 6 | zotxt*.xpi 7 | node_modules 8 | .signatures.json 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pandoc-zotxt.lua"] 2 | path = pandoc-zotxt.lua 3 | url = git@github.com:odkr/pandoc-zotxt.lua.git 4 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | venv 2 | pandoc-zotxt/pandoc_zotxt.egg-info/ 3 | pandoc-zotxt/build 4 | pandoc-zotxt/dist 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | /* from airbnb style guide */ 2 | { 3 | /* 4 | * ENVIRONMENTS 5 | * ================= 6 | */ 7 | 8 | // Define globals exposed by modern browsers. 9 | "browser": true, 10 | 11 | // Define globals exposed by jQuery. 12 | "jquery": true, 13 | 14 | // Define globals exposed by Node.js. 15 | "node": true, 16 | 17 | /* 18 | * ENFORCING OPTIONS 19 | * ================= 20 | */ 21 | 22 | // Force all variable names to use either camelCase style or UPPER_CASE 23 | // with underscores. 24 | "camelcase": true, 25 | 26 | // Prohibit use of == and != in favor of === and !==. 27 | "eqeqeq": true, 28 | 29 | // Enforce tab width of 2 spaces. 30 | "indent": 2, 31 | 32 | // Prohibit use of a variable before it is defined. 33 | "latedef": true, 34 | 35 | // Enforce line length to 80 characters 36 | "maxlen": 120, 37 | 38 | // Require capitalized names for constructor functions. 39 | "newcap": true, 40 | 41 | // Enforce use of single quotation marks for strings. 42 | "quotmark": "single", 43 | 44 | // Enforce placing 'use strict' at the top function scope 45 | "strict": true, 46 | 47 | // Prohibit use of explicitly undeclared variables. 48 | "undef": true, 49 | 50 | // Warn when variables are defined but never used. 51 | "unused": true, 52 | 53 | // use es6 - EGH 54 | "moz": true, 55 | 56 | // otherwise use strict complains about bind(this) - EGH 57 | "validthis": true, 58 | 59 | /* 60 | * RELAXING OPTIONS 61 | * ================= 62 | */ 63 | 64 | // Suppress warnings about == null comparisons. 65 | "eqnull": true 66 | } 67 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean test unittest dist 2 | 3 | VERSION=$(shell jq .version extension/manifest.json -r) 4 | 5 | dist: test zotxt-$(VERSION).xpi ; 6 | 7 | notest: zotxt-$(VERSION).xpi ; 8 | 9 | zotxt-$(VERSION).xpi: extension/*.js extension/resource/translators/EasyKeyExporter.js 10 | cd extension && zip -r ../zotxt-$(VERSION).xpi * 11 | 12 | clean: 13 | rm -f zotxt-*.xpi 14 | 15 | unittest: 16 | npm test 17 | 18 | test: unittest 19 | cd test && ruby test.rb 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zotxt: a Zotero extension for text 2 | 3 | zotxt is a Zotero extension for supporting utilities that deal with 4 | plain text files (e.g., markdown, reStructuredText, latex, etc.) 5 | 6 | ## Installation 7 | 8 | 1. Visit 9 | 2. Download the latest `.xpi` file. If you are using Firefox, you will need to right-click and "Save as" 10 | 3. Start Zotero standalone. 11 | 4. In Zotero, select Tools -\> Add-ons -\> Gear (upper right) -\> Install Add-On from file, and install the downloaded xpi file. 12 | 13 | ## pandoc integration 14 | 15 | Please install the [pandoc-zotxt.lua](https://github.com/odkr/pandoc-zotxt.lua) extension to pandoc as well as [Better BibTeX](https://github.com/retorquere/zotero-better-bibtex/wiki/Installation), 16 | which provides excellent citation key management. 17 | 18 | Using the [citation keys](https://retorque.re/zotero-better-bibtex/citing/) provided by Better BibTeX in pandoc citation format, you can automatically connect pandoc to a running Zotero instance to generate citations. (An example of a pandoc citation is `[@Doe2006]` where `Doe2006` is the citation key set by Better BibTex.) 19 | 20 | For example: 21 | 22 | pandoc -L pandoc-zotxt.lua -C file.md -t pdf -o file.pdf 23 | 24 | ## emacs integration 25 | 26 | See [zotxt-emacs](https://github.com/egh/zotxt-emacs) 27 | 28 | Zotxt API 29 | --------- 30 | 31 | The Zotxt API is exposed via `http://127.0.0.1:23119/zotxt/`. To get an idea of what is possible, your best bet is probably to have a look at the `test/test.rb` file. 32 | -------------------------------------------------------------------------------- /extension/bootstrap.js: -------------------------------------------------------------------------------- 1 | // This file is part of zotxt. 2 | 3 | // zotxt is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // Foobar is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with Foobar. If not, see . 15 | 16 | /* global Components, Set, FileUtils, NetUtil, Q, parseEasyKey, runSearch, buildRawSearch, buildEasyKeySearch, findByKey, cleanQuery, buildSearch, makeCslEngine, findByEasyKey, findByCitationKey, jsonStringify, item2key, makeClientError, ClientError, ensureLoaded */ 17 | 'use strict'; 18 | 19 | var uuidRe = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}/; 20 | 21 | function makeEasyKeyExporterMetadata() { 22 | return { 23 | 'translatorID':'9d774afe-a51d-4055-a6c7-23bc96d19fe7', 24 | 'label': 'Easy Citekey', 25 | 'creator': 'Erik Hetzner', 26 | 'target': 'txt', 27 | 'minVersion': '2.1.9', 28 | 'maxVersion': '', 29 | 'priority': 200, 30 | 'inRepository': false, 31 | 'translatorType': 2, 32 | 'browserSupport': 'gcs', 33 | 'displayOptions': { 34 | 'Alternate (@DoeTitle2000)': false 35 | }, 36 | 'lastUpdated':'2013-07-15 07:03:17' 37 | }; 38 | } 39 | 40 | const jsonMediaType = 'application/json; charset=UTF-8'; 41 | const textMediaType = 'text/plain; charset=UTF-8'; 42 | const badRequestCode = 400; 43 | const okCode = 200; 44 | 45 | function collectionSearch(name) { 46 | let collections = Zotero.Collections.getByLibrary(Zotero.Libraries.userLibraryID, true); 47 | for (let collection of collections) { 48 | if (collection.name === name) { 49 | return Promise.resolve(collection.getChildItems()); 50 | } 51 | } 52 | return makeClientError(`collection ${name} not found`); 53 | } 54 | 55 | function processCitationItem (citation) { 56 | let cloneButSetId = (item)=>{ 57 | let retval = Object.assign({}, citation); 58 | retval.id = item.id; 59 | delete(retval.easyKey); 60 | delete(retval.key); 61 | return retval; 62 | }; 63 | if ('easyKey' in citation) { 64 | return findByEasyKey(citation.easyKey, Zotero).then(cloneButSetId); 65 | } else if ('key' in citation) { 66 | return findByKey(citation.key, Zotero).then(cloneButSetId); 67 | } else { 68 | return Promise.resolve(citation); 69 | } 70 | } 71 | 72 | /** 73 | * Map the easykeys in the citations to ids. 74 | */ 75 | function processCitationsGroup (citationGroup) { 76 | let citationItems = citationGroup.citationItems.map(processCitationItem); 77 | return Promise.all(citationItems).then((items)=>{ 78 | return { 79 | 'properties': citationGroup.properties, 80 | 'citationItems': items 81 | }; 82 | }); 83 | } 84 | 85 | /** 86 | * Extract the ids from an array of citationGroups. 87 | */ 88 | function extractIds (citationGroups) { 89 | let ids = []; 90 | citationGroups.map (function(group) { 91 | group.citationItems.map (function(citationItem) { 92 | ids.push(citationItem.id); 93 | }); 94 | }); 95 | return ids; 96 | } 97 | 98 | function myExport (items, translatorId) { 99 | let callback = function (resolve, reject) { 100 | let translation = new Zotero.Translate.Export(); 101 | translation.setItems(items); 102 | translation.setTranslator(translatorId); 103 | /* I don't understand why Zotero still has `setHandler` now that we are in 104 | * promise-land, but OK */ 105 | translation.setHandler("done", function (obj, worked) { 106 | if (worked) { 107 | resolve(obj.string); 108 | } else { 109 | reject(); 110 | } 111 | }); 112 | if (translatorId === 'a515a220-6fef-45ea-9842-8025dfebcc8f') { 113 | translation.setDisplayOptions({quickCopyMode: 'citekeys'}); 114 | } 115 | translation.translate(); 116 | }; 117 | let promise = new Promise(callback); 118 | return promise; 119 | } 120 | 121 | /** 122 | * Build a response based on items and a format parameter. 123 | */ 124 | function buildResponse(items, format, style, locale) { 125 | return ensureLoaded(items, Zotero).then((items)=>{ 126 | if (format === 'key') { 127 | return [okCode, 'application/json', jsonStringify(items.map(item2key))]; 128 | } else if (format === 'easykey') { 129 | return buildEasyKeyResponse(items); 130 | } else if (format === 'betterbibtexkey' || format === 'citekey') { 131 | return buildBBTKeyResponse(items); 132 | } else if (format === 'bibtex') { 133 | return buildBibTeXResponse(items); 134 | } else if (format === 'bibliography') { 135 | return buildBibliographyResponse(items, style, locale); 136 | } else if (format === 'quickBib') { 137 | return buildQuickBibResponse(items); 138 | } else if (format === 'paths') { 139 | return buildPathsResponse(items, style); 140 | } else if (format && format.match(uuidRe)) { 141 | return buildExportResponse(items, format); 142 | } else { 143 | return buildJsonResponse(items); 144 | } 145 | }); 146 | } 147 | 148 | function buildJsonResponse(items) { 149 | /* Use BetterBibTeX JSON if available */ 150 | if (Zotero.BetterBibTeX) { 151 | return buildExportResponse(items, 'f4b52ab0-f878-4556-85a0-c7aeedd09dfc'); 152 | } else { 153 | return buildExportResponse(items, 'bc03b4fe-436d-4a1f-ba59-de4d2d7a63f7'); 154 | } 155 | } 156 | 157 | /** 158 | * Build a response of a set of citation keys based on a set of items and a 159 | * translatorId via the Zotero export process. 160 | */ 161 | function buildKeyResponse(items, translatorId) { 162 | if (items.length === 0) { 163 | return [okCode, 'application/json', jsonStringify([])]; 164 | } else { 165 | return myExport(items, translatorId).then((rawKeys)=>{ 166 | let keys = rawKeys.split(/[ ,]/); 167 | // remove leading @ 168 | let keys2 = keys.map(function(key) { return key.replace(/[\[\]@]/g, ''); }); 169 | return [okCode, jsonMediaType, jsonStringify(keys2)]; 170 | }); 171 | } 172 | } 173 | 174 | function buildEasyKeyResponse(items) { 175 | return buildKeyResponse(items, makeEasyKeyExporterMetadata().translatorID); 176 | } 177 | 178 | function buildBBTKeyResponse(items) { 179 | if (!Zotero.BetterBibTeX) { 180 | return makeClientError('BetterBibTex not installed.'); 181 | } else { 182 | return buildKeyResponse(items, 'a515a220-6fef-45ea-9842-8025dfebcc8f'); 183 | } 184 | } 185 | 186 | function buildExportResponse(items, translatorId) { 187 | return myExport(items, translatorId).then((data) => { 188 | return [okCode, textMediaType, data]; 189 | }); 190 | } 191 | 192 | function buildBibTeXResponse(items) { 193 | return buildExportResponse(items, '9cb70025-a888-4a29-a210-93ec52da40d4'); 194 | } 195 | 196 | function buildBibliographyResponse(items, style, locale) { 197 | let htmlCsl = makeCslEngine(style, locale, Zotero, 'html'); 198 | let textCsl = makeCslEngine(style, locale, Zotero, 'text'); 199 | let responseData = items.map ((item)=>{ 200 | htmlCsl.updateItems([item.id], true); 201 | textCsl.updateItems([item.id], true); 202 | return { 203 | 'key': ((item.libraryID || '0') + '_' + item.key), 204 | 'html': Zotero.Cite.makeFormattedBibliography(htmlCsl, 'html'), 205 | // strip newlines 206 | 'text': Zotero.Cite.makeFormattedBibliography(textCsl, 'text').replace(/(\r\n|\n|\r)/gm,'') 207 | }; 208 | }); 209 | return [okCode, jsonMediaType, jsonStringify(responseData)]; 210 | } 211 | 212 | function buildQuickBibResponse(items) { 213 | let responseData = []; 214 | for (let item of items) { 215 | if (item.isRegularItem()) { 216 | let creators = item.getCreators(); 217 | let authors = []; 218 | for (let i=0; i 0) { 234 | let nameString = names[0].lastName + ', ' + names[0].firstName; 235 | if (names.length == 2) { 236 | nameString += " & " + names[1].lastName + ', ' + names[1].firstName; 237 | } else if (names.length > 1) { 238 | nameString += ", et al."; 239 | } 240 | return nameString; 241 | } 242 | } 243 | 244 | function buildPathsResponse(items) { 245 | let regularItems = items.filter((item)=>{ 246 | return item.isRegularItem(); 247 | }); 248 | let itemsWithPaths = regularItems.map((item)=>{ 249 | let attachments = item.getAttachments(false).map((attachmentId)=>{ 250 | return Zotero.Items.get(attachmentId); 251 | }); 252 | return Zotero.Promise.filter(attachments, (attachment)=>{ 253 | return attachment.isFileAttachment(); 254 | }).then ((attachments)=>{ 255 | return attachments.map((a)=> { 256 | return a.getFilePathAsync().then((path)=>{ 257 | if (path) { 258 | return path; 259 | } else { 260 | return Zotero.Sync.Runner.downloadFile(a).then((_)=>{ 261 | return a.getFilePathAsync(); 262 | }); 263 | } 264 | }); 265 | }); 266 | }).then((paths)=>{ 267 | return Promise.all(paths).then((paths)=>{ 268 | return { 'key': ((item.libraryID || '0') + '_' + item.key), 'paths': paths }; 269 | }); 270 | }); 271 | }); 272 | return Promise.all(itemsWithPaths).then((responseData)=>{ 273 | return [okCode, jsonMediaType, jsonStringify(responseData)]; 274 | }); 275 | } 276 | 277 | function handleErrors(f) { 278 | return (...args)=>{ 279 | return f(...args).catch((ex)=>{ 280 | if (ex instanceof ClientError) { 281 | return [badRequestCode, jsonMediaType, `"${ex.message}"`]; 282 | } else { 283 | return [500, textMediaType, (ex && (ex.message + ex.stack))]; 284 | } 285 | }); 286 | }; 287 | } 288 | 289 | function bibliographyEndpoint(options) { 290 | let cslEngine = makeCslEngine(options.data.styleId, options.data.locale, Zotero, 'html'); 291 | if (!cslEngine) { 292 | return makeClientError('No style found.'); 293 | } else { 294 | cslEngine.setOutputFormat('html'); 295 | let groups = options.data.citationGroups.map(processCitationsGroup); 296 | return Promise.all(groups).then((citationGroups)=>{ 297 | cslEngine.updateItems(extractIds(citationGroups)); 298 | let retval = {}; 299 | retval.bibliography = cslEngine.makeBibliography(); 300 | retval.citationClusters = []; 301 | citationGroups.map (function (citationGroup) { 302 | cslEngine.appendCitationCluster(citationGroup).map(function(updated) { 303 | retval.citationClusters[updated[0]] = updated[1]; 304 | }); 305 | }); 306 | return [okCode, jsonMediaType, jsonStringify(retval)]; 307 | }); 308 | } 309 | } 310 | 311 | function makeVersionEndpoint(version) { 312 | return (options)=>{ 313 | let retval = {"version": version}; 314 | return [okCode, jsonMediaType, jsonStringify(retval)]; 315 | } 316 | } 317 | 318 | function completeEndpoint(options) { 319 | const easykey = options.searchParams.get('easykey'); 320 | if (!easykey) { 321 | return makeClientError('Option easykey is required.'); 322 | } else { 323 | let search = buildEasyKeySearch(new Zotero.Search(), parseEasyKey(easykey, Zotero)); 324 | return runSearch(search, Zotero).then((items)=>{ 325 | if (!items) { 326 | return makeClientError('EasyKey must be of the form DoeTitle2000 or doe:2000title'); 327 | } else { 328 | return buildResponse(items, 'easykey'); 329 | } 330 | }); 331 | } 332 | } 333 | 334 | function searchEndpoint(options) { 335 | const q = options.searchParams.get('q'); 336 | const library = options.searchParams.get('library'); 337 | const method = options.searchParams.get('method'); 338 | const format = options.searchParams.get('format'); 339 | const style = options.searchParams.get('style'); 340 | const locale = options.searchParams.get('locale'); 341 | if (q) { 342 | let search = buildSearch(new Zotero.Search(), q, method); 343 | if (!library) { 344 | search.libraryID = Zotero.Libraries.userLibraryID; 345 | } else if (library !== "all") { 346 | search.libraryID = library; 347 | } 348 | return runSearch(search, Zotero).then((items)=>{ 349 | return buildResponse(items, format, style, locale); 350 | }); 351 | } else { 352 | return makeClientError('q param required.'); 353 | } 354 | } 355 | 356 | function selectEndpoint(options) { 357 | let ZoteroPane = Components.classes['@mozilla.org/appshell/window-mediator;1']. 358 | getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('navigator:browser').ZoteroPane; 359 | let promise = null; 360 | const easykey = options.searchParams.get('easykey'); 361 | const key = options.searchParams.get('key'); 362 | const betterbibtexkey = options.searchParams.get('betterbibtexkey'); 363 | const citekey = options.searchParams.get('citekey'); 364 | if (easykey) { 365 | promise = findByEasyKey(easykey, Zotero); 366 | } else if (key) { 367 | promise = findByKey(key, Zotero); 368 | } else if (betterbibtexkey || citekey) { 369 | promise = findByCitationKey(betterbibtexkey || citekey, Zotero); 370 | } else { 371 | return makeClientError('No param supplied!'); 372 | } 373 | return promise.then(function(item) { 374 | if (item === false) { 375 | return makeClientError('item with key ' + key + ' not found!'); 376 | } 377 | ZoteroPane.selectItem(item.id); 378 | return [okCode, jsonMediaType, jsonStringify('success')]; 379 | }); 380 | } 381 | 382 | function itemsEndpoint(options) { 383 | const selected = options.searchParams.get('selected'); 384 | const easykey = options.searchParams.get('easykey'); 385 | const key = options.searchParams.get('key'); 386 | const betterbibtexkey = options.searchParams.get('betterbibtexkey'); 387 | const citekey = options.searchParams.get('citekey'); 388 | const collection = options.searchParams.get('collection'); 389 | const all = options.searchParams.get('all'); 390 | const format = options.searchParams.get('format'); 391 | const style = options.searchParams.get('style'); 392 | const locale = options.searchParams.get('locale'); 393 | let items = []; 394 | let responder = (items) => { return buildResponse(items, format, style, locale); }; 395 | if (selected) { 396 | return responder(Zotero.getActiveZoteroPane().getSelectedItems()); 397 | } else if (key) { 398 | let keys = key.split(','); 399 | return Promise.all( 400 | keys.map((key)=>{ 401 | return findByKey(key, Zotero); 402 | })).then(responder); 403 | } else if (easykey) { 404 | let keys = easykey.split(','); 405 | return Promise.all( 406 | keys.map((key) => { 407 | return findByEasyKey(key, Zotero); 408 | })).then(responder); 409 | } else if (betterbibtexkey || citekey) { 410 | let keys = (betterbibtexkey ? betterbibtexkey.split(',') : citekey.split(',')); 411 | return Promise.all( 412 | keys.map((key) => { 413 | return findByCitationKey(key, Zotero); 414 | })).then(responder); 415 | } else if (collection) 416 | return collectionSearch(collection).then(responder); 417 | else if (all) { 418 | return Zotero.Items.getAll(Zotero.Libraries.userLibraryID).then(responder); 419 | } else { 420 | return makeClientError('No param supplied!'); 421 | } 422 | } 423 | 424 | function stylesEndpoint(options) { 425 | return [okCode, jsonMediaType, jsonStringify(Zotero.Styles.getVisible())]; 426 | } 427 | 428 | function localesEndpoint(options) { 429 | return [okCode, jsonMediaType, jsonStringify(Zotero.Locale.availableLocales)]; 430 | } 431 | 432 | /** 433 | * Function to load our endpoints into the Zotero connector server. 434 | */ 435 | function loadEndpoints (version) { 436 | let endpoints = { 437 | 'version': { 438 | supportedMethods: ['GET'], 439 | supportedDataType : ['application/x-www-form-urlencoded'], 440 | init : makeVersionEndpoint(version) 441 | }, 442 | 'complete' : { 443 | supportedMethods: ['GET'], 444 | supportedDataType : ['application/x-www-form-urlencoded'], 445 | init : handleErrors(completeEndpoint) 446 | }, 447 | 'bibliography' : { 448 | supportedMethods: ['POST'], 449 | supportedDataType: ['application/x-www-form-urlencoded'], 450 | init: handleErrors(bibliographyEndpoint) 451 | }, 452 | 'search' : { 453 | supportedMethods: ['GET'], 454 | supportedDataType : ['application/x-www-form-urlencoded'], 455 | init : handleErrors(searchEndpoint) 456 | }, 457 | 'select' : { 458 | supportedMethods:['GET'], 459 | supportedDataType : ['application/x-www-form-urlencoded'], 460 | init : handleErrors(selectEndpoint) 461 | }, 462 | 'items' : { 463 | supportedMethods:['GET'], 464 | supportedDataType : ['application/x-www-form-urlencoded'], 465 | init : handleErrors(itemsEndpoint) 466 | }, 467 | 'styles': { 468 | supportedMethods:['GET'], 469 | supportedDataType : ['application/x-www-form-urlencoded'], 470 | init : stylesEndpoint 471 | }, 472 | 'locales': { 473 | supportedMethods:['GET'], 474 | supportedDataType : ['application/x-www-form-urlencoded'], 475 | init : localesEndpoint 476 | } 477 | }; 478 | for (let e in endpoints) { 479 | let ep = Zotero.Server.Endpoints['/zotxt/' + e] = function() {}; 480 | ep.prototype = endpoints[e]; 481 | } 482 | } 483 | 484 | function startup({id, version, resourceURI, rootURI = resourceURI.spec}) { 485 | Zotero.debug(rootURI + 'core.js'); 486 | Services.scriptloader.loadSubScript(rootURI + 'core.js'); 487 | loadEndpoints(version); 488 | } 489 | 490 | function shutdown () { 491 | } 492 | 493 | function uninstall() { 494 | } 495 | 496 | function install() { 497 | } 498 | -------------------------------------------------------------------------------- /extension/chrome.manifest: -------------------------------------------------------------------------------- 1 | content zotxt content/ 2 | -------------------------------------------------------------------------------- /extension/core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses an easy key. Returns {creator: ..., title: ..., date: ...} or null if it 3 | * did not parse correctly. 4 | */ 5 | function parseEasyKey(key, zotero) { 6 | const easyKeyRe = zotero.Utilities.XRegExp('^(\\p{Lu}[\\p{Ll}_-]+)(\\p{Lu}\\p{Ll}+)?([0-9]{4})?'); 7 | const alternateEasyKeyRe = zotero.Utilities.XRegExp('^([\\p{Ll}_-]+):([0-9]{4})?(\\p{Ll}+)?'); 8 | let result = easyKeyRe.exec(key); 9 | if (result) { 10 | return {creator: result[1], title: result[2], date: result[3]}; 11 | } else { 12 | result = alternateEasyKeyRe.exec(key); 13 | if (result) { 14 | return {creator: result[1], title: result[3], date: result[2]}; 15 | } else { 16 | return null; 17 | } 18 | } 19 | } 20 | 21 | function fixStyleId(styleId) { 22 | if (!styleId) { 23 | return 'http://www.zotero.org/styles/chicago-note-bibliography'; 24 | } else if (!styleId.match(/^https?:/)) { 25 | return 'http://www.zotero.org/styles/' + styleId; 26 | } else { 27 | return styleId; 28 | } 29 | } 30 | 31 | /** 32 | * Prepare query values for us. 33 | */ 34 | function cleanQuery(q) { 35 | let retval = {}; 36 | for (let key in q) { 37 | retval[key] = q[key].replace('+', ' '); 38 | } 39 | return retval; 40 | } 41 | 42 | /* Given a iterable of promises that return an item, return a deduped iterable 43 | * of promises (based on the id). */ 44 | function dedupItems(items, zotero) { 45 | let seenIds = new Set([]); // To uniqify results 46 | return zotero.Promise.filter(items, (item) => { 47 | if (seenIds.has(item.id)) { 48 | return false; 49 | } else { 50 | seenIds.add(item.id); 51 | return true; 52 | } 53 | }); 54 | }; 55 | 56 | function ensureLoaded(items, zotero) { 57 | return zotero.Promise.map(items, (item)=>{ 58 | return item.loadAllData().then(()=> { 59 | return item; 60 | }); 61 | }); 62 | } 63 | 64 | function item2key(item) { 65 | return ((item.libraryID || '1') + '_' + item.key); 66 | } 67 | 68 | function findByKey(key, zotero) { 69 | let rejectIfUndefined = (item)=>{ 70 | if (!item) { 71 | return makeClientError(`${key} not found`); 72 | } else { 73 | return item; 74 | } 75 | }; 76 | if (key.indexOf('/') !== -1) { 77 | let lkh = zotero.Items.parseLibraryKey(key); 78 | return zotero.Items.getByLibraryAndKeyAsync(lkh.libraryID, lkh.key).then(rejectIfUndefined); 79 | } else if (key.indexOf('_') !== -1) { 80 | let [libraryId, key2] = key.split('_'); 81 | return zotero.Items.getByLibraryAndKeyAsync(parseInt(libraryId), key2).then(rejectIfUndefined); 82 | } else { 83 | return zotero.Items.getByLibraryAndKeyAsync(1, key).then(rejectIfUndefined); 84 | } 85 | } 86 | 87 | function checkStyleId(styleId, zotero) { 88 | const styleIds = Object.keys(zotero.Styles.getAll()); 89 | if (styleIds.indexOf(styleId) === -1) { 90 | throw new ClientError(`Style ${styleId} is not installed.`); 91 | } 92 | return styleId; 93 | } 94 | 95 | function makeCslEngine (styleIdRaw, locale, zotero, format) { 96 | const styleId = fixStyleId(styleIdRaw); 97 | checkStyleId(styleId, zotero); 98 | let style = zotero.Styles.get(styleId); 99 | if (!style) { 100 | return null; 101 | } else { 102 | // jshint camelcase: false 103 | let csl = style.getCiteProc(locale, format); 104 | csl.opt.development_extensions.wrap_url_and_doi = true; 105 | return csl; 106 | } 107 | } 108 | 109 | function getItemOrParent(item, zotero) { 110 | // not Regular item or standalone note/attachment 111 | if (!item.isRegularItem() && item.parentKey) { 112 | return findByKey(item.parentKey, zotero); 113 | } else { 114 | return item; 115 | } 116 | } 117 | 118 | /** 119 | * Returns a promise resolving to an iterable. 120 | */ 121 | function runSearch(s, zotero) { 122 | return s.search().then((ids) => { 123 | let items = zotero.Items.getAsync(ids); 124 | let items2 = zotero.Promise.map(items, (item)=>{ 125 | return getItemOrParent(item, zotero) 126 | }); 127 | return dedupItems(items2, zotero); 128 | }); 129 | } 130 | 131 | function buildRawSearch(s, key) { 132 | let str = '@' + key; 133 | s.addCondition('joinMode', 'any'); 134 | s.addCondition('tag', 'is', str); 135 | s.addCondition('note', 'contains', str); 136 | return s; 137 | } 138 | 139 | /** 140 | * Find many items by a (possibly incomplete) parsed easy key. 141 | */ 142 | function buildEasyKeySearch(s, parsedKey) { 143 | /* allow multiple names separated by _ */ 144 | var splitName = parsedKey.creator.split('_'); 145 | for (let name of splitName) { 146 | s.addCondition('creator', 'contains', name); 147 | } 148 | if (parsedKey.title != null) { 149 | s.addCondition('title', 'contains', parsedKey.title); 150 | } 151 | if (parsedKey.date != null) { 152 | s.addCondition('date', 'is', parsedKey.date); 153 | } 154 | return s; 155 | } 156 | 157 | function buildSearch(s, query, method) { 158 | if (!method) { method = 'titleCreatorYear'; } 159 | s.addCondition('joinMode', 'all'); 160 | for (let word of query.split(/(?:\+|\s+)/)) { 161 | s.addCondition('quicksearch-' + method, 'contains', word); 162 | } 163 | return s; 164 | } 165 | 166 | 167 | function ClientError(message) { 168 | this.name = 'ClientError'; 169 | this.message = message; 170 | this.stack = (new Error()).stack; 171 | } 172 | 173 | ClientError.prototype = new Error; 174 | 175 | function makeClientError(str) { 176 | return Promise.reject(new ClientError(str)); 177 | } 178 | 179 | /** 180 | * Find a single item by its easy key, caching the result. 181 | */ 182 | function findByEasyKey(key, zotero) { 183 | let parsedKey = parseEasyKey(key, zotero); 184 | if (!parsedKey) { 185 | return makeClientError(`${key} must be of the form DoeTitle2000 or doe:2000title`); 186 | } else { 187 | /* first try raw search */ 188 | let search = buildRawSearch(new zotero.Search(), key); 189 | return runSearch(search, zotero).then(function(items) { 190 | if (items.length === 1) { 191 | return items[0]; 192 | } else if (items.length > 1) { 193 | return makeClientError(`${key} returned multiple items`); 194 | } else { 195 | let search = buildEasyKeySearch(new zotero.Search(), parsedKey); 196 | return runSearch(search, zotero).then (function (items) { 197 | if (items.length > 1) { 198 | // hack to ignore group library duplicates 199 | // remove all items not in the local library 200 | items = items.filter(function (item) { return item.libraryID === zotero.Libraries.userLibraryID; }); 201 | } 202 | if (items.length === 1) { 203 | return items[0]; 204 | } else if (items.length > 1) { 205 | return makeClientError(`${key} returned multiple items`); 206 | } else { 207 | return makeClientError(`${key} had no results`); 208 | } 209 | }); 210 | } 211 | }); 212 | } 213 | } 214 | 215 | async function findByCitationKey(citekey, zotero) { 216 | const search = new zotero.Search(); 217 | search.addCondition('libraryID', 'is', zotero.Libraries.userLibraryID); 218 | search.addCondition('citationKey', 'is', citekey); 219 | const itemID = await search.search(); 220 | return itemID.length ? await zotero.Items.getAsync(itemID[0]) : undefined; 221 | } 222 | 223 | function jsonStringify(json) { 224 | return JSON.stringify(json, null, ' '); 225 | } 226 | 227 | /* Exported for tests in nodejs */ 228 | if (typeof module !== 'undefined') { 229 | module.exports = { 230 | buildEasyKeySearch, 231 | buildRawSearch, 232 | buildSearch, 233 | checkStyleId, 234 | cleanQuery, 235 | dedupItems, 236 | findByCitationKey, 237 | findByEasyKey, 238 | findByKey, 239 | fixStyleId, 240 | getItemOrParent, 241 | item2key, 242 | jsonStringify, 243 | makeCslEngine, 244 | parseEasyKey 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "zotxt", 4 | "version": "7.0.1", 5 | "description": "Plain text extensions for Zotero", 6 | "author": "Erik Hetzner", 7 | "applications": { 8 | "zotero": { 9 | "id": "zotxt@e6h.org", 10 | "update_url": "https://raw.githubusercontent.com/egh/zotxt/master/update.json", 11 | "strict_min_version": "6.999", 12 | "strict_max_version": "7.*" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /extension/resource/translators/EasyKeyExporter.js: -------------------------------------------------------------------------------- 1 | // This file is part of zotxt. 2 | 3 | // zotxt is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // zotxt is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with zotxt. If not, see . 15 | 16 | // from bibtex exporter 17 | var citeKeyCleanRe = ZU.XRegExp('[^\\P{Lu}\\P{Ll}0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\?\\[\\]\\^\\_\\`\\|]+', 'g'); 18 | 19 | function determineYear (item) { 20 | var year = ""; 21 | var rawdate = item['date']; 22 | if (rawdate) { 23 | var yearMd = rawdate.match(/[0-9]{4}/); 24 | if (yearMd && yearMd[0]) { 25 | year = yearMd[0]; 26 | }; 27 | } 28 | return year; 29 | } 30 | 31 | function stripFormatting(str) { 32 | return str.replace(new RegExp("(|||||]+>)", "g"), ""); 33 | } 34 | 35 | function cleanString(string) { 36 | return ZU.XRegExp.replace(string.replace(/\s+/g, "_"), citeKeyCleanRe, ""); 37 | } 38 | 39 | function determineAuthor (item) { 40 | var primaryCreatorType = ZU.getCreatorsForType(item.itemType)[0]; 41 | var primaryCreators = item.creators.filter(function (c) { 42 | return (c.creatorType === primaryCreatorType); 43 | }); 44 | var creator = primaryCreators[0] || item.creators[0]; 45 | var author = (creator && creator['lastName']) || "Anonymous"; 46 | return author; 47 | } 48 | 49 | var stopwords = ["a", "aan", "abans", "aber", "acaba", "acerca", "ach", "aderton", "adertonde", "adesso", "adjö", "af", "agora", "ai", "aj", "al", "albo", "aldrig", "algmas", "algun", "alguna", "algunas", "algunes", "alguno", "algunos", "alguns", "algún", "ali", "alla", "allas", "alle", "allo", "allora", "allt", "alltid", "alltså", "alors", "als", "altmýþ", "altre", "altri", "altro", "altý", "am", "ama", "amb", "ambdós", "ambos", "ampleamos", "an", "anar", "anche", "andet", "andra", "andras", "andre", "annan", "annat", "ans", "ante", "antes", "apontar", "aquel", "aquela", "aquelas", "aquele", "aqueles", "aquell", "aquellas", "aquelles", "aquellos", "aquells", "aqui", "aquí", "arbeid", "arriba", "artonde", "artonn", "at", "atras", "atrás", "att", "au", "auch", "aucuns", "auf", "aus", "aussi", "autre", "av", "avant", "avec", "avere", "aveva", "avevano", "avoir", "az", "bajo", "bakom", "bana", "bara", "bardzo", "bastant", "bastante", "bazý", "be", "begge", "behöva", "behövas", "behövde", "behövt", "bei", "belki", "bem", "ben", "benden", "beni", "benim", "beslut", "beslutat", "beslutit", "bez", "beþ", "bien", "bij", "bin", "bir", "biri", "birkaç", "birkez", "birþey", "birþeyi", "bis", "bist", "biz", "bizden", "bizi", "bizim", "bland", "blev", "bli", "blir", "blivit", "bo", "bom", "bon", "bort", "borta", "bra", "bruke", "bu", "buna", "bunda", "bundan", "bunu", "bunun", "buono", "być", "bäst", "bättre", "båda", "bådas", "bé", "cada", "caminho", "car", "ce", "cela", "ces", "ceux", "chaque", "che", "chi", "ci", "ciebie", "cierta", "ciertas", "cierto", "ciertos", "cima", "cinque", "cię", "co", "com", "comme", "comment", "como", "comprare", "comprido", "con", "conhecido", "consecutivi", "consecutivo", "consegueixo", "conseguim", "conseguimos", "conseguir", "consigo", "consigue", "consigueix", "consigueixen", "consigueixes", "consiguen", "consigues", "corrente", "cosa", "csak", "cual", "cuando", "cui", "czy", "da", "dadurch", "dag", "dagar", "dagarna", "dagen", "daha", "daher", "dahi", "daleko", "dalt", "dan", "dans", "darum", "das", "dass", "dat", "daß", "de", "debaixo", "dedans", "defa", "dehors", "dein", "deine", "del", "delen", "della", "dello", "dem", "den", "denne", "dentro", "depuis", "der", "deras", "deres", "des", "desde", "deshalb", "desligado", "dess", "dessen", "det", "detta", "dette", "deve", "devem", "deverá", "devo", "devrait", "di", "die", "dies", "dieser", "dieses", "dig", "din", "dina", "dins", "direita", "disse", "dit", "ditt", "diye", "diz", "dizer", "dla", "dlaczego", "dlatego", "do", "dobrze", "doch", "dock", "dog", "dois", "doit", "doksan", "dokuz", "dokąd", "donc", "donde", "doppio", "dort", "dos", "dość", "droite", "du", "durch", "dużo", "dwa", "dwaj", "dwie", "dwoje", "dzisiaj", "dziś", "där", "därför", "då", "début", "dört", "e", "ecco", "een", "efter", "eftersom", "egy", "ein", "eine", "einem", "einen", "einer", "eines", "ej", "el", "ela", "ele", "eles", "elfte", "ellas", "elle", "eller", "elles", "elli", "ellos", "ells", "els", "elva", "em", "empleais", "emplean", "emplear", "empleas", "empleo", "en", "encima", "encore", "end", "ene", "eneste", "enhver", "enkel", "enkelt", "enkla", "enligt", "enn", "enquanto", "entonces", "entre", "então", "er", "eramos", "eran", "eras", "erem", "eren", "eres", "ert", "es", "essai", "est", "esta", "estaba", "estado", "estais", "estamos", "estan", "estar", "estará", "estat", "estava", "este", "estem", "estes", "esteu", "esteve", "estic", "estive", "estivemos", "estiveram", "estoy", "està", "está", "estão", "et", "ets", "ett", "ettusen", "eu", "euer", "eure", "fa", "faig", "fait", "faites", "fan", "fanns", "fare", "fará", "fas", "faz", "fazer", "fazia", "fel", "fem", "femte", "femtio", "femtionde", "femton", "femtonde", "fer", "feu", "fez", "fi", "fick", "fim", "fin", "fine", "finnas", "finns", "fino", "fire", "fjorton", "fjortonde", "fjärde", "fler", "flera", "flere", "flesta", "fleste", "foi", "fois", "for", "fora", "force", "fordi", "forrige", "forsÛke", "fra", "fram", "framför", "från", "fue", "fueron", "fui", "fuimos", "fyra", "fyrtio", "fyrtionde", "fÅ", "fÛr", "fÛrst", "få", "får", "fått", "följande", "för", "före", "förlåt", "förra", "första", "før", "für", "gdyby", "gdzie", "genast", "genom", "gente", "gibi", "gick", "giu", "gjorde", "gjort", "gjÛre", "goda", "godare", "godast", "gott", "gueno", "gÅ", "gälla", "gäller", "gällt", "gärna", "gå", "går", "gått", "gör", "göra", "ha", "hace", "haceis", "hacemos", "hacen", "hacer", "haces", "had", "hadde", "hade", "haft", "hago", "hai", "han", "hanno", "hans", "har", "hatte", "hatten", "hattest", "hattet", "haut", "haver", "heb", "heller", "hellre", "helst", "helt", "hem", "hendes", "henne", "hennes", "hep", "hepsi", "her", "het", "hier", "hij", "hinter", "hit", "hiç", "ho", "hoe", "hogy", "hon", "honom", "horas", "hors", "hun", "hundra", "hundraen", "hundraett", "hur", "hva", "hvad", "hvem", "hver", "hvilken", "hvis", "hvor", "hvordan", "hvorfor", "hvornår", "hát", "här", "hög", "höger", "högre", "högst", "i", "ibland", "ich", "ici", "idag", "ide", "igen", "igår", "ihr", "ihre", "ik", "iki", "ikke", "il", "ile", "ils", "im", "imorgon", "in", "incluso", "inclòs", "ind", "indietro", "inför", "inga", "ingen", "ingenting", "inget", "iniciar", "inicio", "innan", "inne", "innen", "inny", "inom", "inte", "intenta", "intentais", "intentamos", "intentan", "intentar", "intentas", "intento", "intet", "inuti", "invece", "io", "ir", "irá", "is", "ise", "ist", "ista", "iste", "isto", "için", "ja", "jag", "jak", "jakby", "jaki", "je", "jede", "jedem", "jeden", "jeder", "jedes", "jedna", "jedno", "jeg", "jego", "jej", "jemu", "jener", "jenes", "jeres", "jest", "jestem", "jetzt", "jeśli", "jeżeli", "jo", "juste", "już", "jämfört", "ją", "kan", "kann", "kannst", "kanske", "katrilyon", "każdy", "kez", "ki", "kiedy", "kierunku", "kim", "kimden", "kime", "kimi", "knappast", "kom", "komma", "kommer", "kommit", "kr", "kto", "ku", "kunde", "kunna", "kunnat", "kunne", "kvar", "können", "könnt", "kýrk", "la", "lage", "lang", "largo", "las", "lav", "lavoro", "le", "legat", "lei", "les", "lesz", "leur", "lidt", "ligado", "ligga", "ligger", "lik", "lika", "like", "likställd", "likställda", "lilla", "lille", "lite", "liten", "litet", "llarg", "llavors", "lo", "loro", "los", "lub", "lui", "lungo", "là", "länge", "längre", "längst", "lätt", "lättare", "lättast", "långsam", "långsammare", "långsammast", "långsamt", "långt", "ma", "machen", "maintenant", "maioria", "maiorias", "mais", "mają", "makt", "mam", "mand", "mange", "mas", "me", "med", "meg", "meget", "meglio", "mein", "meine", "mellan", "mentre", "mera", "mere", "mes", "mesmo", "mest", "meu", "mi", "mientras", "mig", "mij", "milyar", "milyon", "min", "mina", "mindre", "mine", "minst", "mio", "mit", "mitt", "mittemot", "mnie", "mną", "mode", "modo", "moi", "moins", "moja", "moje", "molt", "molta", "molti", "molto", "molts", "mon", "mot", "może", "mu", "muchos", "muito", "muitos", "musst", "muy", "muß", "mußt", "my", "mycket", "mye", "mÅ", "mÅte", "många", "måste", "même", "mój", "möjlig", "möjligen", "möjligt", "möjligtvis", "mü", "müssen", "müßt", "mý", "na", "nach", "nachdem", "nam", "nami", "nas", "nasi", "nasz", "nasza", "nasze", "nasýl", "natychmiast", "navn", "ne", "ned", "neden", "nederst", "nedersta", "nedre", "nei", "nein", "nej", "nella", "nem", "ner", "nerde", "nerede", "nereye", "ni", "nic", "nich", "nicht", "nie", "niego", "niej", "niemu", "nigdy", "nim", "nimi", "nio", "nionde", "nittio", "nittionde", "nitton", "nittonde", "niye", "niçin", "nią", "niż", "no", "nog", "nogen", "noget", "noi", "noll", "nome", "nommés", "nos", "nosaltres", "nosotros", "nosso", "nostro", "notre", "nous", "nouveaux", "nove", "novo", "nr", "nu", "nummer", "nun", "nuovi", "nuovo", "ny", "nyt", "nÅ", "nÅr", "não", "när", "nästa", "någon", "någonting", "något", "några", "nær", "næste", "næsten", "nós", "nödvändig", "nödvändiga", "nödvändigt", "nödvändigtvis", "o", "obok", "och", "också", "od", "oda", "oder", "of", "ofta", "oftast", "og", "ogsÅ", "około", "olika", "olikt", "oltre", "om", "on", "ona", "ondan", "onde", "one", "oni", "onlar", "onlardan", "onlari", "onlarýn", "ono", "ons", "onu", "ook", "op", "opp", "ora", "os", "oss", "otro", "otte", "otto", "otuz", "ou", "outro", "over", "owszem", "où", "par", "para", "parce", "parole", "part", "parte", "pas", "pegar", "peggio", "pelo", "per", "pero", "perquè", "persone", "personnes", "però", "pessoas", "peu", "peut", "piu", "pièce", "plupart", "po", "poco", "pod", "pode", "podeis", "podem", "podemos", "poden", "poder", "poderá", "podeu", "podia", "podria", "podriais", "podriamos", "podrian", "podrias", "ponieważ", "por", "porque", "potser", "pour", "pourquoi", "povo", "primer", "primero", "primo", "promeiro", "promesso", "przed", "przedtem", "puc", "puede", "pueden", "puedo", "punkt", "pÅ", "på", "qua", "qual", "qualquer", "quan", "quand", "quando", "quant", "quarto", "quasi", "quattro", "que", "quel", "quelle", "quelles", "quello", "quels", "quem", "questo", "qui", "quien", "quieto", "quindi", "quinto", "qué", "quê", "rakt", "redan", "rett", "riktig", "rispetto", "rá", "rätt", "sa", "sabe", "sabeis", "sabem", "sabemos", "saben", "saber", "sabes", "sabeu", "sade", "sagt", "sam", "sama", "samma", "samme", "sanki", "sans", "sant", "sap", "saps", "sara", "se", "secondo", "sedan", "sei", "seid", "sein", "seine", "sekiz", "seks", "seksen", "sem", "sembra", "sembrava", "sen", "senare", "senast", "senden", "seni", "senin", "sense", "sent", "senza", "ser", "ses", "sette", "seu", "seulement", "seus", "sex", "sextio", "sextionde", "sexton", "sextonde", "si", "sia", "siamo", "sich", "siden", "sie", "sien", "siendo", "siete", "sig", "sina", "sind", "sist", "sista", "siste", "sitt", "siz", "sizden", "sizi", "sizin", "się", "sju", "sjunde", "sjuttio", "sjuttionde", "sjutton", "sjuttonde", "sjätte", "ska", "skall", "skulle", "skąd", "slik", "slutligen", "slutt", "små", "smått", "snart", "sobre", "soc", "sois", "solament", "solamente", "soll", "sollen", "sollst", "sollt", "solo", "sols", "som", "somente", "somos", "son", "sono", "sonst", "sont", "sopra", "soprattutto", "sota", "sotto", "sous", "soweit", "sowie", "soy", "soyez", "start", "stati", "stato", "stesso", "stille", "stor", "stora", "store", "stort", "större", "störst", "su", "subito", "sujet", "sul", "sulla", "sur", "sus", "syv", "szét", "sÅ", "são", "säga", "säger", "sämre", "sämst", "så", "są", "ta", "tak", "taki", "tal", "tam", "también", "també", "também", "tandis", "tanto", "te", "tellement", "tels", "tem", "tempo", "ten", "tene", "teneis", "tenemos", "tener", "tengo", "tenho", "tenim", "tenir", "teniu", "tentar", "tentaram", "tente", "tentei", "terzo", "tes", "teu", "teve", "the", "ti", "tid", "tidig", "tidigare", "tidigast", "tidigt", "tiempo", "tiene", "tienen", "til", "tilbake", "till", "tills", "tillsammans", "tilstand", "tinc", "tio", "tionde", "tipo", "tive", "tjugo", "tjugoen", "tjugoett", "tjugonde", "tjugotre", "tjugotvå", "tjungo", "to", "tobie", "tobą", "todo", "todos", "tolfte", "tolv", "ton", "tot", "tous", "tout", "tra", "trabaja", "trabajais", "trabajamos", "trabajan", "trabajar", "trabajas", "trabajo", "trabalhar", "trabalho", "tras", "tre", "tredje", "trettio", "trettionde", "tretton", "trettonde", "trilyon", "triplo", "trop", "très", "tu", "tutaj", "tuyo", "två", "tvåhundra", "twoi", "twoja", "twoje", "twój", "ty", "têm", "tüm", "ud", "uit", "ultimo", "um", "uma", "umas", "un", "una", "unas", "und", "under", "unes", "uno", "unos", "uns", "unser", "unsere", "unter", "upp", "ur", "ursäkt", "usa", "usais", "usamos", "usan", "usar", "usas", "uso", "ut", "utan", "utanför", "ute", "uten", "va", "vad", "vagy", "vai", "vaig", "vais", "valeur", "valor", "vamos", "van", "var", "vara", "varför", "varifrån", "varit", "varken", "varsågod", "vart", "vaya", "ve", "ved", "veja", "vem", "vems", "ver", "verdad", "verdade", "verdadeiro", "verdadera", "verdadero", "verdi", "verkligen", "veya", "vi", "vid", "vidare", "viktig", "viktigare", "viktigast", "viktigt", "vil", "vilka", "vilken", "vilket", "vill", "ville", "vissza", "vite", "você", "voi", "voie", "voient", "volte", "vom", "von", "vont", "vor", "vosaltres", "vosotras", "vosotros", "vostro", "votre", "vous", "voy", "vu", "vÅr", "vÖre", "vÖrt", "vänster", "vänstra", "värre", "vår", "våra", "vårt", "wam", "wami", "wann", "warum", "was", "wasi", "wasz", "wasza", "wasze", "wat", "we", "weiter", "weitere", "wel", "wenn", "wer", "werde", "werden", "werdet", "weshalb", "wie", "wieder", "wieso", "wij", "wir", "wird", "wirst", "więc", "wo", "woher", "wohin", "wszystko", "wtedy", "wy", "ya", "yani", "yedi", "yetmiþ", "yirmi", "yo", "yüz", "zal", "zawsze", "ze", "zei", "zij", "zo", "zou", "zu", "zum", "zur", "Å", "át", "än", "ännu", "även", "åtminstone", "åtta", "åttio", "åttionde", "åttonde", "ça", "çok", "çünkü", "é", "én", "és", "éssent", "étaient", "état", "étions", "été", "être", "õ", "õk", "ön", "össze", "över", "övermorgon", "överst", "övre", "últim", "último", "ús", "über", "üç", "þey", "þeyden", "þeyi", "þeyler", "þu", "þuna", "þunda", "þundan", "þunu", "żaden", "że", "αὐτόσ", "γάρ", "γα^", "γε", "δέ", "δή", "δαί", "δαίσ", "διά", "δ’", "εἰ", "εἰμί", "εἰσ", "εἴμι", "καί", "κατά", "μέν", "μή", "μετά", "οἱ", "οὐ", "οὐδέ", "οὐδείσ", "οὐκ", "οὔτε", "οὕτωσ", "οὖν", "οὗτοσ", "παρά", "περί", "πρόσ", "σόσ", "σύ", "σύν", "τά", "τήν", "τί", "τίσ", "τε", "τι", "τισ", "τοί", "τοιοῦτοσ", "τούσ", "τοῦ", "τό", "τόν", "τῆσ", "τῇ", "τῶν", "τῷ", "а", "алло", "без", "близко", "более", "больше", "будем", "будет", "будете", "будешь", "будто", "буду", "будут", "будь", "бы", "бывает", "бывь", "был", "была", "были", "было", "быть", "в", "важная", "важное", "важные", "важный", "вам", "вами", "вас", "ваш", "ваша", "ваше", "ваши", "вверх", "вдали", "вдруг", "ведь", "везде", "весь", "вниз", "внизу", "во", "вокруг", "вон", "восемнадцатый", "восемнадцать", "восемь", "восьмой", "вот", "впрочем", "времени", "время", "все", "всегда", "всего", "всем", "всеми", "всему", "всех", "всею", "всю", "всюду", "вся", "всё", "второй", "вы", "г", "где", "говорил", "говорит", "год", "года", "году", "да", "давно", "даже", "далеко", "дальше", "даром", "два", "двадцатый", "двадцать", "две", "двенадцатый", "двенадцать", "двух", "девятнадцатый", "девятнадцать", "девятый", "девять", "действительно", "дел", "день", "десятый", "десять", "для", "до", "довольно", "долго", "должно", "другая", "другие", "других", "друго", "другое", "другой", "е", "его", "ее", "ей", "ему", "если", "есть", "еще", "ещё", "ею", "её", "ж", "же", "жизнь", "за", "занят", "занята", "занято", "заняты", "затем", "зато", "зачем", "здесь", "значит", "и", "из", "или", "им", "именно", "иметь", "ими", "имя", "иногда", "их", "к", "каждая", "каждое", "каждые", "каждый", "кажется", "как", "какая", "какой", "кем", "когда", "кого", "ком", "кому", "конечно", "которая", "которого", "которой", "которые", "который", "которых", "кроме", "кругом", "кто", "куда", "лет", "ли", "лишь", "лучше", "люди", "м", "мало", "между", "меля", "менее", "меньше", "меня", "миллионов", "мимо", "мира", "мне", "много", "многочисленная", "многочисленное", "многочисленные", "многочисленный", "мной", "мною", "мог", "могут", "мож", "может", "можно", "можхо", "мои", "мой", "мор", "мочь", "моя", "моё", "мы", "на", "наверху", "над", "надо", "назад", "наиболее", "наконец", "нам", "нами", "нас", "начала", "наш", "наша", "наше", "наши", "не", "него", "недавно", "недалеко", "нее", "ней", "нельзя", "нем", "немного", "нему", "непрерывно", "нередко", "несколько", "нет", "нею", "неё", "ни", "нибудь", "ниже", "низко", "никогда", "никуда", "ними", "них", "ничего", "но", "ну", "нужно", "нх", "о", "об", "оба", "обычно", "один", "одиннадцатый", "одиннадцать", "однажды", "однако", "одного", "одной", "около", "он", "она", "они", "оно", "опять", "особенно", "от", "отовсюду", "отсюда", "очень", "первый", "перед", "по", "под", "пожалуйста", "позже", "пока", "пор", "пора", "после", "посреди", "потом", "потому", "почему", "почти", "прекрасно", "при", "про", "просто", "против", "процентов", "пятнадцатый", "пятнадцать", "пятый", "пять", "раз", "разве", "рано", "раньше", "рядом", "с", "сам", "сама", "сами", "самим", "самими", "самих", "само", "самого", "самой", "самом", "самому", "саму", "свое", "своего", "своей", "свои", "своих", "свою", "сеаой", "себе", "себя", "сегодня", "седьмой", "сейчас", "семнадцатый", "семнадцать", "семь", "сих", "сказал", "сказала", "сказать", "сколько", "слишком", "сначала", "снова", "со", "собой", "собою", "совсем", "спасибо", "стал", "суть", "т", "та", "так", "такая", "также", "такие", "такое", "такой", "там", "твой", "твоя", "твоё", "те", "тебе", "тебя", "тем", "теми", "теперь", "тех", "то", "тобой", "тобою", "тогда", "того", "тоже", "только", "том", "тому", "тот", "тою", "третий", "три", "тринадцатый", "тринадцать", "ту", "туда", "тут", "ты", "тысяч", "у", "уж", "уже", "уметь", "хорошо", "хотеть", "хоть", "хотя", "хочешь", "часто", "чаще", "чего", "человек", "чем", "чему", "через", "четвертый", "четыре", "четырнадцатый", "четырнадцать", "что", "чтоб", "чтобы", "чуть", "шестнадцатый", "шестнадцать", "шестой", "шесть", "эта", "эти", "этим", "этими", "этих", "это", "этого", "этой", "этом", "этому", "этот", "эту", "я", "ἀλλά", "ἀλλ’", "ἀπό", "ἄλλοσ", "ἄν", "ἄρα", "ἐάν", "ἐγώ", "ἐκ", "ἐμόσ", "ἐν", "ἐπί", "ἑαυτοῦ", "ἔτι", "ἡ", "ἤ", "ὁ", "ὅδε", "ὅσ", "ὅστισ", "ὅτι", "ὑμόσ", "ὑπέρ", "ὑπό", "ὡσ", "ὥστε", "ὦ"]; 50 | // Based on http://www.ranks.nl/stopwords/ 51 | 52 | function determineTitleWord(item) { 53 | var cleanTitle = stripFormatting(item['title'].toLowerCase()); 54 | var words = ZU.XRegExp.split(cleanTitle, ZU.XRegExp("\\s+|\\p{P}")); 55 | var filteredWords = words.filter(function (word) { 56 | return (stopwords.indexOf(word) == -1 && 57 | word.length > 1 && 58 | !ZU.XRegExp.test(word, ZU.XRegExp('^\\p{P}+$')) && 59 | !ZU.XRegExp.test(word, ZU.XRegExp('^[0-9]+$'))); 60 | }); 61 | return filteredWords[0] || "unknown"; 62 | } 63 | 64 | function doExport () { 65 | var item; 66 | var first = true; 67 | while((item = Zotero.nextItem())) { 68 | // only write spaces after the first export 69 | if (!first) { 70 | Zotero.write(" "); 71 | } else { 72 | first = false; 73 | } 74 | var year = determineYear(item); 75 | var author = determineAuthor(item); 76 | var titleword = determineTitleWord(item); 77 | if (Zotero.getOption("alternate")) { 78 | Zotero.write("@" + ZU.capitalizeTitle(author, true) + ZU.capitalizeTitle(titleword, true) + year); 79 | } else { 80 | Zotero.write("@" + cleanString(author.toLowerCase()) + ":" + year + cleanString(titleword.toLowerCase())); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /misc/parser.pegjs: -------------------------------------------------------------------------------- 1 | cite = textualCite / normalCite 2 | 3 | // characters 4 | 5 | sp = " " / "\u0160" 6 | 7 | notspace = [^ \u0160] 8 | 9 | ts = " " / "\t" 10 | 11 | nl = "\n" / "\u2019" 12 | 13 | spnl = sp* nl? sp* 14 | 15 | // equivalent to notFollowedBy space << rawToken 16 | // wordFollowedBySpace = first:(. !sp)* last:. 17 | // { var first1 = first.map(function (x) { return x[0]; }).join(""); 18 | // return first1 + last; 19 | // } 20 | 21 | word = word:([^ \u0160\];])+ 22 | { 23 | return word.join(""); 24 | } 25 | 26 | wordWithDigits = word:word &{ return word.match(/[0-9]/); } 27 | { 28 | return word; 29 | } 30 | 31 | citeKey = minus:("-"?) "@" first:[a-z]i rest:[a-z0-9:.#$%&_+?<>~/-]i* 32 | { 33 | return (minus + "@" + first + rest.join("")); 34 | } 35 | 36 | prefix = first:(. !citeKey)* last:. 37 | { 38 | return first.map(function (x) { return x[0]; }).join("") + last; 39 | } 40 | 41 | locator = ","? sp first:word rest:(sp wordWithDigits)+ 42 | { 43 | return { "locator" : first + " " + rest.map(function(x){ return x[1]; }).join(" ") }; 44 | } 45 | 46 | suffix = suffix:[^\];]+ 47 | { 48 | return { "suffix": suffix.join("") }; 49 | } 50 | 51 | locatorSuffix = locator !suffix / !locator suffix / (locator suffix) 52 | 53 | citation = prefix:prefix? citeKey:citeKey locatorSuffix:locatorSuffix? 54 | { 55 | return { "prefix":prefix, "citeKey": citeKey , "locatorSuffix": locatorSuffix}; 56 | } 57 | 58 | citeList = citation (";" citation)* 59 | 60 | normalCite = "[" spnl citeList spnl "]" 61 | 62 | bareloc = spnl "[" locatorSuffix (";" citeList )? spnl "]" 63 | 64 | textualCite = citeKey spnl bareloc? 65 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zotxt", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "bluebird": "^3.5.1", 9 | "mocha": "^11.0.1", 10 | "sinon": "^4.0.2", 11 | "xregexp": "^3.2.0" 12 | } 13 | }, 14 | "node_modules/@isaacs/cliui": { 15 | "version": "8.0.2", 16 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 17 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 18 | "dependencies": { 19 | "string-width": "^5.1.2", 20 | "string-width-cjs": "npm:string-width@^4.2.0", 21 | "strip-ansi": "^7.0.1", 22 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 23 | "wrap-ansi": "^8.1.0", 24 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 25 | }, 26 | "engines": { 27 | "node": ">=12" 28 | } 29 | }, 30 | "node_modules/@isaacs/cliui/node_modules/ansi-regex": { 31 | "version": "6.1.0", 32 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 33 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 34 | "engines": { 35 | "node": ">=12" 36 | }, 37 | "funding": { 38 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 39 | } 40 | }, 41 | "node_modules/@isaacs/cliui/node_modules/ansi-styles": { 42 | "version": "6.2.1", 43 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 44 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 45 | "engines": { 46 | "node": ">=12" 47 | }, 48 | "funding": { 49 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 50 | } 51 | }, 52 | "node_modules/@isaacs/cliui/node_modules/emoji-regex": { 53 | "version": "9.2.2", 54 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 55 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" 56 | }, 57 | "node_modules/@isaacs/cliui/node_modules/string-width": { 58 | "version": "5.1.2", 59 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 60 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 61 | "dependencies": { 62 | "eastasianwidth": "^0.2.0", 63 | "emoji-regex": "^9.2.2", 64 | "strip-ansi": "^7.0.1" 65 | }, 66 | "engines": { 67 | "node": ">=12" 68 | }, 69 | "funding": { 70 | "url": "https://github.com/sponsors/sindresorhus" 71 | } 72 | }, 73 | "node_modules/@isaacs/cliui/node_modules/strip-ansi": { 74 | "version": "7.1.0", 75 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 76 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 77 | "dependencies": { 78 | "ansi-regex": "^6.0.1" 79 | }, 80 | "engines": { 81 | "node": ">=12" 82 | }, 83 | "funding": { 84 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 85 | } 86 | }, 87 | "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { 88 | "version": "8.1.0", 89 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 90 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 91 | "dependencies": { 92 | "ansi-styles": "^6.1.0", 93 | "string-width": "^5.0.1", 94 | "strip-ansi": "^7.0.1" 95 | }, 96 | "engines": { 97 | "node": ">=12" 98 | }, 99 | "funding": { 100 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 101 | } 102 | }, 103 | "node_modules/@pkgjs/parseargs": { 104 | "version": "0.11.0", 105 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 106 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 107 | "optional": true, 108 | "engines": { 109 | "node": ">=14" 110 | } 111 | }, 112 | "node_modules/@sinonjs/commons": { 113 | "version": "1.8.6", 114 | "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", 115 | "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", 116 | "license": "BSD-3-Clause", 117 | "dependencies": { 118 | "type-detect": "4.0.8" 119 | } 120 | }, 121 | "node_modules/@sinonjs/commons/node_modules/type-detect": { 122 | "version": "4.0.8", 123 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 124 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 125 | "license": "MIT", 126 | "engines": { 127 | "node": ">=4" 128 | } 129 | }, 130 | "node_modules/@sinonjs/formatio": { 131 | "version": "2.0.0", 132 | "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", 133 | "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", 134 | "license": "BSD-3-Clause", 135 | "dependencies": { 136 | "samsam": "1.3.0" 137 | } 138 | }, 139 | "node_modules/@sinonjs/samsam": { 140 | "version": "3.3.3", 141 | "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", 142 | "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", 143 | "license": "BSD-3-Clause", 144 | "dependencies": { 145 | "@sinonjs/commons": "^1.3.0", 146 | "array-from": "^2.1.1", 147 | "lodash": "^4.17.15" 148 | } 149 | }, 150 | "node_modules/@sinonjs/text-encoding": { 151 | "version": "0.7.3", 152 | "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", 153 | "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", 154 | "license": "(Unlicense OR Apache-2.0)" 155 | }, 156 | "node_modules/ansi-colors": { 157 | "version": "4.1.3", 158 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", 159 | "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", 160 | "engines": { 161 | "node": ">=6" 162 | } 163 | }, 164 | "node_modules/ansi-regex": { 165 | "version": "5.0.1", 166 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 167 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 168 | "license": "MIT", 169 | "engines": { 170 | "node": ">=8" 171 | } 172 | }, 173 | "node_modules/ansi-styles": { 174 | "version": "4.3.0", 175 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 176 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 177 | "license": "MIT", 178 | "dependencies": { 179 | "color-convert": "^2.0.1" 180 | }, 181 | "engines": { 182 | "node": ">=8" 183 | }, 184 | "funding": { 185 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 186 | } 187 | }, 188 | "node_modules/anymatch": { 189 | "version": "3.1.3", 190 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 191 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 192 | "license": "ISC", 193 | "dependencies": { 194 | "normalize-path": "^3.0.0", 195 | "picomatch": "^2.0.4" 196 | }, 197 | "engines": { 198 | "node": ">= 8" 199 | } 200 | }, 201 | "node_modules/argparse": { 202 | "version": "2.0.1", 203 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 204 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 205 | "license": "Python-2.0" 206 | }, 207 | "node_modules/array-from": { 208 | "version": "2.1.1", 209 | "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", 210 | "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", 211 | "license": "MIT" 212 | }, 213 | "node_modules/balanced-match": { 214 | "version": "1.0.2", 215 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 216 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 217 | }, 218 | "node_modules/binary-extensions": { 219 | "version": "2.3.0", 220 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 221 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 222 | "license": "MIT", 223 | "engines": { 224 | "node": ">=8" 225 | }, 226 | "funding": { 227 | "url": "https://github.com/sponsors/sindresorhus" 228 | } 229 | }, 230 | "node_modules/bluebird": { 231 | "version": "3.7.2", 232 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 233 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", 234 | "license": "MIT" 235 | }, 236 | "node_modules/brace-expansion": { 237 | "version": "2.0.1", 238 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 239 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 240 | "dependencies": { 241 | "balanced-match": "^1.0.0" 242 | } 243 | }, 244 | "node_modules/braces": { 245 | "version": "3.0.3", 246 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 247 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 248 | "license": "MIT", 249 | "dependencies": { 250 | "fill-range": "^7.1.1" 251 | }, 252 | "engines": { 253 | "node": ">=8" 254 | } 255 | }, 256 | "node_modules/browser-stdout": { 257 | "version": "1.3.1", 258 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 259 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 260 | "license": "ISC" 261 | }, 262 | "node_modules/camelcase": { 263 | "version": "6.3.0", 264 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 265 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 266 | "license": "MIT", 267 | "engines": { 268 | "node": ">=10" 269 | }, 270 | "funding": { 271 | "url": "https://github.com/sponsors/sindresorhus" 272 | } 273 | }, 274 | "node_modules/chalk": { 275 | "version": "4.1.2", 276 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 277 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 278 | "license": "MIT", 279 | "dependencies": { 280 | "ansi-styles": "^4.1.0", 281 | "supports-color": "^7.1.0" 282 | }, 283 | "engines": { 284 | "node": ">=10" 285 | }, 286 | "funding": { 287 | "url": "https://github.com/chalk/chalk?sponsor=1" 288 | } 289 | }, 290 | "node_modules/chalk/node_modules/supports-color": { 291 | "version": "7.2.0", 292 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 293 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 294 | "license": "MIT", 295 | "dependencies": { 296 | "has-flag": "^4.0.0" 297 | }, 298 | "engines": { 299 | "node": ">=8" 300 | } 301 | }, 302 | "node_modules/chokidar": { 303 | "version": "3.5.3", 304 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 305 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 306 | "funding": [ 307 | { 308 | "type": "individual", 309 | "url": "https://paulmillr.com/funding/" 310 | } 311 | ], 312 | "license": "MIT", 313 | "dependencies": { 314 | "anymatch": "~3.1.2", 315 | "braces": "~3.0.2", 316 | "glob-parent": "~5.1.2", 317 | "is-binary-path": "~2.1.0", 318 | "is-glob": "~4.0.1", 319 | "normalize-path": "~3.0.0", 320 | "readdirp": "~3.6.0" 321 | }, 322 | "engines": { 323 | "node": ">= 8.10.0" 324 | }, 325 | "optionalDependencies": { 326 | "fsevents": "~2.3.2" 327 | } 328 | }, 329 | "node_modules/cliui": { 330 | "version": "7.0.4", 331 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 332 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 333 | "license": "ISC", 334 | "dependencies": { 335 | "string-width": "^4.2.0", 336 | "strip-ansi": "^6.0.0", 337 | "wrap-ansi": "^7.0.0" 338 | } 339 | }, 340 | "node_modules/color-convert": { 341 | "version": "2.0.1", 342 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 343 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 344 | "license": "MIT", 345 | "dependencies": { 346 | "color-name": "~1.1.4" 347 | }, 348 | "engines": { 349 | "node": ">=7.0.0" 350 | } 351 | }, 352 | "node_modules/color-name": { 353 | "version": "1.1.4", 354 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 355 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 356 | "license": "MIT" 357 | }, 358 | "node_modules/cross-spawn": { 359 | "version": "7.0.6", 360 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 361 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 362 | "dependencies": { 363 | "path-key": "^3.1.0", 364 | "shebang-command": "^2.0.0", 365 | "which": "^2.0.1" 366 | }, 367 | "engines": { 368 | "node": ">= 8" 369 | } 370 | }, 371 | "node_modules/debug": { 372 | "version": "4.4.0", 373 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 374 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 375 | "dependencies": { 376 | "ms": "^2.1.3" 377 | }, 378 | "engines": { 379 | "node": ">=6.0" 380 | }, 381 | "peerDependenciesMeta": { 382 | "supports-color": { 383 | "optional": true 384 | } 385 | } 386 | }, 387 | "node_modules/decamelize": { 388 | "version": "4.0.0", 389 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 390 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 391 | "license": "MIT", 392 | "engines": { 393 | "node": ">=10" 394 | }, 395 | "funding": { 396 | "url": "https://github.com/sponsors/sindresorhus" 397 | } 398 | }, 399 | "node_modules/diff": { 400 | "version": "5.2.0", 401 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", 402 | "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", 403 | "engines": { 404 | "node": ">=0.3.1" 405 | } 406 | }, 407 | "node_modules/eastasianwidth": { 408 | "version": "0.2.0", 409 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 410 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" 411 | }, 412 | "node_modules/emoji-regex": { 413 | "version": "8.0.0", 414 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 415 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 416 | "license": "MIT" 417 | }, 418 | "node_modules/escalade": { 419 | "version": "3.2.0", 420 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 421 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 422 | "license": "MIT", 423 | "engines": { 424 | "node": ">=6" 425 | } 426 | }, 427 | "node_modules/escape-string-regexp": { 428 | "version": "4.0.0", 429 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 430 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 431 | "license": "MIT", 432 | "engines": { 433 | "node": ">=10" 434 | }, 435 | "funding": { 436 | "url": "https://github.com/sponsors/sindresorhus" 437 | } 438 | }, 439 | "node_modules/fill-range": { 440 | "version": "7.1.1", 441 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 442 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 443 | "license": "MIT", 444 | "dependencies": { 445 | "to-regex-range": "^5.0.1" 446 | }, 447 | "engines": { 448 | "node": ">=8" 449 | } 450 | }, 451 | "node_modules/find-up": { 452 | "version": "5.0.0", 453 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 454 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 455 | "license": "MIT", 456 | "dependencies": { 457 | "locate-path": "^6.0.0", 458 | "path-exists": "^4.0.0" 459 | }, 460 | "engines": { 461 | "node": ">=10" 462 | }, 463 | "funding": { 464 | "url": "https://github.com/sponsors/sindresorhus" 465 | } 466 | }, 467 | "node_modules/flat": { 468 | "version": "5.0.2", 469 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 470 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 471 | "license": "BSD-3-Clause", 472 | "bin": { 473 | "flat": "cli.js" 474 | } 475 | }, 476 | "node_modules/foreground-child": { 477 | "version": "3.3.0", 478 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", 479 | "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", 480 | "dependencies": { 481 | "cross-spawn": "^7.0.0", 482 | "signal-exit": "^4.0.1" 483 | }, 484 | "engines": { 485 | "node": ">=14" 486 | }, 487 | "funding": { 488 | "url": "https://github.com/sponsors/isaacs" 489 | } 490 | }, 491 | "node_modules/fsevents": { 492 | "version": "2.3.3", 493 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 494 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 495 | "hasInstallScript": true, 496 | "license": "MIT", 497 | "optional": true, 498 | "os": [ 499 | "darwin" 500 | ], 501 | "engines": { 502 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 503 | } 504 | }, 505 | "node_modules/get-caller-file": { 506 | "version": "2.0.5", 507 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 508 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 509 | "license": "ISC", 510 | "engines": { 511 | "node": "6.* || 8.* || >= 10.*" 512 | } 513 | }, 514 | "node_modules/glob": { 515 | "version": "10.4.5", 516 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", 517 | "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", 518 | "dependencies": { 519 | "foreground-child": "^3.1.0", 520 | "jackspeak": "^3.1.2", 521 | "minimatch": "^9.0.4", 522 | "minipass": "^7.1.2", 523 | "package-json-from-dist": "^1.0.0", 524 | "path-scurry": "^1.11.1" 525 | }, 526 | "bin": { 527 | "glob": "dist/esm/bin.mjs" 528 | }, 529 | "funding": { 530 | "url": "https://github.com/sponsors/isaacs" 531 | } 532 | }, 533 | "node_modules/glob-parent": { 534 | "version": "5.1.2", 535 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 536 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 537 | "license": "ISC", 538 | "dependencies": { 539 | "is-glob": "^4.0.1" 540 | }, 541 | "engines": { 542 | "node": ">= 6" 543 | } 544 | }, 545 | "node_modules/glob/node_modules/minimatch": { 546 | "version": "9.0.5", 547 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 548 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 549 | "dependencies": { 550 | "brace-expansion": "^2.0.1" 551 | }, 552 | "engines": { 553 | "node": ">=16 || 14 >=14.17" 554 | }, 555 | "funding": { 556 | "url": "https://github.com/sponsors/isaacs" 557 | } 558 | }, 559 | "node_modules/has-flag": { 560 | "version": "4.0.0", 561 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 562 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 563 | "license": "MIT", 564 | "engines": { 565 | "node": ">=8" 566 | } 567 | }, 568 | "node_modules/he": { 569 | "version": "1.2.0", 570 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 571 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 572 | "license": "MIT", 573 | "bin": { 574 | "he": "bin/he" 575 | } 576 | }, 577 | "node_modules/is-binary-path": { 578 | "version": "2.1.0", 579 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 580 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 581 | "license": "MIT", 582 | "dependencies": { 583 | "binary-extensions": "^2.0.0" 584 | }, 585 | "engines": { 586 | "node": ">=8" 587 | } 588 | }, 589 | "node_modules/is-extglob": { 590 | "version": "2.1.1", 591 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 592 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 593 | "license": "MIT", 594 | "engines": { 595 | "node": ">=0.10.0" 596 | } 597 | }, 598 | "node_modules/is-fullwidth-code-point": { 599 | "version": "3.0.0", 600 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 601 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 602 | "license": "MIT", 603 | "engines": { 604 | "node": ">=8" 605 | } 606 | }, 607 | "node_modules/is-glob": { 608 | "version": "4.0.3", 609 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 610 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 611 | "license": "MIT", 612 | "dependencies": { 613 | "is-extglob": "^2.1.1" 614 | }, 615 | "engines": { 616 | "node": ">=0.10.0" 617 | } 618 | }, 619 | "node_modules/is-number": { 620 | "version": "7.0.0", 621 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 622 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 623 | "license": "MIT", 624 | "engines": { 625 | "node": ">=0.12.0" 626 | } 627 | }, 628 | "node_modules/is-plain-obj": { 629 | "version": "2.1.0", 630 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 631 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 632 | "license": "MIT", 633 | "engines": { 634 | "node": ">=8" 635 | } 636 | }, 637 | "node_modules/is-unicode-supported": { 638 | "version": "0.1.0", 639 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 640 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 641 | "license": "MIT", 642 | "engines": { 643 | "node": ">=10" 644 | }, 645 | "funding": { 646 | "url": "https://github.com/sponsors/sindresorhus" 647 | } 648 | }, 649 | "node_modules/isarray": { 650 | "version": "0.0.1", 651 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 652 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", 653 | "license": "MIT" 654 | }, 655 | "node_modules/isexe": { 656 | "version": "2.0.0", 657 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 658 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 659 | }, 660 | "node_modules/jackspeak": { 661 | "version": "3.4.3", 662 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", 663 | "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", 664 | "dependencies": { 665 | "@isaacs/cliui": "^8.0.2" 666 | }, 667 | "funding": { 668 | "url": "https://github.com/sponsors/isaacs" 669 | }, 670 | "optionalDependencies": { 671 | "@pkgjs/parseargs": "^0.11.0" 672 | } 673 | }, 674 | "node_modules/js-yaml": { 675 | "version": "4.1.0", 676 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 677 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 678 | "license": "MIT", 679 | "dependencies": { 680 | "argparse": "^2.0.1" 681 | }, 682 | "bin": { 683 | "js-yaml": "bin/js-yaml.js" 684 | } 685 | }, 686 | "node_modules/just-extend": { 687 | "version": "4.2.1", 688 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", 689 | "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", 690 | "license": "MIT" 691 | }, 692 | "node_modules/locate-path": { 693 | "version": "6.0.0", 694 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 695 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 696 | "license": "MIT", 697 | "dependencies": { 698 | "p-locate": "^5.0.0" 699 | }, 700 | "engines": { 701 | "node": ">=10" 702 | }, 703 | "funding": { 704 | "url": "https://github.com/sponsors/sindresorhus" 705 | } 706 | }, 707 | "node_modules/lodash": { 708 | "version": "4.17.21", 709 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 710 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 711 | "license": "MIT" 712 | }, 713 | "node_modules/lodash.get": { 714 | "version": "4.4.2", 715 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 716 | "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", 717 | "license": "MIT" 718 | }, 719 | "node_modules/log-symbols": { 720 | "version": "4.1.0", 721 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 722 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 723 | "license": "MIT", 724 | "dependencies": { 725 | "chalk": "^4.1.0", 726 | "is-unicode-supported": "^0.1.0" 727 | }, 728 | "engines": { 729 | "node": ">=10" 730 | }, 731 | "funding": { 732 | "url": "https://github.com/sponsors/sindresorhus" 733 | } 734 | }, 735 | "node_modules/lolex": { 736 | "version": "2.7.5", 737 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", 738 | "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", 739 | "license": "BSD-3-Clause" 740 | }, 741 | "node_modules/lru-cache": { 742 | "version": "10.4.3", 743 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 744 | "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" 745 | }, 746 | "node_modules/minimatch": { 747 | "version": "5.1.6", 748 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 749 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 750 | "dependencies": { 751 | "brace-expansion": "^2.0.1" 752 | }, 753 | "engines": { 754 | "node": ">=10" 755 | } 756 | }, 757 | "node_modules/minipass": { 758 | "version": "7.1.2", 759 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 760 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 761 | "engines": { 762 | "node": ">=16 || 14 >=14.17" 763 | } 764 | }, 765 | "node_modules/mocha": { 766 | "version": "11.0.1", 767 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.0.1.tgz", 768 | "integrity": "sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==", 769 | "dependencies": { 770 | "ansi-colors": "^4.1.3", 771 | "browser-stdout": "^1.3.1", 772 | "chokidar": "^3.5.3", 773 | "debug": "^4.3.5", 774 | "diff": "^5.2.0", 775 | "escape-string-regexp": "^4.0.0", 776 | "find-up": "^5.0.0", 777 | "glob": "^10.4.5", 778 | "he": "^1.2.0", 779 | "js-yaml": "^4.1.0", 780 | "log-symbols": "^4.1.0", 781 | "minimatch": "^5.1.6", 782 | "ms": "^2.1.3", 783 | "serialize-javascript": "^6.0.2", 784 | "strip-json-comments": "^3.1.1", 785 | "supports-color": "^8.1.1", 786 | "workerpool": "^6.5.1", 787 | "yargs": "^16.2.0", 788 | "yargs-parser": "^20.2.9", 789 | "yargs-unparser": "^2.0.0" 790 | }, 791 | "bin": { 792 | "_mocha": "bin/_mocha", 793 | "mocha": "bin/mocha.js" 794 | }, 795 | "engines": { 796 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 797 | } 798 | }, 799 | "node_modules/ms": { 800 | "version": "2.1.3", 801 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 802 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 803 | "license": "MIT" 804 | }, 805 | "node_modules/nise": { 806 | "version": "1.5.3", 807 | "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", 808 | "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", 809 | "license": "BSD-3-Clause", 810 | "dependencies": { 811 | "@sinonjs/formatio": "^3.2.1", 812 | "@sinonjs/text-encoding": "^0.7.1", 813 | "just-extend": "^4.0.2", 814 | "lolex": "^5.0.1", 815 | "path-to-regexp": "^1.7.0" 816 | } 817 | }, 818 | "node_modules/nise/node_modules/@sinonjs/formatio": { 819 | "version": "3.2.2", 820 | "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", 821 | "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", 822 | "license": "BSD-3-Clause", 823 | "dependencies": { 824 | "@sinonjs/commons": "^1", 825 | "@sinonjs/samsam": "^3.1.0" 826 | } 827 | }, 828 | "node_modules/nise/node_modules/lolex": { 829 | "version": "5.1.2", 830 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", 831 | "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", 832 | "license": "BSD-3-Clause", 833 | "dependencies": { 834 | "@sinonjs/commons": "^1.7.0" 835 | } 836 | }, 837 | "node_modules/normalize-path": { 838 | "version": "3.0.0", 839 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 840 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 841 | "license": "MIT", 842 | "engines": { 843 | "node": ">=0.10.0" 844 | } 845 | }, 846 | "node_modules/p-limit": { 847 | "version": "3.1.0", 848 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 849 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 850 | "license": "MIT", 851 | "dependencies": { 852 | "yocto-queue": "^0.1.0" 853 | }, 854 | "engines": { 855 | "node": ">=10" 856 | }, 857 | "funding": { 858 | "url": "https://github.com/sponsors/sindresorhus" 859 | } 860 | }, 861 | "node_modules/p-locate": { 862 | "version": "5.0.0", 863 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 864 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 865 | "license": "MIT", 866 | "dependencies": { 867 | "p-limit": "^3.0.2" 868 | }, 869 | "engines": { 870 | "node": ">=10" 871 | }, 872 | "funding": { 873 | "url": "https://github.com/sponsors/sindresorhus" 874 | } 875 | }, 876 | "node_modules/package-json-from-dist": { 877 | "version": "1.0.1", 878 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 879 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" 880 | }, 881 | "node_modules/path-exists": { 882 | "version": "4.0.0", 883 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 884 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 885 | "license": "MIT", 886 | "engines": { 887 | "node": ">=8" 888 | } 889 | }, 890 | "node_modules/path-key": { 891 | "version": "3.1.1", 892 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 893 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 894 | "engines": { 895 | "node": ">=8" 896 | } 897 | }, 898 | "node_modules/path-scurry": { 899 | "version": "1.11.1", 900 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", 901 | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", 902 | "dependencies": { 903 | "lru-cache": "^10.2.0", 904 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 905 | }, 906 | "engines": { 907 | "node": ">=16 || 14 >=14.18" 908 | }, 909 | "funding": { 910 | "url": "https://github.com/sponsors/isaacs" 911 | } 912 | }, 913 | "node_modules/path-to-regexp": { 914 | "version": "1.9.0", 915 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", 916 | "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", 917 | "license": "MIT", 918 | "dependencies": { 919 | "isarray": "0.0.1" 920 | } 921 | }, 922 | "node_modules/picomatch": { 923 | "version": "2.3.1", 924 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 925 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 926 | "license": "MIT", 927 | "engines": { 928 | "node": ">=8.6" 929 | }, 930 | "funding": { 931 | "url": "https://github.com/sponsors/jonschlinkert" 932 | } 933 | }, 934 | "node_modules/randombytes": { 935 | "version": "2.1.0", 936 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 937 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 938 | "dependencies": { 939 | "safe-buffer": "^5.1.0" 940 | } 941 | }, 942 | "node_modules/readdirp": { 943 | "version": "3.6.0", 944 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 945 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 946 | "license": "MIT", 947 | "dependencies": { 948 | "picomatch": "^2.2.1" 949 | }, 950 | "engines": { 951 | "node": ">=8.10.0" 952 | } 953 | }, 954 | "node_modules/require-directory": { 955 | "version": "2.1.1", 956 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 957 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 958 | "license": "MIT", 959 | "engines": { 960 | "node": ">=0.10.0" 961 | } 962 | }, 963 | "node_modules/safe-buffer": { 964 | "version": "5.2.1", 965 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 966 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 967 | "funding": [ 968 | { 969 | "type": "github", 970 | "url": "https://github.com/sponsors/feross" 971 | }, 972 | { 973 | "type": "patreon", 974 | "url": "https://www.patreon.com/feross" 975 | }, 976 | { 977 | "type": "consulting", 978 | "url": "https://feross.org/support" 979 | } 980 | ] 981 | }, 982 | "node_modules/samsam": { 983 | "version": "1.3.0", 984 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", 985 | "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", 986 | "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", 987 | "license": "BSD-3-Clause" 988 | }, 989 | "node_modules/serialize-javascript": { 990 | "version": "6.0.2", 991 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", 992 | "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", 993 | "dependencies": { 994 | "randombytes": "^2.1.0" 995 | } 996 | }, 997 | "node_modules/shebang-command": { 998 | "version": "2.0.0", 999 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1000 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1001 | "dependencies": { 1002 | "shebang-regex": "^3.0.0" 1003 | }, 1004 | "engines": { 1005 | "node": ">=8" 1006 | } 1007 | }, 1008 | "node_modules/shebang-regex": { 1009 | "version": "3.0.0", 1010 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1011 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1012 | "engines": { 1013 | "node": ">=8" 1014 | } 1015 | }, 1016 | "node_modules/signal-exit": { 1017 | "version": "4.1.0", 1018 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1019 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1020 | "engines": { 1021 | "node": ">=14" 1022 | }, 1023 | "funding": { 1024 | "url": "https://github.com/sponsors/isaacs" 1025 | } 1026 | }, 1027 | "node_modules/sinon": { 1028 | "version": "4.5.0", 1029 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", 1030 | "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", 1031 | "deprecated": "16.1.1", 1032 | "hasInstallScript": true, 1033 | "license": "BSD-3-Clause", 1034 | "dependencies": { 1035 | "@sinonjs/formatio": "^2.0.0", 1036 | "diff": "^3.1.0", 1037 | "lodash.get": "^4.4.2", 1038 | "lolex": "^2.2.0", 1039 | "nise": "^1.2.0", 1040 | "supports-color": "^5.1.0", 1041 | "type-detect": "^4.0.5" 1042 | } 1043 | }, 1044 | "node_modules/sinon/node_modules/diff": { 1045 | "version": "3.5.0", 1046 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 1047 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 1048 | "license": "BSD-3-Clause", 1049 | "engines": { 1050 | "node": ">=0.3.1" 1051 | } 1052 | }, 1053 | "node_modules/sinon/node_modules/has-flag": { 1054 | "version": "3.0.0", 1055 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1056 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 1057 | "license": "MIT", 1058 | "engines": { 1059 | "node": ">=4" 1060 | } 1061 | }, 1062 | "node_modules/sinon/node_modules/supports-color": { 1063 | "version": "5.5.0", 1064 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1065 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1066 | "license": "MIT", 1067 | "dependencies": { 1068 | "has-flag": "^3.0.0" 1069 | }, 1070 | "engines": { 1071 | "node": ">=4" 1072 | } 1073 | }, 1074 | "node_modules/string-width": { 1075 | "version": "4.2.3", 1076 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1077 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1078 | "license": "MIT", 1079 | "dependencies": { 1080 | "emoji-regex": "^8.0.0", 1081 | "is-fullwidth-code-point": "^3.0.0", 1082 | "strip-ansi": "^6.0.1" 1083 | }, 1084 | "engines": { 1085 | "node": ">=8" 1086 | } 1087 | }, 1088 | "node_modules/string-width-cjs": { 1089 | "name": "string-width", 1090 | "version": "4.2.3", 1091 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1092 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1093 | "dependencies": { 1094 | "emoji-regex": "^8.0.0", 1095 | "is-fullwidth-code-point": "^3.0.0", 1096 | "strip-ansi": "^6.0.1" 1097 | }, 1098 | "engines": { 1099 | "node": ">=8" 1100 | } 1101 | }, 1102 | "node_modules/strip-ansi": { 1103 | "version": "6.0.1", 1104 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1105 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1106 | "license": "MIT", 1107 | "dependencies": { 1108 | "ansi-regex": "^5.0.1" 1109 | }, 1110 | "engines": { 1111 | "node": ">=8" 1112 | } 1113 | }, 1114 | "node_modules/strip-ansi-cjs": { 1115 | "name": "strip-ansi", 1116 | "version": "6.0.1", 1117 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1118 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1119 | "dependencies": { 1120 | "ansi-regex": "^5.0.1" 1121 | }, 1122 | "engines": { 1123 | "node": ">=8" 1124 | } 1125 | }, 1126 | "node_modules/strip-json-comments": { 1127 | "version": "3.1.1", 1128 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1129 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1130 | "license": "MIT", 1131 | "engines": { 1132 | "node": ">=8" 1133 | }, 1134 | "funding": { 1135 | "url": "https://github.com/sponsors/sindresorhus" 1136 | } 1137 | }, 1138 | "node_modules/supports-color": { 1139 | "version": "8.1.1", 1140 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1141 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1142 | "license": "MIT", 1143 | "dependencies": { 1144 | "has-flag": "^4.0.0" 1145 | }, 1146 | "engines": { 1147 | "node": ">=10" 1148 | }, 1149 | "funding": { 1150 | "url": "https://github.com/chalk/supports-color?sponsor=1" 1151 | } 1152 | }, 1153 | "node_modules/to-regex-range": { 1154 | "version": "5.0.1", 1155 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1156 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1157 | "license": "MIT", 1158 | "dependencies": { 1159 | "is-number": "^7.0.0" 1160 | }, 1161 | "engines": { 1162 | "node": ">=8.0" 1163 | } 1164 | }, 1165 | "node_modules/type-detect": { 1166 | "version": "4.1.0", 1167 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", 1168 | "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", 1169 | "license": "MIT", 1170 | "engines": { 1171 | "node": ">=4" 1172 | } 1173 | }, 1174 | "node_modules/which": { 1175 | "version": "2.0.2", 1176 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1177 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1178 | "dependencies": { 1179 | "isexe": "^2.0.0" 1180 | }, 1181 | "bin": { 1182 | "node-which": "bin/node-which" 1183 | }, 1184 | "engines": { 1185 | "node": ">= 8" 1186 | } 1187 | }, 1188 | "node_modules/workerpool": { 1189 | "version": "6.5.1", 1190 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", 1191 | "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==" 1192 | }, 1193 | "node_modules/wrap-ansi": { 1194 | "version": "7.0.0", 1195 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1196 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1197 | "license": "MIT", 1198 | "dependencies": { 1199 | "ansi-styles": "^4.0.0", 1200 | "string-width": "^4.1.0", 1201 | "strip-ansi": "^6.0.0" 1202 | }, 1203 | "engines": { 1204 | "node": ">=10" 1205 | }, 1206 | "funding": { 1207 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1208 | } 1209 | }, 1210 | "node_modules/wrap-ansi-cjs": { 1211 | "name": "wrap-ansi", 1212 | "version": "7.0.0", 1213 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1214 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1215 | "dependencies": { 1216 | "ansi-styles": "^4.0.0", 1217 | "string-width": "^4.1.0", 1218 | "strip-ansi": "^6.0.0" 1219 | }, 1220 | "engines": { 1221 | "node": ">=10" 1222 | }, 1223 | "funding": { 1224 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1225 | } 1226 | }, 1227 | "node_modules/xregexp": { 1228 | "version": "3.2.0", 1229 | "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-3.2.0.tgz", 1230 | "integrity": "sha512-tWodXkrdYZPGadukpkmhKAbyp37CV5ZiFHacIVPhRZ4/sSt7qtOYHLv2dAqcPN0mBsViY2Qai9JkO7v2TBP6hg==", 1231 | "license": "MIT" 1232 | }, 1233 | "node_modules/y18n": { 1234 | "version": "5.0.8", 1235 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1236 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1237 | "license": "ISC", 1238 | "engines": { 1239 | "node": ">=10" 1240 | } 1241 | }, 1242 | "node_modules/yargs": { 1243 | "version": "16.2.0", 1244 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1245 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1246 | "license": "MIT", 1247 | "dependencies": { 1248 | "cliui": "^7.0.2", 1249 | "escalade": "^3.1.1", 1250 | "get-caller-file": "^2.0.5", 1251 | "require-directory": "^2.1.1", 1252 | "string-width": "^4.2.0", 1253 | "y18n": "^5.0.5", 1254 | "yargs-parser": "^20.2.2" 1255 | }, 1256 | "engines": { 1257 | "node": ">=10" 1258 | } 1259 | }, 1260 | "node_modules/yargs-parser": { 1261 | "version": "20.2.9", 1262 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 1263 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 1264 | "engines": { 1265 | "node": ">=10" 1266 | } 1267 | }, 1268 | "node_modules/yargs-unparser": { 1269 | "version": "2.0.0", 1270 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 1271 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 1272 | "license": "MIT", 1273 | "dependencies": { 1274 | "camelcase": "^6.0.0", 1275 | "decamelize": "^4.0.0", 1276 | "flat": "^5.0.2", 1277 | "is-plain-obj": "^2.1.0" 1278 | }, 1279 | "engines": { 1280 | "node": ">=10" 1281 | } 1282 | }, 1283 | "node_modules/yocto-queue": { 1284 | "version": "0.1.0", 1285 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1286 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1287 | "license": "MIT", 1288 | "engines": { 1289 | "node": ">=10" 1290 | }, 1291 | "funding": { 1292 | "url": "https://github.com/sponsors/sindresorhus" 1293 | } 1294 | } 1295 | } 1296 | } 1297 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "mocha" 4 | }, 5 | "type": "commonjs", 6 | "dependencies": { 7 | "bluebird": "^3.5.1", 8 | "mocha": "^11.0.1", 9 | "sinon": "^4.0.2", 10 | "xregexp": "^3.2.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/extractcites.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This file is part of zotxt. 4 | # 5 | # zotxt is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Foobar is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Foobar. If not, see . 17 | 18 | import codecs 19 | import json 20 | import re 21 | import sys 22 | import urllib, urllib2 23 | 24 | f = codecs.open(sys.argv[1], 'r', encoding='utf-8') 25 | cite_re = re.compile(r'(?:@)[\w:\.#$%&_+?<>~/-]+', re.U) 26 | known_keys = set([]) 27 | for line in f: 28 | citekeys = re.findall(cite_re, line) 29 | for citekey in citekeys: 30 | if citekey not in known_keys: 31 | known_keys.add(citekey[1:]) 32 | 33 | cites = [] 34 | for key in known_keys: 35 | try: 36 | q = {'easykey' : key.encode('utf8')} 37 | encq = urllib.urlencode(q) 38 | cite = json.load(urllib2.urlopen("http://127.0.0.1:23119/zotxt/items?" + encq))[0] 39 | cite["id"] = key 40 | cites.append(cite) 41 | except urllib2.HTTPError, e: 42 | sys.stderr.write("error with %s : %s \n"%(key, e.read())) 43 | print json.dumps(cites, indent=2) 44 | -------------------------------------------------------------------------------- /test/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "httpclient" 6 | gem "json" 7 | gem "minitest" 8 | -------------------------------------------------------------------------------- /test/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | httpclient (2.8.3) 5 | json (2.6.3) 6 | minitest (5.18.1) 7 | 8 | PLATFORMS 9 | ruby 10 | 11 | DEPENDENCIES 12 | httpclient 13 | json 14 | minitest 15 | 16 | BUNDLED WITH 17 | 2.2.22 18 | -------------------------------------------------------------------------------- /test/test-data/files/7438/doe: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /test/test-data/zotxt test.rdf: -------------------------------------------------------------------------------- 1 | 11 | 12 | journalArticle 13 | 14 | 15 | 6 16 | Journal of Generic Studies 17 | 18 | 19 | 20 | 21 | 22 | 23 | Doe 24 | John 25 | 26 | 27 | 28 | 29 | 30 | 33-34 31 | 2006 32 | Article 33 | 34 | 35 | attachment 36 | 37 | doe 38 | text/plain 39 | <div data-schema-version="5"><p>test</p> 40 | </div> 41 | 42 | 43 | book 44 | 45 | 46 | 47 | Hüáéèñ 48 | 49 | 50 | 51 | 2015 52 | Citation Key: hüáéèñ:2015acćénts 53 | acćénts 54 | 55 | 56 | book 57 | 58 | 59 | 60 | 61 | New York, NY 62 | 63 | 64 | HARPER COLLINS PUBLISHERS 65 | 66 | 67 | 68 | 69 | 70 | 71 | Jenkins 72 | Philip 73 | 74 | 75 | 76 | 77 | Reprint edition 78 | ISBN 978-0-06-147281-7 79 | 1 Dec. 2009 80 | Amazon 81 | English 82 | --This text refers to an alternate Paperback edition. 83 | The Lost History of Christianity 84 | 304 85 | 86 | 87 | document 88 | 89 | 90 | 91 | 92 | Roe Doe 93 | Jane 94 | 95 | 96 | 97 | 98 | 2015 99 | Double Name 100 | 101 | 102 | note 103 | <p>Test standalone.</p> 104 | <p> </p> 105 | <p>Hello world.</p> 106 | 107 | 108 | book 109 | 110 | 111 | 112 | 113 | United Nations 114 | 115 | 116 | 117 | 118 | 2005 119 | A Wonderful book 120 | 121 | 122 | document 123 | 124 | 125 | 126 | 127 | Doe 128 | John 129 | 130 | 131 | 132 | 133 | 1 Jan 2001 134 | Foo 135 | betterbibtex JSON 136 | 137 | 138 | document 139 | 140 | 141 | 142 | 143 | Roe-Doe 144 | John 145 | 146 | 147 | 148 | 149 | 2015 150 | Citation Key: Roe-Doe2015 151 | Hyphens 152 | 153 | 154 | bookSection 155 | 156 | 157 | 158 | 159 | Institut für Deutsche Sprache, Jahrbuch 2011 160 | 161 | 162 | ISBN 978-3-11-028354-9 163 | Deutsch im Sprachvergleich. Grammatische Kontraste und Konvergenzen 164 | 165 | 166 | 167 | 168 | 169 | 170 | Berlin 171 | 172 | 173 | De Gruyter 174 | 175 | 176 | 177 | 178 | 179 | 180 | Matthias Hüning 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | Gunkel 190 | Lutz 191 | 192 | 193 | 194 | 195 | Zifonun 196 | Gisela 197 | 198 | 199 | 200 | 201 | 161-186 202 | 2012 203 | Open WorldCat 204 | German 205 | Wortbildung im niederländisch-deutschen Sprachvergleich 206 | 207 | 208 | bookSection 209 | 210 | Third Book 211 | 212 | 213 | 214 | 215 | 216 | Oxford 217 | 218 | 219 | Oxford University Press 220 | 221 | 222 | 223 | 224 | 225 | 226 | Smith 227 | Sam 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | Doe 237 | John 238 | 239 | 240 | 241 | 242 | Roe 243 | Jenny 244 | 245 | 246 | 247 | 248 | 2007 249 | <i>Why Water</i> Is Wet 250 | 251 | 252 | document 253 | 254 | 255 | 256 | 257 | Doe 258 | Jane 259 | 260 | 261 | 262 | 263 | 2015 264 | Duplicated in group library 265 | 266 | 267 | book 268 | 269 | 270 | 271 | 272 | Cambridge 273 | 274 | 275 | Cambridge University Press 276 | 277 | 278 | 279 | 280 | 281 | 282 | Doe 283 | John 284 | 285 | 286 | 287 | 288 | 2005 289 | First Book 290 | 291 | 292 | book 293 | 294 | 295 | 296 | 297 | New York 298 | 299 | 300 | HarperOne 301 | 302 | 303 | 304 | 305 | 306 | 307 | Jenkins 308 | John Philip 309 | 310 | 311 | 312 | 313 | Reprint edition 314 | ISBN 978-0-06-176893-4 315 | March 8, 2011 316 | Amazon 317 | English 318 | Jesus Wars: How Four Patriarchs, Three Queens, and Two Emperors Decided What Christians Would Believe for the Next 1,500 years 319 | Jesus Wars 320 | 352 321 | 322 | 323 | document 324 | 325 | 326 | 327 | 328 | Doe 329 | Jane 330 | 331 | 332 | 333 | 334 | 2005 335 | Ambiguous One 336 | 337 | 338 | document 339 | 340 | 341 | 342 | 343 | Doe 344 | John 345 | 346 | 347 | 348 | 349 | 2005 350 | Ambiguous Two 351 | 352 | 353 | book 354 | 355 | 356 | 357 | 358 | Толстой 359 | Лев Николаевич 360 | 361 | 362 | 363 | 364 | 365 | ISBN 978-966-284-200-5 366 | Amazon 367 | Отправка из Нью-Йорка от 7 до 21 дня. Книга 1 том 1-2 Voina i mir Tom 1 2 in Russian (Russian) Hardcover - 2000 Имеется продолжение книга 2 том 3-4. Продается отдельно. There are 2 book that continuation of 3-4. Sold separately. 368 | Война и мир. Книга 1 Том I-2 War and Peace. book 1 Volume I-2 Voina i mir Tom 1 2 in Russian 369 | 370 | 371 | attachment 372 | 373 | 374 | https://smile.amazon.com/%D0%92%D0%BE%D0%B9%D0%BD%D0%B0-%D0%9A%D0%BD%D0%B8%D0%B3%D0%B0-Peace-Voina-Russian/dp/9662842004 375 | 376 | 377 | 2020-04-04 01:49:14 378 | Amazon.com Link 379 | 3 380 | text/html 381 | 382 | 383 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* global require describe it */ 2 | 3 | const assert = require('assert'); 4 | const sinon = require('sinon'); 5 | const xregexp = require('xregexp'); 6 | const bluebird = require('bluebird'); 7 | 8 | const core = require('../extension/core.js'); 9 | 10 | describe('#core.fixStyleId()', () => { 11 | it('should return chicago as default', ()=>{ 12 | assert.equal('http://www.zotero.org/styles/chicago-note-bibliography', core.fixStyleId()); 13 | }); 14 | 15 | it('should append the prefix if necessary', ()=>{ 16 | assert.equal('http://www.zotero.org/styles/foobar', core.fixStyleId('foobar')); 17 | }); 18 | 19 | it('should return the full url otherwise', ()=>{ 20 | const url = 'http://example.org/foo'; 21 | assert.equal(url, core.fixStyleId(url)); 22 | }); 23 | }); 24 | 25 | 26 | describe('#core.checkStyleId()', () => { 27 | const styleId = "http://example.org/chicago" 28 | beforeEach(()=>{ 29 | getAll = sinon.stub().returns({[styleId]: {"styleID": styleId}}); 30 | zotero = { Styles : { getAll } }; 31 | }); 32 | 33 | it('should do nothing for good styles', ()=>{ 34 | assert.equal(styleId, core.checkStyleId(styleId, zotero)); 35 | }); 36 | 37 | it('should throw an exception for a bad style', ()=>{ 38 | assert.throws( 39 | ()=> { 40 | core.checkStyleId('http://example.org/bad', zotero); 41 | }, 42 | /Style .*bad is not installed/); 43 | }); 44 | }); 45 | 46 | 47 | describe('#core.parseEasyKey()', () => { 48 | const zotero = { Utilities: { XRegExp: xregexp } }; 49 | const altRes = { 50 | creator: 'foo', 51 | date: '2016', 52 | title: 'bar' 53 | }; 54 | 55 | const res = { 56 | creator: 'Foo', 57 | date: '2016', 58 | title: 'Bar' 59 | }; 60 | 61 | it('should parse an alternative easykey', ()=>{ 62 | assert.deepEqual(altRes, core.parseEasyKey('foo:2016bar', zotero)); 63 | }); 64 | 65 | it('should parse a normal easykey', ()=>{ 66 | assert.deepEqual(res, core.parseEasyKey('FooBar2016', zotero)); 67 | }); 68 | }); 69 | 70 | describe('#core.cleanQuery()', () => { 71 | it('should replace all + with space', ()=>{ 72 | assert.deepEqual({foo: 'foo bar'}, core.cleanQuery({foo: 'foo+bar'})); 73 | }); 74 | }); 75 | 76 | describe('#core.dedupItems()', () => { 77 | const item1 = {id: 1}; 78 | const item2 = {id: 2}; 79 | const item3 = {id: 3}; 80 | const zotero = { Promise: { filter: bluebird.filter } }; 81 | const mkPromises = (...rest)=>{ return rest.map(bluebird.resolve); }; 82 | it('should return an Promise that resolves to an iterable', ()=>{ 83 | return core.dedupItems(mkPromises(item1), zotero).then((items)=>{ 84 | assert.equal(1, items.length); 85 | assert.deepEqual(item1, items[0]); 86 | }); 87 | }); 88 | 89 | it('should dedup with the same id', ()=>{ 90 | return core.dedupItems(mkPromises(item1, item1), zotero).then((items)=>{ 91 | assert.equal(1, items.length); 92 | assert.deepEqual(item1, items[0]); 93 | }); 94 | }); 95 | 96 | it('should dedup and return in order', ()=>{ 97 | return core.dedupItems(mkPromises(item1, item2, item1, item3, item1), zotero).then((items)=>{ 98 | assert.equal(3, items.length); 99 | assert.deepEqual(item1, items[0]); 100 | assert.deepEqual(item2, items[1]); 101 | assert.deepEqual(item3, items[2]); 102 | }); 103 | }); 104 | }); 105 | 106 | describe('#core.item2key()', () => { 107 | it('builds a key', ()=>{ 108 | const item = { libraryID: '2', key: 'foo' }; 109 | assert('2_foo', core.item2key(item)); 110 | }); 111 | 112 | it('defaults to librar 1', ()=>{ 113 | const item = { key: 'foo' }; 114 | assert('1_foo', core.item2key(item)); 115 | }); 116 | }); 117 | 118 | describe('#core.findByKey()', () => { 119 | let getByLibraryAndKeyAsync, parseLibraryKey, zotero; 120 | 121 | beforeEach(()=>{ 122 | getByLibraryAndKeyAsync = sinon.stub().returns(Promise.resolve(true)); 123 | parseLibraryKey = sinon.stub().returns({ libraryID: 2, key: 'bar' }); 124 | zotero = { Items : { getByLibraryAndKeyAsync, parseLibraryKey } }; 125 | }); 126 | 127 | it("calls getByLibraryAndKeyAsync when a key without a / is passed in", ()=>{ 128 | const key = 'foo-bar'; 129 | core.findByKey(key, zotero); 130 | assert(parseLibraryKey.notCalled); 131 | assert(getByLibraryAndKeyAsync.calledOnce); 132 | assert(getByLibraryAndKeyAsync.calledWith(1, key)); 133 | }); 134 | 135 | it("calls getByLibraryAndKeyAsync when a key with a _ is passed in", ()=>{ 136 | const key = '3_bar'; 137 | core.findByKey(key, zotero); 138 | assert(parseLibraryKey.notCalled); 139 | assert(getByLibraryAndKeyAsync.calledOnce); 140 | assert(getByLibraryAndKeyAsync.calledWith(3, 'bar')); 141 | }); 142 | 143 | it("fetches the library id and calls getByLibraryAndKeyAsync when a key with a / is used", ()=>{ 144 | const key = 'foo/bar'; 145 | core.findByKey(key, zotero); 146 | assert(parseLibraryKey.calledOnce); 147 | assert(parseLibraryKey.calledWith(key)); 148 | assert(getByLibraryAndKeyAsync.calledOnce); 149 | assert(getByLibraryAndKeyAsync.calledWith(2, 'bar')); 150 | }); 151 | }); 152 | 153 | 154 | describe('#core.makeCslEngine()', () => { 155 | let opt, getCiteProc, style, get, zotero, styleName, locale, styleId, getAll; 156 | 157 | beforeEach(()=>{ 158 | opt = { development_extensions: { wrap_url_and_doi: false } }; 159 | getCiteProc = sinon.stub().returns({ opt }); 160 | style = { getCiteProc }; 161 | styleName = 'foo'; 162 | locale = 'en-US'; 163 | styleId = `http://www.zotero.org/styles/${styleName}`; 164 | get = sinon.stub().returns(style); 165 | getAll = sinon.stub().returns({[styleId]: {"styleID": styleId}}); 166 | zotero = { Styles : { get, getAll } }; 167 | }); 168 | 169 | it("sets wrap_url_and_dio", ()=>{ 170 | core.makeCslEngine(styleName, locale, zotero, 'html'); 171 | assert.equal(true, opt.development_extensions.wrap_url_and_doi); 172 | }); 173 | 174 | it("sets calls Styles.get", ()=>{ 175 | core.makeCslEngine(styleName, locale, zotero, 'text'); 176 | sinon.assert.calledOnce(get); 177 | sinon.assert.calledWith(get, styleId); 178 | }); 179 | 180 | it("sets calls getCiteProc with locale", ()=>{ 181 | core.makeCslEngine(styleName, locale, zotero, 'html'); 182 | sinon.assert.calledOnce(getCiteProc); 183 | sinon.assert.calledWith(getCiteProc, 'en-US', 'html'); 184 | }); 185 | }); 186 | 187 | describe('#core.getItemOrParent()', () => { 188 | it("returns item when item is a regularItem", ()=>{ 189 | const isRegularItem = sinon.stub().returns(true); 190 | const item = { isRegularItem }; 191 | assert.equal(item, core.getItemOrParent(item, undefined)); 192 | }); 193 | 194 | it("returns item when item is not a regularItem but the parentKey is falsey", ()=>{ 195 | const isRegularItem = sinon.stub().returns(false); 196 | const parentKey = undefined; 197 | const item = { isRegularItem, parentKey }; 198 | assert.equal(item, core.getItemOrParent(item, undefined)); 199 | }); 200 | 201 | it("returns item when item is not a regularItem but the parentKey is falsey", ()=>{ 202 | const retval = 'foo'; 203 | const isRegularItem = sinon.stub().returns(false); 204 | const parentKey = "foo_bar"; 205 | const item = { isRegularItem, parentKey }; 206 | const getByLibraryAndKeyAsync = sinon.stub().returns(Promise.resolve(retval)); 207 | const Items = { getByLibraryAndKeyAsync }; 208 | const zotero = { Items }; 209 | core.getItemOrParent(item, zotero).then((item)=> { assert.equal(retval, item); }); 210 | }); 211 | }); 212 | 213 | describe('#core.buildSearch()', () => { 214 | let addCondition, search; 215 | 216 | beforeEach(()=>{ 217 | addCondition = sinon.stub(); 218 | search = { addCondition }; 219 | }); 220 | 221 | it("sets joinMode to all ", ()=>{ 222 | core.buildSearch(search, 'foo', null); 223 | sinon.assert.calledWith(addCondition, 'joinMode', 'all'); 224 | }); 225 | 226 | it("searches titleCreatorYear by default", ()=>{ 227 | core.buildSearch(search, 'foo', null); 228 | sinon.assert.calledWith(addCondition, 'quicksearch-titleCreatorYear', 'contains', 'foo'); 229 | }); 230 | 231 | it("uses the method if passed in by default", ()=>{ 232 | core.buildSearch(search, 'foo', 'test'); 233 | sinon.assert.calledWith(addCondition, 'quicksearch-test', 'contains', 'foo'); 234 | }); 235 | 236 | it("splits words for search", ()=>{ 237 | core.buildSearch(search, 'foo bar', null); 238 | sinon.assert.calledWith(addCondition, 'quicksearch-titleCreatorYear', 'contains', 'foo'); 239 | sinon.assert.calledWith(addCondition, 'quicksearch-titleCreatorYear', 'contains', 'bar'); 240 | }); 241 | }); 242 | 243 | describe('#core.buildRawSearch()', () => { 244 | let addCondition, search; 245 | 246 | beforeEach(()=>{ 247 | addCondition = sinon.stub(); 248 | search = { addCondition }; 249 | }); 250 | 251 | it("sets up the search as expected ", ()=>{ 252 | core.buildRawSearch(search, 'foo:2016bar'); 253 | sinon.assert.calledWith(addCondition, 'joinMode', 'any'); 254 | sinon.assert.calledWith(addCondition, 'tag', 'is', '@foo:2016bar'); 255 | sinon.assert.calledWith(addCondition, 'note', 'contains', '@foo:2016bar'); 256 | }); 257 | }); 258 | 259 | 260 | describe('#core.buildEasyKeySearch()', () => { 261 | let addCondition, search; 262 | const zotero = { Utilities: { XRegExp: xregexp } }; 263 | 264 | beforeEach(()=>{ 265 | addCondition = sinon.stub(); 266 | search = { addCondition }; 267 | }); 268 | 269 | it("adds all fields if provided", ()=>{ 270 | const key = core.parseEasyKey('foo:2016bar', zotero); 271 | core.buildEasyKeySearch(search, key); 272 | sinon.assert.calledWith(addCondition, 'creator', 'contains', 'foo'); 273 | sinon.assert.calledWith(addCondition, 'title', 'contains', 'bar'); 274 | sinon.assert.calledWith(addCondition, 'date', 'is', '2016'); 275 | }); 276 | 277 | it("splits names", ()=>{ 278 | const key = core.parseEasyKey('foo_bar:2016baz', zotero); 279 | core.buildEasyKeySearch(search, key); 280 | sinon.assert.calledWith(addCondition, 'creator', 'contains', 'foo'); 281 | sinon.assert.calledWith(addCondition, 'creator', 'contains', 'bar'); 282 | }); 283 | 284 | it("works when date not provided", ()=>{ 285 | const key = core.parseEasyKey('foo:bar', zotero); 286 | core.buildEasyKeySearch(search, key); 287 | sinon.assert.calledWith(addCondition, 'creator', 'contains', 'foo'); 288 | sinon.assert.calledWith(addCondition, 'title', 'contains', 'bar'); 289 | sinon.assert.calledTwice(addCondition); 290 | }); 291 | 292 | it("works when title not provided", ()=>{ 293 | const key = core.parseEasyKey('foo:2016', zotero); 294 | core.buildEasyKeySearch(search, key); 295 | sinon.assert.calledWith(addCondition, 'creator', 'contains', 'foo'); 296 | sinon.assert.calledWith(addCondition, 'date', 'is', '2016'); 297 | sinon.assert.calledTwice(addCondition); 298 | }); 299 | 300 | it("works when date title not provided", ()=>{ 301 | const key = core.parseEasyKey('foo:', zotero); 302 | core.buildEasyKeySearch(search, key); 303 | sinon.assert.calledWith(addCondition, 'creator', 'contains', 'foo'); 304 | sinon.assert.calledOnce(addCondition); 305 | }); 306 | }); 307 | -------------------------------------------------------------------------------- /test/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "minitest/autorun" 4 | require "httpclient" 5 | require "json" 6 | require "open3" 7 | 8 | class ZotxtTest < Minitest::Test 9 | def setup 10 | @client = HTTPClient.new 11 | @base_url = "http://127.0.0.1:23119/zotxt" 12 | @item_url = "#{@base_url}/items" 13 | @complete_url = "#{@base_url}/complete" 14 | @version_url = "#{@base_url}/version" 15 | @bibliography_url = "#{@base_url}/bibliography" 16 | @search_url = "#{@base_url}/search" 17 | @select_url = "#{@base_url}/select" 18 | @locales_url = "#{@base_url}/locales" 19 | @styles_url = "#{@base_url}/styles" 20 | @doe_first_book_key = find_item_key("doe first book 2005") 21 | @doe_article_key = find_item_key("doe article 2006") 22 | @roe_doe_hyphens_key = find_item_key("roe doe hyphens") 23 | @doe_duplicated = find_item_key("doe duplicated") 24 | end 25 | 26 | def find_item_key(q) 27 | resp = @client.get(@search_url, { "q" => q, "format" => "key" }) 28 | JSON.parse(resp.body)[0] 29 | end 30 | 31 | def test_items_nothing 32 | resp = @client.get(@item_url) 33 | assert_equal 400, resp.status 34 | end 35 | 36 | def test_items_easykey 37 | resp = @client.get(@item_url, { "easykey" => "DoeBook2005" }) 38 | assert_equal 200, resp.status 39 | i = JSON.parse(resp.body) 40 | assert_equal "book", i[0]["type"] 41 | assert_equal "First Book", i[0]["title"] 42 | assert_equal "Doe", i[0]["author"][0]["family"] 43 | end 44 | 45 | def test_items_easykey_key_format 46 | resp = @client.get(@item_url, { "easykey" => "DoeBook2005", "format" => "key" }) 47 | assert_equal 200, resp.status 48 | i = JSON.parse(resp.body) 49 | assert_equal @doe_first_book_key, i[0] 50 | end 51 | 52 | def test_items_easykey_json_format 53 | resp = @client.get(@item_url, { "easykey" => "DoeBook2005", "format" => "json" }) 54 | assert_equal 200, resp.status 55 | i = JSON.parse(resp.body) 56 | i[0].delete("id") 57 | assert_equal({ "citation-key" => "doe:2005first", 58 | "type" => "book", 59 | "title" => "First Book", 60 | "publisher" => "Cambridge University Press", 61 | "author" => [{ "family" => "Doe", "given" => "John" }], 62 | "issued" => { "date-parts" => [["2005"]] }, 63 | "publisher-place" => "Cambridge", 64 | "event-place" => "Cambridge" }, i[0]) 65 | end 66 | 67 | def test_items_easykey_paths_format 68 | resp = @client.get(@item_url, { "easykey" => "doe:2006article", "format" => "paths" }) 69 | assert_equal 200, resp.status 70 | i = JSON.parse(resp.body) 71 | assert_equal(@doe_article_key, i[0]["key"]) 72 | assert(i[0]["paths"][0] =~ %r{storage/[A-Z0-9]{8}/doe$}) 73 | end 74 | 75 | def test_items_easykey_paths_format_after_deletion 76 | skip("requires sync setup") 77 | resp = @client.get(@item_url, { "easykey" => "doe:2006article", "format" => "paths" }) 78 | i = JSON.parse(resp.body) 79 | 80 | # should be fetched afer deletion 81 | File.unlink(i[0]["paths"][0]) 82 | resp = @client.get(@item_url, { "easykey" => "doe:2006article", "format" => "paths" }) 83 | assert_equal 200, resp.status 84 | i = JSON.parse(resp.body) 85 | assert_equal(@doe_article_key, i[0]["key"]) 86 | # assert_match not working? 87 | assert(i[0]["paths"][0] =~ %r{storage/[A-Z0-9]{8}/doe$}) 88 | end 89 | 90 | def test_items_easykey_two_word 91 | key = find_item_key("united nations wonderful") 92 | resp = @client.get(@item_url, { "easykey" => "united_nations:2005book", "format" => "key" }) 93 | assert_equal 200, resp.status 94 | i = JSON.parse(resp.body) 95 | assert_equal key, i[0] 96 | end 97 | 98 | def test_items_easykey_double_name 99 | key = find_item_key("roe doe double") 100 | resp = @client.get(@item_url, { "easykey" => "roe_doe:2015double", "format" => "key" }) 101 | assert_equal 200, resp.status 102 | i = JSON.parse(resp.body) 103 | assert_equal key, i[0] 104 | end 105 | 106 | def test_items_easykey_hyphenated_name 107 | resp = @client.get(@item_url, { "easykey" => "roe-doe:2015hyphens", "format" => "key" }) 108 | assert_equal 200, resp.status 109 | i = JSON.parse(resp.body) 110 | assert_equal @roe_doe_hyphens_key, i[0] 111 | end 112 | 113 | def test_items_easykey_alternate_key_format 114 | resp = @client.get(@item_url, { "easykey" => "doe:2005book", "format" => "key" }) 115 | assert_equal 200, resp.status 116 | i = JSON.parse(resp.body) 117 | assert_equal @doe_first_book_key, i[0] 118 | end 119 | 120 | def test_items_easykey_two_items 121 | resp = @client.get(@item_url, { "easykey" => "doe:2005book,roe-doe:2015hyphens", "format" => "key" }) 122 | assert_equal 200, resp.status 123 | i = JSON.parse(resp.body) 124 | assert_equal [@doe_first_book_key, @roe_doe_hyphens_key].sort, i.sort 125 | end 126 | 127 | def test_items_easykey_ambiguous 128 | resp = @client.get(@item_url, { "easykey" => "doe:2005ambiguous" }) 129 | assert_equal 400, resp.status 130 | assert_equal '"doe:2005ambiguous returned multiple items"', resp.body 131 | end 132 | 133 | def test_items_easykey_bad 134 | resp = @client.get(@item_url, { "easykey" => "doe:2005foobar" }) 135 | assert_equal 400, resp.status 136 | assert_equal '"doe:2005foobar had no results"', resp.body 137 | end 138 | 139 | def test_betterbibtexkey 140 | %w[betterbibtexkey citekey].each do |name| 141 | resp = @client.get(@item_url, { name => "doe:2005first", "format" => "key" }) 142 | assert_equal 200, resp.status 143 | i = JSON.parse(resp.body) 144 | assert_equal [@doe_first_book_key], i 145 | end 146 | end 147 | 148 | def test_betterbibtexkey_two_items 149 | %w[betterbibtexkey citekey].each do |name| 150 | resp = @client.get(@item_url, { name => "doe:2005first,doe:2006article", "format" => "key" }) 151 | assert_equal 200, resp.status 152 | i = JSON.parse(resp.body) 153 | assert_equal [@doe_first_book_key, @doe_article_key].sort, i.sort 154 | end 155 | end 156 | 157 | # doesn't work 158 | # def test_betterbibtexkey_hyphen 159 | # resp = @client.get(@item_url, {"betterbibtexkey" => "Roe-Doe2005", "format" => "key"}) 160 | # assert_equal 200, resp.status 161 | # i = JSON.parse(resp.body) 162 | # assert_equal ["xyz"], i 163 | # end 164 | 165 | def test_items_easykey_bibliography_format 166 | resp = @client.get(@item_url, { "easykey" => "DoeBook2005", "format" => "bibliography" }) 167 | assert_equal 200, resp.status 168 | i = JSON.parse(resp.body) 169 | assert(i[0].key?("html")) 170 | 171 | # with style 172 | resp = @client.get(@item_url, 173 | { "easykey" => "DoeBook2005", "format" => "bibliography", 174 | "style" => "http://www.zotero.org/styles/chicago-note-bibliography" }) 175 | assert_equal 200, resp.status 176 | i = JSON.parse(resp.body) 177 | assert(i[0].key?("html")) 178 | 179 | # with short style 180 | resp = @client.get(@item_url, 181 | { "easykey" => "DoeBook2005", "format" => "bibliography", 182 | "style" => "chicago-note-bibliography" }) 183 | assert_equal 200, resp.status 184 | i = JSON.parse(resp.body) 185 | assert(i[0].key?("html")) 186 | end 187 | 188 | def test_items_key 189 | resp = @client.get(@item_url, { "key" => @doe_first_book_key, "format" => "key" }) 190 | assert_equal 200, resp.status 191 | i = JSON.parse(resp.body) 192 | assert_equal @doe_first_book_key, i[0] 193 | end 194 | 195 | def test_items_style_param 196 | resp = @client.get(@item_url, { "key" => @doe_first_book_key, "format" => "bibliography", "style" => "ieee" }) 197 | assert_equal 200, resp.status 198 | i = JSON.parse(resp.body) 199 | assert_equal "[1] J. Doe, First Book. Cambridge: Cambridge University Press, 2005.", i[0]["text"] 200 | end 201 | 202 | def test_items_bad_key 203 | resp = @client.get(@item_url, { "key" => "1_ZBZQ4KMXXXX", "format" => "key" }) 204 | assert_equal 400, resp.status 205 | assert_equal '"1_ZBZQ4KMXXXX not found"', resp.body 206 | end 207 | 208 | def test_items_multiple_easykeys 209 | resp = @client.get(@item_url, { "easykey" => "DoeBook2005,Doe2006article", "format" => "key" }) 210 | assert_equal 200, resp.status 211 | i = JSON.parse(resp.body) 212 | assert_equal 2, i.length 213 | end 214 | 215 | def test_items_multiple_keys 216 | resp = @client.get(@item_url, { "key" => "#{@doe_first_book_key},#{@doe_article_key}", "format" => "key" }) 217 | assert_equal 200, resp.status 218 | i = JSON.parse(resp.body) 219 | assert_equal 2, i.length 220 | end 221 | 222 | def test_selected 223 | resp = @client.get(@item_url, { "selected" => "selected" }) 224 | assert_equal 200, resp.status 225 | end 226 | 227 | def test_bibliography 228 | r = { 229 | "styleId" => "chicago-author-date", 230 | "citationGroups" => [ 231 | { "citationItems" => [{ "easyKey" => "DoeBook2005" }], 232 | "properties" => { "noteIndex" => 0 } }, 233 | ], 234 | } 235 | header = { "Content-Type" => "application/json" } 236 | resp = @client.post(@bibliography_url, header: header, body: JSON.dump(r)) 237 | assert_equal 200, resp.status 238 | i = JSON.parse(resp.body) 239 | assert_equal ["(Doe 2005)"], i["citationClusters"] 240 | end 241 | 242 | def test_bibliography_error 243 | r = { 244 | "styleId" => "chicago-author-date", 245 | "citationGroups" => [ 246 | { "citationItems" => [{ "easyKey" => "doe:2005ambiguous" }], 247 | "properties" => { "noteIndex" => 0 } }, 248 | { "citationItems" => [{ "easyKey" => "doe:2005first" }], 249 | "properties" => { "noteIndex" => 0 } }, 250 | ], 251 | } 252 | header = { "Content-Type" => "application/json" } 253 | resp = @client.post(@bibliography_url, header: header, body: JSON.dump(r)) 254 | assert_equal 400, resp.status 255 | assert_equal '"doe:2005ambiguous returned multiple items"', resp.body 256 | end 257 | 258 | def test_ambiguous_bibliography 259 | r = { 260 | "styleId" => "chicago-author-date", 261 | "citationGroups" => [ 262 | { "citationItems" => [{ "easyKey" => "jenkins:2011jesus" }], 263 | "properties" => { "index" => 0, "noteIndex" => 0 } }, 264 | { "citationItems" => [{ "easyKey" => "jenkins:2009lost" }], 265 | "properties" => { "index" => 1, "noteIndex" => 0 } }, 266 | ], 267 | } 268 | header = { "Content-Type" => "application/json" } 269 | resp = @client.post(@bibliography_url, header: header, body: JSON.dump(r)) 270 | assert_equal 200, resp.status 271 | i = JSON.parse(resp.body) 272 | assert_equal ["(J. P. Jenkins 2011)", "(P. Jenkins 2009)"], i["citationClusters"] 273 | end 274 | 275 | def test_bibliography_key 276 | key = find_item_key("doe 2005 first book") 277 | r = { 278 | "styleId" => "chicago-author-date", 279 | "citationGroups" => [ 280 | { "citationItems" => [{ "key" => key }], 281 | "properties" => { "noteIndex" => 0 } }, 282 | ], 283 | } 284 | header = { "Content-Type" => "application/json" } 285 | resp = @client.post(@bibliography_url, header: header, body: JSON.dump(r)) 286 | assert_equal 200, resp.status 287 | i = JSON.parse(resp.body) 288 | assert_equal ["(Doe 2005)"], i["citationClusters"] 289 | end 290 | 291 | def test_bad_bibliography 292 | r = { 293 | "styleId" => "chicago-author-date", 294 | "citationGroups" => [ 295 | { "citationItems" => [{ "easyKey" => "FooBar0000" }], 296 | "properties" => { "noteIndex" => 0 } }, 297 | ], 298 | } 299 | header = { "Content-Type" => "application/json" } 300 | resp = @client.post(@bibliography_url, header: header, body: JSON.dump(r)) 301 | assert_equal 400, resp.status 302 | end 303 | 304 | def test_no_param 305 | resp = @client.get(@item_url) 306 | assert_equal 400, resp.status 307 | end 308 | 309 | def test_all 310 | skip("Too slow, times out") 311 | resp = @client.get(@item_url, { "all" => "all", "format" => "key" }) 312 | assert_equal 200, resp.status 313 | end 314 | 315 | def test_bad_easykey 316 | resp = @client.get(@item_url, { "easykey" => "XXX" }) 317 | assert_equal 400, resp.status 318 | end 319 | 320 | def test_unicode_easykey 321 | resp = @client.get(@item_url, { "easykey" => "HüningWortbildung2012" }) 322 | assert_equal 200, resp.status 323 | end 324 | 325 | def test_bad_easykey 326 | resp = @client.get(@item_url, { "easykey" => "XXX" }) 327 | assert_equal 400, resp.status 328 | end 329 | 330 | def test_custom_key 331 | skip("this seems broken") 332 | resp = @client.get(@item_url, { "easykey" => "hüning:2012foo" }) 333 | assert_equal 200, resp.status 334 | end 335 | 336 | def test_accent_easykey_export 337 | skip("easykey is deprecated") 338 | key = find_item_key("acćénts") 339 | resp = @client.get(@item_url, { "key" => key, "format" => "easykey" }) 340 | assert_equal 200, resp.status 341 | results = JSON.parse(resp.body) 342 | assert_equal "hüáéèñ:2015acćénts", results[0] 343 | end 344 | 345 | def test_accent_easykey_fetch 346 | key = find_item_key("acćénts") 347 | resp = @client.get(@item_url, { "easykey" => "hüáéèñ:2015acćénts", "format" => "key" }) 348 | assert_equal 200, resp.status 349 | results = JSON.parse(resp.body) 350 | assert_equal key, results[0] 351 | end 352 | 353 | def test_duplicate_in_group_library 354 | resp = @client.get(@item_url, { "easykey" => "doe:2015duplicated", "format" => "key" }) 355 | assert_equal 200, resp.status 356 | results = JSON.parse(resp.body) 357 | assert_equal @doe_duplicated, results[0] 358 | end 359 | 360 | def test_collection_search 361 | resp = @client.get(@item_url, { "collection" => "zotxt test" }) 362 | assert_equal 200, resp.status 363 | end 364 | 365 | def test_collection_search_bad_name 366 | resp = @client.get(@item_url, { "collection" => "missing collection" }) 367 | assert_equal 400, resp.status 368 | assert_equal '"collection missing collection not found"', resp.body 369 | end 370 | 371 | def test_format_bibtex 372 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "bibtex" }) 373 | assert_equal 200, resp.status 374 | assert_match( 375 | Regexp.new( 376 | Regexp.quote("" " 377 | @article{doe:2006article, 378 | title = {Article}, 379 | volume = {6}, 380 | journal = {Journal of Generic Studies}, 381 | author = {Doe, John}, 382 | year = {2006}, 383 | pages = {33--34}" "") 384 | ), 385 | resp.body 386 | ) 387 | end 388 | 389 | def test_format_bibliography 390 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "bibliography" }) 391 | assert_equal 200, resp.status 392 | results = JSON.parse(resp.body) 393 | assert_equal({ "text" => "Doe, John. “Article.” Journal of Generic Studies 6 (2006): 33–34.", 394 | "html" => "" "
395 |
Doe, John. “Article.” Journal of Generic Studies 6 (2006): 33–34.
396 | 397 |
" "", 398 | "key" => @doe_article_key }, results[0]) 399 | end 400 | 401 | def test_format_bibliography_with_locale 402 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "bibliography", "locale" => "es-ES" }) 403 | assert_equal 200, resp.status 404 | results = JSON.parse(resp.body) 405 | assert_equal({ "text" => "Doe, John. «Article». Journal of Generic Studies 6 (2006): 33-34.", 406 | "html" => "" "
407 |
Doe, John. «Article». Journal of Generic Studies 6 (2006): 33-34.
408 | 409 |
" "", 410 | "key" => @doe_article_key }, results[0]) 411 | end 412 | 413 | def test_format_bibliography_bad_style 414 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "bibliography", "style": "bad-style" }) 415 | assert_equal 400, resp.status 416 | assert_equal('"Style http://www.zotero.org/styles/bad-style is not installed."', resp.body) 417 | end 418 | 419 | def test_format_quickbib 420 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "quickBib" }) 421 | assert_equal 200, resp.status 422 | results = JSON.parse(resp.body) 423 | assert_equal({ "key" => @doe_article_key, "quickBib" => "Doe, John - 2006 - Article" }, results[0]) 424 | end 425 | 426 | def test_format_easykey 427 | skip("easykey is deprecated") 428 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "easykey" }) 429 | assert_equal 200, resp.status 430 | results = JSON.parse(resp.body) 431 | assert_equal "doe:2006article", results[0] 432 | end 433 | 434 | def test_format_easykey_clean_html 435 | skip("easykey is deprecated") 436 | resp = @client.get(@item_url, { "easykey" => "doe:2007why", "format" => "easykey" }) 437 | assert_equal 200, resp.status 438 | results = JSON.parse(resp.body) 439 | assert_equal "doe:2007why", results[0] 440 | end 441 | 442 | def test_select 443 | resp = @client.get(@item_url, { "selected" => "t", "format" => "easykey" }) 444 | assert_equal 200, resp.status 445 | results = JSON.parse(resp.body) 446 | assert [[], ["doe:2006article"]].include?(results) 447 | end 448 | 449 | def test_format_betterbibtex 450 | %w[betterbibtexkey citekey].each do |name| 451 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => name }) 452 | assert_equal 200, resp.status 453 | results = JSON.parse(resp.body) 454 | assert_equal "doe:2006article", results[0] 455 | end 456 | end 457 | 458 | def test_format_export_uuid 459 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "bc03b4fe-436d-4a1f-ba59-de4d2d7a63f7" }) 460 | assert_equal 200, resp.status 461 | assert_equal "text/plain; charset=UTF-8", resp.content_type 462 | end 463 | 464 | def test_format_export_bad_uuid 465 | skip("Times out in Zotero 5") 466 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "248bebf1-46ab-dead-beef-ec3d2960d0cd" }) 467 | assert_equal 400, resp.status 468 | end 469 | 470 | def test_format_key 471 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "key" }) 472 | assert_equal 200, resp.status 473 | results = JSON.parse(resp.body) 474 | assert_equal @doe_article_key, results[0] 475 | end 476 | 477 | def test_format_json 478 | resp = @client.get(@item_url, { "key" => @doe_article_key, "format" => "json" }) 479 | assert_equal 200, resp.status 480 | results = JSON.parse(resp.body) 481 | results[0].delete("id") 482 | assert_equal({ "citation-key" => "doe:2006article", 483 | "type" => "article-journal", 484 | "title" => "Article", 485 | "container-title" => "Journal of Generic Studies", 486 | "page" => "33-34", 487 | "volume" => "6", 488 | "author" => [{ "family" => "Doe", "given" => "John" }], 489 | "issued" => { "date-parts" => [["2006"]] } }, results[0]) 490 | end 491 | 492 | def test_completion 493 | skip("easykey is deprecated") 494 | resp = @client.get(@complete_url, { "citekey" => "doe:" }) 495 | results = JSON.parse(resp.body) 496 | assert(results.size > 4) 497 | 498 | resp = @client.get(@complete_url, { "easykey" => "doe:20" }) 499 | results = JSON.parse(resp.body) 500 | assert(results.size > 4) 501 | 502 | resp = @client.get(@complete_url, { "easykey" => "doe:2006" }) 503 | results = JSON.parse(resp.body) 504 | assert_equal ["doe:2006article"], results 505 | 506 | resp = @client.get(@complete_url, { "easykey" => "doe:2006art" }) 507 | results = JSON.parse(resp.body) 508 | assert_equal ["doe:2006article"], results 509 | end 510 | 511 | def test_search 512 | resp = @client.get(@search_url, { "q" => "doe article", "format" => "key" }) 513 | assert_equal 200, resp.status 514 | results = JSON.parse(resp.body) 515 | assert_equal @doe_article_key, results[0] 516 | end 517 | 518 | def test_search_style_param 519 | resp = @client.get(@search_url, { "q" => "doe first book", "format" => "bibliography", "style" => "ieee" }) 520 | assert_equal 200, resp.status 521 | i = JSON.parse(resp.body) 522 | assert_equal "[1] J. Doe, First Book. Cambridge: Cambridge University Press, 2005.", i[0]["text"] 523 | end 524 | 525 | def test_search_everything_standalone_note 526 | resp = @client.get(@search_url, { "q" => "standalone", "method" => "everything", "format" => "key" }) 527 | assert_equal 200, resp.status 528 | results = JSON.parse(resp.body) 529 | assert results.size.positive? 530 | end 531 | 532 | def test_select 533 | resp = @client.get(@select_url, { "key" => @doe_article_key }) 534 | assert_equal 200, resp.status 535 | resp = @client.get(@select_url, { "easykey" => "doe:2006article" }) 536 | assert_equal 200, resp.status 537 | # bad key 538 | resp = @client.get(@select_url, { "key" => "1_4T8MCITQXXX" }) 539 | assert_equal 400, resp.status 540 | resp = @client.get(@select_url, { "easykey" => "XXX" }) 541 | assert_equal 400, resp.status 542 | end 543 | 544 | def test_version 545 | resp = @client.get(@version_url) 546 | assert_equal 200, resp.status 547 | assert_match(/^7.0/, JSON.parse(resp.body)["version"]) 548 | end 549 | 550 | def test_locales 551 | resp = @client.get(@locales_url) 552 | assert_equal 200, resp.status 553 | assert_equal "Español", JSON.parse(resp.body)["es-ES"] 554 | end 555 | 556 | def test_styles 557 | resp = @client.get(@styles_url) 558 | assert_equal 200, resp.status 559 | style = JSON.parse(resp.body).find do |value| 560 | value["styleID"] == "http://www.zotero.org/styles/chicago-fullnote-bibliography" 561 | end 562 | assert_equal "note", style["categories"] 563 | assert_equal "Chicago Manual of Style 17th edition (full note)", style["title"] 564 | end 565 | end 566 | -------------------------------------------------------------------------------- /update.json: -------------------------------------------------------------------------------- 1 | { 2 | "addons": { 3 | "zotxt@e6h.org": { 4 | "updates": [ 5 | { 6 | "version": "6.0.3", 7 | "update_link": "https://github.com/egh/zotxt/releases/download/v6.0.3/zotxt-6.0.3.xpi" 8 | "update_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 9 | "applications": { 10 | "gecko": { 11 | "strict_min_version": "60.9", 12 | "strict_max_version": "60.9" 13 | } 14 | } 15 | }, 16 | { 17 | "version": "7.0.1", 18 | "update_link": "https://github.com/egh/zotxt/releases/download/v7.0.1/zotxt-7.0.1.xpi" 19 | "update_hash": "sha256:4a6dd04c197629a02a9c6beaa9ebd52a69bb683f8400243bcdf95847f0ee254a", 20 | "applications": { 21 | "gecko": { 22 | "strict_min_version": "60.9", 23 | "strict_max_version": "60.9" 24 | }, 25 | "zotero": { 26 | "strict_min_version": "6.999", 27 | "strict_max_version": "7.0.*" 28 | } 29 | 30 | } 31 | } 32 | ] 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------