├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── i18n ├── de.json ├── el.json ├── en.json ├── fr.json ├── ko.json └── nl.json ├── index.html ├── lib ├── i18next-1.7.1.min.js ├── jquery.js ├── jquery.simplemodal.1.4.4.min.js └── xregexp-all-min.js ├── package.json ├── patterns ├── de.json └── en.json ├── res ├── close.png ├── loading.gif ├── search.png └── style.css ├── src ├── api.js ├── ask.js ├── index.js └── parser.js └── test ├── qunit-1.13.0.css ├── qunit-1.13.0.js ├── qunit.html ├── test_api.js ├── test_ask.js └── test_parser.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | before_script: 5 | - npm install -g grunt-cli 6 | notifications: 7 | irc: "irc.freenode.org##benestar" 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt ) { 2 | grunt.loadNpmTasks( 'grunt-contrib-qunit' ); 3 | grunt.loadNpmTasks( 'grunt-contrib-jshint' ); 4 | grunt.loadNpmTasks( 'grunt-jsonlint' ); 5 | 6 | grunt.initConfig( { 7 | qunit: { 8 | all: [ 'test/qunit.html' ] 9 | }, 10 | jshint: { 11 | all: [ 'Gruntfile.js', 'src/*.js', 'test/test_*.js' ] 12 | }, 13 | jsonlint: { 14 | all: [ 'package.json', 'i18n/*.json', 'patterns/*.json' ] 15 | } 16 | } ); 17 | 18 | grunt.registerTask( 'test', [ 'qunit', 'jshint', 'jsonlint' ] ); 19 | grunt.registerTask( 'default', [ 'test' ] ); 20 | }; 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Ask Wikidata! [![Build Status](https://api.travis-ci.org/Benestar/ask-wikidata.png?branch=master)](https://travis-ci.org/Benestar/ask-wikidata) 2 | 3 | Do you have a question? Ask Wikidata! This tool lets you enter a question and tries to parse it. If it understands what you want to know, it will search for this information on Wikidata. 4 | 5 | ### How to contribute 6 | 7 | If you want to translate this tool, you can take a look at the `i18n` folder to translate some system messages, but you may also modify the question and answer patterns which are listed in the `patterns` folder. Please check if the json is [valid](http://jsonlint.com) before sending a pull request. Also tests for the patterns are encouraged. 8 | 9 | Contributions to the code like bug fixes or new features are also very welcome. 10 | 11 | ### License 12 | 13 | The whole code is licensed under the GNU General Public License v2. You can find a copy of the license in the `LICENSE` file. 14 | -------------------------------------------------------------------------------- /i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Frag Wikidata!", 3 | "intro": "Tipp deine Frage in das Textfeld ein und drücke Enter.", 4 | "enterQuestion": "Gib deine Frage hier ein", 5 | "language": "Sprache: ", 6 | "chooseEntity": "Bitte wähle ein Objekt vom Type __type__", 7 | "cancel": "Abbrechen", 8 | "alsoKnown": "Auch bekannt als: ", 9 | "and": "und", 10 | "unparsable": "Die Frage wurde nicht verstanden.", 11 | "propertynotfound": "Die Eigenschaft \"__property__\" wurde nicht gefunden.", 12 | "nodata": "Es gibt keine Informationen für __subject__.", 13 | "notformatted": "Die Werte konnten nicht formatiert werden.", 14 | "itemnotfound": "Das Objekt \"__item__\" wurde nicht gefunden." 15 | } -------------------------------------------------------------------------------- /i18n/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ρώτησε το Wikidata!", 3 | "intro": "Γράψε την ερώτηση σου στο παραπάνω κουτί και πάτησε.", 4 | "enterQuestion": "Δώσε την ερώτησή σου εδώ", 5 | "language": "Γλώσσα: ", 6 | "chooseEntity": "Παρακαλώ επιλέξε μια οντότητα του τύπου __type__", 7 | "cancel": "ακύρωση", 8 | "alsoKnown": "Γνωστό και ως: ", 9 | "and": "και", 10 | "unparsable": "Η ερώτηση δεν μπορούσε να αναλυθεί.", 11 | "propertynotfound": "Η ιδιότητα \"__property__\" δεν βρέθηκε.", 12 | "nodata": "Δεν υπάρχουν δεδομένα για __subject__.", 13 | "notformatted": "Τα αποτελέσματα δεν μπόρεσαν να διαμορφωθούν.", 14 | "itemnotfound": "Το αντικείμενο \"__item__\" δεν βρέθηκε." 15 | } 16 | -------------------------------------------------------------------------------- /i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ask Wikidata!", 3 | "intro": "Type your question into the input box above and press enter.", 4 | "enterQuestion": "Enter your question here", 5 | "language": "Language: ", 6 | "chooseEntity": "Please choose an entity of the type __type__", 7 | "cancel": "cancel", 8 | "alsoKnown": "Also known as: ", 9 | "and": "and", 10 | "unparsable": "The question could not be parsed.", 11 | "propertynotfound": "The property \"__property__\" could not be found.", 12 | "nodata": "There is no data about __subject__.", 13 | "notformatted": "The values could not be formatted.", 14 | "itemnotfound": "The item \"__item__\" could not be found." 15 | } -------------------------------------------------------------------------------- /i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Demandez à Wikidata!", 3 | "intro": "Tapez votre question dans l'espace ci-dessus et pressez Entrer.", 4 | "enterQuestion": "Entrez votre question ici", 5 | "language": "Langue: ", 6 | "chooseEntity": "Choisissez une entité de type __type__", 7 | "cancel": "annuler", 8 | "alsoKnown": "Aussi appelé: ", 9 | "and": "et", 10 | "unparsable": "La question n'a pas pu être parsée.", 11 | "propertynotfound": "La propriété \"__property__\" n'a pas été trouvée.", 12 | "nodata": "Aucune donnée disponible sur __subject__.", 13 | "notformatted": "Les valeurs n'ont pas pu être formatées.", 14 | "itemnotfound": "L'article \"__item__\" n'a pas été trouvé." 15 | } 16 | -------------------------------------------------------------------------------- /i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "위키데이터에 물어보아요!", 3 | "intro": "입력창에 당신의 질문을 입력하고 엔터를 누르세요.", 4 | "enterQuestion": "여기에 질문을 입력하세요", 5 | "language": "언어: ", 6 | "chooseEntity": "종류에 대한 개체를 선택해 주세요. __type__", 7 | "cancel": "취소", 8 | "alsoKnown": "별명: ", 9 | "and": "그리고", 10 | "unparsable": "질문을 처리할 수 없었습니다.", 11 | "propertynotfound": "속성 \"__property__\" 을 찾지 못했습니다.", 12 | "nodata": "__subject__에 대한 데이터가 없습니다.", 13 | "notformatted": "값이 포맷될 수 없습니다.", 14 | "itemnotfound": "항목 \"__item__\" 을(를) 찾을 수 없었습니다." 15 | } 16 | 17 | -------------------------------------------------------------------------------- /i18n/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Vraag het Wikidata!", 3 | "intro": "Vul je vraag hierboven in en druk op enter.", 4 | "enterQuestion": "Vul je vraag hier in", 5 | "language": "Taal: ", 6 | "chooseEntity": "Please choose an entity of the type __type__", 7 | "cancel": "annuleren", 8 | "alsoKnown": "Ook bekend als: ", 9 | "and": "en", 10 | "unparsable": "De vraag kon niet worden geparset.", 11 | "propertynotfound": "De eigenschap \"__property__\" kon niet worden gevonden.", 12 | "nodata": "Er zijn geen gegevens over __subject__.", 13 | "notformatted": "The values could not be formatted.", 14 | "itemnotfound": "Het item \"__item__\" kon niet worden gevonden." 15 | } 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Fork me on GitHub 18 |
19 |

20 |
21 | 22 | 23 |
24 | 35 |
36 |
37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/i18next-1.7.1.min.js: -------------------------------------------------------------------------------- 1 | // i18next, v1.7.1 2 | // Copyright (c)2013 Jan Mühlemann (jamuhl). 3 | // Distributed under MIT license 4 | // http://i18next.com 5 | !function(){function a(a,b){if(!b||"function"==typeof b)return a;for(var c in b)a[c]=b[c];return a}function b(a,b,c){var d,e=0,f=a.length,g=void 0===f||"function"==typeof a;if(c)if(g){for(d in a)if(b.apply(a[d],c)===!1)break}else for(;f>e&&b.apply(a[e++],c)!==!1;);else if(g){for(d in a)if(b.call(a[d],d,a[d])===!1)break}else for(;f>e&&b.call(a[e],e,a[e++])!==!1;);return a}function c(a){return"string"==typeof a?a.replace(/[&<>"'\/]/g,function(a){return H[a]}):a}function d(a){var b=function(a){if(window.XMLHttpRequest)return a(null,new XMLHttpRequest);if(window.ActiveXObject)try{return a(null,new ActiveXObject("Msxml2.XMLHTTP"))}catch(b){return a(null,new ActiveXObject("Microsoft.XMLHTTP"))}return a(new Error)},c=function(a){if("string"==typeof a)return a;var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(encodeURIComponent(c)+"="+encodeURIComponent(a[c]));return b.join("&")},d=function(a){a=a.replace(/\r\n/g,"\n");for(var b="",c=0;cd?b+=String.fromCharCode(d):d>127&&2048>d?(b+=String.fromCharCode(192|d>>6),b+=String.fromCharCode(128|63&d)):(b+=String.fromCharCode(224|d>>12),b+=String.fromCharCode(128|63&d>>6),b+=String.fromCharCode(128|63&d))}return b},e=function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a=d(a);var c,e,f,g,h,i,j,k="",l=0;do c=a.charCodeAt(l++),e=a.charCodeAt(l++),f=a.charCodeAt(l++),g=c>>2,h=(3&c)<<4|e>>4,i=(15&e)<<2|f>>6,j=63&f,isNaN(e)?i=j=64:isNaN(f)&&(j=64),k+=b.charAt(g)+b.charAt(h)+b.charAt(i)+b.charAt(j),c=e=f="",g=h=i=j="";while(l1&&(d+=d.indexOf("?")>-1?"&"+k:"?"+k),e.jsonp){var l=document.getElementsByTagName("head")[0],m=document.createElement("script");return m.type="text/javascript",m.src=d,l.appendChild(m),void 0}}b(function(b,c){if(b)return h(b);c.open(a,d,e.async);for(var f in j)j.hasOwnProperty(f)&&c.setRequestHeader(f,j[f]);c.onreadystatechange=function(){if(4===c.readyState){var a=c.responseText||"";if(!h)return;h(c.status,{text:function(){return a},json:function(){return JSON.parse(a)}})}},c.send(i)})},h={authBasic:function(a,b){g.headers.Authorization="Basic "+e(a+":"+b)},connect:function(a,b,c){return g("CONNECT",a,b,c)},del:function(a,b,c){return g("DELETE",a,b,c)},get:function(a,b,c){return g("GET",a,b,c)},head:function(a,b,c){return g("HEAD",a,b,c)},headers:function(a){g.headers=a||{}},isAllowed:function(a,b,c){this.options(a,function(a,d){c(-1!==d.text().indexOf(b))})},options:function(a,b,c){return g("OPTIONS",a,b,c)},patch:function(a,b,c){return g("PATCH",a,b,c)},post:function(a,b,c){return g("POST",a,b,c)},put:function(a,b,c){return g("PUT",a,b,c)},trace:function(a,b,c){return g("TRACE",a,b,c)}},i=a.type?a.type.toLowerCase():"get";h[i](a.url,a,function(b,c){200===b?a.success(c.json(),b,null):a.error(c.text(),b,null)})}function e(a,b){"function"==typeof a&&(b=a,a={}),a=a||{},K.extend(G,a),delete G.fixLng,"string"==typeof G.ns&&(G.ns={namespaces:[G.ns],defaultNs:G.ns}),"string"==typeof G.fallbackNS&&(G.fallbackNS=[G.fallbackNS]),G.interpolationPrefixEscaped=K.regexEscape(G.interpolationPrefix),G.interpolationSuffixEscaped=K.regexEscape(G.interpolationSuffix),G.lng||(G.lng=K.detectLanguage()),G.lng?G.useCookie&&K.cookie.create(G.cookieName,G.lng,G.cookieExpirationTime,G.cookieDomain):(G.lng=G.fallbackLng,G.useCookie&&K.cookie.remove(G.cookieName)),E=K.toLanguages(G.lng),y=E[0],K.log("currentLng set to: "+y);var c=s;a.fixLng&&(c=function(a,b){return b=b||{},b.lng=b.lng||c.lng,s(a,b)},c.lng=y),M.setCurrentLng(y),A&&G.setJqueryExt&&m();var d;if(A&&A.Deferred&&(d=A.Deferred()),!G.resStore){var e=K.toLanguages(G.lng);"string"==typeof G.preload&&(G.preload=[G.preload]);for(var f=0,g=G.preload.length;g>f;f++)for(var h=K.toLanguages(G.preload[f]),i=0,j=h.length;j>i;i++)e.indexOf(h[i])<0&&e.push(h[i]);return B.sync.load(e,G,function(a,e){C=e,F=!0,b&&b(c),d&&d.resolve(c)}),d?d.promise():void 0}return C=G.resStore,F=!0,b&&b(c),d&&d.resolve(c),d?d.promise():void 0}function f(a,b){"string"==typeof a&&(a=[a]);for(var c=0,d=a.length;d>c;c++)G.preload.indexOf(a[c])<0&&G.preload.push(a[c]);return e(b)}function g(a,b,c){"string"!=typeof b?(c=b,b=G.ns.defaultNs):G.ns.namespaces.indexOf(b)<0&&G.ns.namespaces.push(b),C[a]=C[a]||{},C[a][b]=C[a][b]||{},K.extend(C[a][b],c)}function h(a){G.ns.defaultNs=a}function i(a,b){j([a],b)}function j(a,b){var c={dynamicLoad:G.dynamicLoad,resGetPath:G.resGetPath,getAsync:G.getAsync,customLoad:G.customLoad,ns:{namespaces:a,defaultNs:""}},d=K.toLanguages(G.lng);"string"==typeof G.preload&&(G.preload=[G.preload]);for(var e=0,f=G.preload.length;f>e;e++)for(var g=K.toLanguages(G.preload[e]),h=0,i=g.length;i>h;h++)d.indexOf(g[h])<0&&d.push(g[h]);for(var j=[],k=0,l=d.length;l>k;k++){var m=!1,n=C[d[k]];if(n)for(var o=0,p=a.length;p>o;o++)n[a[o]]||(m=!0);else m=!0;m&&j.push(d[k])}j.length?B.sync._fetch(j,c,function(c,d){var e=a.length*j.length;K.each(a,function(a,c){G.ns.namespaces.indexOf(c)<0&&G.ns.namespaces.push(c),K.each(j,function(a,f){C[f]=C[f]||{},C[f][c]=d[f][c],e--,0===e&&b&&(G.useLocalStorage&&B.sync._storeLocal(C),b())})})}):b&&b()}function k(a,b,c){return"function"==typeof b&&(c=b,b={}),b.lng=a,e(b,c)}function l(){return y}function m(){function a(a,b,c){if(0!==b.length){var d="text";if(0===b.indexOf("[")){var e=b.split("]");b=e[1],d=e[0].substr(1,e[0].length-1)}b.indexOf(";")===b.length-1&&(b=b.substr(0,b.length-2));var f;"html"===d?(f=G.defaultValueFromContent?A.extend({defaultValue:a.html()},c):c,a.html(A.t(b,f))):"text"===d?(f=G.defaultValueFromContent?A.extend({defaultValue:a.text()},c):c,a.text(A.t(b,f))):(f=G.defaultValueFromContent?A.extend({defaultValue:a.attr(d)},c):c,a.attr(d,A.t(b,f)))}}function b(b,c){var d=b.attr(G.selectorAttr);if(d||"undefined"==typeof d||d===!1||(d=b.text()||b.val()),d){var e=b,f=b.data("i18n-target");if(f&&(e=b.find(f)||b),c||G.useDataAttrOptions!==!0||(c=b.data("i18n-options")),c=c||{},d.indexOf(";")>=0){var g=d.split(";");A.each(g,function(b,d){""!==d&&a(e,d,c)})}else a(e,d,c);G.useDataAttrOptions===!0&&b.data("i18n-options",c)}}A.t=A.t||s,A.fn.i18n=function(a){return this.each(function(){b(A(this),a);var c=A(this).find("["+G.selectorAttr+"]");c.each(function(){b(A(this),a)})})}}function n(a,b,c,d){if(!a)return a;if(d=d||b,a.indexOf(d.interpolationPrefix||G.interpolationPrefix)<0)return a;var e=d.interpolationPrefix?K.regexEscape(d.interpolationPrefix):G.interpolationPrefixEscaped,f=d.interpolationSuffix?K.regexEscape(d.interpolationSuffix):G.interpolationSuffixEscaped,g="HTML"+f;return K.each(b,function(b,h){var i=c?c+G.keyseparator+b:b;"object"==typeof h&&null!==h?a=n(a,h,i,d):d.escapeInterpolation||G.escapeInterpolation?(a=a.replace(new RegExp([e,i,g].join(""),"g"),h),a=a.replace(new RegExp([e,i,f].join(""),"g"),K.escape(h))):a=a.replace(new RegExp([e,i,f].join(""),"g"),h)}),a}function o(a,b){var c=",",d="{",e="}",f=K.extend({},b);for(delete f.postProcess;-1!=a.indexOf(G.reusePrefix)&&(D++,!(D>G.maxRecursion));){var g=a.lastIndexOf(G.reusePrefix),h=a.indexOf(G.reuseSuffix,g)+G.reuseSuffix.length,i=a.substring(g,h),j=i.replace(G.reusePrefix,"").replace(G.reuseSuffix,"");if(-1!=j.indexOf(c)){var k=j.indexOf(c);if(-1!=j.indexOf(d,k)&&-1!=j.indexOf(e,k)){var l=j.indexOf(d,k),m=j.indexOf(e,l)+e.length;try{f=K.extend(f,JSON.parse(j.substring(l,m))),j=j.substring(0,k)}catch(n){}}}var o=v(j,f);a=a.replace(i,o)}return a}function p(a){return a.context&&"string"==typeof a.context}function q(a){return void 0!==a.count&&"string"!=typeof a.count&&1!==a.count}function r(a,b){b=b||{};var c=t(a,b),d=w(a,b);return void 0!==d||d===c}function s(a,b){return F?(D=0,v.apply(null,arguments)):(K.log("i18next not finished initialization. you might have called t function before loading resources finished."),b.defaultValue||"")}function t(a,b){return void 0!==b.defaultValue?b.defaultValue:a}function u(){for(var a=[],b=1;b-1&&(e=c.split(G.nsseparator),i=e[0],c=e[1]),void 0===g&&G.sendMissing&&(b.lng?L.postMissing(h[0],i,c,f,h):L.postMissing(G.lng,i,c,f,h));var j=b.postProcess||G.postProcess;void 0!==g&&j&&N[j]&&(g=N[j](g,c,b));var k=f;if(f.indexOf(G.nsseparator)>-1&&(e=f.split(G.nsseparator),k=e[1]),k===c&&G.parseMissingKey&&(f=G.parseMissingKey(f)),void 0===g&&(f=n(f,b),f=o(f,b),j&&N[j])){var l=t(c,b);g=N[j](l,c,b)}return void 0!==g?g:f}function w(a,b){b=b||{};var c,d,e=t(a,b),f=E;if(!C)return e;if(b.lng&&(f=K.toLanguages(b.lng),!C[f[0]])){var g=G.getAsync;G.getAsync=!1,B.sync.load(f,G,function(a,b){K.extend(C,b),G.getAsync=g})}var h=b.ns||G.ns.defaultNs;if(a.indexOf(G.nsseparator)>-1){var i=a.split(G.nsseparator);h=i[0],a=i[1]}if(p(b)){c=K.extend({},b),delete c.context,c.defaultValue=G.contextNotFound;var j=h+G.nsseparator+a+"_"+b.context;if(d=s(j,c),d!=G.contextNotFound)return n(d,{context:b.context})}if(q(b)){c=K.extend({},b),delete c.count,c.defaultValue=G.pluralNotFound;var k=h+G.nsseparator+a+G.pluralSuffix,l=M.get(f[0],b.count);if(l>=0?k=k+"_"+l:1===l&&(k=h+G.nsseparator+a),d=s(k,c),d!=G.pluralNotFound)return n(d,{count:b.count,interpolationPrefix:b.interpolationPrefix,interpolationSuffix:b.interpolationSuffix})}for(var m,r=a.split(G.keyseparator),u=0,x=f.length;x>u&&void 0===m;u++){for(var y=f[u],z=0,A=C[y]&&C[y][h];r[z];)A=A&&A[r[z]],z++;if(void 0!==A){if("string"==typeof A)A=n(A,b),A=o(A,b);else if("[object Array]"!==Object.prototype.toString.apply(A)||G.returnObjectTrees||b.returnObjectTrees){if(null===A&&G.fallbackOnNull===!0)A=void 0;else if(null!==A)if(G.returnObjectTrees||b.returnObjectTrees){if("number"!=typeof A){var D={};K.each(A,function(c){D[c]=v(h+G.nsseparator+a+G.keyseparator+c,b)}),A=D}}else A="key '"+h+":"+a+" ("+y+")' "+"returned a object instead of string.",K.log(A)}else A=A.join("\n"),A=n(A,b),A=o(A,b);m=A}}if(void 0===m&&!b.isFallbackLookup&&(G.fallbackToDefaultNS===!0||G.fallbackNS&&G.fallbackNS.length>0))if(b.isFallbackLookup=!0,G.fallbackNS.length){for(var F=0,H=G.fallbackNS.length;H>F;F++)if(m=w(G.fallbackNS[F]+G.nsseparator+a,b)){var I=m.indexOf(G.nsseparator)>-1?m.split(G.nsseparator)[1]:m,J=e.indexOf(G.nsseparator)>-1?e.split(G.nsseparator)[1]:e;if(I!==J)break}}else m=w(a,b);return m}function x(){var a,b=[];if("undefined"!=typeof window&&(!function(){for(var a=window.location.search.substring(1),c=a.split("&"),d=0;d0){var f=c[d].substring(0,e),g=c[d].substring(e+1);b[f]=g}}}(),b[G.detectLngQS]&&(a=b[G.detectLngQS])),!a&&"undefined"!=typeof document&&G.useCookie){var c=K.cookie.read(G.cookieName);c&&(a=c)}return a||"undefined"==typeof navigator||(a=navigator.language?navigator.language:navigator.userLanguage),a}Array.prototype.indexOf||(Array.prototype.indexOf=function(a){"use strict";if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),d!=d?d=0:0!=d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),Array.prototype.lastIndexOf||(Array.prototype.lastIndexOf=function(a){"use strict";if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=c;arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!=d&&d!=1/0&&d!=-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d))));for(var e=d>=0?Math.min(d,c-1):c-Math.abs(d);e>=0;e--)if(e in b&&b[e]===a)return e;return-1});var y,z=this,A=z.jQuery||z.Zepto,B={},C={},D=0,E=[],F=!1;"undefined"!=typeof module&&module.exports?module.exports=B:(A&&(A.i18n=A.i18n||B),z.i18n=z.i18n||B);var G={lng:void 0,load:"all",preload:[],lowerCaseLng:!1,returnObjectTrees:!1,fallbackLng:"dev",fallbackNS:[],detectLngQS:"setLng",ns:"translation",fallbackOnNull:!0,fallbackToDefaultNS:!1,nsseparator:":",keyseparator:".",selectorAttr:"data-i18n",debug:!1,resGetPath:"locales/__lng__/__ns__.json",resPostPath:"locales/add/__lng__/__ns__",getAsync:!0,postAsync:!0,resStore:void 0,useLocalStorage:!1,localStorageExpirationTime:6048e5,dynamicLoad:!1,sendMissing:!1,sendMissingTo:"fallback",sendType:"POST",interpolationPrefix:"__",interpolationSuffix:"__",reusePrefix:"$t(",reuseSuffix:")",pluralSuffix:"_plural",pluralNotFound:["plural_not_found",Math.random()].join(""),contextNotFound:["context_not_found",Math.random()].join(""),escapeInterpolation:!1,setJqueryExt:!0,defaultValueFromContent:!0,useDataAttrOptions:!1,cookieExpirationTime:void 0,useCookie:!0,cookieName:"i18next",cookieDomain:void 0,postProcess:void 0,parseMissingKey:void 0,shortcutFunction:"sprintf"},H={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},I={create:function(a,b,c,d){var e;if(c){var f=new Date;f.setTime(f.getTime()+1e3*60*c),e="; expires="+f.toGMTString()}else e="";d=d?"domain="+d+";":"",document.cookie=a+"="+b+e+";"+d+"path=/"},read:function(a){for(var b=a+"=",c=document.cookie.split(";"),d=0;d-1){var c=a.split("-");a=G.lowerCaseLng?c[0].toLowerCase()+"-"+c[1].toLowerCase():c[0].toLowerCase()+"-"+c[1].toUpperCase(),"unspecific"!==G.load&&b.push(a),"current"!==G.load&&b.push(c[0])}else b.push(a);return-1===b.indexOf(G.fallbackLng)&&G.fallbackLng&&b.push(G.fallbackLng),b},regexEscape:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}};K.applyReplacement=n;var L={load:function(a,b,c){b.useLocalStorage?L._loadLocal(a,b,function(d,e){for(var f=[],g=0,h=a.length;h>g;g++)e[a[g]]||f.push(a[g]);f.length>0?L._fetch(f,b,function(a,b){K.extend(e,b),L._storeLocal(b),c(null,e)}):c(null,e)}):L._fetch(a,b,function(a,b){c(null,b)})},_loadLocal:function(a,b,c){var d={},e=(new Date).getTime();if(window.localStorage){var f=a.length;K.each(a,function(a,g){var h=window.localStorage.getItem("res_"+g);h&&(h=JSON.parse(h),h.i18nStamp&&h.i18nStamp+b.localStorageExpirationTime>e&&(d[g]=h)),f--,0===f&&c(null,d)})}},_storeLocal:function(a){if(window.localStorage)for(var b in a)a[b].i18nStamp=(new Date).getTime(),window.localStorage.setItem("res_"+b,JSON.stringify(a[b]))},_fetch:function(a,b,c){var d=b.ns,e={};if(b.dynamicLoad){var f=function(a,b){c(null,b)};if("function"==typeof b.customLoad)b.customLoad(a,d.namespaces,b,f);else{var g=n(b.resGetPath,{lng:a.join("+"),ns:d.namespaces.join("+")});K.ajax({url:g,success:function(a){K.log("loaded: "+g),f(null,a)},error:function(a,b,c){K.log("failed loading: "+g),f("failed loading resource.json error: "+c)},dataType:"json",async:b.getAsync})}}else{var h,i=d.namespaces.length*a.length;K.each(d.namespaces,function(d,f){K.each(a,function(a,d){var g=function(a,b){a&&(h=h||[],h.push(a)),e[d]=e[d]||{},e[d][f]=b,i--,0===i&&c(h,e)};"function"==typeof b.customLoad?b.customLoad(d,f,b,g):L._fetchOne(d,f,b,g)})})}},_fetchOne:function(a,b,c,d){var e=n(c.resGetPath,{lng:a,ns:b});K.ajax({url:e,success:function(a){K.log("loaded: "+e),d(null,a)},error:function(a,b,c){K.log("failed loading: "+e),d(c,{})},dataType:"json",async:c.getAsync})},postMissing:function(a,b,c,d,e){var f={};f[c]=d;var g=[];if("fallback"===G.sendMissingTo&&G.fallbackLng!==!1)g.push({lng:G.fallbackLng,url:n(G.resPostPath,{lng:G.fallbackLng,ns:b})});else if("current"===G.sendMissingTo||"fallback"===G.sendMissingTo&&G.fallbackLng===!1)g.push({lng:a,url:n(G.resPostPath,{lng:a,ns:b})});else if("all"===G.sendMissingTo)for(var h=0,i=e.length;i>h;h++)g.push({lng:e[h],url:n(G.resPostPath,{lng:e[h],ns:b})});for(var j=0,k=g.length;k>j;j++){var l=g[j];K.ajax({url:l.url,type:G.sendType,data:f,success:function(){K.log("posted missing key '"+c+"' to: "+l.url);for(var a=c.split("."),e=0,f=C[l.lng][b];a[e];)f=f[a[e]]=e===a.length-1?d:f[a[e]]||{},e++},error:function(){K.log("failed posting missing key '"+c+"' to: "+l.url)},dataType:"json",async:G.postAsync})}}},M={rules:{ach:{name:"Acholi",numbers:[1,2],plurals:function(a){return Number(a>1)}},af:{name:"Afrikaans",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ak:{name:"Akan",numbers:[1,2],plurals:function(a){return Number(a>1)}},am:{name:"Amharic",numbers:[1,2],plurals:function(a){return Number(a>1)}},an:{name:"Aragonese",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ar:{name:"Arabic",numbers:[0,1,2,3,11,100],plurals:function(a){return Number(0===a?0:1==a?1:2==a?2:a%100>=3&&10>=a%100?3:a%100>=11?4:5)}},arn:{name:"Mapudungun",numbers:[1,2],plurals:function(a){return Number(a>1)}},ast:{name:"Asturian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ay:{name:"Aymará",numbers:[1],plurals:function(){return 0}},az:{name:"Azerbaijani",numbers:[1,2],plurals:function(a){return Number(1!=a)}},be:{name:"Belarusian",numbers:[1,2,5],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},bg:{name:"Bulgarian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},bn:{name:"Bengali",numbers:[1,2],plurals:function(a){return Number(1!=a)}},bo:{name:"Tibetan",numbers:[1],plurals:function(){return 0}},br:{name:"Breton",numbers:[1,2],plurals:function(a){return Number(a>1)}},bs:{name:"Bosnian",numbers:[1,2,5],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},ca:{name:"Catalan",numbers:[1,2],plurals:function(a){return Number(1!=a)}},cgg:{name:"Chiga",numbers:[1],plurals:function(){return 0}},cs:{name:"Czech",numbers:[1,2,5],plurals:function(a){return Number(1==a?0:a>=2&&4>=a?1:2)}},csb:{name:"Kashubian",numbers:[1,2,5],plurals:function(a){return Number(1==a?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},cy:{name:"Welsh",numbers:[1,2,3,8],plurals:function(a){return Number(1==a?0:2==a?1:8!=a&&11!=a?2:3)}},da:{name:"Danish",numbers:[1,2],plurals:function(a){return Number(1!=a)}},de:{name:"German",numbers:[1,2],plurals:function(a){return Number(1!=a)}},dz:{name:"Dzongkha",numbers:[1],plurals:function(){return 0}},el:{name:"Greek",numbers:[1,2],plurals:function(a){return Number(1!=a)}},en:{name:"English",numbers:[1,2],plurals:function(a){return Number(1!=a)}},eo:{name:"Esperanto",numbers:[1,2],plurals:function(a){return Number(1!=a)}},es:{name:"Spanish",numbers:[1,2],plurals:function(a){return Number(1!=a)}},es_ar:{name:"Argentinean Spanish",numbers:[1,2],plurals:function(a){return Number(1!=a)}},et:{name:"Estonian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},eu:{name:"Basque",numbers:[1,2],plurals:function(a){return Number(1!=a)}},fa:{name:"Persian",numbers:[1],plurals:function(){return 0}},fi:{name:"Finnish",numbers:[1,2],plurals:function(a){return Number(1!=a)}},fil:{name:"Filipino",numbers:[1,2],plurals:function(a){return Number(a>1)}},fo:{name:"Faroese",numbers:[1,2],plurals:function(a){return Number(1!=a)}},fr:{name:"French",numbers:[1,2],plurals:function(a){return Number(a>1)}},fur:{name:"Friulian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},fy:{name:"Frisian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ga:{name:"Irish",numbers:[1,2,3,7,11],plurals:function(a){return Number(1==a?0:2==a?1:7>a?2:11>a?3:4)}},gd:{name:"Scottish Gaelic",numbers:[1,2,3,20],plurals:function(a){return Number(1==a||11==a?0:2==a||12==a?1:a>2&&20>a?2:3)}},gl:{name:"Galician",numbers:[1,2],plurals:function(a){return Number(1!=a)}},gu:{name:"Gujarati",numbers:[1,2],plurals:function(a){return Number(1!=a)}},gun:{name:"Gun",numbers:[1,2],plurals:function(a){return Number(a>1)}},ha:{name:"Hausa",numbers:[1,2],plurals:function(a){return Number(1!=a)}},he:{name:"Hebrew",numbers:[1,2],plurals:function(a){return Number(1!=a)}},hi:{name:"Hindi",numbers:[1,2],plurals:function(a){return Number(1!=a)}},hr:{name:"Croatian",numbers:[1,2,5],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},hu:{name:"Hungarian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},hy:{name:"Armenian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ia:{name:"Interlingua",numbers:[1,2],plurals:function(a){return Number(1!=a)}},id:{name:"Indonesian",numbers:[1],plurals:function(){return 0}},is:{name:"Icelandic",numbers:[1,2],plurals:function(a){return Number(1!=a%10||11==a%100)}},it:{name:"Italian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ja:{name:"Japanese",numbers:[1],plurals:function(){return 0}},jbo:{name:"Lojban",numbers:[1],plurals:function(){return 0}},jv:{name:"Javanese",numbers:[0,1],plurals:function(a){return Number(0!==a)}},ka:{name:"Georgian",numbers:[1],plurals:function(){return 0}},kk:{name:"Kazakh",numbers:[1],plurals:function(){return 0}},km:{name:"Khmer",numbers:[1],plurals:function(){return 0}},kn:{name:"Kannada",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ko:{name:"Korean",numbers:[1],plurals:function(){return 0}},ku:{name:"Kurdish",numbers:[1,2],plurals:function(a){return Number(1!=a)}},kw:{name:"Cornish",numbers:[1,2,3,4],plurals:function(a){return Number(1==a?0:2==a?1:3==a?2:3)}},ky:{name:"Kyrgyz",numbers:[1],plurals:function(){return 0}},lb:{name:"Letzeburgesch",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ln:{name:"Lingala",numbers:[1,2],plurals:function(a){return Number(a>1)}},lo:{name:"Lao",numbers:[1],plurals:function(){return 0}},lt:{name:"Lithuanian",numbers:[1,2,10],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&(10>a%100||a%100>=20)?1:2)}},lv:{name:"Latvian",numbers:[0,1,2],plurals:function(a){return Number(1==a%10&&11!=a%100?0:0!==a?1:2)}},mai:{name:"Maithili",numbers:[1,2],plurals:function(a){return Number(1!=a)}},mfe:{name:"Mauritian Creole",numbers:[1,2],plurals:function(a){return Number(a>1)}},mg:{name:"Malagasy",numbers:[1,2],plurals:function(a){return Number(a>1)}},mi:{name:"Maori",numbers:[1,2],plurals:function(a){return Number(a>1)}},mk:{name:"Macedonian",numbers:[1,2],plurals:function(a){return Number(1==a||1==a%10?0:1)}},ml:{name:"Malayalam",numbers:[1,2],plurals:function(a){return Number(1!=a)}},mn:{name:"Mongolian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},mnk:{name:"Mandinka",numbers:[0,1,2],plurals:function(a){return Number(1==a?1:2)}},mr:{name:"Marathi",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ms:{name:"Malay",numbers:[1],plurals:function(){return 0}},mt:{name:"Maltese",numbers:[1,2,11,20],plurals:function(a){return Number(1==a?0:0===a||a%100>1&&11>a%100?1:a%100>10&&20>a%100?2:3)}},nah:{name:"Nahuatl",numbers:[1,2],plurals:function(a){return Number(1!=a)}},nap:{name:"Neapolitan",numbers:[1,2],plurals:function(a){return Number(1!=a)}},nb:{name:"Norwegian Bokmal",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ne:{name:"Nepali",numbers:[1,2],plurals:function(a){return Number(1!=a)}},nl:{name:"Dutch",numbers:[1,2],plurals:function(a){return Number(1!=a)}},nn:{name:"Norwegian Nynorsk",numbers:[1,2],plurals:function(a){return Number(1!=a)}},no:{name:"Norwegian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},nso:{name:"Northern Sotho",numbers:[1,2],plurals:function(a){return Number(1!=a)}},oc:{name:"Occitan",numbers:[1,2],plurals:function(a){return Number(a>1)}},or:{name:"Oriya",numbers:[2,1],plurals:function(a){return Number(1!=a)}},pa:{name:"Punjabi",numbers:[1,2],plurals:function(a){return Number(1!=a)}},pap:{name:"Papiamento",numbers:[1,2],plurals:function(a){return Number(1!=a)}},pl:{name:"Polish",numbers:[1,2,5],plurals:function(a){return Number(1==a?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},pms:{name:"Piemontese",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ps:{name:"Pashto",numbers:[1,2],plurals:function(a){return Number(1!=a)}},pt:{name:"Portuguese",numbers:[1,2],plurals:function(a){return Number(1!=a)}},pt_br:{name:"Brazilian Portuguese",numbers:[1,2],plurals:function(a){return Number(1!=a)}},rm:{name:"Romansh",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ro:{name:"Romanian",numbers:[1,2,20],plurals:function(a){return Number(1==a?0:0===a||a%100>0&&20>a%100?1:2)}},ru:{name:"Russian",numbers:[1,2,5],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},sah:{name:"Yakut",numbers:[1],plurals:function(){return 0}},sco:{name:"Scots",numbers:[1,2],plurals:function(a){return Number(1!=a)}},se:{name:"Northern Sami",numbers:[1,2],plurals:function(a){return Number(1!=a)}},si:{name:"Sinhala",numbers:[1,2],plurals:function(a){return Number(1!=a)}},sk:{name:"Slovak",numbers:[1,2,5],plurals:function(a){return Number(1==a?0:a>=2&&4>=a?1:2)}},sl:{name:"Slovenian",numbers:[5,1,2,3],plurals:function(a){return Number(1==a%100?1:2==a%100?2:3==a%100||4==a%100?3:0)}},so:{name:"Somali",numbers:[1,2],plurals:function(a){return Number(1!=a)}},son:{name:"Songhay",numbers:[1,2],plurals:function(a){return Number(1!=a)}},sq:{name:"Albanian",numbers:[1,2],plurals:function(a){return Number(1!=a)}},sr:{name:"Serbian",numbers:[1,2,5],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},su:{name:"Sundanese",numbers:[1],plurals:function(){return 0}},sv:{name:"Swedish",numbers:[1,2],plurals:function(a){return Number(1!=a)}},sw:{name:"Swahili",numbers:[1,2],plurals:function(a){return Number(1!=a)}},ta:{name:"Tamil",numbers:[1,2],plurals:function(a){return Number(1!=a)}},te:{name:"Telugu",numbers:[1,2],plurals:function(a){return Number(1!=a)}},tg:{name:"Tajik",numbers:[1,2],plurals:function(a){return Number(a>1)}},th:{name:"Thai",numbers:[1],plurals:function(){return 0}},ti:{name:"Tigrinya",numbers:[1,2],plurals:function(a){return Number(a>1)}},tk:{name:"Turkmen",numbers:[1,2],plurals:function(a){return Number(1!=a)}},tr:{name:"Turkish",numbers:[1,2],plurals:function(a){return Number(a>1)}},tt:{name:"Tatar",numbers:[1],plurals:function(){return 0}},ug:{name:"Uyghur",numbers:[1],plurals:function(){return 0}},uk:{name:"Ukrainian",numbers:[1,2,5],plurals:function(a){return Number(1==a%10&&11!=a%100?0:a%10>=2&&4>=a%10&&(10>a%100||a%100>=20)?1:2)}},ur:{name:"Urdu",numbers:[1,2],plurals:function(a){return Number(1!=a)}},uz:{name:"Uzbek",numbers:[1,2],plurals:function(a){return Number(a>1)}},vi:{name:"Vietnamese",numbers:[1],plurals:function(){return 0}},wa:{name:"Walloon",numbers:[1,2],plurals:function(a){return Number(a>1)}},wo:{name:"Wolof",numbers:[1],plurals:function(){return 0}},yo:{name:"Yoruba",numbers:[1,2],plurals:function(a){return Number(1!=a)}},zh:{name:"Chinese",numbers:[1],plurals:function(){return 0}}},addRule:function(a,b){M.rules[a]=b},setCurrentLng:function(a){if(!M.currentRule||M.currentRule.lng!==a){var b=a.split("-");M.currentRule={lng:a,rule:M.rules[b[0]]}}},get:function(a,b){function c(b,c){var d;if(d=M.currentRule&&M.currentRule.lng===a?M.currentRule.rule:M.rules[b]){var e=d.plurals(c),f=d.numbers[e];return 2===d.numbers.length&&1===d.numbers[0]&&(2===f?f=-1:1===f&&(f=1)),f}return 1===c?"1":"-1"}var d=a.split("-");return c(d[0],b)}},N={},O=function(a,b){N[a]=b},P=function(){function a(a){return Object.prototype.toString.call(a).slice(8,-1).toLowerCase()}function b(a,b){for(var c=[];b>0;c[--b]=a);return c.join("")}var c=function(){return c.cache.hasOwnProperty(arguments[0])||(c.cache[arguments[0]]=c.parse(arguments[0])),c.format.call(null,c.cache[arguments[0]],arguments)};return c.format=function(c,d){var e,f,g,h,i,j,k,l=1,m=c.length,n="",o=[];for(f=0;m>f;f++)if(n=a(c[f]),"string"===n)o.push(c[f]);else if("array"===n){if(h=c[f],h[2])for(e=d[l],g=0;g=0?"+"+e:e,j=h[4]?"0"==h[4]?"0":h[4].charAt(1):" ",k=h[6]-String(e).length,i=h[6]?b(j,k):"",o.push(h[5]?e+i:i+e)}return o.join("")},c.cache={},c.parse=function(a){for(var b=a,c=[],d=[],e=0;b;){if(null!==(c=/^[^\x25]+/.exec(b)))d.push(c[0]);else if(null!==(c=/^\x25{2}/.exec(b)))d.push("%");else{if(null===(c=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(b)))throw"[sprintf] huh?";if(c[2]){e|=1;var f=[],g=c[2],h=[];if(null===(h=/^([a-z_][a-z_\d]*)/i.exec(g)))throw"[sprintf] huh?";for(f.push(h[1]);""!==(g=g.substring(h[0].length));)if(null!==(h=/^\.([a-z_][a-z_\d]*)/i.exec(g)))f.push(h[1]);else{if(null===(h=/^\[(\d+)\]/.exec(g)))throw"[sprintf] huh?";f.push(h[1])}c[2]=f}else e|=2;if(3===e)throw"[sprintf] mixing positional and named placeholders is not (yet) supported";d.push(c)}b=b.substring(c[0].length)}return d},c}(),Q=function(a,b){return b.unshift(a),P.apply(null,b)};O("sprintf",function(a,b,c){return c.sprintf?"[object Array]"===Object.prototype.toString.apply(c.sprintf)?Q(a,c.sprintf):"object"==typeof c.sprintf?P(a,c.sprintf):a:a}),B.init=e,B.setLng=k,B.preload=f,B.addResourceBundle=g,B.loadNamespace=i,B.loadNamespaces=j,B.setDefaultNamespace=h,B.t=s,B.translate=s,B.exists=r,B.detectLanguage=K.detectLanguage,B.pluralExtensions=M,B.sync=L,B.functions=K,B.lng=l,B.addPostProcessor=O,B.options=G}(); -------------------------------------------------------------------------------- /lib/jquery.simplemodal.1.4.4.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SimpleModal 1.4.4 - jQuery Plugin 3 | * http://simplemodal.com/ 4 | * Copyright (c) 2013 Eric Martin 5 | * Licensed under MIT and GPL 6 | * Date: Sun, Jan 20 2013 15:58:56 -0800 7 | */ 8 | (function(b){"function"===typeof define&&define.amd?define(["jquery"],b):b(jQuery)})(function(b){var j=[],n=b(document),k=navigator.userAgent.toLowerCase(),l=b(window),g=[],o=null,p=/msie/.test(k)&&!/opera/.test(k),q=/opera/.test(k),m,r;m=p&&/msie 6./.test(k)&&"object"!==typeof window.XMLHttpRequest;r=p&&/msie 7.0/.test(k);b.modal=function(a,h){return b.modal.impl.init(a,h)};b.modal.close=function(){b.modal.impl.close()};b.modal.focus=function(a){b.modal.impl.focus(a)};b.modal.setContainerDimensions= 9 | function(){b.modal.impl.setContainerDimensions()};b.modal.setPosition=function(){b.modal.impl.setPosition()};b.modal.update=function(a,h){b.modal.impl.update(a,h)};b.fn.modal=function(a){return b.modal.impl.init(this,a)};b.modal.defaults={appendTo:"body",focus:!0,opacity:50,overlayId:"simplemodal-overlay",overlayCss:{},containerId:"simplemodal-container",containerCss:{},dataId:"simplemodal-data",dataCss:{},minHeight:null,minWidth:null,maxHeight:null,maxWidth:null,autoResize:!1,autoPosition:!0,zIndex:1E3, 10 | close:!0,closeHTML:'',closeClass:"simplemodal-close",escClose:!0,overlayClose:!1,fixed:!0,position:null,persist:!1,modal:!0,onOpen:null,onShow:null,onClose:null};b.modal.impl={d:{},init:function(a,h){if(this.d.data)return!1;o=p&&!b.support.boxModel;this.o=b.extend({},b.modal.defaults,h);this.zIndex=this.o.zIndex;this.occb=!1;if("object"===typeof a){if(a=a instanceof b?a:b(a),this.d.placeholder=!1,0").attr("id", 11 | "simplemodal-placeholder").css({display:"none"})),this.d.placeholder=!0,this.display=a.css("display"),!this.o.persist))this.d.orig=a.clone(!0)}else if("string"===typeof a||"number"===typeof a)a=b("
").html(a);else return alert("SimpleModal Error: Unsupported data type: "+typeof a),this;this.create(a);this.open();b.isFunction(this.o.onShow)&&this.o.onShow.apply(this,[this.d]);return this},create:function(a){this.getDimensions();if(this.o.modal&&m)this.d.iframe=b('').css(b.extend(this.o.iframeCss, 12 | {display:"none",opacity:0,position:"fixed",height:g[0],width:g[1],zIndex:this.o.zIndex,top:0,left:0})).appendTo(this.o.appendTo);this.d.overlay=b("
").attr("id",this.o.overlayId).addClass("simplemodal-overlay").css(b.extend(this.o.overlayCss,{display:"none",opacity:this.o.opacity/100,height:this.o.modal?j[0]:0,width:this.o.modal?j[1]:0,position:"fixed",left:0,top:0,zIndex:this.o.zIndex+1})).appendTo(this.o.appendTo);this.d.container=b("
").attr("id",this.o.containerId).addClass("simplemodal-container").css(b.extend({position:this.o.fixed? 13 | "fixed":"absolute"},this.o.containerCss,{display:"none",zIndex:this.o.zIndex+2})).append(this.o.close&&this.o.closeHTML?b(this.o.closeHTML).addClass(this.o.closeClass):"").appendTo(this.o.appendTo);this.d.wrap=b("
").attr("tabIndex",-1).addClass("simplemodal-wrap").css({height:"100%",outline:0,width:"100%"}).appendTo(this.d.container);this.d.data=a.attr("id",a.attr("id")||this.o.dataId).addClass("simplemodal-data").css(b.extend(this.o.dataCss,{display:"none"})).appendTo("body");this.setContainerDimensions(); 14 | this.d.data.appendTo(this.d.wrap);(m||o)&&this.fixIE()},bindEvents:function(){var a=this;b("."+a.o.closeClass).bind("click.simplemodal",function(b){b.preventDefault();a.close()});a.o.modal&&a.o.close&&a.o.overlayClose&&a.d.overlay.bind("click.simplemodal",function(b){b.preventDefault();a.close()});n.bind("keydown.simplemodal",function(b){a.o.modal&&9===b.keyCode?a.watchTab(b):a.o.close&&a.o.escClose&&27===b.keyCode&&(b.preventDefault(),a.close())});l.bind("resize.simplemodal orientationchange.simplemodal", 15 | function(){a.getDimensions();a.o.autoResize?a.setContainerDimensions():a.o.autoPosition&&a.setPosition();m||o?a.fixIE():a.o.modal&&(a.d.iframe&&a.d.iframe.css({height:g[0],width:g[1]}),a.d.overlay.css({height:j[0],width:j[1]}))})},unbindEvents:function(){b("."+this.o.closeClass).unbind("click.simplemodal");n.unbind("keydown.simplemodal");l.unbind(".simplemodal");this.d.overlay.unbind("click.simplemodal")},fixIE:function(){var a=this.o.position;b.each([this.d.iframe||null,!this.o.modal?null:this.d.overlay, 16 | "fixed"===this.d.container.css("position")?this.d.container:null],function(b,e){if(e){var f=e[0].style;f.position="absolute";if(2>b)f.removeExpression("height"),f.removeExpression("width"),f.setExpression("height",'document.body.scrollHeight > document.body.clientHeight ? document.body.scrollHeight : document.body.clientHeight + "px"'),f.setExpression("width",'document.body.scrollWidth > document.body.clientWidth ? document.body.scrollWidth : document.body.clientWidth + "px"');else{var c,d;a&&a.constructor=== 17 | Array?(c=a[0]?"number"===typeof a[0]?a[0].toString():a[0].replace(/px/,""):e.css("top").replace(/px/,""),c=-1===c.indexOf("%")?c+' + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"':parseInt(c.replace(/%/,""))+' * ((document.documentElement.clientHeight || document.body.clientHeight) / 100) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"',a[1]&&(d="number"===typeof a[1]? 18 | a[1].toString():a[1].replace(/px/,""),d=-1===d.indexOf("%")?d+' + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"':parseInt(d.replace(/%/,""))+' * ((document.documentElement.clientWidth || document.body.clientWidth) / 100) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"')):(c='(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"', 19 | d='(document.documentElement.clientWidth || document.body.clientWidth) / 2 - (this.offsetWidth / 2) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"');f.removeExpression("top");f.removeExpression("left");f.setExpression("top",c);f.setExpression("left",d)}}})},focus:function(a){var h=this,a=a&&-1!==b.inArray(a,["first","last"])?a:"first",e=b(":input:enabled:visible:"+a,h.d.wrap);setTimeout(function(){0c?c:bc?c:this.o.minHeight&&"auto"!==i&&ed?d:ad?d:this.o.minWidth&&"auto"!==c&&fb||f>a?"auto":"visible"});this.o.autoPosition&&this.setPosition()},setPosition:function(){var a,b;a=g[0]/2-this.d.container.outerHeight(!0)/2;b=g[1]/2-this.d.container.outerWidth(!0)/2;var e="fixed"!==this.d.container.css("position")?l.scrollTop():0;this.o.position&&"[object Array]"===Object.prototype.toString.call(this.o.position)?(a=e+(this.o.position[0]||a),b=this.o.position[1]||b): 24 | a=e+a;this.d.container.css({left:b,top:a})},watchTab:function(a){if(0geboren( worden)?)", 5 | "property": "Geburtsdatum", 6 | "callback": "$article $item $verb am $value $2verb." 7 | }, 8 | { 9 | "regex": "wo $verb_art $item (?<2verb>geboren( worden)?)", 10 | "property": "Geburtsort", 11 | "callback": "$article $item $verb in $value $2verb." 12 | }, 13 | { 14 | "regex": "wann starb $item", 15 | "property": "Sterbedatum", 16 | "callback": "$article $item starb am $value." 17 | }, 18 | { 19 | "regex": "wo starb $item", 20 | "property": "Sterbeort", 21 | "callback": "$article $item starb in $value." 22 | }, 23 | { 24 | "regex": "mit wem $verb_art $item verheiratet", 25 | "property": "Ehepartner", 26 | "callback": "$article $item $verb mit $value verheiratet." 27 | }, 28 | { 29 | "regex": "wo $verb_art $item", 30 | "article": "die", 31 | "property": "Position", 32 | "possesive": "von" 33 | }, 34 | { 35 | "regex": "(was|wann|wo|wieso|wer|wessen|welches|wie|wie viel|wie viele) $verb_art (?.+?) $possesive $item" 36 | }, 37 | { 38 | "regex": "(was|wann|wo|wieso|wer|wessen|welches|wie|wie viel|wie viele) $verb_art $item" 39 | } 40 | ], 41 | "attributes": { 42 | "verb_art": "(?ist|war|sind|waren|wurde|wurden)( (?
ein|eine|eines|der|die|das))?", 43 | "item": "(?.+?)", 44 | "possesive": "(?(von|an|bei|der|eines|einer)( (ein|eine|eines|einem|einem|dem|den))?)" 45 | } 46 | } -------------------------------------------------------------------------------- /patterns/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "regexes": [ 3 | { 4 | "regex": "when $verb_art $item born", 5 | "property": "date of birth", 6 | "callback": "$article $item $verb born on $value." 7 | }, 8 | { 9 | "regex": "where $verb_art $item born", 10 | "property": "place of birth", 11 | "callback": "$article $item $verb born in $value." 12 | }, 13 | { 14 | "regex": "when did $item die", 15 | "property": "date of death", 16 | "callback": "$article $item died on $value." 17 | }, 18 | { 19 | "regex": "where did $item die", 20 | "property": "place of death", 21 | "callback": "$article $item died in $value." 22 | }, 23 | { 24 | "regex": "who $verb_art $item married to", 25 | "property": "spouse", 26 | "callback": "$article $item $verb married to $value." 27 | }, 28 | { 29 | "regex": "where $verb_art $item", 30 | "property": "is located in", 31 | "callback": "$article $item $property $value." 32 | }, 33 | { 34 | "regex": "(what|when|where|why|who|whose|which|how|how much|how many) $verb_art (?.+?) $possesive $item" 35 | }, 36 | { 37 | "regex": "(what|when|where|why|who|whose|which|how|how much|how many) $verb_art $item" 38 | } 39 | ], 40 | "attributes": { 41 | "verb_art": "(?is|was|are|were)( (?
a|an|the))?", 42 | "item": "(?.+?)", 43 | "possesive": "(?(of|from|by)( (a|an|the))?)" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /res/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benestar/ask-wikidata/3d78cafc3727838b6f01766010006c43c2b8aff9/res/close.png -------------------------------------------------------------------------------- /res/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benestar/ask-wikidata/3d78cafc3727838b6f01766010006c43c2b8aff9/res/loading.gif -------------------------------------------------------------------------------- /res/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benestar/ask-wikidata/3d78cafc3727838b6f01766010006c43c2b8aff9/res/search.png -------------------------------------------------------------------------------- /res/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #666; 3 | font-family: Helvetica,Arial,sans-serif; 4 | } 5 | h1 { 6 | text-align: center; 7 | } 8 | #main { 9 | margin: 50px auto 0px; 10 | padding: 20px; 11 | width:800px; 12 | min-height:500px; 13 | background:#eee; 14 | border:1px solid; 15 | border-color:#e5e5e5 #dbdbdb #d2d2d2; 16 | -webkit-box-shadow:rgba(0,0,0,0.3) 0 1px 3px; 17 | -moz-box-shadow:rgba(0,0,0,0.3) 0 1px 3px; 18 | box-shadow:rgba(0,0,0,0.3) 0 1px 3px; 19 | } 20 | #question-form { 21 | width: 700px; 22 | margin: 0px auto; 23 | position: relative; 24 | } 25 | #question { 26 | display: block; 27 | width: 680px; 28 | margin: 0px auto; 29 | padding: 10px; 30 | font-size: 1.5em; 31 | border:none; 32 | } 33 | #submit { 34 | background: none; 35 | border: none; 36 | position: absolute; 37 | right: 0; 38 | top: 0; 39 | cursor: pointer; 40 | padding: 10px; 41 | } 42 | #result { 43 | margin: 15px 0px; 44 | } 45 | #footer { 46 | border-top: 1px solid #dbdbdb; 47 | font-size: small; 48 | } 49 | #language-selector { 50 | float: right; 51 | } 52 | #legal { 53 | margin: 25px auto; 54 | width: 800px; 55 | font-size: small; 56 | } 57 | #legal img { 58 | vertical-align: middle; 59 | } 60 | .error { 61 | color: red; 62 | } 63 | .inner { 64 | padding: 10px; 65 | } 66 | .simplemodal-container { 67 | background-color: white; 68 | border: 2px solid; 69 | border-color: #e5e5e5 #dbdbdb #d2d2d2; 70 | } 71 | .dialog-header { 72 | padding: 10px; 73 | margin: 5px 0px 0px; 74 | border-bottom: 7px solid #dbdbdb; 75 | } 76 | .close { 77 | float: right; 78 | } 79 | .entity-select { 80 | margin: 0; 81 | cursor: pointer; 82 | padding: 10px; 83 | border-bottom: 1px solid #dbdbdb; 84 | } 85 | .entity-select:hover { 86 | background-color: #4c59a6; 87 | color: white; 88 | } 89 | .label { 90 | font-weight: bold; 91 | } 92 | .aliases { 93 | font-style: italic; 94 | } -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript api for Wikibase repo 3 | * Dependencies: 4 | * # jQuery < http://jquery.com/ > 5 | * 6 | * @author Bene 7 | * @license GNU GPL v2+ 8 | * @version 0.1 9 | */ 10 | ( function( $, ns ) { 11 | 'use strict'; 12 | 13 | /** 14 | * Constructor to create a new api object for interacton with the wikibase api. 15 | * 16 | * @param {string} url 17 | * @param {string} language 18 | * 19 | * @constructor 20 | */ 21 | ns.Api = function Api( url, language ) { 22 | this._url = url; 23 | this._language = language; 24 | }; 25 | 26 | $.extend( ns.Api.prototype, { 27 | 28 | /** 29 | * Sets or gets the language. 30 | * 31 | * @param {string} language 32 | */ 33 | language: function( language ) { 34 | if ( language ) { 35 | this._language = language; 36 | } 37 | return this._language; 38 | }, 39 | 40 | /** 41 | * Searches for entities with the given type. 42 | * 43 | * @param {string} type 44 | * @param {string} search 45 | */ 46 | searchEntities: function( type, search ) { 47 | return this._get( { 48 | action: 'wbsearchentities', 49 | language: this._language, 50 | search: search, 51 | type: type 52 | } ); 53 | }, 54 | 55 | /** 56 | * Gets the entities with the ids and adds the props to the result. 57 | * 58 | * @param {string} ids 59 | * @param {string} props 60 | */ 61 | getEntities: function( ids, props ) { 62 | return this._get( { 63 | action: 'wbgetentities', 64 | languages: this._language, 65 | ids: ids, 66 | props: props 67 | } ); 68 | }, 69 | 70 | /** 71 | * Gets the claims for the property in the given entity. 72 | * 73 | * @param {string} entityId 74 | * @param {string} propertyId 75 | */ 76 | getClaims: function( entityId, propertyId ) { 77 | return this._get( { 78 | action: 'wbgetclaims', 79 | entity: entityId, 80 | property: propertyId 81 | } ); 82 | }, 83 | 84 | /** 85 | * Formats the datavalue. 86 | * 87 | * @param {object} datavalue 88 | */ 89 | formatDatavalue: function( datavalue ) { 90 | return this._get( { 91 | action: 'wbformatvalue', 92 | generate: 'text/plain', 93 | datavalue: JSON.stringify( datavalue ), 94 | options: JSON.stringify( { 95 | lang: this._language, 96 | geoformat: 'dms' 97 | } ) 98 | } ); 99 | }, 100 | 101 | /** 102 | * Perform a jsonp get request to the api. 103 | * 104 | * @param {object} params 105 | * @return {jqXHR} 106 | */ 107 | _get: function( params ) { 108 | $.extend( params, { 109 | format: 'json' 110 | } ); 111 | // no need for error handling as we are using jsonp 112 | return $.getJSON( this._url + '?callback=?', params ); 113 | } 114 | } ); 115 | 116 | } )( jQuery, this ); -------------------------------------------------------------------------------- /src/ask.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript api to query data from Wikibase 3 | * Dependencies: 4 | * # jQuery < http://jquery.com/ > 5 | * # Wikibase api < api.js > 6 | * 7 | * @author Bene 8 | * @license GNU GPL v2+ 9 | * @version 0.1 10 | */ 11 | ( function( $, ns ) { 12 | 'use strict'; 13 | 14 | /** 15 | * Constructor to create a new api object to parse questions. 16 | * 17 | * @param {Api} api 18 | * 19 | * @constructor 20 | */ 21 | ns.Ask = function Ask( api ) { 22 | this._api = api; 23 | }; 24 | 25 | $.extend( ns.Ask.prototype, { 26 | 27 | /** 28 | * Formats the given datavalues. 29 | * 30 | * @param {array} values 31 | * @return {jQuery.Promise} 32 | */ 33 | formatDatavalues: function( values ) { 34 | var deferred = $.Deferred(); 35 | var deferredsArray = []; 36 | for ( var i in values ) { 37 | deferredsArray.push( this._api.formatDatavalue( values[i] ) ); 38 | } 39 | $.when.apply( $, deferredsArray ) 40 | .then( function( /* arguments */ ) { 41 | var args = Array.prototype.slice.call( arguments ); 42 | if ( values.length > 1 ) { 43 | var formattedValues = []; 44 | for ( var i in args ) { 45 | formattedValues.push( args[i][0].result ); 46 | } 47 | deferred.resolve( formattedValues ); 48 | } else if ( values.length > 0 ) { 49 | deferred.resolve( [ args[0].result ] ); 50 | } else { 51 | deferred.reject(); 52 | } 53 | }, function() { 54 | deferred.reject(); 55 | } ); 56 | return deferred.promise(); 57 | }, 58 | 59 | /** 60 | * Gets the datavalues for the property in the given entity. 61 | * 62 | * @param {string} entityId 63 | * @param {string} propertyId 64 | */ 65 | getDatavalues: function( entityId, propertyId ) { 66 | var deferred = $.Deferred(); 67 | this._api.getClaims( entityId, propertyId ).done( function( claims ) { 68 | if ( claims.claims[propertyId] && claims.claims[propertyId].length > 0 ) { 69 | var values = []; 70 | var preferredValues = []; 71 | var propertyClaims = claims.claims[propertyId]; 72 | for ( var i in propertyClaims ) { 73 | var value = propertyClaims[i].mainsnak.datavalue; 74 | // check the rank of the statement 75 | if ( propertyClaims[i].rank == 'preferred' ) { 76 | preferredValues.push( value ); 77 | } 78 | else if ( propertyClaims[i].rank != 'deprecated' ) { 79 | values.push( value ); 80 | } 81 | } 82 | deferred.resolve( preferredValues.length > 0 ? preferredValues : values ); 83 | } 84 | else { 85 | deferred.reject(); 86 | } 87 | } ); 88 | return deferred.promise(); 89 | }, 90 | 91 | /** 92 | * Sets the entity chooser of the api. 93 | * Note: The chooser must return a jQuery Promise object 94 | * and pass the id of the entity when calling the done function. 95 | * 96 | * @param {function} error 97 | */ 98 | entityChooser: function( entityChooser ) { 99 | if ( typeof entityChooser === 'function' ) { 100 | this._entityChooser = entityChooser; 101 | } 102 | }, 103 | 104 | /** 105 | * Searches an entity of a specific type for the given search. 106 | * This function useses the entity chooser to get an entity if there are several options. 107 | * 108 | * @param {string} type 109 | * @param {string} search 110 | * @return {jQuery.Promise} 111 | */ 112 | searchEntity: function( type, search ) { 113 | var deferred = $.Deferred(); 114 | var self = this; 115 | this._api.searchEntities( type, search ).done( function( data ) { 116 | if ( data.search.length === 0 ) { 117 | // reject if no entities were found 118 | deferred.reject(); 119 | } else if ( data.search.length === 1 ) { 120 | // resolve if exactly one entity was found 121 | deferred.resolve( data.search[0].id ); 122 | } else if ( data.search.length > 1 && 123 | data.search[0].label.toLowerCase().indexOf( search.toLowerCase() ) != -1 && 124 | data.search[1].label.toLowerCase().indexOf( search.toLowerCase() ) == -1 125 | ) { 126 | // resolve if the first value's label contains the search term and the second one's doesn't 127 | deferred.resolve( data.search[0].id ); 128 | } else if ( self._entityChooser ) { 129 | // ask to choose when more than one entity were found 130 | self._entityChooser( type, data.search ) 131 | .done( function( id ) { 132 | deferred.resolve( id ); 133 | } ) 134 | .fail( function() { 135 | deferred.reject( false ); 136 | } ); 137 | } else { 138 | // otherwise reject 139 | deferred.reject(); 140 | } 141 | } ); 142 | return deferred.promise(); 143 | } 144 | } ); 145 | 146 | } )( jQuery, window ); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all the UI stuff 3 | * Dependencies: 4 | * # jQuery < http://jquery.com/ > 5 | * # i18next < http://i18next.com/ > 6 | * # Wikibase api < api.js > 7 | * # Ask < ask.js > 8 | * # Parser < parser.js > 9 | * 10 | * @author Bene 11 | * @license GNU GPL v2+ 12 | * @version 0.1 13 | */ 14 | ( function( $ ) { 15 | 'use strict'; 16 | 17 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 18 | var ask = new Ask( api ); 19 | var parser = new Parser( 'en' ); 20 | 21 | // Initialize i18n as soon as possible 22 | i18nInit(); 23 | 24 | $( function() { 25 | ask.entityChooser( entityChooser ); 26 | $( '#question' ).val( decodeURIComponent( location.hash.slice( 1 ) ) ); 27 | 28 | // Question form submit handler 29 | $( '#question-form' ).submit( function() { 30 | setLoading( true ); 31 | $( '#details' ).text( '' ); 32 | var question = $( '#question' ).val(); 33 | location.hash = '#' + encodeURIComponent( question ); 34 | handleQuestion( question ); 35 | } ); 36 | 37 | // Language change handler 38 | $( '#language' ).change( function() { 39 | i18nInit( $( '#language' ).val() ); 40 | } ); 41 | } ); 42 | 43 | /** 44 | * Initializes the i18n extension. 45 | * 46 | * @param {string} lng 47 | */ 48 | function i18nInit( lng ) { 49 | var options = { 50 | resGetPath: 'i18n/__lng__.json', 51 | fallbackLng: 'en' 52 | }; 53 | if ( lng ) { 54 | options.lng = lng; 55 | } 56 | // Initialize i18n 57 | i18n.init( options, function() { 58 | // Set messages 59 | $( 'title' ).i18n(); 60 | $( 'body' ).i18n(); 61 | var lang = i18n.lng().split( '-' )[0]; 62 | api.language( lang ); 63 | parser.language( lang ); 64 | $( '#language' ).val( lang ); 65 | showIntro(); 66 | 67 | // Question param handler 68 | // Note: this must be called here as the possible 69 | // error messages should already be available 70 | // Bug: when selecting a new language, the patterns 71 | // are not available here yet 72 | if ( $( '#question' ).val() ) { 73 | $( '#question-form' ).submit(); 74 | } 75 | } ); 76 | } 77 | 78 | /** 79 | * Sets whether we are showing a loading icon or not. 80 | * 81 | * @param {boolean} loading 82 | */ 83 | function setLoading( loading ) { 84 | $( '#submit img' ).attr( 'src', loading ? 'res/loading.gif' : 'res/search.png' ); 85 | } 86 | 87 | /** 88 | * Shows the error. 89 | * 90 | * @param {string} error 91 | */ 92 | function showError( error ) { 93 | $( '#result' ).addClass( 'error' ).text( error ); 94 | setLoading( false ); 95 | } 96 | 97 | /** 98 | * Shows the result. 99 | * 100 | * @param {string} result 101 | */ 102 | function showResult( result ) { 103 | $( '#result' ).removeClass( 'error' ).text( result ); 104 | setLoading( false ); 105 | } 106 | 107 | /** 108 | * Shows the intro. 109 | */ 110 | function showIntro() { 111 | $( '#result' ).removeClass( 'error' ).html( $( '' ).text( i18n.t( 'intro' ) ) ); 112 | setLoading( false ); 113 | } 114 | 115 | /** 116 | * Shows the details. 117 | * 118 | * @param {string} details 119 | */ 120 | function showDetails( details ) { 121 | $( '#details' ).append( details ); 122 | } 123 | 124 | /** 125 | * Shows a dialog which allows the user to choose an entity. 126 | * 127 | * @return {jQuery.Promise} 128 | */ 129 | function entityChooser( type, entities ) { 130 | var deferred = $.Deferred(); 131 | var $dialog = $( '
' ).append( 132 | $( '

' ).attr( 'class', 'dialog-header' ).text( i18n.t( 'chooseEntity', { type: type } ) ).append( 133 | $( '' ).attr( { 134 | href: '#', 135 | class: 'close', 136 | title: i18n.t( 'cancel' ) 137 | } ).append( 138 | $( '' ).attr( { 139 | src: 'res/close.png', 140 | alt: i18n.t( 'cancel' ), 141 | width: 20, 142 | height: 20 143 | } ) 144 | ) 145 | .click( function() { 146 | $.modal.close(); 147 | return false; 148 | } ) 149 | ) 150 | ); 151 | /* jshint -W083 */ 152 | for ( var i in entities ) { 153 | $( '

', { class: 'entity-select' } ) 154 | .append( $( '', { class: 'label' } ).text( entities[i].label ) ) 155 | .append( $( '
' ) ) 156 | .append( $( '', { class: 'description' } ).text( entities[i].description ) ) 157 | .append( $( '
' ) ) 158 | .append( $( '', { class: 'aliases' } ).text( entities[i].aliases ? i18n.t( 'alsoKnown' ) + entities[i].aliases.join( ', ' ) : '' ) ) 159 | .click( { id: entities[i].id }, function( e ) { 160 | deferred.resolve( e.data.id ); 161 | $.modal.close(); 162 | } ) 163 | .appendTo( $dialog ); 164 | } 165 | /* jshint +W083 */ 166 | $dialog.modal( { 167 | maxWidth: 700, 168 | onClose: function() { 169 | deferred.reject(); 170 | $.modal.close(); 171 | } 172 | } ); 173 | return deferred.promise(); 174 | } 175 | 176 | /** 177 | * Combines the given values. 178 | */ 179 | function combineValues( values ) { 180 | var value = ''; 181 | for ( var i in values ) { 182 | if ( value !== '' && i == values.length - 1 ) { 183 | value += ' ' + i18n.t( 'and' ) + ' '; 184 | } 185 | else if ( i > 0 ) { 186 | value += ', '; 187 | } 188 | value += values[i]; 189 | } 190 | return value; 191 | } 192 | 193 | /** 194 | * Handles the questions. 195 | * 196 | * @param {string} question 197 | */ 198 | function handleQuestion( question ) { 199 | // parse the question 200 | parser.parseQuestion( question ) 201 | .then( function( parsed ) { 202 | handleParsed( parsed ); 203 | }, function() { 204 | // the question could not be parsed 205 | showError( i18n.t( 'unparsable' ) ); 206 | } ); 207 | } 208 | 209 | /** 210 | * Builds the links for the details. 211 | * 212 | * @param {string} link target/label 213 | */ 214 | function linkToWD( target, label ) { 215 | return '
' + (label||target) + ''; 216 | } 217 | 218 | /** 219 | * Handles the parsed parts of the question. 220 | * 221 | * @param {object} parsed 222 | */ 223 | function handleParsed( parsed ) { 224 | // search the item 225 | ask.searchEntity( 'item', parsed.item ) 226 | .then( function( itemId ) { 227 | showDetails( 'Item: ' + linkToWD( itemId ) ); 228 | // question after a specific property => must be queried 229 | if ( parsed.property ) { 230 | // search the property 231 | ask.searchEntity( 'property', parsed.property ) 232 | .then( function( propertyId ) { 233 | showDetails( ', Property: ' + linkToWD( 'Property:' + propertyId, propertyId ) ); 234 | // get the claims 235 | return ask.getDatavalues( itemId, propertyId ); 236 | }, function( notfound ) { 237 | // property not found 238 | if ( notfound !== false ) { 239 | showError( i18n.t( 'propertynotfound', { property: parsed.property } ) ); 240 | } else { 241 | showIntro(); 242 | } 243 | return false; 244 | } ) 245 | .then( function( values ) { 246 | // format the values 247 | return ask.formatDatavalues( values ); 248 | }, function( state ) { 249 | if ( state !== false ) { 250 | showError( i18n.t( 'nodata', { subject: parsed.article + ' ' + parsed.property + ' ' + parsed.possesive + ' ' + parsed.item } ) ); 251 | } 252 | return false; 253 | } ) 254 | .then( function( formattedValues ) { 255 | // display the values 256 | parsed.value = combineValues( formattedValues ); 257 | console.log( parsed ); 258 | var format = parsed.callback ? parsed.callback : '$article $property $possesive $item $verb $value.'; 259 | var answer = parser.buildAnswer( format, parsed ); 260 | showResult( answer ); 261 | }, function( state ) { 262 | // formatting failed 263 | if ( state !== false ) { 264 | showError( i18n.t( 'notformatted' ) ); 265 | } 266 | return false; 267 | } ); 268 | } 269 | // common question => get the description 270 | else { 271 | api.getEntities( itemId, 'descriptions' ) 272 | .done( function( data ) { 273 | if ( data.entities[itemId].descriptions[api.language()] ) { 274 | showResult( data.entities[itemId].descriptions[api.language()].value ); 275 | } else { 276 | showResult( itemId ); 277 | } 278 | } ); 279 | } 280 | }, function( notfound ) { 281 | // item not found 282 | if ( notfound !== false ) { 283 | showError( i18n.t( 'itemnotfound', { item: parsed.item } ) ); 284 | } else { 285 | showIntro(); 286 | } 287 | } ); 288 | } 289 | 290 | } )( jQuery ); 291 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript api to parse questions optimized to query data from Wikibase 3 | * Dependencies: 4 | * # jQuery < http://jquery.com/ > 5 | * # XRegExp < http://xregexp.com/ > 6 | * 7 | * @author Bene 8 | * @license GNU GPL v2+ 9 | * @version 0.1 10 | */ 11 | ( function( $, regex, ns ) { 12 | 'use strict'; 13 | 14 | /** 15 | * Contains the regexes used for parsing. 16 | * 17 | * @var {object} 18 | */ 19 | var regexes = {}; 20 | 21 | /** 22 | * Contains shortcut attributes that can be used in the reges. 23 | * 24 | * @var {object} 25 | */ 26 | var attributes = {}; 27 | 28 | /** 29 | * Constructor to create a new parser object. 30 | * 31 | * @param {string} language 32 | * 33 | * @constructor 34 | */ 35 | ns.Parser = function( language ) { 36 | this._language = language; 37 | this._initPatterns(); 38 | }; 39 | 40 | $.extend( ns.Parser.prototype, { 41 | 42 | /** 43 | * Sets or gets the language. 44 | * 45 | * @param {string} language 46 | */ 47 | language: function( language ) { 48 | if ( language ) { 49 | this._language = language; 50 | this._initPatterns(); 51 | } 52 | return this._language; 53 | }, 54 | 55 | /** 56 | * Loads the patterns. 57 | */ 58 | _initPatterns: function() { 59 | this._promise = $.getJSON( 'patterns/' + this._language + '.json', function( data ) { 60 | regexes = data.regexes; 61 | attributes = data.attributes; 62 | } ); 63 | }, 64 | 65 | /** 66 | * Builds an answer string based on the given format string and the given attributes. 67 | * 68 | * @param {string} format 69 | * @param {object} attributes 70 | * @return {string} 71 | */ 72 | buildAnswer: function( format, attributes ) { 73 | for ( var i in attributes ) { 74 | format = format.replace( new RegExp( '\\$' + i, 'g' ), attributes[i] ); 75 | } 76 | //format = format.replace( /undefined /g, '' ); 77 | format = format.replace( /\$[\w\d-_\|]+ ?/g, '' ); 78 | format = format.charAt( 0 ).toUpperCase() + format.slice( 1 ); // ucfirst 79 | return format; 80 | }, 81 | 82 | /** 83 | * Parses the question and returns the an object containing some of the following keys: 84 | * [ 'question', 'verb', 'article', 'property', 'possesive', 'item' ] 85 | * 86 | * @param {string} question 87 | * @return {object} 88 | */ 89 | parseQuestion: function( question ) { 90 | var deferred = $.Deferred(); 91 | this._promise.then( function() { 92 | question = question.trim(); 93 | if ( question.indexOf( '?', question.length - 1 ) !== -1 ) { 94 | question = question.substring( 0, question.length - 1 ); 95 | } 96 | for ( var r in regexes ) { 97 | var regString = regexes[r].regex; 98 | for ( var a in attributes ) { 99 | regString = regString.replace( new RegExp( '\\$' + a, 'g' ), attributes[a] ); 100 | } 101 | var reg = regex( '^' + regString + '$', 'i' ); 102 | if ( reg.test( question ) ) { 103 | var parts = regex.exec( question, reg ); 104 | var result = $.extend( {}, regexes[r], parts ); 105 | deferred.resolve( result ); 106 | return; 107 | } 108 | } 109 | deferred.reject(); 110 | } ); 111 | return deferred.promise(); 112 | } 113 | } ); 114 | 115 | } )( jQuery, XRegExp, window ); -------------------------------------------------------------------------------- /test/qunit-1.13.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 1.13.0 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: 2014-01-04T17:09Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699a4; 36 | background-color: #0d3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: normal; 41 | 42 | border-radius: 5px 5px 0 0; 43 | -moz-border-radius: 5px 5px 0 0; 44 | -webkit-border-top-right-radius: 5px; 45 | -webkit-border-top-left-radius: 5px; 46 | } 47 | 48 | #qunit-header a { 49 | text-decoration: none; 50 | color: #c2ccd1; 51 | } 52 | 53 | #qunit-header a:hover, 54 | #qunit-header a:focus { 55 | color: #fff; 56 | } 57 | 58 | #qunit-testrunner-toolbar label { 59 | display: inline-block; 60 | padding: 0 .5em 0 .1em; 61 | } 62 | 63 | #qunit-banner { 64 | height: 5px; 65 | } 66 | 67 | #qunit-testrunner-toolbar { 68 | padding: 0.5em 0 0.5em 2em; 69 | color: #5E740B; 70 | background-color: #eee; 71 | overflow: hidden; 72 | } 73 | 74 | #qunit-userAgent { 75 | padding: 0.5em 0 0.5em 2.5em; 76 | background-color: #2b81af; 77 | color: #fff; 78 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 79 | } 80 | 81 | #qunit-modulefilter-container { 82 | float: right; 83 | } 84 | 85 | /** Tests: Pass/Fail */ 86 | 87 | #qunit-tests { 88 | list-style-position: inside; 89 | } 90 | 91 | #qunit-tests li { 92 | padding: 0.4em 0.5em 0.4em 2.5em; 93 | border-bottom: 1px solid #fff; 94 | list-style-position: inside; 95 | } 96 | 97 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 98 | display: none; 99 | } 100 | 101 | #qunit-tests li strong { 102 | cursor: pointer; 103 | } 104 | 105 | #qunit-tests li a { 106 | padding: 0.5em; 107 | color: #c2ccd1; 108 | text-decoration: none; 109 | } 110 | #qunit-tests li a:hover, 111 | #qunit-tests li a:focus { 112 | color: #000; 113 | } 114 | 115 | #qunit-tests li .runtime { 116 | float: right; 117 | font-size: smaller; 118 | } 119 | 120 | .qunit-assert-list { 121 | margin-top: 0.5em; 122 | padding: 0.5em; 123 | 124 | background-color: #fff; 125 | 126 | border-radius: 5px; 127 | -moz-border-radius: 5px; 128 | -webkit-border-radius: 5px; 129 | } 130 | 131 | .qunit-collapsed { 132 | display: none; 133 | } 134 | 135 | #qunit-tests table { 136 | border-collapse: collapse; 137 | margin-top: .2em; 138 | } 139 | 140 | #qunit-tests th { 141 | text-align: right; 142 | vertical-align: top; 143 | padding: 0 .5em 0 0; 144 | } 145 | 146 | #qunit-tests td { 147 | vertical-align: top; 148 | } 149 | 150 | #qunit-tests pre { 151 | margin: 0; 152 | white-space: pre-wrap; 153 | word-wrap: break-word; 154 | } 155 | 156 | #qunit-tests del { 157 | background-color: #e0f2be; 158 | color: #374e0c; 159 | text-decoration: none; 160 | } 161 | 162 | #qunit-tests ins { 163 | background-color: #ffcaca; 164 | color: #500; 165 | text-decoration: none; 166 | } 167 | 168 | /*** Test Counts */ 169 | 170 | #qunit-tests b.counts { color: black; } 171 | #qunit-tests b.passed { color: #5E740B; } 172 | #qunit-tests b.failed { color: #710909; } 173 | 174 | #qunit-tests li li { 175 | padding: 5px; 176 | background-color: #fff; 177 | border-bottom: none; 178 | list-style-position: inside; 179 | } 180 | 181 | /*** Passing Styles */ 182 | 183 | #qunit-tests li li.pass { 184 | color: #3c510c; 185 | background-color: #fff; 186 | border-left: 10px solid #C6E746; 187 | } 188 | 189 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 190 | #qunit-tests .pass .test-name { color: #366097; } 191 | 192 | #qunit-tests .pass .test-actual, 193 | #qunit-tests .pass .test-expected { color: #999999; } 194 | 195 | #qunit-banner.qunit-pass { background-color: #C6E746; } 196 | 197 | /*** Failing Styles */ 198 | 199 | #qunit-tests li li.fail { 200 | color: #710909; 201 | background-color: #fff; 202 | border-left: 10px solid #EE5757; 203 | white-space: pre; 204 | } 205 | 206 | #qunit-tests > li:last-child { 207 | border-radius: 0 0 5px 5px; 208 | -moz-border-radius: 0 0 5px 5px; 209 | -webkit-border-bottom-right-radius: 5px; 210 | -webkit-border-bottom-left-radius: 5px; 211 | } 212 | 213 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 214 | #qunit-tests .fail .test-name, 215 | #qunit-tests .fail .module-name { color: #000000; } 216 | 217 | #qunit-tests .fail .test-actual { color: #EE5757; } 218 | #qunit-tests .fail .test-expected { color: green; } 219 | 220 | #qunit-banner.qunit-fail { background-color: #EE5757; } 221 | 222 | 223 | /** Result */ 224 | 225 | #qunit-testresult { 226 | padding: 0.5em 0.5em 0.5em 2.5em; 227 | 228 | color: #2b81af; 229 | background-color: #D2E0E6; 230 | 231 | border-bottom: 1px solid white; 232 | } 233 | #qunit-testresult .module-name { 234 | font-weight: bold; 235 | } 236 | 237 | /** Fixture */ 238 | 239 | #qunit-fixture { 240 | position: absolute; 241 | top: -10000px; 242 | left: -10000px; 243 | width: 1000px; 244 | height: 1000px; 245 | } 246 | -------------------------------------------------------------------------------- /test/qunit-1.13.0.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 1.13.0 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: 2014-01-04T17:09Z 10 | */ 11 | 12 | (function( window ) { 13 | 14 | var QUnit, 15 | assert, 16 | config, 17 | onErrorFnPrev, 18 | testId = 0, 19 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 20 | toString = Object.prototype.toString, 21 | hasOwn = Object.prototype.hasOwnProperty, 22 | // Keep a local reference to Date (GH-283) 23 | Date = window.Date, 24 | setTimeout = window.setTimeout, 25 | defined = { 26 | document: typeof window.document !== "undefined", 27 | setTimeout: typeof window.setTimeout !== "undefined", 28 | sessionStorage: (function() { 29 | var x = "qunit-test-string"; 30 | try { 31 | sessionStorage.setItem( x, x ); 32 | sessionStorage.removeItem( x ); 33 | return true; 34 | } catch( e ) { 35 | return false; 36 | } 37 | }()) 38 | }, 39 | /** 40 | * Provides a normalized error string, correcting an issue 41 | * with IE 7 (and prior) where Error.prototype.toString is 42 | * not properly implemented 43 | * 44 | * Based on http://es5.github.com/#x15.11.4.4 45 | * 46 | * @param {String|Error} error 47 | * @return {String} error message 48 | */ 49 | errorString = function( error ) { 50 | var name, message, 51 | errorString = error.toString(); 52 | if ( errorString.substring( 0, 7 ) === "[object" ) { 53 | name = error.name ? error.name.toString() : "Error"; 54 | message = error.message ? error.message.toString() : ""; 55 | if ( name && message ) { 56 | return name + ": " + message; 57 | } else if ( name ) { 58 | return name; 59 | } else if ( message ) { 60 | return message; 61 | } else { 62 | return "Error"; 63 | } 64 | } else { 65 | return errorString; 66 | } 67 | }, 68 | /** 69 | * Makes a clone of an object using only Array or Object as base, 70 | * and copies over the own enumerable properties. 71 | * 72 | * @param {Object} obj 73 | * @return {Object} New object with only the own properties (recursively). 74 | */ 75 | objectValues = function( obj ) { 76 | // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. 77 | /*jshint newcap: false */ 78 | var key, val, 79 | vals = QUnit.is( "array", obj ) ? [] : {}; 80 | for ( key in obj ) { 81 | if ( hasOwn.call( obj, key ) ) { 82 | val = obj[key]; 83 | vals[key] = val === Object(val) ? objectValues(val) : val; 84 | } 85 | } 86 | return vals; 87 | }; 88 | 89 | 90 | // Root QUnit object. 91 | // `QUnit` initialized at top of scope 92 | QUnit = { 93 | 94 | // call on start of module test to prepend name to all tests 95 | module: function( name, testEnvironment ) { 96 | config.currentModule = name; 97 | config.currentModuleTestEnvironment = testEnvironment; 98 | config.modules[name] = true; 99 | }, 100 | 101 | asyncTest: function( testName, expected, callback ) { 102 | if ( arguments.length === 2 ) { 103 | callback = expected; 104 | expected = null; 105 | } 106 | 107 | QUnit.test( testName, expected, callback, true ); 108 | }, 109 | 110 | test: function( testName, expected, callback, async ) { 111 | var test, 112 | nameHtml = "" + escapeText( testName ) + ""; 113 | 114 | if ( arguments.length === 2 ) { 115 | callback = expected; 116 | expected = null; 117 | } 118 | 119 | if ( config.currentModule ) { 120 | nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; 121 | } 122 | 123 | test = new Test({ 124 | nameHtml: nameHtml, 125 | testName: testName, 126 | expected: expected, 127 | async: async, 128 | callback: callback, 129 | module: config.currentModule, 130 | moduleTestEnvironment: config.currentModuleTestEnvironment, 131 | stack: sourceFromStacktrace( 2 ) 132 | }); 133 | 134 | if ( !validTest( test ) ) { 135 | return; 136 | } 137 | 138 | test.queue(); 139 | }, 140 | 141 | // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. 142 | expect: function( asserts ) { 143 | if (arguments.length === 1) { 144 | config.current.expected = asserts; 145 | } else { 146 | return config.current.expected; 147 | } 148 | }, 149 | 150 | start: function( count ) { 151 | // QUnit hasn't been initialized yet. 152 | // Note: RequireJS (et al) may delay onLoad 153 | if ( config.semaphore === undefined ) { 154 | QUnit.begin(function() { 155 | // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first 156 | setTimeout(function() { 157 | QUnit.start( count ); 158 | }); 159 | }); 160 | return; 161 | } 162 | 163 | config.semaphore -= count || 1; 164 | // don't start until equal number of stop-calls 165 | if ( config.semaphore > 0 ) { 166 | return; 167 | } 168 | // ignore if start is called more often then stop 169 | if ( config.semaphore < 0 ) { 170 | config.semaphore = 0; 171 | QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); 172 | return; 173 | } 174 | // A slight delay, to avoid any current callbacks 175 | if ( defined.setTimeout ) { 176 | setTimeout(function() { 177 | if ( config.semaphore > 0 ) { 178 | return; 179 | } 180 | if ( config.timeout ) { 181 | clearTimeout( config.timeout ); 182 | } 183 | 184 | config.blocking = false; 185 | process( true ); 186 | }, 13); 187 | } else { 188 | config.blocking = false; 189 | process( true ); 190 | } 191 | }, 192 | 193 | stop: function( count ) { 194 | config.semaphore += count || 1; 195 | config.blocking = true; 196 | 197 | if ( config.testTimeout && defined.setTimeout ) { 198 | clearTimeout( config.timeout ); 199 | config.timeout = setTimeout(function() { 200 | QUnit.ok( false, "Test timed out" ); 201 | config.semaphore = 1; 202 | QUnit.start(); 203 | }, config.testTimeout ); 204 | } 205 | } 206 | }; 207 | 208 | // We use the prototype to distinguish between properties that should 209 | // be exposed as globals (and in exports) and those that shouldn't 210 | (function() { 211 | function F() {} 212 | F.prototype = QUnit; 213 | QUnit = new F(); 214 | // Make F QUnit's constructor so that we can add to the prototype later 215 | QUnit.constructor = F; 216 | }()); 217 | 218 | /** 219 | * Config object: Maintain internal state 220 | * Later exposed as QUnit.config 221 | * `config` initialized at top of scope 222 | */ 223 | config = { 224 | // The queue of tests to run 225 | queue: [], 226 | 227 | // block until document ready 228 | blocking: true, 229 | 230 | // when enabled, show only failing tests 231 | // gets persisted through sessionStorage and can be changed in UI via checkbox 232 | hidepassed: false, 233 | 234 | // by default, run previously failed tests first 235 | // very useful in combination with "Hide passed tests" checked 236 | reorder: true, 237 | 238 | // by default, modify document.title when suite is done 239 | altertitle: true, 240 | 241 | // when enabled, all tests must call expect() 242 | requireExpects: false, 243 | 244 | // add checkboxes that are persisted in the query-string 245 | // when enabled, the id is set to `true` as a `QUnit.config` property 246 | urlConfig: [ 247 | { 248 | id: "noglobals", 249 | label: "Check for Globals", 250 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 251 | }, 252 | { 253 | id: "notrycatch", 254 | label: "No try-catch", 255 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 256 | } 257 | ], 258 | 259 | // Set of all modules. 260 | modules: {}, 261 | 262 | // logging callback queues 263 | begin: [], 264 | done: [], 265 | log: [], 266 | testStart: [], 267 | testDone: [], 268 | moduleStart: [], 269 | moduleDone: [] 270 | }; 271 | 272 | // Initialize more QUnit.config and QUnit.urlParams 273 | (function() { 274 | var i, 275 | location = window.location || { search: "", protocol: "file:" }, 276 | params = location.search.slice( 1 ).split( "&" ), 277 | length = params.length, 278 | urlParams = {}, 279 | current; 280 | 281 | if ( params[ 0 ] ) { 282 | for ( i = 0; i < length; i++ ) { 283 | current = params[ i ].split( "=" ); 284 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 285 | // allow just a key to turn on a flag, e.g., test.html?noglobals 286 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 287 | urlParams[ current[ 0 ] ] = current[ 1 ]; 288 | } 289 | } 290 | 291 | QUnit.urlParams = urlParams; 292 | 293 | // String search anywhere in moduleName+testName 294 | config.filter = urlParams.filter; 295 | 296 | // Exact match of the module name 297 | config.module = urlParams.module; 298 | 299 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 300 | 301 | // Figure out if we're running the tests from a server or not 302 | QUnit.isLocal = location.protocol === "file:"; 303 | }()); 304 | 305 | extend( QUnit, { 306 | 307 | config: config, 308 | 309 | // Initialize the configuration options 310 | init: function() { 311 | extend( config, { 312 | stats: { all: 0, bad: 0 }, 313 | moduleStats: { all: 0, bad: 0 }, 314 | started: +new Date(), 315 | updateRate: 1000, 316 | blocking: false, 317 | autostart: true, 318 | autorun: false, 319 | filter: "", 320 | queue: [], 321 | semaphore: 1 322 | }); 323 | 324 | var tests, banner, result, 325 | qunit = id( "qunit" ); 326 | 327 | if ( qunit ) { 328 | qunit.innerHTML = 329 | "

" + escapeText( document.title ) + "

" + 330 | "

" + 331 | "
" + 332 | "

" + 333 | "
    "; 334 | } 335 | 336 | tests = id( "qunit-tests" ); 337 | banner = id( "qunit-banner" ); 338 | result = id( "qunit-testresult" ); 339 | 340 | if ( tests ) { 341 | tests.innerHTML = ""; 342 | } 343 | 344 | if ( banner ) { 345 | banner.className = ""; 346 | } 347 | 348 | if ( result ) { 349 | result.parentNode.removeChild( result ); 350 | } 351 | 352 | if ( tests ) { 353 | result = document.createElement( "p" ); 354 | result.id = "qunit-testresult"; 355 | result.className = "result"; 356 | tests.parentNode.insertBefore( result, tests ); 357 | result.innerHTML = "Running...
     "; 358 | } 359 | }, 360 | 361 | // Resets the test setup. Useful for tests that modify the DOM. 362 | /* 363 | DEPRECATED: Use multiple tests instead of resetting inside a test. 364 | Use testStart or testDone for custom cleanup. 365 | This method will throw an error in 2.0, and will be removed in 2.1 366 | */ 367 | reset: function() { 368 | var fixture = id( "qunit-fixture" ); 369 | if ( fixture ) { 370 | fixture.innerHTML = config.fixture; 371 | } 372 | }, 373 | 374 | // Safe object type checking 375 | is: function( type, obj ) { 376 | return QUnit.objectType( obj ) === type; 377 | }, 378 | 379 | objectType: function( obj ) { 380 | if ( typeof obj === "undefined" ) { 381 | return "undefined"; 382 | } 383 | 384 | // Consider: typeof null === object 385 | if ( obj === null ) { 386 | return "null"; 387 | } 388 | 389 | var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), 390 | type = match && match[1] || ""; 391 | 392 | switch ( type ) { 393 | case "Number": 394 | if ( isNaN(obj) ) { 395 | return "nan"; 396 | } 397 | return "number"; 398 | case "String": 399 | case "Boolean": 400 | case "Array": 401 | case "Date": 402 | case "RegExp": 403 | case "Function": 404 | return type.toLowerCase(); 405 | } 406 | if ( typeof obj === "object" ) { 407 | return "object"; 408 | } 409 | return undefined; 410 | }, 411 | 412 | push: function( result, actual, expected, message ) { 413 | if ( !config.current ) { 414 | throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 415 | } 416 | 417 | var output, source, 418 | details = { 419 | module: config.current.module, 420 | name: config.current.testName, 421 | result: result, 422 | message: message, 423 | actual: actual, 424 | expected: expected 425 | }; 426 | 427 | message = escapeText( message ) || ( result ? "okay" : "failed" ); 428 | message = "" + message + ""; 429 | output = message; 430 | 431 | if ( !result ) { 432 | expected = escapeText( QUnit.jsDump.parse(expected) ); 433 | actual = escapeText( QUnit.jsDump.parse(actual) ); 434 | output += ""; 435 | 436 | if ( actual !== expected ) { 437 | output += ""; 438 | output += ""; 439 | } 440 | 441 | source = sourceFromStacktrace(); 442 | 443 | if ( source ) { 444 | details.source = source; 445 | output += ""; 446 | } 447 | 448 | output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeText( source ) + "
    "; 449 | } 450 | 451 | runLoggingCallbacks( "log", QUnit, details ); 452 | 453 | config.current.assertions.push({ 454 | result: !!result, 455 | message: output 456 | }); 457 | }, 458 | 459 | pushFailure: function( message, source, actual ) { 460 | if ( !config.current ) { 461 | throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 462 | } 463 | 464 | var output, 465 | details = { 466 | module: config.current.module, 467 | name: config.current.testName, 468 | result: false, 469 | message: message 470 | }; 471 | 472 | message = escapeText( message ) || "error"; 473 | message = "" + message + ""; 474 | output = message; 475 | 476 | output += ""; 477 | 478 | if ( actual ) { 479 | output += ""; 480 | } 481 | 482 | if ( source ) { 483 | details.source = source; 484 | output += ""; 485 | } 486 | 487 | output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; 488 | 489 | runLoggingCallbacks( "log", QUnit, details ); 490 | 491 | config.current.assertions.push({ 492 | result: false, 493 | message: output 494 | }); 495 | }, 496 | 497 | url: function( params ) { 498 | params = extend( extend( {}, QUnit.urlParams ), params ); 499 | var key, 500 | querystring = "?"; 501 | 502 | for ( key in params ) { 503 | if ( hasOwn.call( params, key ) ) { 504 | querystring += encodeURIComponent( key ) + "=" + 505 | encodeURIComponent( params[ key ] ) + "&"; 506 | } 507 | } 508 | return window.location.protocol + "//" + window.location.host + 509 | window.location.pathname + querystring.slice( 0, -1 ); 510 | }, 511 | 512 | extend: extend, 513 | id: id, 514 | addEvent: addEvent, 515 | addClass: addClass, 516 | hasClass: hasClass, 517 | removeClass: removeClass 518 | // load, equiv, jsDump, diff: Attached later 519 | }); 520 | 521 | /** 522 | * @deprecated: Created for backwards compatibility with test runner that set the hook function 523 | * into QUnit.{hook}, instead of invoking it and passing the hook function. 524 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 525 | * Doing this allows us to tell if the following methods have been overwritten on the actual 526 | * QUnit object. 527 | */ 528 | extend( QUnit.constructor.prototype, { 529 | 530 | // Logging callbacks; all receive a single argument with the listed properties 531 | // run test/logs.html for any related changes 532 | begin: registerLoggingCallback( "begin" ), 533 | 534 | // done: { failed, passed, total, runtime } 535 | done: registerLoggingCallback( "done" ), 536 | 537 | // log: { result, actual, expected, message } 538 | log: registerLoggingCallback( "log" ), 539 | 540 | // testStart: { name } 541 | testStart: registerLoggingCallback( "testStart" ), 542 | 543 | // testDone: { name, failed, passed, total, runtime } 544 | testDone: registerLoggingCallback( "testDone" ), 545 | 546 | // moduleStart: { name } 547 | moduleStart: registerLoggingCallback( "moduleStart" ), 548 | 549 | // moduleDone: { name, failed, passed, total } 550 | moduleDone: registerLoggingCallback( "moduleDone" ) 551 | }); 552 | 553 | if ( !defined.document || document.readyState === "complete" ) { 554 | config.autorun = true; 555 | } 556 | 557 | QUnit.load = function() { 558 | runLoggingCallbacks( "begin", QUnit, {} ); 559 | 560 | // Initialize the config, saving the execution queue 561 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, 562 | urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, 563 | numModules = 0, 564 | moduleNames = [], 565 | moduleFilterHtml = "", 566 | urlConfigHtml = "", 567 | oldconfig = extend( {}, config ); 568 | 569 | QUnit.init(); 570 | extend(config, oldconfig); 571 | 572 | config.blocking = false; 573 | 574 | len = config.urlConfig.length; 575 | 576 | for ( i = 0; i < len; i++ ) { 577 | val = config.urlConfig[i]; 578 | if ( typeof val === "string" ) { 579 | val = { 580 | id: val, 581 | label: val, 582 | tooltip: "[no tooltip available]" 583 | }; 584 | } 585 | config[ val.id ] = QUnit.urlParams[ val.id ]; 586 | urlConfigHtml += ""; 592 | } 593 | for ( i in config.modules ) { 594 | if ( config.modules.hasOwnProperty( i ) ) { 595 | moduleNames.push(i); 596 | } 597 | } 598 | numModules = moduleNames.length; 599 | moduleNames.sort( function( a, b ) { 600 | return a.localeCompare( b ); 601 | }); 602 | moduleFilterHtml += ""; 613 | 614 | // `userAgent` initialized at top of scope 615 | userAgent = id( "qunit-userAgent" ); 616 | if ( userAgent ) { 617 | userAgent.innerHTML = navigator.userAgent; 618 | } 619 | 620 | // `banner` initialized at top of scope 621 | banner = id( "qunit-header" ); 622 | if ( banner ) { 623 | banner.innerHTML = "" + banner.innerHTML + " "; 624 | } 625 | 626 | // `toolbar` initialized at top of scope 627 | toolbar = id( "qunit-testrunner-toolbar" ); 628 | if ( toolbar ) { 629 | // `filter` initialized at top of scope 630 | filter = document.createElement( "input" ); 631 | filter.type = "checkbox"; 632 | filter.id = "qunit-filter-pass"; 633 | 634 | addEvent( filter, "click", function() { 635 | var tmp, 636 | ol = id( "qunit-tests" ); 637 | 638 | if ( filter.checked ) { 639 | ol.className = ol.className + " hidepass"; 640 | } else { 641 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 642 | ol.className = tmp.replace( / hidepass /, " " ); 643 | } 644 | if ( defined.sessionStorage ) { 645 | if (filter.checked) { 646 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 647 | } else { 648 | sessionStorage.removeItem( "qunit-filter-passed-tests" ); 649 | } 650 | } 651 | }); 652 | 653 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 654 | filter.checked = true; 655 | // `ol` initialized at top of scope 656 | ol = id( "qunit-tests" ); 657 | ol.className = ol.className + " hidepass"; 658 | } 659 | toolbar.appendChild( filter ); 660 | 661 | // `label` initialized at top of scope 662 | label = document.createElement( "label" ); 663 | label.setAttribute( "for", "qunit-filter-pass" ); 664 | label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); 665 | label.innerHTML = "Hide passed tests"; 666 | toolbar.appendChild( label ); 667 | 668 | urlConfigCheckboxesContainer = document.createElement("span"); 669 | urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; 670 | urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); 671 | // For oldIE support: 672 | // * Add handlers to the individual elements instead of the container 673 | // * Use "click" instead of "change" 674 | // * Fallback from event.target to event.srcElement 675 | addEvents( urlConfigCheckboxes, "click", function( event ) { 676 | var params = {}, 677 | target = event.target || event.srcElement; 678 | params[ target.name ] = target.checked ? true : undefined; 679 | window.location = QUnit.url( params ); 680 | }); 681 | toolbar.appendChild( urlConfigCheckboxesContainer ); 682 | 683 | if (numModules > 1) { 684 | moduleFilter = document.createElement( "span" ); 685 | moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); 686 | moduleFilter.innerHTML = moduleFilterHtml; 687 | addEvent( moduleFilter.lastChild, "change", function() { 688 | var selectBox = moduleFilter.getElementsByTagName("select")[0], 689 | selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); 690 | 691 | window.location = QUnit.url({ 692 | module: ( selectedModule === "" ) ? undefined : selectedModule, 693 | // Remove any existing filters 694 | filter: undefined, 695 | testNumber: undefined 696 | }); 697 | }); 698 | toolbar.appendChild(moduleFilter); 699 | } 700 | } 701 | 702 | // `main` initialized at top of scope 703 | main = id( "qunit-fixture" ); 704 | if ( main ) { 705 | config.fixture = main.innerHTML; 706 | } 707 | 708 | if ( config.autostart ) { 709 | QUnit.start(); 710 | } 711 | }; 712 | 713 | if ( defined.document ) { 714 | addEvent( window, "load", QUnit.load ); 715 | } 716 | 717 | // `onErrorFnPrev` initialized at top of scope 718 | // Preserve other handlers 719 | onErrorFnPrev = window.onerror; 720 | 721 | // Cover uncaught exceptions 722 | // Returning true will suppress the default browser handler, 723 | // returning false will let it run. 724 | window.onerror = function ( error, filePath, linerNr ) { 725 | var ret = false; 726 | if ( onErrorFnPrev ) { 727 | ret = onErrorFnPrev( error, filePath, linerNr ); 728 | } 729 | 730 | // Treat return value as window.onerror itself does, 731 | // Only do our handling if not suppressed. 732 | if ( ret !== true ) { 733 | if ( QUnit.config.current ) { 734 | if ( QUnit.config.current.ignoreGlobalErrors ) { 735 | return true; 736 | } 737 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 738 | } else { 739 | QUnit.test( "global failure", extend( function() { 740 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 741 | }, { validTest: validTest } ) ); 742 | } 743 | return false; 744 | } 745 | 746 | return ret; 747 | }; 748 | 749 | function done() { 750 | config.autorun = true; 751 | 752 | // Log the last module results 753 | if ( config.previousModule ) { 754 | runLoggingCallbacks( "moduleDone", QUnit, { 755 | name: config.previousModule, 756 | failed: config.moduleStats.bad, 757 | passed: config.moduleStats.all - config.moduleStats.bad, 758 | total: config.moduleStats.all 759 | }); 760 | } 761 | delete config.previousModule; 762 | 763 | var i, key, 764 | banner = id( "qunit-banner" ), 765 | tests = id( "qunit-tests" ), 766 | runtime = +new Date() - config.started, 767 | passed = config.stats.all - config.stats.bad, 768 | html = [ 769 | "Tests completed in ", 770 | runtime, 771 | " milliseconds.
    ", 772 | "", 773 | passed, 774 | " assertions of ", 775 | config.stats.all, 776 | " passed, ", 777 | config.stats.bad, 778 | " failed." 779 | ].join( "" ); 780 | 781 | if ( banner ) { 782 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 783 | } 784 | 785 | if ( tests ) { 786 | id( "qunit-testresult" ).innerHTML = html; 787 | } 788 | 789 | if ( config.altertitle && defined.document && document.title ) { 790 | // show ✖ for good, ✔ for bad suite result in title 791 | // use escape sequences in case file gets loaded with non-utf-8-charset 792 | document.title = [ 793 | ( config.stats.bad ? "\u2716" : "\u2714" ), 794 | document.title.replace( /^[\u2714\u2716] /i, "" ) 795 | ].join( " " ); 796 | } 797 | 798 | // clear own sessionStorage items if all tests passed 799 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 800 | // `key` & `i` initialized at top of scope 801 | for ( i = 0; i < sessionStorage.length; i++ ) { 802 | key = sessionStorage.key( i++ ); 803 | if ( key.indexOf( "qunit-test-" ) === 0 ) { 804 | sessionStorage.removeItem( key ); 805 | } 806 | } 807 | } 808 | 809 | // scroll back to top to show results 810 | if ( window.scrollTo ) { 811 | window.scrollTo(0, 0); 812 | } 813 | 814 | runLoggingCallbacks( "done", QUnit, { 815 | failed: config.stats.bad, 816 | passed: passed, 817 | total: config.stats.all, 818 | runtime: runtime 819 | }); 820 | } 821 | 822 | /** @return Boolean: true if this test should be ran */ 823 | function validTest( test ) { 824 | var include, 825 | filter = config.filter && config.filter.toLowerCase(), 826 | module = config.module && config.module.toLowerCase(), 827 | fullName = (test.module + ": " + test.testName).toLowerCase(); 828 | 829 | // Internally-generated tests are always valid 830 | if ( test.callback && test.callback.validTest === validTest ) { 831 | delete test.callback.validTest; 832 | return true; 833 | } 834 | 835 | if ( config.testNumber ) { 836 | return test.testNumber === config.testNumber; 837 | } 838 | 839 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 840 | return false; 841 | } 842 | 843 | if ( !filter ) { 844 | return true; 845 | } 846 | 847 | include = filter.charAt( 0 ) !== "!"; 848 | if ( !include ) { 849 | filter = filter.slice( 1 ); 850 | } 851 | 852 | // If the filter matches, we need to honour include 853 | if ( fullName.indexOf( filter ) !== -1 ) { 854 | return include; 855 | } 856 | 857 | // Otherwise, do the opposite 858 | return !include; 859 | } 860 | 861 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 862 | // Later Safari and IE10 are supposed to support error.stack as well 863 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 864 | function extractStacktrace( e, offset ) { 865 | offset = offset === undefined ? 3 : offset; 866 | 867 | var stack, include, i; 868 | 869 | if ( e.stacktrace ) { 870 | // Opera 871 | return e.stacktrace.split( "\n" )[ offset + 3 ]; 872 | } else if ( e.stack ) { 873 | // Firefox, Chrome 874 | stack = e.stack.split( "\n" ); 875 | if (/^error$/i.test( stack[0] ) ) { 876 | stack.shift(); 877 | } 878 | if ( fileName ) { 879 | include = []; 880 | for ( i = offset; i < stack.length; i++ ) { 881 | if ( stack[ i ].indexOf( fileName ) !== -1 ) { 882 | break; 883 | } 884 | include.push( stack[ i ] ); 885 | } 886 | if ( include.length ) { 887 | return include.join( "\n" ); 888 | } 889 | } 890 | return stack[ offset ]; 891 | } else if ( e.sourceURL ) { 892 | // Safari, PhantomJS 893 | // hopefully one day Safari provides actual stacktraces 894 | // exclude useless self-reference for generated Error objects 895 | if ( /qunit.js$/.test( e.sourceURL ) ) { 896 | return; 897 | } 898 | // for actual exceptions, this is useful 899 | return e.sourceURL + ":" + e.line; 900 | } 901 | } 902 | function sourceFromStacktrace( offset ) { 903 | try { 904 | throw new Error(); 905 | } catch ( e ) { 906 | return extractStacktrace( e, offset ); 907 | } 908 | } 909 | 910 | /** 911 | * Escape text for attribute or text content. 912 | */ 913 | function escapeText( s ) { 914 | if ( !s ) { 915 | return ""; 916 | } 917 | s = s + ""; 918 | // Both single quotes and double quotes (for attributes) 919 | return s.replace( /['"<>&]/g, function( s ) { 920 | switch( s ) { 921 | case "'": 922 | return "'"; 923 | case "\"": 924 | return """; 925 | case "<": 926 | return "<"; 927 | case ">": 928 | return ">"; 929 | case "&": 930 | return "&"; 931 | } 932 | }); 933 | } 934 | 935 | function synchronize( callback, last ) { 936 | config.queue.push( callback ); 937 | 938 | if ( config.autorun && !config.blocking ) { 939 | process( last ); 940 | } 941 | } 942 | 943 | function process( last ) { 944 | function next() { 945 | process( last ); 946 | } 947 | var start = new Date().getTime(); 948 | config.depth = config.depth ? config.depth + 1 : 1; 949 | 950 | while ( config.queue.length && !config.blocking ) { 951 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 952 | config.queue.shift()(); 953 | } else { 954 | setTimeout( next, 13 ); 955 | break; 956 | } 957 | } 958 | config.depth--; 959 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 960 | done(); 961 | } 962 | } 963 | 964 | function saveGlobal() { 965 | config.pollution = []; 966 | 967 | if ( config.noglobals ) { 968 | for ( var key in window ) { 969 | if ( hasOwn.call( window, key ) ) { 970 | // in Opera sometimes DOM element ids show up here, ignore them 971 | if ( /^qunit-test-output/.test( key ) ) { 972 | continue; 973 | } 974 | config.pollution.push( key ); 975 | } 976 | } 977 | } 978 | } 979 | 980 | function checkPollution() { 981 | var newGlobals, 982 | deletedGlobals, 983 | old = config.pollution; 984 | 985 | saveGlobal(); 986 | 987 | newGlobals = diff( config.pollution, old ); 988 | if ( newGlobals.length > 0 ) { 989 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 990 | } 991 | 992 | deletedGlobals = diff( old, config.pollution ); 993 | if ( deletedGlobals.length > 0 ) { 994 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 995 | } 996 | } 997 | 998 | // returns a new Array with the elements that are in a but not in b 999 | function diff( a, b ) { 1000 | var i, j, 1001 | result = a.slice(); 1002 | 1003 | for ( i = 0; i < result.length; i++ ) { 1004 | for ( j = 0; j < b.length; j++ ) { 1005 | if ( result[i] === b[j] ) { 1006 | result.splice( i, 1 ); 1007 | i--; 1008 | break; 1009 | } 1010 | } 1011 | } 1012 | return result; 1013 | } 1014 | 1015 | function extend( a, b ) { 1016 | for ( var prop in b ) { 1017 | if ( hasOwn.call( b, prop ) ) { 1018 | // Avoid "Member not found" error in IE8 caused by messing with window.constructor 1019 | if ( !( prop === "constructor" && a === window ) ) { 1020 | if ( b[ prop ] === undefined ) { 1021 | delete a[ prop ]; 1022 | } else { 1023 | a[ prop ] = b[ prop ]; 1024 | } 1025 | } 1026 | } 1027 | } 1028 | 1029 | return a; 1030 | } 1031 | 1032 | /** 1033 | * @param {HTMLElement} elem 1034 | * @param {string} type 1035 | * @param {Function} fn 1036 | */ 1037 | function addEvent( elem, type, fn ) { 1038 | if ( elem.addEventListener ) { 1039 | 1040 | // Standards-based browsers 1041 | elem.addEventListener( type, fn, false ); 1042 | } else if ( elem.attachEvent ) { 1043 | 1044 | // support: IE <9 1045 | elem.attachEvent( "on" + type, fn ); 1046 | } else { 1047 | 1048 | // Caller must ensure support for event listeners is present 1049 | throw new Error( "addEvent() was called in a context without event listener support" ); 1050 | } 1051 | } 1052 | 1053 | /** 1054 | * @param {Array|NodeList} elems 1055 | * @param {string} type 1056 | * @param {Function} fn 1057 | */ 1058 | function addEvents( elems, type, fn ) { 1059 | var i = elems.length; 1060 | while ( i-- ) { 1061 | addEvent( elems[i], type, fn ); 1062 | } 1063 | } 1064 | 1065 | function hasClass( elem, name ) { 1066 | return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; 1067 | } 1068 | 1069 | function addClass( elem, name ) { 1070 | if ( !hasClass( elem, name ) ) { 1071 | elem.className += (elem.className ? " " : "") + name; 1072 | } 1073 | } 1074 | 1075 | function removeClass( elem, name ) { 1076 | var set = " " + elem.className + " "; 1077 | // Class name may appear multiple times 1078 | while ( set.indexOf(" " + name + " ") > -1 ) { 1079 | set = set.replace(" " + name + " " , " "); 1080 | } 1081 | // If possible, trim it for prettiness, but not necessarily 1082 | elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); 1083 | } 1084 | 1085 | function id( name ) { 1086 | return defined.document && document.getElementById && document.getElementById( name ); 1087 | } 1088 | 1089 | function registerLoggingCallback( key ) { 1090 | return function( callback ) { 1091 | config[key].push( callback ); 1092 | }; 1093 | } 1094 | 1095 | // Supports deprecated method of completely overwriting logging callbacks 1096 | function runLoggingCallbacks( key, scope, args ) { 1097 | var i, callbacks; 1098 | if ( QUnit.hasOwnProperty( key ) ) { 1099 | QUnit[ key ].call(scope, args ); 1100 | } else { 1101 | callbacks = config[ key ]; 1102 | for ( i = 0; i < callbacks.length; i++ ) { 1103 | callbacks[ i ].call( scope, args ); 1104 | } 1105 | } 1106 | } 1107 | 1108 | // from jquery.js 1109 | function inArray( elem, array ) { 1110 | if ( array.indexOf ) { 1111 | return array.indexOf( elem ); 1112 | } 1113 | 1114 | for ( var i = 0, length = array.length; i < length; i++ ) { 1115 | if ( array[ i ] === elem ) { 1116 | return i; 1117 | } 1118 | } 1119 | 1120 | return -1; 1121 | } 1122 | 1123 | function Test( settings ) { 1124 | extend( this, settings ); 1125 | this.assertions = []; 1126 | this.testNumber = ++Test.count; 1127 | } 1128 | 1129 | Test.count = 0; 1130 | 1131 | Test.prototype = { 1132 | init: function() { 1133 | var a, b, li, 1134 | tests = id( "qunit-tests" ); 1135 | 1136 | if ( tests ) { 1137 | b = document.createElement( "strong" ); 1138 | b.innerHTML = this.nameHtml; 1139 | 1140 | // `a` initialized at top of scope 1141 | a = document.createElement( "a" ); 1142 | a.innerHTML = "Rerun"; 1143 | a.href = QUnit.url({ testNumber: this.testNumber }); 1144 | 1145 | li = document.createElement( "li" ); 1146 | li.appendChild( b ); 1147 | li.appendChild( a ); 1148 | li.className = "running"; 1149 | li.id = this.id = "qunit-test-output" + testId++; 1150 | 1151 | tests.appendChild( li ); 1152 | } 1153 | }, 1154 | setup: function() { 1155 | if ( 1156 | // Emit moduleStart when we're switching from one module to another 1157 | this.module !== config.previousModule || 1158 | // They could be equal (both undefined) but if the previousModule property doesn't 1159 | // yet exist it means this is the first test in a suite that isn't wrapped in a 1160 | // module, in which case we'll just emit a moduleStart event for 'undefined'. 1161 | // Without this, reporters can get testStart before moduleStart which is a problem. 1162 | !hasOwn.call( config, "previousModule" ) 1163 | ) { 1164 | if ( hasOwn.call( config, "previousModule" ) ) { 1165 | runLoggingCallbacks( "moduleDone", QUnit, { 1166 | name: config.previousModule, 1167 | failed: config.moduleStats.bad, 1168 | passed: config.moduleStats.all - config.moduleStats.bad, 1169 | total: config.moduleStats.all 1170 | }); 1171 | } 1172 | config.previousModule = this.module; 1173 | config.moduleStats = { all: 0, bad: 0 }; 1174 | runLoggingCallbacks( "moduleStart", QUnit, { 1175 | name: this.module 1176 | }); 1177 | } 1178 | 1179 | config.current = this; 1180 | 1181 | this.testEnvironment = extend({ 1182 | setup: function() {}, 1183 | teardown: function() {} 1184 | }, this.moduleTestEnvironment ); 1185 | 1186 | this.started = +new Date(); 1187 | runLoggingCallbacks( "testStart", QUnit, { 1188 | name: this.testName, 1189 | module: this.module 1190 | }); 1191 | 1192 | /*jshint camelcase:false */ 1193 | 1194 | 1195 | /** 1196 | * Expose the current test environment. 1197 | * 1198 | * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. 1199 | */ 1200 | QUnit.current_testEnvironment = this.testEnvironment; 1201 | 1202 | /*jshint camelcase:true */ 1203 | 1204 | if ( !config.pollution ) { 1205 | saveGlobal(); 1206 | } 1207 | if ( config.notrycatch ) { 1208 | this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 1209 | return; 1210 | } 1211 | try { 1212 | this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 1213 | } catch( e ) { 1214 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 1215 | } 1216 | }, 1217 | run: function() { 1218 | config.current = this; 1219 | 1220 | var running = id( "qunit-testresult" ); 1221 | 1222 | if ( running ) { 1223 | running.innerHTML = "Running:
    " + this.nameHtml; 1224 | } 1225 | 1226 | if ( this.async ) { 1227 | QUnit.stop(); 1228 | } 1229 | 1230 | this.callbackStarted = +new Date(); 1231 | 1232 | if ( config.notrycatch ) { 1233 | this.callback.call( this.testEnvironment, QUnit.assert ); 1234 | this.callbackRuntime = +new Date() - this.callbackStarted; 1235 | return; 1236 | } 1237 | 1238 | try { 1239 | this.callback.call( this.testEnvironment, QUnit.assert ); 1240 | this.callbackRuntime = +new Date() - this.callbackStarted; 1241 | } catch( e ) { 1242 | this.callbackRuntime = +new Date() - this.callbackStarted; 1243 | 1244 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 1245 | // else next test will carry the responsibility 1246 | saveGlobal(); 1247 | 1248 | // Restart the tests if they're blocking 1249 | if ( config.blocking ) { 1250 | QUnit.start(); 1251 | } 1252 | } 1253 | }, 1254 | teardown: function() { 1255 | config.current = this; 1256 | if ( config.notrycatch ) { 1257 | if ( typeof this.callbackRuntime === "undefined" ) { 1258 | this.callbackRuntime = +new Date() - this.callbackStarted; 1259 | } 1260 | this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 1261 | return; 1262 | } else { 1263 | try { 1264 | this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 1265 | } catch( e ) { 1266 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 1267 | } 1268 | } 1269 | checkPollution(); 1270 | }, 1271 | finish: function() { 1272 | config.current = this; 1273 | if ( config.requireExpects && this.expected === null ) { 1274 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 1275 | } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 1276 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 1277 | } else if ( this.expected === null && !this.assertions.length ) { 1278 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 1279 | } 1280 | 1281 | var i, assertion, a, b, time, li, ol, 1282 | test = this, 1283 | good = 0, 1284 | bad = 0, 1285 | tests = id( "qunit-tests" ); 1286 | 1287 | this.runtime = +new Date() - this.started; 1288 | config.stats.all += this.assertions.length; 1289 | config.moduleStats.all += this.assertions.length; 1290 | 1291 | if ( tests ) { 1292 | ol = document.createElement( "ol" ); 1293 | ol.className = "qunit-assert-list"; 1294 | 1295 | for ( i = 0; i < this.assertions.length; i++ ) { 1296 | assertion = this.assertions[i]; 1297 | 1298 | li = document.createElement( "li" ); 1299 | li.className = assertion.result ? "pass" : "fail"; 1300 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 1301 | ol.appendChild( li ); 1302 | 1303 | if ( assertion.result ) { 1304 | good++; 1305 | } else { 1306 | bad++; 1307 | config.stats.bad++; 1308 | config.moduleStats.bad++; 1309 | } 1310 | } 1311 | 1312 | // store result when possible 1313 | if ( QUnit.config.reorder && defined.sessionStorage ) { 1314 | if ( bad ) { 1315 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 1316 | } else { 1317 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 1318 | } 1319 | } 1320 | 1321 | if ( bad === 0 ) { 1322 | addClass( ol, "qunit-collapsed" ); 1323 | } 1324 | 1325 | // `b` initialized at top of scope 1326 | b = document.createElement( "strong" ); 1327 | b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 1328 | 1329 | addEvent(b, "click", function() { 1330 | var next = b.parentNode.lastChild, 1331 | collapsed = hasClass( next, "qunit-collapsed" ); 1332 | ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); 1333 | }); 1334 | 1335 | addEvent(b, "dblclick", function( e ) { 1336 | var target = e && e.target ? e.target : window.event.srcElement; 1337 | if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { 1338 | target = target.parentNode; 1339 | } 1340 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 1341 | window.location = QUnit.url({ testNumber: test.testNumber }); 1342 | } 1343 | }); 1344 | 1345 | // `time` initialized at top of scope 1346 | time = document.createElement( "span" ); 1347 | time.className = "runtime"; 1348 | time.innerHTML = this.runtime + " ms"; 1349 | 1350 | // `li` initialized at top of scope 1351 | li = id( this.id ); 1352 | li.className = bad ? "fail" : "pass"; 1353 | li.removeChild( li.firstChild ); 1354 | a = li.firstChild; 1355 | li.appendChild( b ); 1356 | li.appendChild( a ); 1357 | li.appendChild( time ); 1358 | li.appendChild( ol ); 1359 | 1360 | } else { 1361 | for ( i = 0; i < this.assertions.length; i++ ) { 1362 | if ( !this.assertions[i].result ) { 1363 | bad++; 1364 | config.stats.bad++; 1365 | config.moduleStats.bad++; 1366 | } 1367 | } 1368 | } 1369 | 1370 | runLoggingCallbacks( "testDone", QUnit, { 1371 | name: this.testName, 1372 | module: this.module, 1373 | failed: bad, 1374 | passed: this.assertions.length - bad, 1375 | total: this.assertions.length, 1376 | runtime: this.runtime, 1377 | // DEPRECATED: this property will be removed in 2.0.0, use runtime instead 1378 | duration: this.runtime, 1379 | }); 1380 | 1381 | QUnit.reset(); 1382 | 1383 | config.current = undefined; 1384 | }, 1385 | 1386 | queue: function() { 1387 | var bad, 1388 | test = this; 1389 | 1390 | synchronize(function() { 1391 | test.init(); 1392 | }); 1393 | function run() { 1394 | // each of these can by async 1395 | synchronize(function() { 1396 | test.setup(); 1397 | }); 1398 | synchronize(function() { 1399 | test.run(); 1400 | }); 1401 | synchronize(function() { 1402 | test.teardown(); 1403 | }); 1404 | synchronize(function() { 1405 | test.finish(); 1406 | }); 1407 | } 1408 | 1409 | // `bad` initialized at top of scope 1410 | // defer when previous test run passed, if storage is available 1411 | bad = QUnit.config.reorder && defined.sessionStorage && 1412 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 1413 | 1414 | if ( bad ) { 1415 | run(); 1416 | } else { 1417 | synchronize( run, true ); 1418 | } 1419 | } 1420 | }; 1421 | 1422 | // `assert` initialized at top of scope 1423 | // Assert helpers 1424 | // All of these must either call QUnit.push() or manually do: 1425 | // - runLoggingCallbacks( "log", .. ); 1426 | // - config.current.assertions.push({ .. }); 1427 | assert = QUnit.assert = { 1428 | /** 1429 | * Asserts rough true-ish result. 1430 | * @name ok 1431 | * @function 1432 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 1433 | */ 1434 | ok: function( result, msg ) { 1435 | if ( !config.current ) { 1436 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 1437 | } 1438 | result = !!result; 1439 | msg = msg || ( result ? "okay" : "failed" ); 1440 | 1441 | var source, 1442 | details = { 1443 | module: config.current.module, 1444 | name: config.current.testName, 1445 | result: result, 1446 | message: msg 1447 | }; 1448 | 1449 | msg = "" + escapeText( msg ) + ""; 1450 | 1451 | if ( !result ) { 1452 | source = sourceFromStacktrace( 2 ); 1453 | if ( source ) { 1454 | details.source = source; 1455 | msg += "
    Source:
    " +
    1456 | 					escapeText( source ) +
    1457 | 					"
    "; 1458 | } 1459 | } 1460 | runLoggingCallbacks( "log", QUnit, details ); 1461 | config.current.assertions.push({ 1462 | result: result, 1463 | message: msg 1464 | }); 1465 | }, 1466 | 1467 | /** 1468 | * Assert that the first two arguments are equal, with an optional message. 1469 | * Prints out both actual and expected values. 1470 | * @name equal 1471 | * @function 1472 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 1473 | */ 1474 | equal: function( actual, expected, message ) { 1475 | /*jshint eqeqeq:false */ 1476 | QUnit.push( expected == actual, actual, expected, message ); 1477 | }, 1478 | 1479 | /** 1480 | * @name notEqual 1481 | * @function 1482 | */ 1483 | notEqual: function( actual, expected, message ) { 1484 | /*jshint eqeqeq:false */ 1485 | QUnit.push( expected != actual, actual, expected, message ); 1486 | }, 1487 | 1488 | /** 1489 | * @name propEqual 1490 | * @function 1491 | */ 1492 | propEqual: function( actual, expected, message ) { 1493 | actual = objectValues(actual); 1494 | expected = objectValues(expected); 1495 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 1496 | }, 1497 | 1498 | /** 1499 | * @name notPropEqual 1500 | * @function 1501 | */ 1502 | notPropEqual: function( actual, expected, message ) { 1503 | actual = objectValues(actual); 1504 | expected = objectValues(expected); 1505 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 1506 | }, 1507 | 1508 | /** 1509 | * @name deepEqual 1510 | * @function 1511 | */ 1512 | deepEqual: function( actual, expected, message ) { 1513 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 1514 | }, 1515 | 1516 | /** 1517 | * @name notDeepEqual 1518 | * @function 1519 | */ 1520 | notDeepEqual: function( actual, expected, message ) { 1521 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 1522 | }, 1523 | 1524 | /** 1525 | * @name strictEqual 1526 | * @function 1527 | */ 1528 | strictEqual: function( actual, expected, message ) { 1529 | QUnit.push( expected === actual, actual, expected, message ); 1530 | }, 1531 | 1532 | /** 1533 | * @name notStrictEqual 1534 | * @function 1535 | */ 1536 | notStrictEqual: function( actual, expected, message ) { 1537 | QUnit.push( expected !== actual, actual, expected, message ); 1538 | }, 1539 | 1540 | "throws": function( block, expected, message ) { 1541 | var actual, 1542 | expectedOutput = expected, 1543 | ok = false; 1544 | 1545 | // 'expected' is optional 1546 | if ( typeof expected === "string" ) { 1547 | message = expected; 1548 | expected = null; 1549 | } 1550 | 1551 | config.current.ignoreGlobalErrors = true; 1552 | try { 1553 | block.call( config.current.testEnvironment ); 1554 | } catch (e) { 1555 | actual = e; 1556 | } 1557 | config.current.ignoreGlobalErrors = false; 1558 | 1559 | if ( actual ) { 1560 | // we don't want to validate thrown error 1561 | if ( !expected ) { 1562 | ok = true; 1563 | expectedOutput = null; 1564 | // expected is a regexp 1565 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 1566 | ok = expected.test( errorString( actual ) ); 1567 | // expected is a constructor 1568 | } else if ( actual instanceof expected ) { 1569 | ok = true; 1570 | // expected is a validation function which returns true is validation passed 1571 | } else if ( expected.call( {}, actual ) === true ) { 1572 | expectedOutput = null; 1573 | ok = true; 1574 | } 1575 | 1576 | QUnit.push( ok, actual, expectedOutput, message ); 1577 | } else { 1578 | QUnit.pushFailure( message, null, "No exception was thrown." ); 1579 | } 1580 | } 1581 | }; 1582 | 1583 | /** 1584 | * @deprecated since 1.8.0 1585 | * Kept assertion helpers in root for backwards compatibility. 1586 | */ 1587 | extend( QUnit.constructor.prototype, assert ); 1588 | 1589 | /** 1590 | * @deprecated since 1.9.0 1591 | * Kept to avoid TypeErrors for undefined methods. 1592 | */ 1593 | QUnit.constructor.prototype.raises = function() { 1594 | QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); 1595 | }; 1596 | 1597 | /** 1598 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 1599 | * Kept to avoid TypeErrors for undefined methods. 1600 | */ 1601 | QUnit.constructor.prototype.equals = function() { 1602 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 1603 | }; 1604 | QUnit.constructor.prototype.same = function() { 1605 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 1606 | }; 1607 | 1608 | // Test for equality any JavaScript type. 1609 | // Author: Philippe Rathé 1610 | QUnit.equiv = (function() { 1611 | 1612 | // Call the o related callback with the given arguments. 1613 | function bindCallbacks( o, callbacks, args ) { 1614 | var prop = QUnit.objectType( o ); 1615 | if ( prop ) { 1616 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1617 | return callbacks[ prop ].apply( callbacks, args ); 1618 | } else { 1619 | return callbacks[ prop ]; // or undefined 1620 | } 1621 | } 1622 | } 1623 | 1624 | // the real equiv function 1625 | var innerEquiv, 1626 | // stack to decide between skip/abort functions 1627 | callers = [], 1628 | // stack to avoiding loops from circular referencing 1629 | parents = [], 1630 | parentsB = [], 1631 | 1632 | getProto = Object.getPrototypeOf || function ( obj ) { 1633 | /*jshint camelcase:false */ 1634 | return obj.__proto__; 1635 | }, 1636 | callbacks = (function () { 1637 | 1638 | // for string, boolean, number and null 1639 | function useStrictEquality( b, a ) { 1640 | /*jshint eqeqeq:false */ 1641 | if ( b instanceof a.constructor || a instanceof b.constructor ) { 1642 | // to catch short annotation VS 'new' annotation of a 1643 | // declaration 1644 | // e.g. var i = 1; 1645 | // var j = new Number(1); 1646 | return a == b; 1647 | } else { 1648 | return a === b; 1649 | } 1650 | } 1651 | 1652 | return { 1653 | "string": useStrictEquality, 1654 | "boolean": useStrictEquality, 1655 | "number": useStrictEquality, 1656 | "null": useStrictEquality, 1657 | "undefined": useStrictEquality, 1658 | 1659 | "nan": function( b ) { 1660 | return isNaN( b ); 1661 | }, 1662 | 1663 | "date": function( b, a ) { 1664 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1665 | }, 1666 | 1667 | "regexp": function( b, a ) { 1668 | return QUnit.objectType( b ) === "regexp" && 1669 | // the regex itself 1670 | a.source === b.source && 1671 | // and its modifiers 1672 | a.global === b.global && 1673 | // (gmi) ... 1674 | a.ignoreCase === b.ignoreCase && 1675 | a.multiline === b.multiline && 1676 | a.sticky === b.sticky; 1677 | }, 1678 | 1679 | // - skip when the property is a method of an instance (OOP) 1680 | // - abort otherwise, 1681 | // initial === would have catch identical references anyway 1682 | "function": function() { 1683 | var caller = callers[callers.length - 1]; 1684 | return caller !== Object && typeof caller !== "undefined"; 1685 | }, 1686 | 1687 | "array": function( b, a ) { 1688 | var i, j, len, loop, aCircular, bCircular; 1689 | 1690 | // b could be an object literal here 1691 | if ( QUnit.objectType( b ) !== "array" ) { 1692 | return false; 1693 | } 1694 | 1695 | len = a.length; 1696 | if ( len !== b.length ) { 1697 | // safe and faster 1698 | return false; 1699 | } 1700 | 1701 | // track reference to avoid circular references 1702 | parents.push( a ); 1703 | parentsB.push( b ); 1704 | for ( i = 0; i < len; i++ ) { 1705 | loop = false; 1706 | for ( j = 0; j < parents.length; j++ ) { 1707 | aCircular = parents[j] === a[i]; 1708 | bCircular = parentsB[j] === b[i]; 1709 | if ( aCircular || bCircular ) { 1710 | if ( a[i] === b[i] || aCircular && bCircular ) { 1711 | loop = true; 1712 | } else { 1713 | parents.pop(); 1714 | parentsB.pop(); 1715 | return false; 1716 | } 1717 | } 1718 | } 1719 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1720 | parents.pop(); 1721 | parentsB.pop(); 1722 | return false; 1723 | } 1724 | } 1725 | parents.pop(); 1726 | parentsB.pop(); 1727 | return true; 1728 | }, 1729 | 1730 | "object": function( b, a ) { 1731 | /*jshint forin:false */ 1732 | var i, j, loop, aCircular, bCircular, 1733 | // Default to true 1734 | eq = true, 1735 | aProperties = [], 1736 | bProperties = []; 1737 | 1738 | // comparing constructors is more strict than using 1739 | // instanceof 1740 | if ( a.constructor !== b.constructor ) { 1741 | // Allow objects with no prototype to be equivalent to 1742 | // objects with Object as their constructor. 1743 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1744 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1745 | return false; 1746 | } 1747 | } 1748 | 1749 | // stack constructor before traversing properties 1750 | callers.push( a.constructor ); 1751 | 1752 | // track reference to avoid circular references 1753 | parents.push( a ); 1754 | parentsB.push( b ); 1755 | 1756 | // be strict: don't ensure hasOwnProperty and go deep 1757 | for ( i in a ) { 1758 | loop = false; 1759 | for ( j = 0; j < parents.length; j++ ) { 1760 | aCircular = parents[j] === a[i]; 1761 | bCircular = parentsB[j] === b[i]; 1762 | if ( aCircular || bCircular ) { 1763 | if ( a[i] === b[i] || aCircular && bCircular ) { 1764 | loop = true; 1765 | } else { 1766 | eq = false; 1767 | break; 1768 | } 1769 | } 1770 | } 1771 | aProperties.push(i); 1772 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1773 | eq = false; 1774 | break; 1775 | } 1776 | } 1777 | 1778 | parents.pop(); 1779 | parentsB.pop(); 1780 | callers.pop(); // unstack, we are done 1781 | 1782 | for ( i in b ) { 1783 | bProperties.push( i ); // collect b's properties 1784 | } 1785 | 1786 | // Ensures identical properties name 1787 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1788 | } 1789 | }; 1790 | }()); 1791 | 1792 | innerEquiv = function() { // can take multiple arguments 1793 | var args = [].slice.apply( arguments ); 1794 | if ( args.length < 2 ) { 1795 | return true; // end transition 1796 | } 1797 | 1798 | return (function( a, b ) { 1799 | if ( a === b ) { 1800 | return true; // catch the most you can 1801 | } else if ( a === null || b === null || typeof a === "undefined" || 1802 | typeof b === "undefined" || 1803 | QUnit.objectType(a) !== QUnit.objectType(b) ) { 1804 | return false; // don't lose time with error prone cases 1805 | } else { 1806 | return bindCallbacks(a, callbacks, [ b, a ]); 1807 | } 1808 | 1809 | // apply transition with (1..n) arguments 1810 | }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); 1811 | }; 1812 | 1813 | return innerEquiv; 1814 | }()); 1815 | 1816 | /** 1817 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1818 | * http://flesler.blogspot.com Licensed under BSD 1819 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1820 | * 1821 | * @projectDescription Advanced and extensible data dumping for Javascript. 1822 | * @version 1.0.0 1823 | * @author Ariel Flesler 1824 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1825 | */ 1826 | QUnit.jsDump = (function() { 1827 | function quote( str ) { 1828 | return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; 1829 | } 1830 | function literal( o ) { 1831 | return o + ""; 1832 | } 1833 | function join( pre, arr, post ) { 1834 | var s = jsDump.separator(), 1835 | base = jsDump.indent(), 1836 | inner = jsDump.indent(1); 1837 | if ( arr.join ) { 1838 | arr = arr.join( "," + s + inner ); 1839 | } 1840 | if ( !arr ) { 1841 | return pre + post; 1842 | } 1843 | return [ pre, inner + arr, base + post ].join(s); 1844 | } 1845 | function array( arr, stack ) { 1846 | var i = arr.length, ret = new Array(i); 1847 | this.up(); 1848 | while ( i-- ) { 1849 | ret[i] = this.parse( arr[i] , undefined , stack); 1850 | } 1851 | this.down(); 1852 | return join( "[", ret, "]" ); 1853 | } 1854 | 1855 | var reName = /^function (\w+)/, 1856 | jsDump = { 1857 | // type is used mostly internally, you can fix a (custom)type in advance 1858 | parse: function( obj, type, stack ) { 1859 | stack = stack || [ ]; 1860 | var inStack, res, 1861 | parser = this.parsers[ type || this.typeOf(obj) ]; 1862 | 1863 | type = typeof parser; 1864 | inStack = inArray( obj, stack ); 1865 | 1866 | if ( inStack !== -1 ) { 1867 | return "recursion(" + (inStack - stack.length) + ")"; 1868 | } 1869 | if ( type === "function" ) { 1870 | stack.push( obj ); 1871 | res = parser.call( this, obj, stack ); 1872 | stack.pop(); 1873 | return res; 1874 | } 1875 | return ( type === "string" ) ? parser : this.parsers.error; 1876 | }, 1877 | typeOf: function( obj ) { 1878 | var type; 1879 | if ( obj === null ) { 1880 | type = "null"; 1881 | } else if ( typeof obj === "undefined" ) { 1882 | type = "undefined"; 1883 | } else if ( QUnit.is( "regexp", obj) ) { 1884 | type = "regexp"; 1885 | } else if ( QUnit.is( "date", obj) ) { 1886 | type = "date"; 1887 | } else if ( QUnit.is( "function", obj) ) { 1888 | type = "function"; 1889 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1890 | type = "window"; 1891 | } else if ( obj.nodeType === 9 ) { 1892 | type = "document"; 1893 | } else if ( obj.nodeType ) { 1894 | type = "node"; 1895 | } else if ( 1896 | // native arrays 1897 | toString.call( obj ) === "[object Array]" || 1898 | // NodeList objects 1899 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1900 | ) { 1901 | type = "array"; 1902 | } else if ( obj.constructor === Error.prototype.constructor ) { 1903 | type = "error"; 1904 | } else { 1905 | type = typeof obj; 1906 | } 1907 | return type; 1908 | }, 1909 | separator: function() { 1910 | return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; 1911 | }, 1912 | // extra can be a number, shortcut for increasing-calling-decreasing 1913 | indent: function( extra ) { 1914 | if ( !this.multiline ) { 1915 | return ""; 1916 | } 1917 | var chr = this.indentChar; 1918 | if ( this.HTML ) { 1919 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1920 | } 1921 | return new Array( this.depth + ( extra || 0 ) ).join(chr); 1922 | }, 1923 | up: function( a ) { 1924 | this.depth += a || 1; 1925 | }, 1926 | down: function( a ) { 1927 | this.depth -= a || 1; 1928 | }, 1929 | setParser: function( name, parser ) { 1930 | this.parsers[name] = parser; 1931 | }, 1932 | // The next 3 are exposed so you can use them 1933 | quote: quote, 1934 | literal: literal, 1935 | join: join, 1936 | // 1937 | depth: 1, 1938 | // This is the list of parsers, to modify them, use jsDump.setParser 1939 | parsers: { 1940 | window: "[Window]", 1941 | document: "[Document]", 1942 | error: function(error) { 1943 | return "Error(\"" + error.message + "\")"; 1944 | }, 1945 | unknown: "[Unknown]", 1946 | "null": "null", 1947 | "undefined": "undefined", 1948 | "function": function( fn ) { 1949 | var ret = "function", 1950 | // functions never have name in IE 1951 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; 1952 | 1953 | if ( name ) { 1954 | ret += " " + name; 1955 | } 1956 | ret += "( "; 1957 | 1958 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 1959 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 1960 | }, 1961 | array: array, 1962 | nodelist: array, 1963 | "arguments": array, 1964 | object: function( map, stack ) { 1965 | /*jshint forin:false */ 1966 | var ret = [ ], keys, key, val, i; 1967 | QUnit.jsDump.up(); 1968 | keys = []; 1969 | for ( key in map ) { 1970 | keys.push( key ); 1971 | } 1972 | keys.sort(); 1973 | for ( i = 0; i < keys.length; i++ ) { 1974 | key = keys[ i ]; 1975 | val = map[ key ]; 1976 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1977 | } 1978 | QUnit.jsDump.down(); 1979 | return join( "{", ret, "}" ); 1980 | }, 1981 | node: function( node ) { 1982 | var len, i, val, 1983 | open = QUnit.jsDump.HTML ? "<" : "<", 1984 | close = QUnit.jsDump.HTML ? ">" : ">", 1985 | tag = node.nodeName.toLowerCase(), 1986 | ret = open + tag, 1987 | attrs = node.attributes; 1988 | 1989 | if ( attrs ) { 1990 | for ( i = 0, len = attrs.length; i < len; i++ ) { 1991 | val = attrs[i].nodeValue; 1992 | // IE6 includes all attributes in .attributes, even ones not explicitly set. 1993 | // Those have values like undefined, null, 0, false, "" or "inherit". 1994 | if ( val && val !== "inherit" ) { 1995 | ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); 1996 | } 1997 | } 1998 | } 1999 | ret += close; 2000 | 2001 | // Show content of TextNode or CDATASection 2002 | if ( node.nodeType === 3 || node.nodeType === 4 ) { 2003 | ret += node.nodeValue; 2004 | } 2005 | 2006 | return ret + open + "/" + tag + close; 2007 | }, 2008 | // function calls it internally, it's the arguments part of the function 2009 | functionArgs: function( fn ) { 2010 | var args, 2011 | l = fn.length; 2012 | 2013 | if ( !l ) { 2014 | return ""; 2015 | } 2016 | 2017 | args = new Array(l); 2018 | while ( l-- ) { 2019 | // 97 is 'a' 2020 | args[l] = String.fromCharCode(97+l); 2021 | } 2022 | return " " + args.join( ", " ) + " "; 2023 | }, 2024 | // object calls it internally, the key part of an item in a map 2025 | key: quote, 2026 | // function calls it internally, it's the content of the function 2027 | functionCode: "[code]", 2028 | // node calls it internally, it's an html attribute value 2029 | attribute: quote, 2030 | string: quote, 2031 | date: quote, 2032 | regexp: literal, 2033 | number: literal, 2034 | "boolean": literal 2035 | }, 2036 | // if true, entities are escaped ( <, >, \t, space and \n ) 2037 | HTML: false, 2038 | // indentation unit 2039 | indentChar: " ", 2040 | // if true, items in a collection, are separated by a \n, else just a space. 2041 | multiline: true 2042 | }; 2043 | 2044 | return jsDump; 2045 | }()); 2046 | 2047 | /* 2048 | * Javascript Diff Algorithm 2049 | * By John Resig (http://ejohn.org/) 2050 | * Modified by Chu Alan "sprite" 2051 | * 2052 | * Released under the MIT license. 2053 | * 2054 | * More Info: 2055 | * http://ejohn.org/projects/javascript-diff-algorithm/ 2056 | * 2057 | * Usage: QUnit.diff(expected, actual) 2058 | * 2059 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" 2060 | */ 2061 | QUnit.diff = (function() { 2062 | /*jshint eqeqeq:false, eqnull:true */ 2063 | function diff( o, n ) { 2064 | var i, 2065 | ns = {}, 2066 | os = {}; 2067 | 2068 | for ( i = 0; i < n.length; i++ ) { 2069 | if ( !hasOwn.call( ns, n[i] ) ) { 2070 | ns[ n[i] ] = { 2071 | rows: [], 2072 | o: null 2073 | }; 2074 | } 2075 | ns[ n[i] ].rows.push( i ); 2076 | } 2077 | 2078 | for ( i = 0; i < o.length; i++ ) { 2079 | if ( !hasOwn.call( os, o[i] ) ) { 2080 | os[ o[i] ] = { 2081 | rows: [], 2082 | n: null 2083 | }; 2084 | } 2085 | os[ o[i] ].rows.push( i ); 2086 | } 2087 | 2088 | for ( i in ns ) { 2089 | if ( hasOwn.call( ns, i ) ) { 2090 | if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { 2091 | n[ ns[i].rows[0] ] = { 2092 | text: n[ ns[i].rows[0] ], 2093 | row: os[i].rows[0] 2094 | }; 2095 | o[ os[i].rows[0] ] = { 2096 | text: o[ os[i].rows[0] ], 2097 | row: ns[i].rows[0] 2098 | }; 2099 | } 2100 | } 2101 | } 2102 | 2103 | for ( i = 0; i < n.length - 1; i++ ) { 2104 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 2105 | n[ i + 1 ] == o[ n[i].row + 1 ] ) { 2106 | 2107 | n[ i + 1 ] = { 2108 | text: n[ i + 1 ], 2109 | row: n[i].row + 1 2110 | }; 2111 | o[ n[i].row + 1 ] = { 2112 | text: o[ n[i].row + 1 ], 2113 | row: i + 1 2114 | }; 2115 | } 2116 | } 2117 | 2118 | for ( i = n.length - 1; i > 0; i-- ) { 2119 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 2120 | n[ i - 1 ] == o[ n[i].row - 1 ]) { 2121 | 2122 | n[ i - 1 ] = { 2123 | text: n[ i - 1 ], 2124 | row: n[i].row - 1 2125 | }; 2126 | o[ n[i].row - 1 ] = { 2127 | text: o[ n[i].row - 1 ], 2128 | row: i - 1 2129 | }; 2130 | } 2131 | } 2132 | 2133 | return { 2134 | o: o, 2135 | n: n 2136 | }; 2137 | } 2138 | 2139 | return function( o, n ) { 2140 | o = o.replace( /\s+$/, "" ); 2141 | n = n.replace( /\s+$/, "" ); 2142 | 2143 | var i, pre, 2144 | str = "", 2145 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 2146 | oSpace = o.match(/\s+/g), 2147 | nSpace = n.match(/\s+/g); 2148 | 2149 | if ( oSpace == null ) { 2150 | oSpace = [ " " ]; 2151 | } 2152 | else { 2153 | oSpace.push( " " ); 2154 | } 2155 | 2156 | if ( nSpace == null ) { 2157 | nSpace = [ " " ]; 2158 | } 2159 | else { 2160 | nSpace.push( " " ); 2161 | } 2162 | 2163 | if ( out.n.length === 0 ) { 2164 | for ( i = 0; i < out.o.length; i++ ) { 2165 | str += "" + out.o[i] + oSpace[i] + ""; 2166 | } 2167 | } 2168 | else { 2169 | if ( out.n[0].text == null ) { 2170 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 2171 | str += "" + out.o[n] + oSpace[n] + ""; 2172 | } 2173 | } 2174 | 2175 | for ( i = 0; i < out.n.length; i++ ) { 2176 | if (out.n[i].text == null) { 2177 | str += "" + out.n[i] + nSpace[i] + ""; 2178 | } 2179 | else { 2180 | // `pre` initialized at top of scope 2181 | pre = ""; 2182 | 2183 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 2184 | pre += "" + out.o[n] + oSpace[n] + ""; 2185 | } 2186 | str += " " + out.n[i].text + nSpace[i] + pre; 2187 | } 2188 | } 2189 | } 2190 | 2191 | return str; 2192 | }; 2193 | }()); 2194 | 2195 | // For browser, export only select globals 2196 | if ( typeof window !== "undefined" ) { 2197 | extend( window, QUnit.constructor.prototype ); 2198 | window.QUnit = QUnit; 2199 | } 2200 | 2201 | // For CommonJS environments, export everything 2202 | if ( typeof module !== "undefined" && module.exports ) { 2203 | module.exports = QUnit; 2204 | } 2205 | 2206 | 2207 | // Get a reference to the global object, like window in browsers 2208 | }( (function() { 2209 | return this; 2210 | })() )); 2211 | -------------------------------------------------------------------------------- /test/qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/test_api.js: -------------------------------------------------------------------------------- 1 | module( 'api' ); 2 | 3 | test( 'constructor', function() { 4 | var api = new Api( 'https://example.com/api.php', 'en' ); 5 | equal( api._url, 'https://example.com/api.php', 'url value' ); 6 | equal( api._language, 'en', 'language value' ); 7 | } ); 8 | 9 | test( 'langauge', function() { 10 | var api = new Api( 'https://example.com/api.php', 'en' ); 11 | equal( api.language(), 'en', 'get language value' ); 12 | equal( api.language( 'de' ), 'de', 'get and set language value' ); 13 | equal( api._language, 'de', 'saved language value' ); 14 | } ); 15 | 16 | asyncTest( '_get', function() { 17 | expect( 1 ); 18 | 19 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 20 | api._get( { 21 | action: 'test' 22 | } ) 23 | .done( function( data ) { 24 | equal( data.error.info, 'Unrecognized value for parameter \'action\': test', 'error message' ); 25 | } ) 26 | .always( start ); 27 | } ); 28 | 29 | asyncTest( 'format datavalue', function() { 30 | expect( 2 ); 31 | 32 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 33 | var datavalue1 = {"value":{"time":"+00000002001-06-16T00:00:00Z","timezone":0,"before":0,"after":0,"precision":11,"calendarmodel":"http://www.wikidata.org/entity/Q1985727"},"type":"time"}; 34 | var datavalue2 = {"value":{"entity-type":"item","numeric-id":1208},"type":"wikibase-entityid"}; 35 | $.when( 36 | api.formatDatavalue( datavalue1 ), 37 | $.getJSON( 'https://www.wikidata.org/w/api.php?action=wbformatvalue&generate=text%2Fplain&datavalue=' + encodeURIComponent( JSON.stringify( datavalue1 ) ) + '&options=%7B%22lang%22%3A%22en%22%2C%22geoformat%22%3A%22dms%22%7D&format=json&callback=?' ), 38 | api.formatDatavalue( datavalue2 ), 39 | $.getJSON( 'https://www.wikidata.org/w/api.php?action=wbformatvalue&generate=text%2Fplain&datavalue=' + encodeURIComponent( JSON.stringify( datavalue2 ) ) + '&options=%7B%22lang%22%3A%22en%22%2C%22geoformat%22%3A%22dms%22%7D&format=json&callback=?') 40 | ) 41 | .then( function( data1, expected1, data2, expected2 ) { 42 | deepEqual( 43 | data1[0], 44 | expected1[0], 45 | 'time data value' 46 | ); 47 | deepEqual( 48 | data2[0], 49 | expected2[0], 50 | 'entity id data value' 51 | ); 52 | } ) 53 | .always( start ); 54 | } ); 55 | 56 | asyncTest( 'get claims', function() { 57 | expect( 1 ); 58 | 59 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 60 | $.when( 61 | api.getClaims( 'q76', 'p21' ), 62 | $.getJSON( 'https://www.wikidata.org/w/api.php?action=wbgetclaims&entity=q76&property=p21&format=json&callback=?' ) 63 | ) 64 | .then( function( data1, expected1 ) { 65 | deepEqual( 66 | data1[0], 67 | expected1[0], 68 | 'Checking result' 69 | ); 70 | } ) 71 | .always( start ); 72 | } ); 73 | 74 | asyncTest( 'get entities', function() { 75 | expect( 1 ); 76 | 77 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 78 | $.when( 79 | api.getEntities( 'q76', 'labels' ), 80 | $.getJSON( 'https://www.wikidata.org/w/api.php?action=wbgetentities&languages=en&ids=q76&props=labels&format=json&callback=?' ) 81 | ) 82 | .then( function( data1, expected1 ) { 83 | deepEqual( 84 | data1[0], 85 | expected1[0], 86 | 'Checking result' 87 | ); 88 | } ) 89 | .always( start ); 90 | } ); 91 | 92 | asyncTest( 'search entities', function() { 93 | expect( 2 ); 94 | 95 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 96 | $.when( 97 | api.searchEntities( 'item', 'United States' ), 98 | $.getJSON( 'https://www.wikidata.org/w/api.php?action=wbsearchentities&language=en&search=United+States&type=item&format=json&callback=?' ), 99 | api.searchEntities( 'property', 'location' ), 100 | $.getJSON( 'https://www.wikidata.org/w/api.php?action=wbsearchentities&language=en&search=location&type=property&format=json&callback=?' ) 101 | ) 102 | .then( function( data1, expected1, data2, expected2 ) { 103 | deepEqual( 104 | data1[0], 105 | expected1[0], 106 | 'Checking result' 107 | ); 108 | deepEqual( 109 | data2[0], 110 | expected2[0], 111 | 'Checking result' 112 | ); 113 | } ) 114 | .always( start ); 115 | } ); 116 | -------------------------------------------------------------------------------- /test/test_ask.js: -------------------------------------------------------------------------------- 1 | module( 'Ask' ); 2 | 3 | test( 'constructor', function() { 4 | var api = new Api( 'https://www.wikidata.org/w/api.php', 'en' ); 5 | var ask = new Ask( api ); 6 | equal( ask._api, api, 'api value' ); 7 | } ); 8 | 9 | asyncTest( 'search entity', function() { 10 | expect( 5 ); 11 | 12 | var ask = new Ask( new Api( 'https://www.wikidata.org/w/api.php', 'en' ) ); 13 | ask.entityChooser( function( type, entities ) { 14 | equal( type, 'item', 'Checking type' ); 15 | ok( true, 'Entity choser should be called when the query returns more than one value' ); 16 | return $.Deferred().resolve( entities[1].id ).promise(); 17 | } ); 18 | 19 | $.when( ask.searchEntity( 'item', 'asdfg' ) ) 20 | .then( null, function() { 21 | ok( true, 'No item should be found by searching for "asdfg"' ); 22 | return ask.searchEntity( 'item', 'Bayerische Motoren Werke' ); 23 | } ) 24 | .then( function( item ) { 25 | equal( item, 'Q26678', 'One item should be found by searching for "Bayerische Motoren Werke"' ); 26 | return ask.searchEntity( 'item', 'BMW' ); 27 | } ) 28 | .then( function( item ) { 29 | equal( item, 'Q564512', 'Several items should be found by searching for "BMW" and the second one should be choosen' ); 30 | } ) 31 | .always( start ); 32 | } ); 33 | 34 | asyncTest( 'get datavalues', function() { 35 | expect( 1 ); 36 | 37 | var ask = new Ask( new Api( 'https://www.wikidata.org/w/api.php', 'en' ) ); 38 | ask.getDatavalues( 'Q76', 'P21' ) 39 | .done( function( values ) { 40 | deepEqual( values, [ {"value":{"entity-type":"item","numeric-id":6581097},"type":"wikibase-entityid"} ], 'Check values' ); 41 | } ) 42 | .always( start ); 43 | } ); 44 | 45 | asyncTest( 'format datavalues', function() { 46 | expect( 4 ); 47 | 48 | var ask = new Ask( new Api( 'https://www.wikidata.org/w/api.php', 'en' ) ); 49 | $.when( ask.formatDatavalues( [] ) ) 50 | .then( null, function() { 51 | ok( true, 'If no property values are given it must fail' ); 52 | return ask.formatDatavalues( [{"value":{"entity-type":"item","numeric-id":1208},"type":"wikibase-entityid"},{"value":{"entity-type":"item","numeric-id":1201238},"type":"wikibase-entityid"}] ); 53 | } ) 54 | .then( function( values ) { 55 | // this test fails perhaps because travis does not know the "arguments" feature used in ask.js on line 40. Tips are welcome. 56 | deepEqual( values, [ 'Brandenburg', 'Q1201238' ], 'Check entity id value' ); 57 | return ask.formatDatavalues( [{"value":{"time":"+00000002001-06-16T00:00:00Z","timezone":0,"before":0,"after":0,"precision":11,"calendarmodel":"http://www.wikidata.org/entity/Q1985727"},"type":"time"}] ); 58 | } ) 59 | .then( function( values ) { 60 | deepEqual( values, [ '16 June 2001' ], 'Check time value' ); 61 | return ask.formatDatavalues( [{"value":{"latitude":52.516666666667,"longitude":13.383333333333,"altitude":null,"precision":0.016666666666667,"globe":"http://www.wikidata.org/entity/Q2"},"type":"globecoordinate"}] ); 62 | } ) 63 | .then( function( values ) { 64 | deepEqual( values, [ '52° 31\' 0", 13° 22\' 60"' ], 'Check globecoordinate value' ); 65 | } ) 66 | .always( start ); 67 | } ); -------------------------------------------------------------------------------- /test/test_parser.js: -------------------------------------------------------------------------------- 1 | module( 'Parser' ); 2 | 3 | test( 'constructor', function() { 4 | var parser = new Parser( 'en' ); 5 | equal( parser._language, 'en', 'language value' ); 6 | } ); 7 | 8 | test( 'language', function() { 9 | var parser = new Parser( 'en' ); 10 | equal( parser.language(), 'en', 'get language value' ); 11 | equal( parser.language( 'de' ), 'de', 'get and set language value' ); 12 | equal( parser._language, 'de', 'saved language value' ); 13 | } ); 14 | 15 | test( 'build answer', function() { 16 | var parser = new Parser( 'en' ); 17 | var format = '$abc $foo. $hij . $xxx; $bar'; 18 | var attributes = { 19 | 'abc': 'def', 20 | 'foo': 'bar', 21 | 'bar': 'foo' 22 | }; 23 | 24 | var answer = parser.buildAnswer( format, attributes ); 25 | equal( answer, 'Def bar. . ; foo', 'build a proper answer' ); 26 | } ); 27 | 28 | asyncTest( 'parse question (en)', function() { 29 | expect( 8 ); 30 | 31 | var parser = new Parser( 'en' ); 32 | var question1 = 'Who is Barack Obama?'; 33 | var question2 = 'Who are the presidents of the United States'; 34 | 35 | $.when( parser.parseQuestion( question1 ) ) 36 | .then( function( parsed1 ) { 37 | equal( parsed1.verb, 'is', 'verb' ); 38 | equal( parsed1.item, 'Barack Obama', 'item' ); 39 | return parser.parseQuestion( question2 ); 40 | } ) 41 | .then( function( parsed2 ) { 42 | equal( parsed2.verb, 'are', 'verb' ); 43 | equal( parsed2.article, 'the', 'article' ); 44 | equal( parsed2.property, 'presidents', 'property' ); 45 | equal( parsed2.possesive, 'of the', 'possesive' ); 46 | equal( parsed2.item, 'United States', 'item' ); 47 | return parser.parseQuestion( 'Foo bar' ); 48 | } ) 49 | .then( null, function() { 50 | ok( true, 'Invalid value' ); 51 | } ) 52 | .always( start ); 53 | } ); 54 | 55 | asyncTest( 'parse question (de)', function() { 56 | expect( 8 ); 57 | 58 | var parser = new Parser( 'de' ); 59 | var question1 = 'Wer ist Joachim Gauck?'; 60 | var question2 = 'Wer sind die Präsidenten der Bundesrepublik Deutschland'; 61 | 62 | $.when( parser.parseQuestion( question1 ) ) 63 | .then( function( parsed1 ) { 64 | equal( parsed1.verb, 'ist', 'verb' ); 65 | equal( parsed1.item, 'Joachim Gauck', 'item' ); 66 | return parser.parseQuestion( question2 ); 67 | } ) 68 | .then( function( parsed2 ) { 69 | equal( parsed2.verb, 'sind', 'verb' ); 70 | equal( parsed2.article, 'die', 'article' ); 71 | equal( parsed2.property, 'Präsidenten', 'property' ); 72 | equal( parsed2.possesive, 'der', 'possesive' ); 73 | equal( parsed2.item, 'Bundesrepublik Deutschland', 'item' ); 74 | return parser.parseQuestion( 'Foo bar' ); 75 | } ) 76 | .then( null, function() { 77 | ok( true, 'Invalid value' ); 78 | } ) 79 | .always( start ); 80 | } ); 81 | --------------------------------------------------------------------------------