pax_global_header00006660000000000000000000000064144352332050014513gustar00rootroot0000000000000052 comment=8c948f0999d0cf3cd0a00c4d35e18d92d54e8b6d goldendict-1.5.0/000077500000000000000000000000001443523320500136325ustar00rootroot00000000000000goldendict-1.5.0/.gitignore000066400000000000000000000007371443523320500156310ustar00rootroot00000000000000*.o *~ mouseover_win32/*.dll mouseover_win32/*.a locale/*.qm debug/ build/ release/ tmp/ goldendict winlibs/lib32-48/ Makefile Makefile.Debug Makefile.Release goldendict.pro.user goldendict.pro.user.* hotkeys.pro.user hotkeys.pro.user.* object_script.goldendict.Debug object_script.goldendict.Release version.txt .gitattributes /goldendict.app/ /GoldenDict.app/ *.dmg .DS_Store Info.plist GoldenDict.xcodeproj/ # visual studio files *.sdf *.opensdf *.suo *.vcxproj.user goldendict-1.5.0/.gitmodules000066400000000000000000000000001443523320500157750ustar00rootroot00000000000000goldendict-1.5.0/.travis.yml000066400000000000000000000041621443523320500157460ustar00rootroot00000000000000language: cpp compiler: gcc sudo: require dist: xenial before_install: # sudo add-apt-repository ppa:beineri/opt-qt593-trusty -y - sudo apt-get update -qq install: - | sudo apt-get -y install git pkg-config build-essential qt5-qmake \ libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev \ qtdeclarative5-dev libqtwebkit-dev libxtst-dev liblzo2-dev libbz2-dev \ libao-dev libavutil-dev libavformat-dev libswresample-dev libtiff5-dev libeb16-dev \ libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev \ qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins libopencc-dev liblzma-dev libzstd1-dev doxygen cmake # source /opt/qt*/bin/qt*-env.sh script: - git clone https://github.com/BYVoid/OpenCC - cd OpenCC/ - make PREFIX=/usr -j$(nproc) - sudo make install - cd .. - qmake CONFIG+=release PREFIX=/usr CONFIG+=old_hunspell CONFIG+=zim_support # CONFIG+=chinese_conversion_support - make -j$(nproc) - make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/ - wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" - chmod a+x linuxdeployqt-continuous-x86_64.AppImage - unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH - # Add ssl libraries to .Appimage - mkdir -p appdir/usr/lib/ - cp /lib/x86_64-linux-gnu/libssl.so.1.0.0 appdir/usr/lib/ - cp /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 appdir/usr/lib/ - # - export VERSION=$(git rev-parse --short HEAD) # linuxdeployqt uses this for naming the file - ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -appimage after_success: - find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq - # curl --upload-file GoldenDict*.AppImage https://transfer.sh/GoldenDict-git.$(git rev-parse --short HEAD)-x86_64.AppImage - wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh - bash upload.sh GoldenDict*.AppImage* branches: except: - # Do not build tags that we create when we upload to GitHub Releases - /^(?i:continuous)/ goldendict-1.5.0/CREDITS.txt000066400000000000000000000043731443523320500154770ustar00rootroot00000000000000Konstantin Isakov : Principal author of the program Jennie Petoumenou : Greek transliteration and Greek translation Mindaugas Baranauskas : Lithuanian translation Vit Pelcak : Czech translation Hero Phương , Trần Đình Anh Tuấn : Vietnamese translation Daniele Di Pisa : Italian translation Daniel Kaneider: German translation Zhang Jinsong : Simplified Chinese translation Usama Akkad , Linux Arab Community Team : Arabic translation Korostil Daniel : Ukrainian translation Grzegorz Karpowicz : Polish translation Nardog : Japanese translation Maha 吳寶原, Ray Chou 周邦信, Marcus Bingenheimer, 黃文龍 : Traditional Chinese translation Besmir Godole : Albanian translation Leonardo Montenegro , MCHAL , Alexandro Casanova : Brazilian Portuguese translation Julian Depetris Chauvin : Interface enhancements Yanina Weingast : Argentinian Spanish translation Tvangeste : Interface enhancements Zdenko Podobný : Slovak translation Şükrü Yekta Karabulut : Turkish translation Amos Batto : Quechua, Aymara and Bolivian Spanish translations Carlos A. Cortijo Bon : Spanish from Spain translation Victor Ibragimov : Tajik translation Maksim Tamkovič : Belarusian translation and transliteration VirtualTam : French translation Panho Ku : Korean translation Тим Перце : Serbian translation Cris van Minnen: Dutch translation Timon Wong : MDict (*.mdx/*.mdd) dictionary format PICTT Turkmenistan: Turkmen translation Nasrollah Noori : Persian translation Vladimir Gerovski: Macedonian Translation Nikolay Korotkiy : Finnish translation Robin Townsend: Lojban translation goldendict-1.5.0/GoldenDict.exe.manifest000066400000000000000000000011371443523320500201600ustar00rootroot00000000000000 goldendict-1.5.0/LICENSE.txt000066400000000000000000001056171443523320500154670ustar00rootroot00000000000000 GoldenDict, a dictionary lookup program. Copyright (C) 2008-2012 Konstantin Isakov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. The text of the license follows. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . goldendict-1.5.0/MSBuild/000077500000000000000000000000001443523320500151315ustar00rootroot00000000000000goldendict-1.5.0/MSBuild/QtExtensions/000077500000000000000000000000001443523320500175755ustar00rootroot00000000000000goldendict-1.5.0/MSBuild/QtExtensions/lrelease.props000066400000000000000000000016401443523320500224570ustar00rootroot00000000000000 %(Filename).qm false $(QTDIR)\bin\lrelease.exe %(LRELEASE) [AllOptions] [AdditionalOptions] [Inputs] $(BuildGenerateSourcesTargets);_QtTranslate goldendict-1.5.0/MSBuild/QtExtensions/lrelease.targets000066400000000000000000000056731443523320500227770ustar00rootroot00000000000000 QtTranslate $(MSBuildAllProjects);$(MSBuildThisFileFullPath) $(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml @(QtTranslate, '|') True %(QtTranslate.Identity) goldendict-1.5.0/MSBuild/QtExtensions/lrelease.xml000066400000000000000000000051101443523320500221100ustar00rootroot00000000000000 goldendict-1.5.0/MSBuild/QtExtensions/moc.props000066400000000000000000000054721443523320500214500ustar00rootroot00000000000000 %(Filename)%(Extension).$(Platform).$(Configuration).cpp false false false $(QTDIR)\bin\moc.exe %(MOC) [AllOptions] [AdditionalOptions] [Inputs] $(BuildGenerateSourcesTargets);_QtMOCCompile goldendict-1.5.0/MSBuild/QtExtensions/moc.targets000066400000000000000000000146161443523320500217560ustar00rootroot00000000000000 QtMOCCompile $(MSBuildAllProjects);$(MSBuildThisFileFullPath) $(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml true @(BuddyClCompile->'%(AdditionalIncludeDirectories)') @(BuddyClCompile->'%(PreprocessorDefinitions)') @(BuddyClCompile->'%(UndefinePreprocessorDefinitions)') @(QtMOCCompile, '|') True %(QtMOCCompile.Identity) TurnOffAllWarnings NotUsing %(QtMOCCompile.AdditionalIncludeDirectories);%(QtMOCCompile.DefaultIncludeDirectories) %(QtMOCCompile.DefaultPreprocessorDefinitions);%(QtMOCCompile.PreprocessorDefinitions) %(QtMOCCompile.DefaultUndefinePreprocessorDefinitions);%(QtMOCCompile.UndefinePreprocessorDefinitions) %(QtMOCCompile.ClCompileAssemblerListingLocation) %(QtMOCCompile.ClCompileObjectFileName) %(QtMOCCompile.ClCompileXMLDocumentationFileName) goldendict-1.5.0/MSBuild/QtExtensions/moc.xml000066400000000000000000000117241443523320500211020ustar00rootroot00000000000000 goldendict-1.5.0/MSBuild/QtExtensions/rcc.props000066400000000000000000000045231443523320500214350ustar00rootroot00000000000000 %(Filename)%(Extension).cpp %(Filename) -1 true false $(QTDIR)\bin\rcc.exe $(QTDIR)\bin\rcc.exe;$(MSBuildProjectFile) %(RCC) [AllOptions] [AdditionalOptions] [Inputs] $(BuildGenerateSourcesTargets);_QtRCCCompile goldendict-1.5.0/MSBuild/QtExtensions/rcc.targets000066400000000000000000000113121443523320500217350ustar00rootroot00000000000000 QtRCCCompile $(MSBuildAllProjects);$(MSBuildThisFileFullPath) $(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml @(QtRCCDeps);%(QtRCCCompile.AdditionalDependencies) @(QtRCCCompile, '|') True %(QtRCCCompile.Identity) TurnOffAllWarnings NotUsing %(QtRCCCompile.ClCompileAssemblerListingLocation) %(QtRCCCompile.ClCompileObjectFileName) %(QtRCCCompile.ClCompileXMLDocumentationFileName) goldendict-1.5.0/MSBuild/QtExtensions/rcc.xml000066400000000000000000000127611443523320500210750ustar00rootroot00000000000000 goldendict-1.5.0/MSBuild/QtExtensions/uic.props000066400000000000000000000020521443523320500214410ustar00rootroot00000000000000 %(Filename)%(Extension).h false $(QTDIR)\bin\uic.exe %(UIC) [AllOptions] [AdditionalOptions] [Inputs] $(BuildGenerateSourcesTargets);_QtUICCompile goldendict-1.5.0/MSBuild/QtExtensions/uic.targets000066400000000000000000000061161443523320500217540ustar00rootroot00000000000000 QtUICCompile $(MSBuildAllProjects);$(MSBuildThisFileFullPath) $(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml @(QtUICCompile, '|') True %(QtUICCompile.Identity) goldendict-1.5.0/MSBuild/QtExtensions/uic.xml000066400000000000000000000055721443523320500211100ustar00rootroot00000000000000 goldendict-1.5.0/MSBuild/custom.props000066400000000000000000000005511443523320500175310ustar00rootroot00000000000000 goldendict-1.5.0/MSBuild/custom.targets000066400000000000000000000031301443523320500200330ustar00rootroot00000000000000 $(ProjectDir)version.txt $(IntDir)version.txt $([System.IO.File]::ReadAllText($(VersionFile)).Trim()) $([System.IO.File]::ReadAllText($(NewVersionFile)).Trim()) goldendict-1.5.0/README.md000066400000000000000000000136251443523320500151200ustar00rootroot00000000000000## Introduction GoldenDict is a feature-rich dictionary lookup program, supporting multiple dictionary formats (StarDict/Babylon/Lingvo/Dictd/AARD/MDict/SDict) and online dictionaries, featuring perfect article rendering with the complete markup, illustrations and other content retained, and allowing you to type in words without any accents or correct case. ## Requirements This code has been run and tested on Windows XP/Vista/7, Ubuntu Linux, Mac OS X. ### External Deps * Make, GCC, Git * Qt framework. Minimum required version is 4.6. But Qt 4.7 or 4.8 is recommended. * Qt Creator IDE is recommended for development * Various libraries on Linux (png, zlib, etc) * On Mac and Windows all the libraries are already included in the repository ### Installing External Deps on Ubuntu Linux sudo apt-get install git pkg-config build-essential qt4-qmake \ libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev \ libqt4-dev libqtwebkit-dev libxtst-dev liblzo2-dev libbz2-dev \ libao-dev libavutil-dev libavformat-dev libtiff5-dev libeb16-dev #### Installing External Deps on Ubuntu Linux for Qt5 sudo apt-get install git pkg-config build-essential qt5-qmake \ libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev \ qtdeclarative5-dev libxtst-dev liblzo2-dev libbz2-dev \ libao-dev libavutil-dev libavformat-dev libtiff5-dev libeb16-dev \ libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev \ qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins ## How to build First, clone this repository, e.g.: git clone https://github.com/goldendict/goldendict.git And then invoke `qmake-qt4` and `make`: cd goldendict && qmake-qt4 && make In case when `qmake-qt4` does not exist, try using `qmake` but make sure it is indeed from the Qt 4 installation. On the other hand, if you want to use `qt5`, make sure that `qmake` is from Qt 5 installation. If not, you can try finding it at a path like `/usr/lib/x86_64-linux-gnu/qt5/bin/qmake`. Alternatively, you might want to load `goldendict.pro` file from within Qt Creator, especially on Windows. Note: To compile with `libhunspell` older than 1.5 pass `"CONFIG+=old_hunspell"` to `qmake`. ### Building with Chinese conversion support To add Chinese conversion support you need at first install libopencc-dev package: sudo apt-get install libopencc-dev Then pass `"CONFIG+=chinese_conversion_support"` to `qmake` qmake "CONFIG+=chinese_conversion_support" ### Building with Zim dictionaries support To add Zim and Slob formats support you need at first install lzma-dev and zstd-dev packages: sudo apt-get install liblzma-dev libzstd-dev Then pass `"CONFIG+=zim_support"` to `qmake` qmake "CONFIG+=zim_support" ### Building without extra tiff handler If you have problem building with libtiff5-dev package, you can pass `"CONFIG+=no_extra_tiff_handler"` to `qmake` in order to disable extra tiff support (without such extra support some b/w tiff images will not be displayed): qmake "CONFIG+=no_extra_tiff_handler" ### Building without Epwing format support If you have problem building with libeb-dev package, you can pass `"CONFIG+=no_epwing_support"` to `qmake` in order to disable Epwing format support qmake "CONFIG+=no_epwing_support" ### Building without internal audio players If you have problem building with FFmpeg/libao (for example, Ubuntu older than 12.04), you can pass `"CONFIG+=no_ffmpeg_player"` to `qmake` in order to disable FFmpeg+libao internal audio player back end: qmake "CONFIG+=no_ffmpeg_player" If you have problem building with Qt5 Multimedia or experience GStreamer run-time errors (for example, Ubuntu 14.04), you can pass `"CONFIG+=no_qtmultimedia_player"` to `qmake` in order to disable Qt Multimedia internal audio player back end: qmake "CONFIG+=no_qtmultimedia_player" NB: All additional settings for `qmake` that you need must be combined in one `qmake` launch, for example: qmake "CONFIG+=zim_support" "CONFIG+=no_extra_tiff_handler" "CONFIG+=no_ffmpeg_player" Then, invoke `make clean` before `make` because the setting change: make clean && make ### Building under Windows with MS Visual Studio To build GoldenDict with Visual Studio take one of next library packs and unpack it to `"winlibs/lib/msvc"` folder in GoldenDict sources folder. [GoldenDict_libs_VS2013_x86_v4.7z](http://www.mediafire.com/file/3il4vr1l8299nxn/GoldenDict_libs_VS2013_x86_v4.7z) - for MS Visual Studio 2013, 32 bit [GoldenDict_libs_VS2013_x64_v4.7z](http://www.mediafire.com/file/2itgg8bafppg6lw/GoldenDict_libs_VS2013_x64_v4.7z) - for MS Visual Studio 2013, 64 bit [GoldenDict_libs_VS2015_x86_v4.7z](http://www.mediafire.com/file/0a7ygy9rn99oevm/GoldenDict_libs_VS2015_x86_v4.7z) - for MS Visual Studio 2015, 32 bit [GoldenDict_libs_VS2015_x64_v4.7z](http://www.mediafire.com/file/yoy2q8af0s1467m/GoldenDict_libs_VS2015_x64_v4.7z) - for MS Visual Studio 2015, 64 bit To create project files for Visual Studio you can pass `"-tp vc"` option to `qmake`. Note: In Qt 5.6.0 and later the `Webkit` module was removed from official release builds. You should build it from sources to compile GoldenDict. ## Installation Installation is an optional step since the built binary can be used as-is without installation. But you can properly install via: make install NB: Don't do that on Windows! You can uninstall via: make uninstall ## License This project is licensed under the GNU GPLv3+ license, a copy of which can be found in the `LICENSE.txt` file. ## Support Users looking for support should file an issue in the official [GoldenDict issue tracker](https://github.com/goldendict/goldendict/issues), or even better: submit a [pull request](https://github.com/goldendict/goldendict/pulls) if you have a fix available. General questions should be asked on the [official GoldenDict forum](http://goldendict.org/forum/). goldendict-1.5.0/aard.cc000066400000000000000000000776101443523320500150630ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "aard.hh" #include "btreeidx.hh" #include "folding.hh" #include "utf8.hh" #include "chunkedstorage.hh" #include "langcoder.hh" #include "fsencoding.hh" #include "decompress.hh" #include "gddebug.hh" #include "ftshelpers.hh" #include "htmlescape.hh" #include #include #include #ifdef _MSC_VER #include #endif #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) #include #endif #include "ufile.hh" #include "wstring_qt.hh" #include "qt4x5.hh" namespace Aard { using std::map; using std::multimap; using std::pair; using std::set; using std::string; using gd::wstring; using BtreeIndexing::WordArticleLink; using BtreeIndexing::IndexedWords; using BtreeIndexing::IndexInfo; namespace { DEF_EX_STR( exNotAardFile, "Not an AARD file", Dictionary::Ex ) DEF_EX_STR( exCantReadFile, "Can't read file", Dictionary::Ex ) DEF_EX_STR( exWordIsTooLarge, "Enountered a word that is too large:", Dictionary::Ex ) DEF_EX_STR( exSuddenEndOfFile, "Sudden end of file", Dictionary::Ex ) #pragma pack( push, 1 ) /// AAR file header struct AAR_header { char signature[4]; char checksum[40]; quint16 version; char uuid[16]; quint16 volume; quint16 totalVolumes; quint32 metaLength; quint32 wordsCount; quint32 articleOffset; char indexItemFormat[4]; char keyLengthFormat[2]; char articleLengthFormat[2]; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct IndexElement { quint32 wordOffset; quint32 articleOffset; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct IndexElement64 { quint32 wordOffset; quint64 articleOffset; } #ifndef _MSC_VER __attribute__((packed)) #endif ; enum { Signature = 0x58524141, // AARX on little-endian, XRAA on big-endian CurrentFormatVersion = 4 + BtreeIndexing::FormatVersion + Folding::Version }; struct IdxHeader { quint32 signature; // First comes the signature, AARX quint32 formatVersion; // File format version (CurrentFormatVersion) quint32 chunksOffset; // The offset to chunks' storage quint32 indexBtreeMaxElements; // Two fields from IndexInfo quint32 indexRootOffset; quint32 wordCount; quint32 articleCount; quint32 langFrom; // Source language quint32 langTo; // Target language } #ifndef _MSC_VER __attribute__((packed)) #endif ; #pragma pack( pop ) bool indexIsOldOrBad( string const & indexFile ) { File::Class idx( indexFile, "rb" ); IdxHeader header; return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != Signature || header.formatVersion != CurrentFormatVersion; } void readJSONValue( string const & source, string & str, string::size_type & pos) { int level = 1; char endChar; str.push_back( source[pos] ); if( source[pos] == '{') endChar = '}'; else if( source[pos] == '[' ) endChar = ']'; else if( source[pos] == '\"' ) { str.clear(); endChar = '\"'; } else endChar = ','; pos++; char ch = 0; char lastCh = 0; while( !( ch == endChar && lastCh != '\\' && level == 0 ) && pos < source.size() ) { lastCh = ch; ch = source[ pos++ ]; if( ( ch == '{' || ch == '[' ) && lastCh != '\\' ) level++; if( ( ch == '}' || ch == ']' ) && lastCh != '\\' ) level--; if( ch == endChar && ( ( ch == '\"' && lastCh != '\\' ) || ch == ',' ) && level == 1) break; str.push_back( ch ); } } map< string, string > parseMetaData( string const & metaData ) { // Parsing JSON string map< string, string > data; string name, value; string::size_type n = 0; while( n < metaData.length() && metaData[n] != '{' ) n++; while( n < metaData.length() ) { // Skip to '"' while( n < metaData.length() && metaData[n] != '\"' ) n++; if( ++n >= metaData.length() ) break; // Read name while( n < metaData.length() && !( ( metaData[n] == '\"' || metaData[n] == '{' ) && metaData[n-1] != '\\' ) ) name.push_back( metaData[n++]); // Skip to ':' if( ++n >= metaData.length() ) break; while( n < metaData.length() && metaData[n] != ':' ) n++; if( ++n >= metaData.length() ) break; // Find value start after ':' while( n < metaData.length() && !( ( metaData[n] == '\"' || metaData[n] == '{' || metaData[n] == '[' || ( metaData[n] >= '0' && metaData[n] <= '9' ) ) && metaData[n-1] != '\\' ) ) n++; if( n >= metaData.length() ) break; readJSONValue( metaData, value, n); data[name] = value; name.clear(); value.clear(); if( ++n >= metaData.length() ) break; } return data; } class AardDictionary: public BtreeIndexing::BtreeDictionary { Mutex idxMutex; Mutex aardMutex; File::Class idx; IdxHeader idxHeader; ChunkedStorage::Reader chunks; string dictionaryName; File::Class df; public: AardDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ); ~AardDictionary(); virtual string getName() throw() { return dictionaryName; } virtual map< Dictionary::Property, string > getProperties() throw() { return map< Dictionary::Property, string >(); } virtual unsigned long getArticleCount() throw() { return idxHeader.articleCount; } virtual unsigned long getWordCount() throw() { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const { return idxHeader.langFrom; } inline virtual quint32 getLangTo() const { return idxHeader.langTo; } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ); virtual QString const& getDescription(); virtual sptr< Dictionary::DataRequest > getSearchResults( QString const & searchString, int searchMode, bool matchCase, int distanceBetweenWords, int maxResults, bool ignoreWordsOrder, bool ignoreDiacritics, QThreadPool * ftsThreadPoolPtr ); virtual void getArticleText( uint32_t articleAddress, QString & headword, QString & text ); virtual void makeFTSIndex(QAtomicInt & isCancelled, bool firstIteration ); virtual void setFTSParameters( Config::FullTextSearch const & fts ) { can_FTS = fts.enabled && !fts.disabledTypes.contains( "AARD", Qt::CaseInsensitive ) && ( fts.maxDictionarySize == 0 || getArticleCount() <= fts.maxDictionarySize ); } protected: virtual void loadIcon() throw(); private: /// Loads the article. void loadArticle( quint32 address, string & articleText, bool rawText = false ); string convert( string const & in_data ); friend class AardArticleRequest; }; AardDictionary::AardDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ): BtreeDictionary( id, dictionaryFiles ), idx( indexFile, "rb" ), idxHeader( idx.read< IdxHeader >() ), chunks( idx, idxHeader.chunksOffset ), df( dictionaryFiles[ 0 ], "rb" ) { // Read dictionary name idx.seek( sizeof( idxHeader ) ); vector< char > dName( idx.read< quint32 >() ); if( dName.size() ) { idx.read( &dName.front(), dName.size() ); dictionaryName = string( &dName.front(), dName.size() ); } // Initialize the index openIndex( IndexInfo( idxHeader.indexBtreeMaxElements, idxHeader.indexRootOffset ), idx, idxMutex ); // Full-text search parameters can_FTS = true; ftsIdxName = indexFile + "_FTS"; if( !Dictionary::needToRebuildIndex( dictionaryFiles, ftsIdxName ) && !FtsHelpers::ftsIndexIsOldOrBad( ftsIdxName, this ) ) FTS_index_completed.ref(); } AardDictionary::~AardDictionary() { df.close(); } void AardDictionary::loadIcon() throw() { if ( dictionaryIconLoaded ) return; QString fileName = QDir::fromNativeSeparators( FsEncoding::decode( getDictionaryFilenames()[ 0 ].c_str() ) ); // Remove the extension fileName.chop( 3 ); if( !loadIconFromFile( fileName ) ) { // Load failed -- use default icons dictionaryNativeIcon = dictionaryIcon = QIcon(":/icons/icon32_aard.png"); } dictionaryIconLoaded = true; } string AardDictionary::convert( const string & in ) { string inConverted; char inCh, lastCh = 0; bool afterEol = false; for( string::const_iterator i = in.begin(), j = in.end(); i != j; ++i ) { inCh = *i; if( lastCh == '\\' ) { inConverted.erase( inConverted.size() - 1 ); lastCh = 0; if( inCh == 'n' ) { inConverted.append( "
"); afterEol = true; continue; } else if( inCh == 'r') continue; } else if( inCh == ' ' && afterEol ) { inConverted.append( " " ); continue; } else lastCh = inCh; afterEol = false; inConverted.push_back( inCh ); } QString text = QString::fromUtf8( inConverted.c_str() ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) text.replace( QRegularExpression( "<\\s*a\\s+href\\s*=\\s*\\\"(w:|s:){0,1}([^#](?!ttp://)[^\\\"]*)(.)", QRegularExpression::DotMatchesEverythingOption ), "]*)/>", QRegularExpression::CaseInsensitiveOption ); //
text.replace( self_closing_divs, "\\1>
" ); #else text.replace( QRegExp( "<\\s*a\\s+href\\s*=\\s*\\\"(w:|s:){0,1}([^#](?!ttp://)[^\\\"]*)(.)" ), "
]*)/>", Qt::CaseInsensitive ); //
text.replace( self_closing_divs, "\\1>
" ); #endif // Fix outstanding elements text += "
"; return text.toUtf8().data(); } void AardDictionary::loadArticle( quint32 address, string & articleText, bool rawText ) { quint32 articleOffset = address; quint32 articleSize; quint32 size; vector< char > articleBody; articleText.clear(); while( 1 ) { articleText = string( QObject::tr( "Article loading error" ).toUtf8().constData() ); try { Mutex::Lock _( aardMutex ); df.seek( articleOffset ); df.read( &size, sizeof(size) ); articleSize = qFromBigEndian( size ); // Don't try to read and decode too big articles, // it is most likely error in dictionary if( articleSize > 1048576 ) break; articleBody.resize( articleSize ); df.read( &articleBody.front(), articleSize ); } catch( std::exception &ex ) { gdWarning( "AARD: Failed loading article from \"%s\", reason: %s\n", getName().c_str(), ex.what() ); break; } catch(...) { break; } if ( articleBody.empty() ) break; articleText.clear(); string text = decompressBzip2( articleBody.data(), articleSize ); if( text.empty() ) text = decompressZlib( articleBody.data(), articleSize ); if( text.empty() ) text = string( articleBody.data(), articleSize ); if( text.empty() || text[ 0 ] != '[' ) break; string::size_type n = text.find( '\"' ); if( n == string::npos ) break; readJSONValue( text, articleText, n ); if( articleText.empty() ) { n = text.find( "\"r\"" ); if( n != string::npos && n + 3 < text.size() ) { n = text.find( '\"', n + 3 ); if( n == string::npos ) break; string link; readJSONValue( text, link, n ); if( !link.empty() ) { string encodedLink; encodedLink.reserve( link.size() ); bool prev = false; for( string::const_iterator i = link.begin(); i != link.end(); ++i ) { if( *i == '\\' ) { if( !prev ) { prev = true; continue; } } encodedLink.push_back( *i ); prev = false; } encodedLink = string( QUrl( QString::fromUtf8( encodedLink.data(), encodedLink.size() ) ).toEncoded().data() ); articleText = "
" + link + ""; } } } break; } if( !articleText.empty() ) { if( rawText ) return; articleText = convert( articleText ); } else articleText = string( QObject::tr( "Article decoding error" ).toUtf8().constData() ); // See Issue #271: A mechanism to clean-up invalid HTML cards. string cleaner = """""""""""" """""""""""" "" "" ""; string prefix( "
"; } QString const& AardDictionary::getDescription() { if( !dictionaryDescription.isEmpty() ) return dictionaryDescription; AAR_header dictHeader; quint32 size; vector< char > data; { Mutex::Lock _( aardMutex ); df.seek( 0 ); df.read( &dictHeader, sizeof(dictHeader) ); size = qFromBigEndian( dictHeader.metaLength ); data.resize( size ); df.read( &data.front(), size ); } string metaStr = decompressBzip2( data.data(), size ); if( metaStr.empty() ) metaStr = decompressZlib( data.data(), size ); map< string, string > meta = parseMetaData( metaStr ); if( !meta.empty() ) { map< string, string >::const_iterator iter = meta.find( "copyright" ); if( iter != meta.end() ) dictionaryDescription = QString( QObject::tr( "Copyright: %1%2" ) ).arg( QString::fromUtf8( iter->second.c_str() ) ).arg( "\n\n" ); iter = meta.find( "version" ); if( iter != meta.end() ) dictionaryDescription = QString( QObject::tr( "Version: %1%2" ) ).arg( QString::fromUtf8( iter->second.c_str() ) ).arg( "\n\n" ); iter = meta.find( "description" ); if( iter != meta.end() ) { QString desc = QString::fromUtf8( iter->second.c_str() ); desc.replace( "\\n", "\n" ); desc.replace( "\\t", "\t" ); dictionaryDescription += desc; } } if( dictionaryDescription.isEmpty() ) dictionaryDescription = "NONE"; return dictionaryDescription; } void AardDictionary::makeFTSIndex( QAtomicInt & isCancelled, bool firstIteration ) { if( !( Dictionary::needToRebuildIndex( getDictionaryFilenames(), ftsIdxName ) || FtsHelpers::ftsIndexIsOldOrBad( ftsIdxName, this ) ) ) FTS_index_completed.ref(); if( haveFTSIndex() ) return; if( ensureInitDone().size() ) return; if( firstIteration && getArticleCount() > FTS::MaxDictionarySizeForFastSearch ) return; gdDebug( "Aard: Building the full-text index for dictionary: %s\n", getName().c_str() ); try { FtsHelpers::makeFTSIndex( this, isCancelled ); FTS_index_completed.ref(); } catch( std::exception &ex ) { gdWarning( "Aard: Failed building full-text search index for \"%s\", reason: %s\n", getName().c_str(), ex.what() ); QFile::remove( FsEncoding::decode( ftsIdxName.c_str() ) ); } } void AardDictionary::getArticleText( uint32_t articleAddress, QString & headword, QString & text ) { try { headword.clear(); string articleText; loadArticle( articleAddress, articleText ); text = Html::unescape( QString::fromUtf8( articleText.data(), articleText.size() ) ); } catch( std::exception &ex ) { gdWarning( "Aard: Failed retrieving article from \"%s\", reason: %s\n", getName().c_str(), ex.what() ); } } sptr< Dictionary::DataRequest > AardDictionary::getSearchResults( QString const & searchString, int searchMode, bool matchCase, int distanceBetweenWords, int maxResults, bool ignoreWordsOrder, bool ignoreDiacritics, QThreadPool * ftsThreadPoolPtr ) { return new FtsHelpers::FTSResultsRequest( *this, searchString,searchMode, matchCase, distanceBetweenWords, maxResults, ignoreWordsOrder, ignoreDiacritics, ftsThreadPoolPtr ); } /// AardDictionary::getArticle() class AardArticleRequest; class AardArticleRequestRunnable: public QRunnable { AardArticleRequest & r; QSemaphore & hasExited; public: AardArticleRequestRunnable( AardArticleRequest & r_, QSemaphore & hasExited_ ): r( r_ ), hasExited( hasExited_ ) {} ~AardArticleRequestRunnable() { hasExited.release(); } virtual void run(); }; class AardArticleRequest: public Dictionary::DataRequest { friend class AardArticleRequestRunnable; wstring word; vector< wstring > alts; AardDictionary & dict; bool ignoreDiacritics; QAtomicInt isCancelled; QSemaphore hasExited; public: AardArticleRequest( wstring const & word_, vector< wstring > const & alts_, AardDictionary & dict_, bool ignoreDiacritics_ ): word( word_ ), alts( alts_ ), dict( dict_ ), ignoreDiacritics( ignoreDiacritics_ ) { QThreadPool::globalInstance()->start( new AardArticleRequestRunnable( *this, hasExited ) ); } void run(); // Run from another thread by DslArticleRequestRunnable virtual void cancel() { isCancelled.ref(); } ~AardArticleRequest() { isCancelled.ref(); hasExited.acquire(); } }; void AardArticleRequestRunnable::run() { r.run(); } void AardArticleRequest::run() { if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } vector< WordArticleLink > chain = dict.findArticles( word, ignoreDiacritics ); for( unsigned x = 0; x < alts.size(); ++x ) { /// Make an additional query for each alt vector< WordArticleLink > altChain = dict.findArticles( alts[ x ], ignoreDiacritics ); chain.insert( chain.end(), altChain.begin(), altChain.end() ); } multimap< wstring, pair< string, string > > mainArticles, alternateArticles; set< quint32 > articlesIncluded; // Some synonims make it that the articles // appear several times. We combat this // by only allowing them to appear once. wstring wordCaseFolded = Folding::applySimpleCaseOnly( word ); if( ignoreDiacritics ) wordCaseFolded = Folding::applyDiacriticsOnly( wordCaseFolded ); for( unsigned x = 0; x < chain.size(); ++x ) { if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } if ( articlesIncluded.find( chain[ x ].articleOffset ) != articlesIncluded.end() ) continue; // We already have this article in the body. // Now grab that article string headword, articleText; headword = chain[ x ].word; try { dict.loadArticle( chain[ x ].articleOffset, articleText ); } catch(...) { } // Ok. Now, does it go to main articles, or to alternate ones? We list // main ones first, and alternates after. // We do the case-folded comparison here. wstring headwordStripped = Folding::applySimpleCaseOnly( Utf8::decode( headword ) ); if( ignoreDiacritics ) headwordStripped = Folding::applyDiacriticsOnly( headwordStripped ); multimap< wstring, pair< string, string > > & mapToUse = ( wordCaseFolded == headwordStripped ) ? mainArticles : alternateArticles; mapToUse.insert( pair< wstring, pair< string, string > >( Folding::applySimpleCaseOnly( Utf8::decode( headword ) ), pair< string, string >( headword, articleText ) ) ); articlesIncluded.insert( chain[ x ].articleOffset ); } if ( mainArticles.empty() && alternateArticles.empty() ) { // No such word finish(); return; } string result; multimap< wstring, pair< string, string > >::const_iterator i; for( i = mainArticles.begin(); i != mainArticles.end(); ++i ) { result += "

"; result += i->second.first; result += "

"; result += i->second.second; } for( i = alternateArticles.begin(); i != alternateArticles.end(); ++i ) { result += "

"; result += i->second.first; result += "

"; result += i->second.second; } Mutex::Lock _( dataMutex ); data.resize( result.size() ); memcpy( &data.front(), result.data(), result.size() ); hasAnyData = true; finish(); } sptr< Dictionary::DataRequest > AardDictionary::getArticle( wstring const & word, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ) { return new AardArticleRequest( word, alts, *this, ignoreDiacritics ); } } // anonymous namespace vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing & initializing, unsigned maxHeadwordsToExpand ) THROW_SPEC( std::exception ) { vector< sptr< Dictionary::Class > > dictionaries; for( vector< string >::const_iterator i = fileNames.begin(); i != fileNames.end(); ++i ) { // Skip files with the extensions different to .aar to speed up the // scanning if ( i->size() < 4 || strcasecmp( i->c_str() + ( i->size() - 4 ), ".aar" ) != 0 ) continue; // Got the file -- check if we need to rebuid the index vector< string > dictFiles( 1, *i ); string dictId = Dictionary::makeDictionaryId( dictFiles ); string indexFile = indicesDir + dictId; if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { try { gdDebug( "Aard: Building the index for dictionary: %s\n", i->c_str() ); { QFileInfo info( FsEncoding::decode( i->c_str() ) ); if( static_cast< quint64 >( info.size() ) > ULONG_MAX ) { gdWarning( "File %s is too large\n", i->c_str() ); continue; } } File::Class df( *i, "rb" ); AAR_header dictHeader; df.read( &dictHeader, sizeof(dictHeader) ); bool has64bitIndex = !strncmp( dictHeader.indexItemFormat, ">LQ", 4 ); if( strncmp( dictHeader.signature, "aard", 4 ) || ( !has64bitIndex && strncmp( dictHeader.indexItemFormat, ">LL", 4 ) ) || strncmp( dictHeader.keyLengthFormat, ">H", 2 ) || strncmp( dictHeader.articleLengthFormat, ">L", 2) ) { gdWarning( "File %s is not in supported aard format\n", i->c_str() ); continue; } vector< char > data; quint32 size = qFromBigEndian( dictHeader.metaLength ); if( size == 0 ) { gdWarning( "File %s has invalid metadata", i->c_str() ); continue; } data.resize( size ); df.read( &data.front(), size ); string metaStr = decompressBzip2( data.data(), size ); if( metaStr.empty() ) metaStr = decompressZlib( data.data(), size ); map< string, string > meta = parseMetaData( metaStr ); if( meta.empty() ) { gdWarning( "File %s has invalid metadata", i->c_str() ); continue; } string dictName; map< string, string >::const_iterator iter = meta.find( "title" ); if( iter != meta.end() ) dictName = iter->second; string langFrom; iter = meta.find( "index_language" ); if( iter != meta.end() ) langFrom = iter->second; string langTo; iter = meta.find( "article_language" ); if( iter != meta.end() ) langTo = iter->second; if( ( dictName.compare( "Wikipedia") == 0 || dictName.compare( "Wikiquote" ) == 0 || dictName.compare( "Wiktionary" ) == 0 ) && !langTo.empty() ) { string capitalized = langTo.c_str(); capitalized[0] = toupper( capitalized[0] ); dictName = dictName + " (" + capitalized + ")"; } quint16 volumes = qFromBigEndian( dictHeader.totalVolumes ); if( volumes > 1 ) { QString const ss = QString( " (%1/%2)" ).arg( qFromBigEndian( dictHeader.volume ) ).arg( volumes ); dictName += ss.toLocal8Bit().data(); } initializing.indexingDictionary( dictName ); File::Class idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); // We write a dummy header first. At the end of the process the header // will be rewritten with the right values. idx.write( idxHeader ); idx.write( (quint32) dictName.size() ); if( !dictName.empty() ) idx.write( dictName.data(), dictName.size() ); IndexedWords indexedWords; ChunkedStorage::Writer chunks( idx ); quint32 wordCount = qFromBigEndian( dictHeader.wordsCount ); set< quint32 > articleOffsets; quint32 pos = df.tell(); quint32 wordsBase = pos + wordCount * ( has64bitIndex ? sizeof( IndexElement64 ) : sizeof( IndexElement ) ); quint32 articlesBase = qFromBigEndian( dictHeader.articleOffset ); data.clear(); for( quint32 j = 0; j < wordCount; j++ ) { quint32 articleOffset; quint32 wordOffset; if( has64bitIndex ) { IndexElement64 el64; df.seek( pos ); df.read( &el64, sizeof(el64) ); articleOffset = articlesBase + qFromBigEndian( el64.articleOffset ); wordOffset = wordsBase + qFromBigEndian( el64.wordOffset ); } else { IndexElement el; df.seek( pos ); df.read( &el, sizeof(el) ); articleOffset = articlesBase + qFromBigEndian( el.articleOffset ); wordOffset = wordsBase + qFromBigEndian( el.wordOffset ); } df.seek( wordOffset ); quint16 sizeBE; df.read( &sizeBE, sizeof(sizeBE) ); quint16 wordSize = qFromBigEndian( sizeBE ); if( data.size() < wordSize ) data.resize( wordSize ); df.read( &data.front(), wordSize ); if( articleOffsets.find( articleOffset ) == articleOffsets.end() ) articleOffsets.insert( articleOffset ); // Insert new entry wstring word = Utf8::decode( string( data.data(), wordSize ) ); if( maxHeadwordsToExpand && dictHeader.wordsCount >= maxHeadwordsToExpand ) indexedWords.addSingleWord( word, articleOffset); else indexedWords.addWord( word, articleOffset); pos += has64bitIndex ? sizeof( IndexElement64 ) : sizeof( IndexElement ); } data.clear(); idxHeader.articleCount = articleOffsets.size(); articleOffsets.clear(); // Finish with the chunks idxHeader.chunksOffset = chunks.finish(); // Build index IndexInfo idxInfo = BtreeIndexing::buildIndex( indexedWords, idx ); idxHeader.indexBtreeMaxElements = idxInfo.btreeMaxElements; idxHeader.indexRootOffset = idxInfo.rootOffset; indexedWords.clear(); // Release memory -- no need for this data // That concludes it. Update the header. idxHeader.signature = Signature; idxHeader.formatVersion = CurrentFormatVersion; idxHeader.wordCount = wordCount; if( langFrom.size() == 3) idxHeader.langFrom = LangCoder::findIdForLanguageCode3( langFrom.c_str() ); else if( langFrom.size() == 2 ) idxHeader.langFrom = LangCoder::code2toInt( langFrom.c_str() ); if( langTo.size() == 3) idxHeader.langTo = LangCoder::findIdForLanguageCode3( langTo.c_str() ); else if( langTo.size() == 2 ) idxHeader.langTo = LangCoder::code2toInt( langTo.c_str() ); idx.rewind(); idx.write( &idxHeader, sizeof( idxHeader ) ); } catch( std::exception & e ) { gdWarning( "Aard dictionary indexing failed: %s, error: %s\n", i->c_str(), e.what() ); continue; } catch( ... ) { gdWarning( "Aard dictionary indexing failed\n" ); continue; } } // if need to rebuild try { dictionaries.push_back( new AardDictionary( dictId, indexFile, dictFiles ) ); } catch( std::exception & e ) { gdWarning( "Aard dictionary initializing failed: %s, error: %s\n", i->c_str(), e.what() ); continue; } } return dictionaries; } } goldendict-1.5.0/aard.hh000066400000000000000000000012751443523320500150670ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __AARD_HH_INCLUDED__ #define __AARD_HH_INCLUDED__ #include "dictionary.hh" /// Support for the aard dictionaries. namespace Aard { using std::vector; using std::string; vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing &, unsigned maxHeadwordsToExpand ) THROW_SPEC( std::exception ); } #endif goldendict-1.5.0/about.cc000066400000000000000000000041601443523320500152540ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "about.hh" #include #include #include "qt4x5.hh" About::About( QWidget * parent ): QDialog( parent ) { ui.setupUi( this ); QFile versionFile( ":/version.txt" ); QString version; if ( !versionFile.open( QFile::ReadOnly ) ) version = tr( "[Unknown]" ); else version = QString::fromLatin1( versionFile.readAll() ).trimmed(); ui.version->setText( version ); #if defined (_MSC_VER) QString compilerVersion = QString( "Visual C++ %1.%2.%3" ) .arg( GD_CXX_MSVC_MAJOR ) .arg( GD_CXX_MSVC_MINOR ) .arg( GD_CXX_MSVC_BUILD ); #elif defined (__clang__) && defined (__clang_version__) QString compilerVersion = QLatin1String( "Clang " ) + QLatin1String( __clang_version__ ); #else QString compilerVersion = QLatin1String( "GCC " ) + QLatin1String( __VERSION__ ); #endif ui.qtVersion->setText( tr( "Based on Qt %1 (%2, %3 bit)" ).arg( QLatin1String( qVersion() ), compilerVersion, QString::number( QSysInfo::WordSize ) ) ); QFile creditsFile( ":/CREDITS.txt" ); if ( creditsFile.open( QFile::ReadOnly ) ) { QStringList creditsList = QString::fromUtf8( creditsFile.readAll() ).split( '\n', Qt4x5::skipEmptyParts() ); QString html = ""; for( int x = 0; x < creditsList.size(); ++x ) { QString str = creditsList[ x ]; str.replace( "\\", "@" ); str = Qt4x5::escape( str ); int colon = str.indexOf( ":" ); if ( colon != -1 ) { QString name( str.left( colon ) ); name.replace( ", ", "
" ); str = "" + name + "
    " + str.mid( colon + 1 ); } html += str; html += "
"; } html += ""; ui.credits->setHtml( html ); } } goldendict-1.5.0/about.hh000066400000000000000000000014771443523320500152760ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef ABOUT_HH #define ABOUT_HH #include "ui_about.h" #include // Microsoft Visual C++ version #if defined (_MSC_VER) // how many digits does the build number have? # if _MSC_FULL_VER / 10000 == _MSC_VER # define GD_CXX_MSVC_BUILD (_MSC_FULL_VER % 10000) // four digits # elif _MSC_FULL_VER / 100000 == _MSC_VER # define GD_CXX_MSVC_BUILD (_MSC_FULL_VER % 100000) // five digits # else # define GD_CXX_MSVC_BUILD 0 # endif # define GD_CXX_MSVC_MAJOR (_MSC_VER/100-6) # define GD_CXX_MSVC_MINOR (_MSC_VER%100) #endif class About: public QDialog { Q_OBJECT public: About( QWidget * parent = 0 ); private: Ui::About ui; }; #endif // ABOUT_HH goldendict-1.5.0/about.ui000066400000000000000000000122511443523320500153040ustar00rootroot00000000000000 About Qt::NonModal 0 0 400 400 About true 80 80 80 80 :/icons/programicon.png true Qt::AlignCenter 10 0 GoldenDict dictionary lookup program, version Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter #.# Qt::TextEditorInteraction (c) 2008-2013 Konstantin Isakov (ikm@goldendict.org) Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Licensed under GNU GPLv3 or later Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Based on Qt #.#.# (GCC #.#, 32/64 bit) Qt::TextEditorInteraction Credits: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true 0 1 Qt::Horizontal QDialogButtonBox::Ok true buttonBox accepted() About accept() 248 254 157 274 buttonBox rejected() About reject() 316 260 286 274 goldendict-1.5.0/article-style-print.css000066400000000000000000000002701443523320500202560ustar00rootroot00000000000000/* This stylesheet is used for printing only, overriding article-style.css */ html, body, .gdarticle { background: white; } /* Hide audio icons */ .dsl_s_wav { display: none; } goldendict-1.5.0/article-style-st-babylon.css000066400000000000000000000031311443523320500211730ustar00rootroot00000000000000html { background-color: white; } body { background: white; font-size: 13px; } .gdarticle { background: white; } h3 { font-size: 1em; } ::selection { background: #4492E8; color: white; } /* Dictionary's name heading */ .gddictname { font-weight: normal; font-size: 11px; border: none; border-top-left-radius: 8px; border-top-right-radius: 0px; background: #F0EDED; padding-top: 5px; padding-bottom: 5px; } .gddicticon { display: inline; padding-right: 5px; } .gddicticon img { border: 0; height: 16px; vertical-align: text-top; } /* The 'From ' string which precedes dictionary name in the heading */ .gdfromprefix { display: none; } /* Move the collapse/expand buttons to the right */ .collapse_expand_area { float: right; margin-right: 2px; } .gdexpandicon, .gdexpandicon:hover, .gdcollapseicon, .gdcollapseicon:hover { width:15px; background-position: center; background-repeat:no-repeat; vertical-align: text-bottom; padding-left: -5px; margin-left: 0px; } .gdexpandicon { background-image: url('qrcx://localhost/icons/expand_article.png'); } .gdexpandicon:hover { background-image: url('qrcx://localhost/icons/expand_article_hovered.png'); } .gdcollapseicon { background-image: url('qrcx://localhost/icons/collapse_article.png'); } .gdcollapseicon:hover { background-image: url('qrcx://localhost/icons/collapse_article_hovered.png'); } .bglpos { color: black; font-weight: bold; font-size: 11px; background: #F0EDED; display: inline; } .bgltrn { color: #808080; font-size: 12px; display: inline; } a, .mwiki a { color: #0066CC; } goldendict-1.5.0/article-style-st-lingoes-blue.css000066400000000000000000000150771443523320500221460ustar00rootroot00000000000000body { background: #EAF0F8; margin: 0.3em; font-family: "Arial Unicode MS", "Lucida Sans Unicode", Tahoma, Verdana, "Palatino Linotype", sans-serif; line-height: 120%; } a { text-decoration: none; color: darkblue; } a:hover { text-decoration: underline; } .gdarticle { background: #FFFEF2; margin-top: 0.1em; margin-bottom: 0.35em; padding: 5px; padding-top: -10px; border: 1px solid #C7D4DC; border-radius: 8px; } .gdactivearticle { background: #FFFEF2; border: 1px solid #92B0DD; } .gdarticleseparator + script + .gdactivearticle .gddictname { border-top: 1px solid #92B0DD; border-right: 1px solid #92B0DD; } .gddictname { display: -webkit-box; /* needed to be able to reorder elements, e.g. icon, dictionary name, collapse icon */ font-size: 12px; font-weight: normal; float: right; border: 0px; border-top: 1px solid #C7D4DC; border-right: 1px solid #C7D4DC; border-top-right-radius: 8px; border-bottom-left-radius: 6px; margin: -6px; margin-bottom: 5px; margin-left: 2px; padding-right: 0.4em; color: #34517D; background: #DDE6F5; -webkit-user-select: none; user-select: none; cursor: default; } /* Actual text with the dictionary title */ .gddicttitle { display:block; -webkit-box-ordinal-group: 1; -webkit-box-flex: 1; } .gdactivearticle .gddictname { border-top: 1px solid #92B0DD; border-right: 1px solid #92B0DD; color: darkblue; background: #CFDDF0; } .gdcollapsedarticle .gddictname { opacity: 0.7; } /* Nice diagonal pattern for the collapsed article */ .gdcollapsedarticle { background-image: -webkit-linear-gradient(left top, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%); background-size: 50px 50px; } /* Move the collapse/expand buttons to the last, 3rd position */ .collapse_expand_area { display: block; -webkit-box-ordinal-group: 3; } .gddicticon { display: block; vertical-align: text-bottom; padding-right: -1em; padding-left: 0.3em; -webkit-box-ordinal-group: 2; } .gdexpandicon, .gdexpandicon:hover, .gdcollapseicon, .gdcollapseicon:hover { width:15px; background-position: center 25%; background-repeat:no-repeat; vertical-align: top; padding-left: -5px; margin-left: 0px; } .gdexpandicon { background-image: url('qrcx://localhost/icons/expand_article.png'); } .gdexpandicon:hover { background-image: url('qrcx://localhost/icons/expand_article_hovered.png'); } .gdcollapseicon { background-image: url('qrcx://localhost/icons/collapse_article.png'); } .gdcollapseicon:hover { background-image: url('qrcx://localhost/icons/collapse_article_hovered.png'); } .gdarticleseparator { display: none; } /* The 'From ' string which preceeds dictionary name in the heading */ .gdfromprefix { display: none; } /* The first headword in a (possibly) multi-headword DSL article */ .gdarticlebody > div:first-child .dsl_headwords, .gdarticlebody > h3:first-child { margin-top: -3px; display: inline-block; } .mwiki .toc, .mwiki .metadata mbox-small plainlinks { display:none } .gddicticon img { border: 0; height: 15px; vertical-align: text-top; } /* Text selection */ ::selection { background: #839EC7; color:#fff; } code::selection { background: #839EC7; } /* Headers */ h3 { color: inherit; font-family: Arial; } /******** DSL Dictionaries ****************/ /* DSL headwords */ .dsl_headwords { color: inherit; font-family: Arial; } /* Formatting */ .dsl_b { font-family: Arial; } .dsl_ex, .dsl_ex .dsl_opt { color: slategray; } .dsl_ref { color: #0000DD; } .dsl_url { color: #0000DD; } /* Stress settings */ .dsl_stress_without_accent, .dsl_opt .dsl_ex .dsl_stress_without_accent { display: none; color: green; } .dsl_stress_with_accent, .dsl_opt .dsl_ex .dsl_stress_with_accent { display: inline; color: #DD0000; } .dsl_stress, .dsl_opt .dsl_ex .dsl_stress { display: inline; color: blue; } /* Sound icon */ .dsl_s_wav img { display: none; } .dsl_s_wav a { text-decoration: none; -webkit-user-select: none; display: inline-block; width: 18px; height: 18px; -webkit-border-radius: 100px; text-align: center; vertical-align: text-bottom; } .dsl_s_wav a { color: #FFF; border: 1px solid #798415; -webkit-box-shadow: 1px 1px #CCC, inset -1px -1px #4E7500; background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,rgba(191,210,85,1)), color-stop(50%,rgba(142,185,42,1)), color-stop(51%,rgba(114,170,0,1)), color-stop(100%,rgba(158,203,45,1))); } .dsl_s_wav a:hover { background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#e6f0a3), color-stop(50%,#d2e638), color-stop(51%,#c3d825), color-stop(100%,#dbf043)); border: 1px solid #A2B01C; -webkit-box-shadow: 1px 1px #CCC, inset -1px -1px #8A991A; } .dsl_s_wav a:active { -webkit-box-shadow: 1px 1px #CCC, inset 1px 1px #B1C421; } .dsl_s_wav a:after { display: inline-block; font-family: "Webdings"; content:"X"; width: 18px; height: 18px; font-size: 16px; text-align: center; vertical-align: center; -webkit-background-clip: text; color: #E6EF8F; -webkit-text-stroke: 1px #51580E; } .dsl_s_wav a:hover:after { -webkit-text-stroke: 1px #656E11; } /******** Verbix Dictionaries ****************/ .sdct_h .normal { font-family: Arial; font-size: 10pt; color:#000000; font-weight: normal; text-decoration:none; } .sdct_h .irregular { font-family: Arial; font-size: 10pt; color:#FF0000; font-weight: bold; text-decoration:none; } .sdct_h .orto { font-family: Arial; font-size: 10pt; color:#0000FF; font-weight: normal; text-decoration:none; } .sdct_h .archaic { font-family: Arial; font-size: 10pt; color:#800080; font-weight: normal; text-decoration:none; } .sdct_h .notused { font-family: Arial; font-size: 10pt; color:#808080; font-weight: normal; text-decoration:none; } .sdct_h .pronoun { font-family: Arial; font-size: 10pt; color:#008000; font-weight: normal; text-decoration:none; } .sdct_h > table { border: 1px solid #444444; border-collapse: collapse; } .sdct_h td.verbtable { border: 1px solid #444444; } .sdct_h th { background-color: #A6C9EF; font-weight: bold; } .sdct_h .mini { font-size: 8pt; font-weight: normal; color: #808080; } .sdct_h div.spacerline { clear: both; margin-left: 8px; width:100%; border-top: 1px solid #ccc; } .sdct_h div.spacer { clear: both; } .sdct_h div.float { margin-left: 8px; float: left; } .sdct_h div.float400 { margin-left: 8px; float: left; width: 400px; } .sdct_h div.float250color { margin-left: 8px; float: left; width: 250px; border-style: dashed; border-color:#0033CC; border-collapse:collapse; border-width:1px; background-color:#eeeeee; padding: 8px 8px 8px 8px; } goldendict-1.5.0/article-style-st-lingoes.css000066400000000000000000000027461443523320500212200ustar00rootroot00000000000000a { color: #2233bb; text-decoration: none; } a:hover { color: #6677ff; } ::selection { background: #3399ff; color: white; } body { background: #fffef2; color: black; font-family: Tahoma, Verdana, SimSun, SimSun-ExtB, sans-serif; margin: 5px; } .gdarticle { background: #fffef2; margin-top: -8px; position: relative; } .gddicticon { display: inline; margin-left: -3px; padding-right: 5px; } .gddicticon img { border: 0px; height: 16px; vertical-align: text-top; } .gdfromprefix { display: none; } .gddictname { display: inline-block; border: 1px solid #92b0dd; padding: 0.2em; padding-left: 0.5em; padding-right: 0.5em; margin-top: 1em; margin-bottom: -0.1em; background: #cfddf0; color: #000080; font-weight: normal; font-size: 12px; } .collapse_expand_area { position: absolute; right: 0px; } .gdexpandicon, .gdexpandicon:hover, .gdcollapseicon, .gdcollapseicon:hover { background-position: center 35%; background-repeat:no-repeat; } .gdexpandicon { background-image: url('qrcx://localhost/icons/expand_article.png'); } .gdexpandicon:hover { background-image: url('qrcx://localhost/icons/expand_article_hovered.png'); } .gdcollapseicon { background-image: url('qrcx://localhost/icons/collapse_article.png'); } .gdcollapseicon:hover { background-image: url('qrcx://localhost/icons/collapse_article_hovered.png'); } .gddictnamebodyseparator { display: inline-block; width: 100%; border-top: 1px solid #92b0dd; } goldendict-1.5.0/article-style-st-lingvo.css000066400000000000000000000026231443523320500210500ustar00rootroot00000000000000html { background-color: white; } body { background: white; } a { text-decoration: none; color: darkblue; } a:hover { text-decoration: underline; } .gdarticle { background: white; } /* Dictionary's name heading */ .gddictname { border: 1px dotted black; padding: 0.2em; padding-left: 0.5em; margin-top: 1.2em; margin-bottom: 0.1em; font-weight: bold; font-size: 14px; background: #87CEEB; } /* The 'From ' string which preceeds dictionary name in the heading */ .gdfromprefix { display: none; } .gddictname { font-size: 13px; font-weight: normal; float: right; border: 1px solid white; margin-top: 7px; color: #7f7f7f; background: white; } .gdarticleseparator + script + .gdarticle .gddictname { border-top: 1px solid #ccc; /* margin-top: 11px;*/ } .gdactivearticle .gddictname { font-size: 13px; font-weight: normal; float: right; border: 1px solid #b3d7f7; margin-top: 7px; color: #0066ff; background: #deecf8; } .gdarticleseparator + script + .gdactivearticle .gddictname { border-top: 1px solid #b3d7f7; /* margin-top: 11px;*/ } .gdarticleseparator { margin-top: 10px; border-bottom: 1px solid #cccccc; margin-bottom: 0px; width: 100%; display: inline-block; clear: both; } .gddictname + h3, .gddictname + .dsl_article > .dsl_headwords { margin-top: 10px; } .dsl_ex, .dsl_ex .dsl_opt { color: inherit; } .dsl_com { color: black; } goldendict-1.5.0/article-style-st-modern.css000066400000000000000000000063311443523320500210360ustar00rootroot00000000000000html { background-color: white; } body { margin-top: 1px; margin-right: 3px; margin-left: 2px; margin-bottom: 3px; background: white; font-family: Tahoma, Verdana, "Lucida Sans Unicode", "Segoe UI", "Palatino Linotype", "Arial Unicode MS", sans-serif; } a { text-decoration: none; color: darkblue; } a:hover { text-decoration: underline; } .gdarticle { margin-top: 0.1em; margin-bottom: 0.4em; padding: 5px; border: 1px solid #d0dde2; border-radius: 8px; background: white; } .gdactivearticle { border: 1px solid #3399FF; } .gdarticleseparator + script + .gdactivearticle .gddictname { border-top: 1px solid #3399FF; } .gdarticleseparator { display: none; } /* Hide the 'From ' string which precedes dictionary name in the heading */ .gdfromprefix { display: none; } .gddictname { display: -webkit-box; /* needed to be able to reorder elements, e.g. icon, dictionary name, collapse icon */ font-size: 12px; font-weight: normal; float: right; border: 0px; border-top-right-radius: 8px; border-bottom-left-radius: 6px; margin: -6px; margin-bottom: 5px; margin-left: 2px; padding-right: 0.3em; color: #4480f8; background: #E0E8F0; -webkit-user-select: none; user-select: none; cursor: default; } /* Actual text with the dictionary title */ .gddicttitle { display:block; -webkit-box-ordinal-group: 1; -webkit-box-flex: 1; } .gdactivearticle .gddictname { font-size: 13px; font-weight: normal; margin: -6px; margin-bottom: 5px; margin-left: 2px; float: right; border: 1px solid #3399FF; color: #0066cc; background: #deecf8; } .gdcollapsedarticle .gddictname { opacity: 0.7; } /* Nice diagonal pattern for the collapsed article */ .gdcollapsedarticle { background-image: -webkit-linear-gradient(left top, #ccc 0%, #ccc 25%, #bbb 25%, #bbb 50%, #ccc 50%, #ccc 75%, #bbb 75%); background-size: 50px 50px; } /* Move the collapse/expand buttons to the last, 3rd position */ .collapse_expand_area { display: block; -webkit-box-ordinal-group: 3; } .gddicticon { display: block; vertical-align: text-bottom; padding-right: -1em; padding-left: 0.3em; -webkit-box-ordinal-group: 2; } .gdexpandicon, .gdexpandicon:hover, .gdcollapseicon, .gdcollapseicon:hover { width:15px; background-position: center 35%; background-repeat:no-repeat; vertical-align: text-bottom; padding-left: -5px; margin-left: 0px; } .gdexpandicon { background-image: url('qrcx://localhost/icons/expand_article.png'); } .gdexpandicon:hover { background-image: url('qrcx://localhost/icons/expand_article_hovered.png'); } .gdcollapseicon { background-image: url('qrcx://localhost/icons/collapse_article.png'); } .gdcollapseicon:hover { background-image: url('qrcx://localhost/icons/collapse_article_hovered.png'); } /* The first headword in a (possibly) multi-headword DSL article */ .gdarticlebody > div:first-child .dsl_headwords, .gdarticlebody > h3:first-child { margin-top: -3px; display: inline-block; } .gdspellsuggestion { /* Add some vertical space before the article */ margin-top: 1.5em; } .dsl_ex, .dsl_ex .dsl_opt { color: steelblue; } ::selection { background:#cc0000; color:#fff; } code::selection { background: #333333; } goldendict-1.5.0/article-style.css000066400000000000000000001556111443523320500171360ustar00rootroot00000000000000/******** Global, non-dictionary-specific classes ***********/ body { background: #fefdeb; font-family: Tahoma, Verdana, "Lucida Sans Unicode", sans-serif; font-size: 13px; } /* This stylesheet is used to highligh current selection when doing a search. It changes the default selection color (e.g. blue under Windows, orange under Ubuntu) */ /* highlight/select color in Windows when a widget is inactive is nearly impossible to read (light gray). * then change it to green to use with the find option. */ ::selection { background: #c00; color: #fff; } /* Don't allow to select [] */ :before, :after { -webkit-user-select: none; user-select: none; } /* Plaintext dictionaries are usually 80-column formatted and take a lot * of space. We try to use smaller fonts for them therefore. */ pre { font-size: 12px; } /* Dictionary's name heading */ .gddictname { border: 1px dotted black; padding: 0.2em; padding-left: 0.5em; margin-top: 1.2em; margin-bottom: 9px; font-weight: bold; font-size: 14px; /*background: #ffffdd;*/ } /* The 'From ' string which precedes dictionary name in the heading */ .gdfromprefix { } /* The article span. Here we have a padding/margin hack to make fragment links behave better (have some space before the start of article) */ .gdarticle { display: block; padding-top: 1px; margin-top: -8px; margin-bottom: 8px; background: #fefdeb; /*fix for invalid blg*/ font-style:normal; } /* CSS trick to prevent the floating elements to overflow the article boundaries, see Issue #333. */ .gdarticle:after { content: ""; display: block; height: 0; clear: both; } /* Appears between the articles */ .gdarticleseparator { } /* Dictionary query error description string */ .gderrordesc { font-style: italic; background: url("qrcx://localhost/icons/warning.png") center left no-repeat !important; padding-left: 22px !important; margin: 1em; } /********* Babylon dictionaries' classes *********/ /* Transcriptions in Babylon dictionaries */ .bgltrn:before { content:"["; } .bgltrn:after { content:"]"; } .bgltrn { display:block; } /* Part of speech in Babylon dictionaries. Usually irrelevant, * therefore hidden */ .bglpos { display: none; } /* Right-to-left text */ .bglrtl { text-align: right; direction: rtl; } /******** StarDict dictionaries' classes *********/ /* StarDict type 'h' -- Html content. We don't normally do anything here. */ div.sdct_h { } .sdict_h_wav img { /* Ugly hack since "vertical-align: middle;" looks _terrible_ in Qt4's webkit: */ vertical-align: -30%; } /* StarDict type 'm' -- Pure meaning. Presented as
 */
pre.sdct_m
{
}

/* StarDict type 'l' -- Pure meaning which used to be in locale encoding. */
pre.sdct_m
{
}

/* StarDict type 'g' -- Pango Markup */
div.sdct_g
{
}

/* StarDict type 't' -- Transcription */
div.sdct_t
{
  font-weight: bold;
}

/* StarDict type 'y' -- Chinese YinBiao or Japanese KANA */
div.sdct_y
{
}

/* StarDict type 'x'. Should have xdxf_*-styled elements inside */
div.sdct_x
{
}

/* Blockquotes in XDXF-styled elements are used
   for indentation purposes, see Issue #305. */
.sdct_x blockquote
{
  margin: 0px;
  padding-left: 9px;
  display: inline-block;
}

/******** XDXF markup classes *********/

div.xdxf
{
}

/* Key -- keys shouldn't normally be present in the article's body since they
 * are accounted for separately, hence we hide them */
.xdxf_k
{
  display: none;
}

/* ...and if there's a break after it, we hide it too so we don't have an
 * extra vertical spacing. */
.xdxf_k + br
{
  display: none;
}

/* Article structure tag */
.xdxf_num
{
    color: red;
    font-weight: bold;
    padding-left:-15px;
}
.xdxf_def
{
    display: block;
    border-color: #e3e3e3;
    border-width: 1px;
    border-style: dashed;
    margin: 1px;
    margin-left: 15px;
}

.xdxf_def:target
{
    /* this pseudoclass is used to marked out the referenced  that was just clicked */
    border-color: red;
    border-width: 1px;
    border-style: double;
}

/* Abbreviation */
.xdxf_abbr
{
  font-style: italic;
  color: seagreen;
  cursor: default;
  border-bottom: 1px seagreen dotted;
}

/* Editorial comment */
.xdxf_co, .xdxf_co_old
{
    color: darkslateblue;
    font-style: italic;
}
.xdxf_co:before
{
    content:"("
}
.xdxf_co:after
{
    content:")"
}

/* Grammar information */
.xdxf_gr
{
    color: orangered;
    display: block;
}

.xdxf_gr_old
{
    color: blue; /*orangered;*/
}

/* Example */
.xdxf_ex
{
    color: #808080;
    display: block;
    margin-left: 14px;
}

.xdxf_ex_old
{
    color: #808080;
}

.xdxf_ex_source:before
{
    content:"["
}
.xdxf_ex_source:after
{
    content:"]"
}

.xdxf_ex_tran:before
{
    content:"— "
}

.xdxf_ex_tran
{
    font-style: italic;
}

/* Direct translation */
.xdxf_dtrn
{
    font-weight: bold;
    color: #dd7800;
}

/* Transcription */
.xdxf_tr:before
{
  content:"[";
}

.xdxf_tr:after
{
  content:"]";
}

.xdxf_tr, .xdxf_tr_old
{
  font-weight: bold;
}

/* Resource reference -- for now just a span. Since there's not yet a mechanism
 * to load resources off XDXF articles in GoldenDict, we just hide them. */
.xdxf_rref
{
  display: none;
}

.xdxf_headwords
{
  font-size: 116%;
  font-weight: bold;
  margin-top: 10px;
  margin-bottom: 10px;
}

/* The words in examples that are meaked out; they are marked out only when placed into  tag */
.xdxf_ex .xdxf_ex_markd, .xdxf_ex_old .xdxf_ex_markd
{
  color:black;
  background-color:lightgray;
}

.xdxf_opt
{
  color: #808080;
}

/******** SDictionary markup classes *********/

.sdict_forms
{
  font-style: italic;
}

/* Transcription */
.sdict_tr:before
{
  content:"[";
}

.sdict_tr:after
{
  content:"] ";
}

.sdict_tr
{
  font-weight: bold;
}

/************* LSA audio archives **************/

/* A table which contains a play icon and a word's link */
.lsa_play
{
  margin-top: 8px;
  margin-left: 8px;
}

.lsa_play a
{
  text-decoration: none;
}

/************* DSL dictionaries **************/

.dsl_u
{
  text-decoration: underline;
}

.dsl_article
{
  display:inline;
}

.dsl_article font[color=c_default_color]
{
  color: green;
}

.dsl_m, .dsl_m0
{
  padding-left: 0px;
}

.dsl_m1
{
  padding-left: 9px;
}

.dsl_m2
{
  padding-left: 18px;
}

.dsl_m3
{
  padding-left: 27px;
}

.dsl_m4
{
  padding-left: 36px;
}

.dsl_m5
{
  padding-left: 45px;
}

.dsl_m6
{
  padding-left: 54px;
}

.dsl_m7
{
  padding-left: 63px;
}

.dsl_m8
{
  padding-left: 72px;
}

.dsl_m9
{
  padding-left: 81px;
}

.dsl_p, .dsl_p :not(a) /* DSL Feature: Enforce the style even if the children tags say otherwise, except links */
{
  color: green !important; /* DSL label color must have highest priority */
  font-style: italic;
  cursor: default;
  font-weight: normal; /* DSL feature: labels are always of normal weight */
}

.dsl_stress_without_accent {
  display: none;
}

.dsl_t
{
  font-weight: bold;
}

.dsl_headwords
{
  font-weight: bold;
  margin-top: 15px;
  margin-bottom: 10px;
}

.dsl_headwords p
{
  font-weight: bold;
  font-size: 15px;
  margin: 0;
}

.dsl_definition p
{
  margin: 0;
}

.dsl_opt
{
  display: none;
}

.dsl_opt,
.dsl_opt .dsl_ex,
.dsl_opt .dsl_ex :not(a),
.dsl_opt .dsl_ex font[color] /* DSL Feature: Enforce the optional zone color even if the children tags say otherwise */
{
  color: gray;
}

.dsl_ex, .dsl_ex .dsl_opt
{
  color: gray;
}

/* Style for expand optional parts button */
.hidden_expand_opt
{
  color: blue;
  cursor: pointer;
  vertical-align: text-bottom;
}

.dsl_s_wav img
{
  /* Ugly hack since "vertical-align: middle;" looks _terrible_ in Qt4's webkit: */
  vertical-align: -30%;
}

.dsl_video .img
{
  display: inline-block;
  background: url('qrcx://localhost/icons/video.png');
  background-repeat: no-repeat;
  /* Ugly hack since "vertical-align: middle;" looks _terrible_ in Qt4's webkit: */
  vertical-align: -30%;
  width: 22px;
  height: 22px;
  margin: 0;
  padding: 0;
}

.dsl_video .filename
{
  display: none; /* by default, the file name is hidden */
  padding-left: 5px;
}

/************* MDict dictionaries **************/
.mdict
{
  margin-top: 1em;
}

.mdict a[name]
{
  text-decoration: none;
  color: inherit;
}

/************* Zim dictionaries **************/

.zimdict
{
  font-size: 110%!important;
}

.zimdict_headword
{
  font-weight: bold!important;
}

/************* Spelling suggestions *****************/

.gdspellsuggestion
{
  /* Add some vertical space before the article */
  margin-top: 1em;
}

/************* Stemmed suggestions *****************/

.gdstemmedsuggestion
{
  /* Add some horizontal and vertical space */
  margin-top: 1em;
  margin-left: 1px;
}


.gdstemmedsuggestion_head
{
  margin-left: 11px;
  font-style: italic;
}

.gdstemmedsuggestion_body
{
}

/************* Dictd articles *****************/
.dictd_article
{
  /* Add some vertical space before the article */
  margin-top: 1em;
}

.dictd_phonetic:before, .dictd_phonetic:after
{
  content: "\\";
  font-style: normal;
}

.dictd_phonetic:after
{
  margin-left: 3px;
}

.dictd_phonetic
{
  font-style: italic;
  color: #009900;
}

.dictd_article a:link
{
  color: #0000FF;
}

.dictserver_from
{
  font-size: 110%;
  font-weight: 600;
}

/************* Epwing dictionaries *****************/

.epwing_narrow_font
{
  width: 7px;
  height: 13px;
  vertical-align: -15%;
}

.epwing_wide_font
{
  width: 13px;
  height: 13px;
  vertical-align: -15%;
}

.epwing_article
{
  display:inline;
}

/************* Websites *****************/

.website_padding
{
  height: 1em;
  clear:both;
}

/************* Forvo **************/

.forvo_headword
{
  margin-top: 15px;
  margin-bottom: 10px;
  font-weight: bold;
  font-size: 15px;
}

/* A table which contains a play icon and information about the sound */
.forvo_play
{
  margin-top: 8px;
  margin-left: 8px;
}

.forvo_user
{
  text-decoration: none;
  color: green;
}

.forvo_location
{
  color: grey;
}

.forvo_positive_votes
{
  font-weight: bold;
  color: green;
}

.forvo_negative_votes
{
  font-weight: bold;
  color: red;
}

/************* Programs **************/

/* A table which contains a play icon and a word's link */
.programs_play
{
  margin-top: 8px;
  margin-left: 8px;
}

.programs_play a
{
  text-decoration: none;
}

.programs_plaintext, .programs_html
{
  margin-top: 15px;
}

/************* Voice engines **************/

.voiceengines_play
{
  margin-top: 8px;
  margin-left: 8px;
}

.voiceengines_play a
{
  text-decoration: none;
}

/************* MediaWiki articles *****************
 The following consist of excerpts from different .css files edited
 with a .mwiki prepended to each record.
*/

.mwiki
{
  /* Add some vertical space before the article */
  margin-top: 1em;
}

.mwiki .topicon
{
  /* Those are padlocks on top and such -- we disable those for GoldenDict
     since they look really ugly here */
  display: none;
}

/************ common.css **************/

/**
 * CSS in this file is used by *all* skins (that have any CSS at all).  Be
 * careful what you put in here, since what looks good in one skin may not in
 * another, but don't ignore the poor non-Monobook users either.
 */
.mwiki .mw-plusminus-null { color: #aaa; }

.mwiki .texvc { direction: ltr; unicode-bidi: embed; }
.mwiki img.tex { vertical-align: middle; }
.mwiki span.texhtml { font-family: serif; }

/* add a bit of margin space between the preview and the toolbar */
/* this replaces the ugly 


we used to insert into the page source */ .mwiki #wikiPreview.ontop { margin-bottom: 1em; } /* Stop floats from intruding into edit area in previews */ .mwiki #toolbar, .mwiki #wpTextbox1 { clear: both; } .mwiki div#mw-js-message { margin: 1em 5%; padding: 0.5em 2.5%; border: solid 1px #ddd; background-color: #fcfcfc; } /* Edit section links */ .mwiki .editsection { float: right; margin-left: 5px; } /** * File histories */ .mwiki table.filehistory { border:1px solid #ccc; border-collapse:collapse; } .mwiki table.filehistory th, .mwiki table.filehistory td { padding: 0 0.2em 0 0.2em; vertical-align:top; border:1px solid #ccc; } .mwiki table.filehistory th { text-align: left; } .mwiki table.filehistory td.mw-imagepage-filesize, .mwiki table.filehistory th.mw-imagepage-filesize { white-space:nowrap; } .mwiki table.filehistory td.filehistory-selected { font-weight: bold; } /* * rev_deleted stuff */ .mwiki li span.deleted, .mwiki span.history-deleted { text-decoration: line-through; color: #888; font-style: italic; } /** * Forms */ .mwiki body.ltr td.mw-label { text-align: right; } .mwiki body.ltr td.mw-input { text-align: left; } .mwiki body.ltr td.mw-submit { text-align: left; } .mwiki body.rtl td.mw-label { text-align: left; } .mwiki body.rtl td.mw-input { text-align: right; } .mwiki body.rtl td.mw-submit { text-align: right; } .mwiki td.mw-label { vertical-align: top; } .mwiki td.mw-submit { white-space: nowrap; } /** * Image captions */ .mwiki body.rtl .thumbcaption { text-align:right; } .mwiki body.rtl .magnify { float:left; } .mwiki body.ltr .thumbcaption { text-align:left; } .mwiki body.ltr .magnify { float:right; } /** * Hidden categories */ .mwiki .mw-hidden-cats-hidden { display: none; } .mwiki .catlinks-allhidden { display: none; } /* Convenience links to edit block, delete and protect reasons */ .mwiki p.mw-ipb-conveniencelinks, .mwiki p.mw-protect-editreasons, .mwiki p.mw-filedelete-editreasons, .mwiki p.mw-delete-editreasons { font-size: 90%; float: right; } /* Search results */ .mwiki div.searchresult { font-size: 95%; width:38em; } .mwiki .mw-search-results li { padding-bottom: 1em; list-style:none; } .mwiki .mw-search-result-data { color: green; font-size: 97%; } .mwiki td#mw-search-menu { padding-left:6em; font-size:85%; } .mwiki div#mw-search-interwiki { float: right; width: 18em; border-style: solid; border-color: #AAAAAA; border-width: 1px; margin-top: 2ex; } .mwiki div#mw-search-interwiki li { font-size: 95%; } .mwiki .mw-search-interwiki-more { float: right; font-size: 90%; } .mwiki span.searchalttitle { font-size: 95%; } .mwiki div.searchdidyoumean { font-size: 127%; margin-bottom: 1ex; margin-top: 1ex; /* Note that this color won't affect the link, as desired. */ color: #c00; } .mwiki div.searchdidyoumean em { font-weight: bold; } .mwiki .searchmatch { font-weight: bold; } .mwiki div.searchresults { border:1px solid darkblue; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 20px; } /* * UserRights stuff */ .mwiki .mw-userrights-disabled { color: #888; } .mwiki table.mw-userrights-groups * td,.mwiki table.mw-userrights-groups * th { padding-right: 1.5em; } /* * OpenSearch ajax suggestions */ .mwiki .os-suggest { overflow: auto; overflow-x: hidden; position: absolute; top: 0px; left: 0px; width: 0px; background-color: white; background-color: Window; border-style: solid; border-color: #AAAAAA; border-width: 1px; z-index:99; visibility:hidden; font-size:95%; } .mwiki table.os-suggest-results { font-size: 95%; cursor: pointer; border: 0; border-collapse: collapse; width: 100%; } .mwiki td.os-suggest-result, .mwiki td.os-suggest-result-hl { white-space: nowrap; background-color: white; background-color: Window; color: black; color: WindowText; padding: 2px; } .mwiki td.os-suggest-result-hl, .mwiki td.os-suggest-result-hl-webkit { background-color: #4C59A6; color: white; } .mwiki td.os-suggest-result-hl { /* System colors are misimplemented in Safari 3.0 and earlier, making highlighted text illegible... */ background-color: Highlight; color: HighlightText; } .mwiki .os-suggest-toggle { position: relative; left: 1ex; font-size: 65%; } .mwiki .os-suggest-toggle-def { position: absolute; top: 0px; left: 0px; font-size: 65%; visibility: hidden; } /* Page history styling */ /* the auto-generated edit comments */ .mwiki .autocomment { color: gray; } .mwiki #pagehistory .history-user { margin-left: 0.4em; margin-right: 0.2em; } .mwiki #pagehistory span.minor { font-weight: bold; } .mwiki #pagehistory li { border: 1px solid white; } .mwiki #pagehistory li.selected { background-color: #f9f9f9; border: 1px dashed #aaa; } /* * Special:ListGroupRights styling * Special:Statistics styling */ .mwiki table.mw-listgrouprights-table, .mwiki table.mw-statistics-table { border: 1px solid #ccc; border-collapse: collapse; } .mwiki table.mw-listgrouprights-table tr { vertical-align: top; } .mwiki table.mw-listgrouprights-table td, .mwiki table.mw-listgrouprights-table th, .mwiki table.mw-statistics-table td, .mwiki table.mw-statistics-table th { padding: 0.5em 0.2em 0.5em 0.2em; border: 1px solid #ccc; } .mwiki td.mw-statistics-numbers { text-align: right; } /* Special:SpecialPages styling */ .mwiki h4.mw-specialpagesgroup { background-color: #dcdcdc; padding: 2px; margin: .3em 0em 0em 0em; } .mwiki .mw-specialpagerestricted { font-weight: bold; } .mwiki #shared-image-dup, .mwiki #shared-image-conflict { font-style: italic; } /* Special:EmailUser styling */ .mwiki table.mw-emailuser-table { width: 98%; } .mwiki td#mw-emailuser-sender, .mwiki td#mw-emailuser-recipient { font-weight: bold; } /* Special:Prefixindex styling */ .mwiki table#mw-prefixindex-list-table, .mwiki table#mw-prefixindex-nav-table { width: 98%; background-color: transparent; } .mwiki td#mw-prefixindex-nav-form { font-size: smaller; margin-bottom: 1em; text-align: right; vertical-align: top; } /* * Recreating deleted page warning * Reupload file warning * Page protection warning * incl. log entries for these warnings */ .mwiki div.mw-warning-with-logexcerpt { padding: 3px; margin-bottom: 3px; border: 2px solid #2F6FAB; } .mwiki div.mw-warning-with-logexcerpt ul li { font-size: 90%; } /* (show/hide) revision deletion links */ .mwiki span.mw-revdelundel-link, .mwiki strong.mw-revdelundel-link { font-family: monospace; font-size: smaller } /* feed links */ .mwiki a.feedlink { background: url("images/feed-icon.png") center left no-repeat; padding-left: 16px; } /************ monobook/main.css **************/ /* ** MediaWiki 'monobook' style sheet for CSS2-capable browsers. ** Copyright Gabriel Wicke - http://wikidev.net/ ** License: GPL (http://www.gnu.org/copyleft/gpl.html) ** ** Loosely based on http://www.positioniseverything.net/ordered-floats.html by Big John ** and the Plone 2.0 styles, see http://plone.org/ (Alexander Limi,Joe Geldart & Tom Croucher, ** Michael Zeltner and Geir Bækholt) ** All you guys rock :) */ .mwiki #column-content { width: 100%; float: right; margin: 0 0 .6em -12.2em; padding: 0; } .mwiki #content { margin: 2.8em 0 0 12.2em; padding: 0 1em 1em 1em; position: relative; z-index: 2; } .mwiki #column-one { padding-top: 160px; } .mwiki #content { background: white; color: black; border: 1px solid #aaa; border-right: none; line-height: 1.5em; } /* the left column width is specified in class .portlet */ /* Font size: ** We take advantage of keyword scaling- browsers won't go below 9px ** More at http://www.w3.org/2003/07/30-font-size ** http://style.cleverchimp.com/font_size_intervals/altintervals.html */ /* body { font: x-small sans-serif; background: #f9f9f9 url(headbg.jpg) 0 0 no-repeat; color: black; margin: 0; padding: 0; } */ /* scale back up to a sane default */ .mwiki #globalWrapper { font-size: 127%; width: 100%; margin: 0; padding: 0; } .mwiki .visualClear { clear: both; } /* general styles */ .mwiki table { font-size: 100%; color: black; /* we don't want the bottom borders of

s to be visible through floated tables */ background-color: white; } .mwiki fieldset table { /* but keep table layouts in forms clean... */ background: none; } .mwiki a { text-decoration: none; color: #002bb8; background: none; } .mwiki a:visited { color: #5a3696; } .mwiki a:active { color: #faa700; } .mwiki a:hover { text-decoration: underline; } .mwiki a.stub { color: #772233; } .mwiki a.new, .mwiki #p-personal a.new { color: #ba0000; } .mwiki a.new:visited, .mwiki #p-personal a.new:visited { color: #a55858; } .mwiki img { border: none; vertical-align: middle; } .mwiki p { margin: .4em 0 .5em 0; line-height: 1.5em; } .mwiki p img { margin: 0; } .mwiki hr { height: 1px; color: #aaa; background-color: #aaa; border: 0; margin: .2em 0 .2em 0; } .mwiki h1, .mwiki h2, .mwiki h3, .mwiki h4, .mwiki h5, .mwiki h6 { color: black; background: none; font-weight: normal; margin: 0; padding-top: .5em; padding-bottom: .17em; border-bottom: 1px solid #aaa; } .mwiki h1 { font-size: 188%; } .mwiki h1 .editsection { font-size: 53%; } .mwiki h2 { font-size: 150%; } .mwiki h2 .editsection { font-size: 67%; } .mwiki h3, .mwiki h4, .mwiki h5, .mwiki h6 { border-bottom: none; font-weight: bold; } .mwiki h3 { font-size: 132%; } .mwiki h3 .editsection { font-size: 76%; font-weight: normal; } .mwiki h4 { font-size: 116%; } .mwiki h4 .editsection { font-size: 86%; font-weight: normal; } .mwiki h5 { font-size: 100%; } .mwiki h5 .editsection { font-weight: normal; } .mwiki h6 { font-size: 80%; } .mwiki h6 .editsection { font-size: 125%; font-weight: normal; } .mwiki ul { line-height: 1.5em; list-style-type: square; margin: .3em 0 0 1.5em; padding: 0; list-style-image: url(bullet.gif); } .mwiki ol { line-height: 1.5em; margin: .3em 0 0 3.2em; padding: 0; list-style-image: none; } .mwiki li { margin-bottom: .1em; } .mwiki dt { font-weight: bold; margin-bottom: .1em; } .mwiki dl { margin-top: .2em; margin-bottom: .5em; } .mwiki dd { line-height: 1.5em; margin-left: 2em; margin-bottom: .1em; } .mwiki fieldset { border: 1px solid #2f6fab; margin: 1em 0 1em 0; padding: 0 1em 1em; line-height: 1.5em; } .mwiki fieldset.nested { margin: 0 0 0.5em 0; padding: 0 0.5em 0.5em; } .mwiki legend { padding: .5em; font-size: 95%; } .mwiki form { border: none; margin: 0; } .mwiki textarea { width: 100%; padding: .1em; } .mwiki input.historysubmit { padding: 0 .3em .3em .3em !important; font-size: 94%; cursor: pointer; height: 1.7em !important; margin-left: 1.6em; } .mwiki select { vertical-align: top; } .mwiki abbr, .mwiki acronym, .mwiki .explain { border-bottom: 1px dotted black; color: black; background: none; cursor: help; } .mwiki q { font-family: Times, "Times New Roman", serif; font-style: italic; } /* disabled for now .mwiki blockquote { font-family: Times, "Times New Roman", serif; font-style: italic; }*/ .mwiki code { background-color: #f9f9f9; } .mwiki pre { padding: 1em; border: 1px dashed #2f6fab; color: black; background-color: #f9f9f9; line-height: 1.1em; } /* ** the main content area */ .mwiki #siteSub { display: none; } .mwiki #jump-to-nav { display: none; } .mwiki #contentSub, .mwiki #contentSub2 { font-size: 84%; line-height: 1.2em; margin: 0 0 1.4em 1em; color: #7d7d7d; width: auto; } .mwiki span.subpages { display: block; } /* Some space under the headers in the content area */ .mwiki #bodyContent h1, .mwiki #bodyContent h2 { margin-bottom: .6em; } .mwiki #bodyContent h3, .mwiki #bodyContent h4, .mwiki #bodyContent h5 { margin-bottom: .3em; } .mwiki #firstHeading { margin-bottom: .1em; /* These two rules hack around bug 2013 (fix for more limited bug 11325). When bug 2013 is fixed properly, they should be removed. */ line-height: 1.2em; padding-bottom: 0; } /* user notification thing */ .mwiki .usermessage { background-color: #ffce7b; border: 1px solid #ffa500; color: black; font-weight: bold; margin: 2em 0 1em; padding: .5em 1em; vertical-align: middle; } .mwiki #siteNotice { text-align: center; font-size: 95%; padding: 0 .9em; } .mwiki #siteNotice p { margin: 0; padding: 0; } .mwiki .success { color: green; font-size: larger; } .mwiki .error { color: red; font-size: larger; } .mwiki .errorbox, .mwiki .successbox { font-size: larger; border: 2px solid; padding: .5em 1em; float: left; margin-bottom: 2em; color: #000; } .mwiki .errorbox { border-color: red; background-color: #fff2f2; } .mwiki .successbox { border-color: green; background-color: #dfd; } .mwiki .errorbox h2, .mwiki .successbox h2 { font-size: 1em; font-weight: bold; display: inline; margin: 0 .5em 0 0; border: none; } .mwiki .catlinks { border: 1px solid #aaa; background-color: #f9f9f9; padding: 5px; margin-top: 1em; clear: both; } /* currently unused, intended to be used by a metadata box in the bottom-right corner of the content area */ .mwiki .documentDescription { /* The summary text describing the document */ font-weight: bold; display: block; margin: 1em 0; line-height: 1.5em; } .mwiki .documentByLine { text-align: right; font-size: 90%; clear: both; font-weight: normal; color: #76797c; } /* emulate center */ .mwiki .center { width: 100%; text-align: center; } .mwiki *.center * { margin-left: auto; margin-right: auto; } /* small for tables and similar */ .mwiki .small, .mwiki .small * { font-size: 94%; } .mwiki table.small { font-size: 100%; } /* ** content styles */ .mwiki #toc, .mwiki .toc, .mwiki .mw-warning { border: 1px solid #aaa; background-color: #f9f9f9; padding: 5px; font-size: 95%; } .mwiki #toc h2, .mwiki .toc h2 { display: inline; border: none; padding: 0; font-size: 100%; font-weight: bold; } .mwiki #toc #toctitle, .mwiki .toc #toctitle, .mwiki #toc .toctitle, .mwiki .toc .toctitle { text-align: center; } .mwiki #toc ul, .mwiki .toc ul { list-style-type: none; list-style-image: none; margin-left: 0; padding-left: 0; text-align: left; } .mwiki #toc ul ul, .mwiki .toc ul ul { margin: 0 0 0 2em; } .mwiki #toc .toctoggle, .mwiki .toc .toctoggle { font-size: 94%; } .mwiki .mw-warning { margin-left: 50px; margin-right: 50px; text-align: center; } /* images */ .mwiki div.floatright, .mwiki table.floatright { clear: right; float: right; position: relative; margin: 0 0 .5em .5em; border: 0; /* border: .5em solid white; border-width: .5em 0 .8em 1.4em; */ } .mwiki div.floatright p { font-style: italic; } .mwiki div.floatleft, .mwiki table.floatleft { float: left; clear: left; position: relative; margin: 0 .5em .5em 0; border: 0; /* margin: .3em .5em .5em 0; border: .5em solid white; border-width: .5em 1.4em .8em 0; */ } .mwiki div.floatleft p { font-style: italic; } /* thumbnails */ .mwiki div.thumb { margin-bottom: .5em; border-style: solid; border-color: white; width: auto; } .mwiki div.thumbinner { border: 1px solid #ccc; padding: 3px !important; background-color: #f9f9f9; font-size: 94%; text-align: center; overflow: hidden; } .mwiki html .thumbimage { border: 1px solid #ccc; } .mwiki html .thumbcaption { border: none; text-align: left; line-height: 1.4em; padding: 3px !important; font-size: 94%; } .mwiki div.magnify { float: right; border: none !important; background: none !important; } .mwiki div.magnify a, .mwiki div.magnify img { display: block; border: none !important; background: none !important; } .mwiki div.tright { clear: right; float: right; border-width: .5em 0 .8em 1.4em; } .mwiki div.tleft { float: left; clear: left; margin-right: .5em; border-width: .5em 1.4em .8em 0; } .mwiki img.thumbborder { border: 1px solid #dddddd; } .mwiki .hiddenStructure { display: none; } /* ** classes for special content elements like town boxes ** intended to be referenced directly from the wiki src */ /* ** User styles */ /* table standards */ .mwiki table.rimage { float: right; position: relative; margin-left: 1em; margin-bottom: 1em; text-align: center; } .mwiki .toccolours { border: 1px solid #aaa; background-color: #f9f9f9; padding: 5px; font-size: 95%; } /* ** edit views etc */ .mwiki .special li { line-height: 1.4em; margin: 0; padding: 0; } /* ** keep the whitespace in front of the ^=, hides rule from konqueror ** this is css3, the validator doesn't like it when validating as css2 */ .mwiki #bodyContent a.external, .mwiki #bodyContent a[href ^="gopher://"] { background: url(external.png) center right no-repeat; padding-right: 13px; } .mwiki #bodyContent a[href ^="https://"], .mwiki .link-https { background: url(lock_icon.gif) center right no-repeat; padding-right: 16px; } .mwiki #bodyContent a[href ^="mailto:"], .mwiki .link-mailto { background: url(mail_icon.gif) center right no-repeat; padding-right: 18px; } .mwiki #bodyContent a[href ^="news://"] { background: url(news_icon.png) center right no-repeat; padding-right: 18px; } .mwiki #bodyContent a[href ^="ftp://"], .mwiki .link-ftp { background: url(file_icon.gif) center right no-repeat; padding-right: 18px; } .mwiki #bodyContent a[href ^="irc://"], .mwiki #bodyContent a.extiw[href ^="irc://"], .mwiki .link-irc { background: url(discussionitem_icon.gif) center right no-repeat; padding-right: 18px; } .mwiki #bodyContent a.external[href $=".ogg"], .mwiki #bodyContent a.external[href $=".OGG"], .mwiki #bodyContent a.external[href $=".oga"], .mwiki #bodyContent a.external[href $=".OGA"], .mwiki #bodyContent a.external[href $=".mid"], .mwiki #bodyContent a.external[href $=".MID"], .mwiki #bodyContent a.external[href $=".midi"], .mwiki #bodyContent a.external[href $=".MIDI"], .mwiki #bodyContent a.external[href $=".mp3"], .mwiki #bodyContent a.external[href $=".MP3"], .mwiki #bodyContent a.external[href $=".wav"], .mwiki #bodyContent a.external[href $=".WAV"], .mwiki #bodyContent a.external[href $=".wma"], .mwiki #bodyContent a.external[href $=".WMA"], .mwiki .link-audio { background: url("audio.png") center right no-repeat; padding-right: 13px; } .mwiki #bodyContent a.external[href $=".ogm"], .mwiki #bodyContent a.external[href $=".OGM"], .mwiki #bodyContent a.external[href $=".avi"], .mwiki #bodyContent a.external[href $=".AVI"], .mwiki #bodyContent a.external[href $=".mpeg"], .mwiki #bodyContent a.external[href $=".MPEG"], .mwiki #bodyContent a.external[href $=".mpg"], .mwiki #bodyContent a.external[href $=".MPG"], .mwiki .link-video { background: url("video.png") center right no-repeat; padding-right: 13px; } .mwiki #bodyContent a.external[href $=".pdf"], .mwiki #bodyContent a.external[href $=".PDF"], .mwiki #bodyContent a.external[href *=".pdf#"], .mwiki #bodyContent a.external[href *=".PDF#"], .mwiki #bodyContent a.external[href *=".pdf?"], .mwiki #bodyContent a.external[href *=".PDF?"], .mwiki .link-document { background: url("document.png") center right no-repeat; padding-right: 12px; } /* disable interwiki styling */ .mwiki #bodyContent a.extiw, .mwiki #bodyContent a.extiw:active { color: #36b; background: none; padding: 0; } .mwiki #bodyContent a.external { color: #36b; } /* this can be used in the content area to switch off special external link styling */ .mwiki #bodyContent .plainlinks a { background: none !important; padding: 0 !important; } /* ** Structural Elements */ /* ** general portlet styles (elements in the quickbar) */ .mwiki .portlet { border: none; margin: 0 0 .5em; padding: 0; float: none; width: 11.6em; overflow: hidden; } .mwiki .portlet h4 { font-size: 95%; font-weight: normal; white-space: nowrap; } .mwiki .portlet h5 { background: transparent; padding: 0 1em 0 .5em; display: inline; height: 1em; text-transform: lowercase; font-size: 91%; font-weight: normal; white-space: nowrap; } .mwiki .portlet h6 { background: #ffae2e; border: 1px solid #2f6fab; border-style: solid solid none solid; padding: 0 1em 0 1em; text-transform: lowercase; display: block; font-size: 1em; height: 1.2em; font-weight: normal; white-space: nowrap; } .mwiki .pBody { font-size: 95%; background-color: white; color: black; border-collapse: collapse; border: 1px solid #aaa; padding: 0 .8em .3em .5em; } .mwiki .portlet h1, .mwiki .portlet h2, .mwiki .portlet h3, .mwiki .portlet h4 { margin: 0; padding: 0; } .mwiki .portlet ul { line-height: 1.5em; list-style-type: square; list-style-image: url(bullet.gif); font-size: 95%; } .mwiki .portlet li { padding: 0; margin: 0; } /* ** Logo properties */ .mwiki #p-logo { top: 0; left: 0; position: absolute; /*needed to use z-index */ z-index: 3; height: 155px; width: 12em; overflow: visible; } .mwiki #p-logo h5 { display: none; } .mwiki #p-logo a, .mwiki #p-logo a:hover { display: block; height: 155px; width: 12.2em; background-repeat: no-repeat; background-position: 35% 50% !important; text-decoration: none; } /* ** Search portlet */ .mwiki #p-search { position: relative; z-index: 3; } .mwiki input.searchButton { margin-top: 1px; font-size: 95%; } .mwiki #searchGoButton { padding-left: .5em; padding-right: .5em; font-weight: bold; } .mwiki #searchInput { width: 10.9em; margin: 0; font-size: 95%; } .mwiki #p-search .pBody { padding: .5em .4em .4em .4em; text-align: center; } .mwiki #p-search #searchform div div { margin-top: .4em; font-size: 95%; } /* ** the personal toolbar */ .mwiki #p-personal { position: absolute; left: 0; top: 0; z-index: 0; } .mwiki #p-personal { width: 100%; white-space: nowrap; padding: 0; margin: 0; border: none; background: none; overflow: visible; line-height: 1.2em; } .mwiki #p-personal h5 { display: none; } .mwiki #p-personal .portlet, .mwiki #p-personal .pBody { z-index: 0; padding: 0; margin: 0; border: none; overflow: visible; background: none; } /* this is the ul contained in the portlet */ .mwiki #p-personal ul { border: none; line-height: 1.4em; color: #2f6fab; padding: 0 2em 0 3em; margin: 0; text-align: right; list-style: none; z-index: 0; background: none; cursor: default; } .mwiki #p-personal li { z-index: 0; border: none; padding: 0; display: inline; color: #2f6fab; margin-left: 1em; line-height: 1.2em; background: none; } .mwiki #p-personal li a { text-decoration: none; color: #005896; padding-bottom: .2em; background: none; } .mwiki #p-personal li a:hover { background-color: white; padding-bottom: .2em; text-decoration: none; } .mwiki #p-personal li.active a:hover { background-color: transparent; } /* the icon in front of the user name, single quotes in bg url to hide it from iemac */ .mwiki li#pt-userpage, .mwiki li#pt-anonuserpage, .mwiki li#pt-login { background: url(user.gif) top left no-repeat; padding-left: 20px; text-transform: none; } .mwiki #p-personal ul { text-transform: lowercase; } .mwiki #p-personal li.active { font-weight: bold; } /* ** the page-related actions- page/talk, edit etc */ .mwiki #p-cactions { position: absolute; top: 1.3em; left: 11.5em; margin: 0; white-space: nowrap; width: 76%; line-height: 1.1em; overflow: visible; background: none; border-collapse: collapse; padding-left: 1em; list-style: none; font-size: 95%; } .mwiki #p-cactions ul { list-style: none; } .mwiki #p-cactions li { display: inline; border: 1px solid #aaa; border-bottom: none; padding: 0 0 .1em 0; margin: 0 .3em 0 0; overflow: visible; background: white; } .mwiki #p-cactions li.selected { border-color: #fabd23; padding: 0 0 .2em 0; font-weight: bold; } .mwiki #p-cactions li a { background-color: #fbfbfb; color: #002bb8; border: none; padding: 0 .8em .3em; position: relative; z-index: 0; margin: 0; text-decoration: none; } .mwiki #p-cactions li.selected a { z-index: 3; padding: 0 1em .2em!important; background-color: white; } .mwiki #p-cactions .new a { color: #ba0000; } .mwiki #p-cactions li a:hover { z-index: 3; text-decoration: none; background-color: white; } .mwiki #p-cactions h5 { display: none; } .mwiki #p-cactions li.istalk { margin-right: 0; } .mwiki #p-cactions li.istalk a { padding-right: .5em; } .mwiki #p-cactions #ca-addsection a { padding-left: .4em; padding-right: .4em; } /* offsets to distinguish the tab groups */ .mwiki li#ca-talk { margin-right: 1.6em; } .mwiki li#ca-watch, .mwiki li#ca-unwatch, .mwiki li#ca-varlang-0, .mwiki li#ca-print { margin-left: 1.6em; } .mwiki #p-cactions .pBody { font-size: 1em; background-color: transparent; color: inherit; border-collapse: inherit; border: 0; padding: 0; } .mwiki #p-cactions .hiddenStructure { display: none; } .mwiki #p-cactions li a { text-transform: lowercase; } .mwiki #p-lang { position: relative; z-index: 3; } /* TODO: #t-iscite is only used by the Cite extension, come up with some * system which allows extensions to add to this file on the fly */ .mwiki #t-ispermalink, .mwiki #t-iscite { color: #999; } /* ** footer */ .mwiki #footer { background-color: white; border-top: 1px solid #fabd23; border-bottom: 1px solid #fabd23; margin: .6em 0 1em 0; padding: .4em 0 1.2em 0; text-align: center; font-size: 90%; } .mwiki #footer li { display: inline; margin: 0 1.3em; } .mwiki #f-poweredbyico, .mwiki #f-copyrightico { margin: 0 8px; position: relative; top: -2px; /* Bump it up just a tad */ } .mwiki #f-poweredbyico { float: right; height: 1%; } .mwiki #f-copyrightico { float: left; height: 1%; } /* js pref toc */ .mwiki #preftoc { margin: 0; padding: 0; width: 100%; clear: both; } .mwiki #preftoc li { background-color: #f0f0f0; color: #000; } .mwiki #preftoc li { margin: 1px -2px 1px 2px; float: left; padding: 2px 0 3px 0; border: 1px solid #fff; border-right-color: #716f64; border-bottom: 0; position: relative; white-space: nowrap; list-style-type: none; list-style-image: none; z-index: 3; } .mwiki #preftoc li.selected { font-weight: bold; background-color: #f9f9f9; border: 1px solid #aaa; border-bottom: none; cursor: default; top: 1px; padding-top: 2px; margin-right: -3px; } .mwiki #preftoc > li.selected { top: 2px; } .mwiki #preftoc a, .mwiki #preftoc a:active { display: block; color: #000; padding: 0 .7em; position: relative; text-decoration: none; } .mwiki #preftoc li.selected a { cursor: default; text-decoration: none; } .mwiki #prefcontrol { padding-top: 2em; clear: both; } .mwiki #preferences { margin: 0; border: 1px solid #aaa; clear: both; padding: 1.5em; background-color: #F9F9F9; } .mwiki .prefsection { border: none; padding: 0; margin: 0; } .mwiki .prefsection fieldset { border: 1px solid #aaa; float: left; margin-right: 2em; } .mwiki .prefsection legend { font-weight: bold; } .mwiki .prefsection table, .mwiki .prefsection legend { background-color: #F9F9F9; } .mwiki .mainLegend { display: none; } .mwiki div.prefsectiontip { font-size: x-small; padding: .2em 2em; color: #666; } .mwiki .btnSavePrefs { font-weight: bold; padding-left: .3em; padding-right: .3em; } .mwiki .preferences-login { clear: both; margin-bottom: 1.5em; } .mwiki .prefcache { font-size: 90%; margin-top: 2em; } .mwiki div#userloginForm form, .mwiki div#userlogin form#userlogin2 { margin: 0 3em 1em 0; border: 1px solid #aaa; clear: both; padding: 1.5em 2em; background-color: #f9f9f9; float: left; } .mwiki .rtl div#userloginForm form, .mwiki .rtl div#userlogin form#userlogin2 { float: right; } .mwiki div#userloginForm table, .mwiki div#userlogin form#userlogin2 table { background-color: #f9f9f9; } .mwiki div#userloginForm h2, .mwiki div#userlogin form#userlogin2 h2 { padding-top: 0; } .mwiki div#userlogin .captcha, .mwiki div#userloginForm .captcha { border: 1px solid #bbb; padding: 1.5em 2em; background-color: white; } .mwiki #loginend, .mwiki #signupend { clear: both; } .mwiki #userloginprompt, .mwiki #languagelinks { font-size: 85%; } .mwiki #login-sectiontip { font-size: 85%; line-height: 1.2; padding-top: 2em; } .mwiki #userlogin .loginText, .mwiki #userlogin .loginPassword { width: 12em; } .mwiki #userloginlink a, .mwiki #wpLoginattempt, .mwiki #wpCreateaccount { font-weight: bold; } /* IE/Mac fixes were cut out */ .mwiki .redirectText { font-size: 150%; margin: 5px; } .mwiki .printfooter { display: none; } .mwiki .not-patrolled { background-color: #ffa; } .mwiki div.patrollink { clear: both; font-size: 75%; text-align: right; } .mwiki span.newpage, .mwiki span.minor, .mwiki span.bot { font-weight: bold; } .mwiki span.unpatrolled { font-weight: bold; color: red; } .mwiki .sharedUploadNotice { font-style: italic; } .mwiki span.updatedmarker { color: black; background-color: #0f0; } .mwiki table.gallery { border: 1px solid #ccc; margin: 2px; padding: 2px; background-color: white; } .mwiki table.gallery tr { vertical-align: top; } .mwiki table.gallery td { vertical-align: top; background-color: #f9f9f9; border: solid 2px white; } /* Keep this temporarily so that cached pages will display right */ .mwiki table.gallery td.galleryheader { text-align: center; font-weight: bold; } .mwiki table.gallery caption { font-weight: bold; } .mwiki div.gallerybox { margin: 2px; } .mwiki div.gallerybox div.thumb { text-align: center; border: 1px solid #ccc; margin: 2px; } .mwiki div.gallerytext { overflow: hidden; font-size: 94%; padding: 2px 4px; } .mwiki span.comment { font-style: italic; } .mwiki span.changedby { font-size: 95%; } .mwiki .previewnote { text-indent: 3em; color: #c00; border-bottom: 1px solid #aaa; padding-bottom: 1em; margin-bottom: 1em; } .mwiki .previewnote p { margin: 0; padding: 0; } .mwiki .editExternally { border: 1px solid gray; background-color: #ffffff; padding: 3px; margin-top: 0.5em; float: left; font-size: small; text-align: center; } .mwiki .editExternallyHelp { font-style: italic; color: gray; } .mwiki .toggle { margin-left: 2em; text-indent: -2em; } /* Classes for EXIF data display */ .mwiki table.mw_metadata { font-size: 0.8em; margin-left: 0.5em; margin-bottom: 0.5em; width: 300px; } .mwiki table.mw_metadata caption { font-weight: bold; } .mwiki table.mw_metadata th { font-weight: normal; } .mwiki table.mw_metadata td { padding: 0.1em; } .mwiki table.mw_metadata { border: none; border-collapse: collapse; } .mwiki table.mw_metadata td, .mwiki table.mw_metadata th { text-align: center; border: 1px solid #aaaaaa; padding-left: 0.1em; padding-right: 0.1em; } .mwiki table.mw_metadata th { background-color: #f9f9f9; } .mwiki table.mw_metadata td { background-color: #fcfcfc; } .mwiki table.collapsed tr.collapsable { display: none; } /* filetoc */ .mwiki ul#filetoc { text-align: center; border: 1px solid #aaaaaa; background-color: #f9f9f9; padding: 5px; font-size: 95%; margin-bottom: 0.5em; margin-left: 0; margin-right: 0; } .mwiki #filetoc li { display: inline; list-style-type: none; padding-right: 2em; } .mwiki input#wpSummary { width: 80%; } /* @bug 1714 */ .mwiki input#wpSave, .mwiki input#wpDiff { margin-right: 0.33em; } .mwiki #wpSave { font-weight: bold; } /* Classes for article validation */ .mwiki table.revisionform_default { border: 1px solid #000000; } .mwiki table.revisionform_focus { border: 1px solid #000000; background-color:#00BBFF; } .mwiki tr.revision_tr_default { background-color:#EEEEEE; } .mwiki tr.revision_tr_first { background-color:#DDDDDD; } .mwiki p.revision_saved { color: green; font-weight:bold; } .mwiki #mw_trackbacks { border: solid 1px #bbbbff; background-color: #eeeeff; padding: 0.2em; } /* Allmessages table */ .mwiki #allmessagestable th { background-color: #b2b2ff; } .mwiki #allmessagestable tr.orig { background-color: #ffe2e2; } .mwiki #allmessagestable tr.new { background-color: #e2ffe2; } .mwiki #allmessagestable tr.def { background-color: #f0f0ff; } /* noarticletext */ .mwiki div.noarticletext { border: 1px solid #ccc; background: #fff; padding: .2em 1em; color: #000; } .mwiki div#searchTargetContainer { left: 10px; top: 10px; width: 90%; background: white; } .mwiki div#searchTarget { padding: 3px; margin: 5px; background: #F0F0F0; border: solid 1px blue; } .mwiki div#searchTarget ul li { list-style: none; } .mwiki div#searchTarget ul li:before { color: orange; content: "\00BB \0020"; } .mwiki div#searchTargetHide { float:right; border:solid 1px black; background:#DCDCDC; padding:2px; } .mwiki #powersearch p { margin-top:0px; } .mwiki div.multipageimagenavbox { border: solid 1px silver; padding: 4px; margin: 1em; background: #f0f0f0; } .mwiki div.multipageimagenavbox div.thumb { border: none; margin-left: 2em; margin-right: 2em; } .mwiki div.multipageimagenavbox hr { margin: 6px; } .mwiki table.multipageimage td { text-align: center; } /** Special:Version */ .mwiki table#sv-ext, table#sv-hooks, .mwiki table#sv-software { margin: 1em; padding:0em; } .mwiki #sv-ext td, .mwiki #sv-hooks td, .mwiki #sv-software td, .mwiki #sv-ext th, .mwiki #sv-hooks th, .mwiki #sv-software th { border: 1px solid #A0A0A0; padding: 0 0.15em 0 0.15em; } .mwiki #sv-ext th, .mwiki #sv-hooks th, .mwiki #sv-software th { background-color: #F0F0F0; color: black; padding: 0 0.15em 0 0.15em; } .mwiki tr.sv-space{ height: 0.8em; border:none; } .mwiki tr.sv-space td { display: none; } /* Table pager (e.g. Special:Imagelist) - remove underlines from the navigation link - collapse borders - set the borders to outsets (similar to Special:Allmessages) - remove line wrapping for all td and th, set background color - restore line wrapping for the last two table cells (description and size) */ .mwiki .TablePager { min-width: 80%; } .mwiki .TablePager_nav a { text-decoration: none; } .mwiki .TablePager { border-collapse: collapse; } .mwiki .TablePager, .mwiki .TablePager td, .mwiki .TablePager th { border: 1px solid #aaaaaa; padding: 0 0.15em 0 0.15em; } .mwiki .TablePager th { background-color: #eeeeff } .mwiki .TablePager td { background-color: #ffffff } .mwiki .TablePager tr:hover td { background-color: #eeeeff } .mwiki .imagelist td, .mwiki .imagelist th { white-space: nowrap } .mwiki .imagelist .TablePager_col_links { background-color: #eeeeff } .mwiki .imagelist .TablePager_col_img_description { white-space: normal } .mwiki .imagelist th.TablePager_sort { background-color: #ccccff } .mwiki .templatesUsed { margin-top: 1.5em; } .mwiki .mw-summary-preview { margin: 0.1em 0; } /* Friendlier slave lag warnings */ .mwiki div.mw-lag-warn-normal, .mwiki div.mw-lag-warn-high { padding: 3px; text-align: center; margin: 3px auto; } .mwiki div.mw-lag-warn-normal { border: 1px solid #FFCC66; background-color: #FFFFCC; } .mwiki div.mw-lag-warn-high { font-weight: bold; border: 2px solid #FF0033; background-color: #FFCCCC; } .mwiki .MediaTransformError { background-color: #ccc; padding: 0.1em; } .mwiki .MediaTransformError td { text-align: center; vertical-align: middle; font-size: 90%; } /** Special:Search stuff */ .mwiki div#mw-search-interwiki-caption { text-align: center; font-weight: bold; font-size: 95%; } .mwiki .mw-search-interwiki-project { font-size: 97%; text-align: left; padding-left: 0.2em; padding-right: 0.15em; padding-bottom: 0.2em; padding-top: 0.15em; background: #cae8ff; } /* God-damned hack for the crappy layout */ .mwiki .os-suggest { font-size: 127%; } /**** Excerpts from http://en.wikipedia.org/wiki/MediaWiki:Common.css ******/ /* Since GoldenDict support is not Wikipedia-specific, we don't include all * of it, and besides, it's large and volatile anyway. */ /* It was also edited for the usually narrow article view */ /* Make the list of references smaller */ .mwiki ol.references { font-size: 100%; } .mwiki .references-small { font-size: 90%; } /* VALIDATOR NOTICE: the following is correct, but the W3C validator doesn't accept it */ /* -moz-* is a vendor-specific extension (CSS 2.1 4.1.2.1) */ /* column-count is from the CSS3 module "CSS Multi-column Layout" */ /* Please ignore any validator errors caused by these two lines */ .mwiki .references-2column { font-size: 90%; -moz-column-count: 2; -webkit-column-count: 2; column-count: 2; } /* Highlight clicked reference in blue to help navigation */ .mwiki ol.references > li:target { background-color: #DEF; } .mwiki sup.reference:target { background-color: #DEF; } /* Styling for citations */ .mwiki cite { font-style: normal; word-wrap: break-word; } /* If there is an inline link to a full citation, the full citation will turn blue when the inline link is clicked */ .mwiki cite:target { background-color: #DEF; } /* wikitable/prettytable class for skinning normal tables */ .mwiki table.wikitable, .mwiki table.prettytable { margin: 1em 1em 1em 0; background: #f9f9f9; border: 1px #aaa solid; border-collapse: collapse; } .mwiki .wikitable th, .mwiki .wikitable td, .mwiki .prettytable th, .mwiki .prettytable td { border: 1px #aaa solid; padding: 0.2em; } .mwiki .wikitable th, .mwiki .prettytable th { background: #f2f2f2; text-align: center; } .mwiki .wikitable caption, .mwiki .prettytable caption { font-weight: bold; } /* default skin for navigation boxes */ .mwiki table.navbox { /* navbox container style */ border:1px solid #aaa; width:100%; margin:auto; clear:both; font-size:88%; text-align:center; padding:1px; } .mwiki table.navbox + table.navbox { margin-top:-1px; /* single pixel border between adjacent navboxes (doesn't work for IE6, but that's okay) */ } .mwiki .navbox-title, .mwiki .navbox-abovebelow, .mwiki table.navbox th { text-align:center; /* title and above/below styles */ padding-left:1em; padding-right:1em; } .mwiki .navbox-group { /* group style */ white-space:nowrap; text-align:right; font-weight:bold; padding-left:1em; padding-right:1em; } .mwiki .navbox, .mwiki .navbox-subgroup { background:#fdfdfd; /* Background color */ } .mwiki .navbox-list { border-color:#fdfdfd; /* Must match background color */ } .mwiki .navbox-title, .mwiki table.navbox th { background:#ccccff; /* Level 1 color */ } .mwiki .navbox-abovebelow, .mwiki .navbox-group, .mwiki .navbox-subgroup .navbox-title { background:#ddddff; /* Level 2 color */ } .mwiki .navbox-subgroup .navbox-group, .mwiki .navbox-subgroup .navbox-abovebelow { background:#e6e6ff; /* Level 3 color */ } .mwiki .navbox-even { background:#f7f7f7; /* Even row striping */ } .mwiki .navbox-odd { background:transparent; /* Odd row striping */ } .mwiki .collapseButton { /* 'show'/'hide' buttons created dynamically by the */ float: right; /* CollapsibleTables javascript in [[MediaWiki:Common.js]] */ font-weight: normal; /* are styled here so they can be customised. */ text-align: right; width: auto; } .mwiki .navbox .collapseButton { /* In navboxes, the show/hide button balances the vde links from */ width: 6em; /* [[Template:Tnavbar]], so they need to be the same width. */ } /* Infobox template style */ .mwiki .infobox { border: 1px solid #aaa; background-color: #f9f9f9; color: black; /* margin: 0.5em 0 0.5em 1em;*/ margin: 10px auto 0; padding: 0.2em; /* float: right; clear: right;*/ } .mwiki .infobox td, .mwiki .infobox th { vertical-align: top; } .mwiki .infobox caption { font-size: larger; } .mwiki .infobox.bordered { border-collapse: collapse; } .mwiki .infobox.bordered td, .mwiki .infobox.bordered th { border: 1px solid #aaa; } .mwiki .infobox.bordered .borderless td, .mwiki .infobox.bordered .borderless th { border: 0; } .mwiki .infobox.sisterproject { width: 20em; font-size: 90%; } .mwiki .infobox.standard-talk { border: 1px solid #c0c090; background-color: #f8eaba; } .mwiki .infobox.standard-talk.bordered td, .mwiki .infobox.standard-talk.bordered th { border: 1px solid #c0c090; } /* styles for bordered infobox with merged rows */ .mwiki .infobox.bordered .mergedtoprow td, .mwiki .infobox.bordered .mergedtoprow th { border: 0; border-top: 1px solid #aaa; border-right: 1px solid #aaa; } .mwiki .infobox.bordered .mergedrow td, .mwiki .infobox.bordered .mergedrow th { border: 0; border-right: 1px solid #aaa; } /* Styles for geography infoboxes, e.g. countries, country subdivisions, cities, etc. */ .mwiki .infobox.geography { text-align: left; border-collapse: collapse; line-height: 1.2em; font-size: 90%; } .mwiki .infobox.geography td, .mwiki .infobox.geography th { border-top: solid 1px #aaa; padding: 0.4em 0.6em 0.4em 0.6em; } .mwiki .infobox.geography .mergedtoprow td, .mwiki .infobox.geography .mergedtoprow th { border-top: solid 1px #aaa; padding: 0.4em 0.6em 0.2em 0.6em; } .mwiki .infobox.geography .mergedrow td, .mwiki .infobox.geography .mergedrow th { border: 0; padding: 0 0.6em 0.2em 0.6em; } .mwiki .infobox.geography .mergedbottomrow td, .mwiki .infobox.geography .mergedbottomrow th { border-top: 0; border-bottom: solid 1px #aaa; padding: 0 0.6em 0.4em 0.6em; } .mwiki .infobox.geography .maptable td, .mwiki .infobox.geography .maptable th { border: 0; padding: 0; } /* Style for "notices" */ .mwiki .notice { margin: 1em; padding: 0.2em; } .mwiki #disambig { border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } .mwiki .rellink, .mwiki .dablink { font-style: italic; padding-left: 2em; } .mwiki .rellink i, .mwiki .dablink i { font-style: normal; } /* Article message box styles */ .mwiki table.ambox { /* 10% = Will not overlap with other elements */ margin: -1px 10% 0px; /* -1px = Single border between stacked boxes in all browsers */ border: 1px solid #aaa; border-left: 10px solid #1e90ff; /* Default "notice" blue */ background: #fbfbfb; } .mwiki .ambox th.mbox-text, .mwiki .ambox td.mbox-text { /* The message body cell(s) */ padding: 0.25em 0.5em; /* 0.5em left/right */ } .mwiki .ambox td.mbox-image { /* The left image cell */ padding: 2px 0 2px 0.5em; /* 0.5em left, 0px right */ } .mwiki .ambox td.mbox-imageright { /* The right image cell */ padding: 2px 0.5em 2px 0; /* 0px left, 0.5em right */ } /* Standard Navigationsleisten, aka box hiding thingy from .de. Documentation at [[Wikipedia:NavFrame]]. */ .mwiki div.NavFrame { margin: 0; padding: 4px; border: 1px solid #aaa; text-align: center; border-collapse: collapse; font-size: 95%; } .mwiki div.NavFrame + div.NavFrame { border-top-style: none; border-top-style: hidden; } .mwiki div.NavPic { background-color: #fff; margin: 0; padding: 2px; float: left; } .mwiki div.NavFrame div.NavHead { height: 1.6em; font-weight: bold; background-color: #ccf; position:relative; } .mwiki div.NavFrame p { font-size: 100%; } .mwiki div.NavFrame div.NavContent { font-size: 100%; } .mwiki div.NavFrame div.NavContent p { font-size: 100%; } .mwiki div.NavEnd { margin: 0; padding: 0; line-height: 1px; clear: both; } .mwiki a.NavToggle { position: absolute; top: 0; right: 3px; font-weight: normal; font-size: 90%; } /****** Wiktionary-specific excerpts *********/ .mwiki .audiolink a { background: url("http://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/Loudspeaker.svg/16px-Loudspeaker.svg.png") center left no-repeat !important; padding-left: 20px !important; padding-right: 0 !important; } /* default setting for [[:Category:Inflection templates]] */ .mwiki .infl-inline { display: inline } .mwiki .infl-table { display: none } /****default hide dict icon ***/ .gddicticon{display:none;} .gdexpandicon { cursor:pointer; width:16px; height:16px; vertical-align: text-bottom; background-image:url('qrcx://localhost/icons/arrow.png'); } .gdcollapseicon { cursor:pointer; width:16px; height:16px; vertical-align: text-bottom; background-image:url('qrcx://localhost/icons/downarrow.png'); } /********** Slob dictionaries ***********/ .slobdict img.imgtex { vertical-align: baseline !important; } /************* GLS dictionaries **************/ .glsdict_headwords { font-weight: bold; margin-top: 15px; margin-bottom: 10px; font-size: 116%; } .gls_wav img { /* Ugly hack since "vertical-align: middle;" looks _terrible_ in Qt4's webkit: */ vertical-align: -30%; } goldendict-1.5.0/article_maker.cc000066400000000000000000001062321443523320500167470ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "article_maker.hh" #include "config.hh" #include "htmlescape.hh" #include "utf8.hh" #include "wstring_qt.hh" #include #include #include #include #include "folding.hh" #include "langcoder.hh" #include "gddebug.hh" #include "qt4x5.hh" using std::vector; using std::string; using gd::wstring; using std::set; using std::list; ArticleMaker::ArticleMaker( vector< sptr< Dictionary::Class > > const & dictionaries_, vector< Instances::Group > const & groups_, QString const & displayStyle_, QString const & addonStyle_): dictionaries( dictionaries_ ), groups( groups_ ), displayStyle( displayStyle_ ), addonStyle( addonStyle_ ), needExpandOptionalParts( true ) , collapseBigArticles( true ) , articleLimitSize( 500 ) { } void ArticleMaker::setDisplayStyle( QString const & st, QString const & adst ) { displayStyle = st; addonStyle = adst; } std::string ArticleMaker::makeHtmlHeader( QString const & word, QString const & icon, bool expandOptionalParts ) const { string result = "" "" ""; // Add a css stylesheet { QFile builtInCssFile( ":/article-style.css" ); builtInCssFile.open( QFile::ReadOnly ); QByteArray css = builtInCssFile.readAll(); if( !css.isEmpty() ) { result += "\n\n"; result += "\n"; } if ( displayStyle.size() ) { // Load an additional stylesheet QFile builtInCssFile( QString( ":/article-style-st-%1.css" ).arg( displayStyle ) ); builtInCssFile.open( QFile::ReadOnly ); css = builtInCssFile.readAll(); if( !css.isEmpty() ) { result += "\n"; result += "\n"; } } QFile cssFile( Config::getUserCssFileName() ); if ( cssFile.open( QFile::ReadOnly ) ) { css = cssFile.readAll(); if( !css.isEmpty() ) { result += "\n"; result += "\n"; } } if( !addonStyle.isEmpty() ) { QString name = Config::getStylesDir() + addonStyle + QDir::separator() + "article-style.css"; QFile addonCss( name ); if( addonCss.open( QFile::ReadOnly ) ) { css = addonCss.readAll(); if( !css.isEmpty() ) { result += "\n"; result += "\n"; } } } // Turn on/off expanding of article optional parts if( expandOptionalParts ) { result += "\n"; result += "\n"; } } // Add print-only css { QFile builtInCssFile( ":/article-style-print.css" ); builtInCssFile.open( QFile::ReadOnly ); QByteArray css = builtInCssFile.readAll(); if( !css.isEmpty() ) { result += "\n"; result += "\n"; } QFile cssFile( Config::getUserCssPrintFileName() ); if ( cssFile.open( QFile::ReadOnly ) ) { css = cssFile.readAll(); if( !css.isEmpty() ) { result += "\n"; result += "\n"; css.clear(); } } if( !addonStyle.isEmpty() ) { QString name = Config::getStylesDir() + addonStyle + QDir::separator() + "article-style-print.css"; QFile addonCss( name ); if( addonCss.open( QFile::ReadOnly ) ) { css = addonCss.readAll(); if( !css.isEmpty() ) { result += "\n"; result += "\n"; } } } } result += "" + Html::escape( Utf8::encode( gd::toWString( word ) ) ) + ""; // This doesn't seem to be much of influence right now, but we'll keep // it anyway. if ( icon.size() ) result += "\n"; result += ""; result += ""; return result; } std::string ArticleMaker::makeNotFoundBody( QString const & word, QString const & group ) { string result( "

" ); QString str( word ); if( str.isRightToLeft() ) { str.insert( 0, (ushort)0x202E ); // RLE, Right-to-Left Embedding str.append( (ushort)0x202C ); // PDF, POP DIRECTIONAL FORMATTING } if ( word.size() ) result += tr( "No translation for %1 was found in group %2." ). arg( QString::fromUtf8( Html::escape( str.toUtf8().data() ).c_str() ) ). arg( QString::fromUtf8( Html::escape( group.toUtf8().data() ).c_str() ) ). toUtf8().data(); else result += tr( "No translation was found in group %1." ). arg( QString::fromUtf8( Html::escape( group.toUtf8().data() ).c_str() ) ). toUtf8().data(); result += "

"; return result; } sptr< Dictionary::DataRequest > ArticleMaker::makeDefinitionFor( Config::InputPhrase const & phrase, unsigned groupId, QMap< QString, QString > const & contexts, QSet< QString > const & mutedDicts, QStringList const & dictIDs , bool ignoreDiacritics ) const { if( !dictIDs.isEmpty() ) { QStringList ids = dictIDs; std::vector< sptr< Dictionary::Class > > ftsDicts; // Find dictionaries by ID's for( unsigned x = 0; x < dictionaries.size(); x++ ) { for( QStringList::Iterator it = ids.begin(); it != ids.end(); ++it ) { if( *it == QString::fromStdString( dictionaries[ x ]->getId() ) ) { ftsDicts.push_back( dictionaries[ x ] ); ids.erase( it ); break; } } if( ids.isEmpty() ) break; } string header = makeHtmlHeader( phrase.phrase, QString(), true ); return new ArticleRequest( phrase, "", contexts, ftsDicts, header, -1, true ); } if ( groupId == Instances::Group::HelpGroupId ) { // This is a special group containing internal welcome/help pages string result = makeHtmlHeader( phrase.phrase, QString(), needExpandOptionalParts ); if ( phrase.phrase == tr( "Welcome!" ) ) { result += tr( "

Welcome to GoldenDict!

" "

To start working with the program, first visit Edit|Dictionaries to add some directory paths where to search " "for the dictionary files, set up various Wikipedia sites or other sources, adjust dictionary order or create dictionary groups." "

And then you're ready to look up your words! You can do that in this window " "by using a pane to the left, or you can look up words from other active applications. " "

To customize program, check out the available preferences at Edit|Preferences. " "All settings there have tooltips, be sure to read them if you are in doubt about anything." "

Should you need further help, have any questions, " "suggestions or just wonder what the others think, you are welcome at the program's forum." "

Check program's website for the updates. " "

(c) 2008-2013 Konstantin Isakov. Licensed under GPLv3 or later." ).toUtf8().data(); } else if ( phrase.phrase == tr( "Working with popup" ) ) { result += ( tr( "

Working with the popup

" "To look up words from other active applications, you would need to first activate the \"Scan popup functionality\" in Preferences, " "and then enable it at any time either by triggering the 'Popup' icon above, or " "by clicking the tray icon down below with your right mouse button and choosing so in the menu you've popped. " ) + #ifdef Q_OS_WIN32 tr( "Then just stop the cursor over the word you want to look up in another application, " "and a window would pop up which would describe it to you." ) #else tr( "Then just select any word you want to look up in another application by your mouse " "(double-click it or swipe it with mouse with the button pressed), " "and a window would pop up which would describe the word to you." ) #endif ).toUtf8().data(); } else { // Not found return makeNotFoundTextFor( phrase.phrase, "help" ); } result += ""; sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true ); r->getData().resize( result.size() ); memcpy( &( r->getData().front() ), result.data(), result.size() ); return r; } // Find the given group Instances::Group const * activeGroup = 0; for( unsigned x = 0; x < groups.size(); ++x ) if ( groups[ x ].id == groupId ) { activeGroup = &groups[ x ]; break; } // If we've found a group, use its dictionaries; otherwise, use the global // heap. std::vector< sptr< Dictionary::Class > > const & activeDicts = activeGroup ? activeGroup->dictionaries : dictionaries; string header = makeHtmlHeader( phrase.phrase, activeGroup && activeGroup->icon.size() ? activeGroup->icon : QString(), needExpandOptionalParts ); if ( mutedDicts.size() ) { std::vector< sptr< Dictionary::Class > > unmutedDicts; unmutedDicts.reserve( activeDicts.size() ); for( unsigned x = 0; x < activeDicts.size(); ++x ) if ( !mutedDicts.contains( QString::fromStdString( activeDicts[ x ]->getId() ) ) ) unmutedDicts.push_back( activeDicts[ x ] ); return new ArticleRequest( phrase, activeGroup ? activeGroup->name : "", contexts, unmutedDicts, header, collapseBigArticles ? articleLimitSize : -1, needExpandOptionalParts, ignoreDiacritics ); } else return new ArticleRequest( phrase, activeGroup ? activeGroup->name : "", contexts, activeDicts, header, collapseBigArticles ? articleLimitSize : -1, needExpandOptionalParts, ignoreDiacritics ); } sptr< Dictionary::DataRequest > ArticleMaker::makeNotFoundTextFor( QString const & word, QString const & group ) const { string result = makeHtmlHeader( word, QString(), true ) + makeNotFoundBody( word, group ) + ""; sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true ); r->getData().resize( result.size() ); memcpy( &( r->getData().front() ), result.data(), result.size() ); return r; } sptr< Dictionary::DataRequest > ArticleMaker::makeEmptyPage() const { string result = makeHtmlHeader( tr( "(untitled)" ), QString(), true ) + ""; sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true ); r->getData().resize( result.size() ); memcpy( &( r->getData().front() ), result.data(), result.size() ); return r; } sptr< Dictionary::DataRequest > ArticleMaker::makePicturePage( string const & url ) const { string result = makeHtmlHeader( tr( "(picture)" ), QString(), true ) + "2) history.go(-1)\">" + "" + ""; sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true ); r->getData().resize( result.size() ); memcpy( &( r->getData().front() ), result.data(), result.size() ); return r; } void ArticleMaker::setExpandOptionalParts( bool expand ) { needExpandOptionalParts = expand; } void ArticleMaker::setCollapseParameters( bool autoCollapse, int articleSize ) { collapseBigArticles = autoCollapse; articleLimitSize = articleSize; } bool ArticleMaker::adjustFilePath( QString & fileName ) { QFileInfo info( fileName ); if( !info.isFile() ) { QString dir = Config::getConfigDir(); dir.chop( 1 ); info.setFile( dir + fileName); if( info.isFile() ) { fileName = info.canonicalFilePath(); return true; } } return false; } //////// ArticleRequest ArticleRequest::ArticleRequest( Config::InputPhrase const & phrase, QString const & group_, QMap< QString, QString > const & contexts_, vector< sptr< Dictionary::Class > > const & activeDicts_, string const & header, int sizeLimit, bool needExpandOptionalParts_, bool ignoreDiacritics_ ): word( phrase.phrase ), group( group_ ), contexts( contexts_ ), activeDicts( activeDicts_ ), altsDone( false ), bodyDone( false ), foundAnyDefinitions( false ), closePrevSpan( false ) , articleSizeLimit( sizeLimit ) , needExpandOptionalParts( needExpandOptionalParts_ ) , ignoreDiacritics( ignoreDiacritics_ ) { if ( !phrase.punctuationSuffix.isEmpty() ) alts.insert( gd::toWString( phrase.phraseWithSuffix() ) ); // No need to lock dataMutex on construction hasAnyData = true; data.resize( header.size() ); memcpy( &data.front(), header.data(), header.size() ); // Accumulate main forms for( unsigned x = 0; x < activeDicts.size(); ++x ) { sptr< Dictionary::WordSearchRequest > s = activeDicts[ x ]->findHeadwordsForSynonym( gd::toWString( word ) ); connect( s.get(), SIGNAL( finished() ), this, SLOT( altSearchFinished() ), Qt::QueuedConnection ); altSearches.push_back( s ); } altSearchFinished(); // Handle any ones which have already finished } void ArticleRequest::altSearchFinished() { if ( altsDone ) return; // Check every request for finishing for( list< sptr< Dictionary::WordSearchRequest > >::iterator i = altSearches.begin(); i != altSearches.end(); ) { if ( (*i)->isFinished() ) { // This one's finished for( size_t count = (*i)->matchesCount(), x = 0; x < count; ++x ) alts.insert( (**i)[ x ].word ); altSearches.erase( i++ ); } else ++i; } if ( altSearches.empty() ) { #ifdef QT_DEBUG qDebug( "alts finished\n" ); #endif // They all've finished! Now we can look up bodies altsDone = true; // So any pending signals in queued mode won't mess us up vector< wstring > altsVector( alts.begin(), alts.end() ); #ifdef QT_DEBUG for( unsigned x = 0; x < altsVector.size(); ++x ) { qDebug() << "Alt:" << gd::toQString( altsVector[ x ] ); } #endif wstring wordStd = gd::toWString( word ); if( activeDicts.size() <= 1 ) articleSizeLimit = -1; // Don't collapse article if only one dictionary presented for( unsigned x = 0; x < activeDicts.size(); ++x ) { try { sptr< Dictionary::DataRequest > r = activeDicts[ x ]->getArticle( wordStd, altsVector, gd::toWString( contexts.value( QString::fromStdString( activeDicts[ x ]->getId() ) ) ), ignoreDiacritics ); connect( r.get(), SIGNAL( finished() ), this, SLOT( bodyFinished() ), Qt::QueuedConnection ); bodyRequests.push_back( r ); } catch( std::exception & e ) { gdWarning( "getArticle request error (%s) in \"%s\"\n", e.what(), activeDicts[ x ]->getName().c_str() ); } } bodyFinished(); // Handle any ones which have already finished } } int ArticleRequest::findEndOfCloseDiv( const QString &str, int pos ) { for( ; ; ) { int n1 = str.indexOf( "

", pos ); if( n1 <= 0 ) return n1; int n2 = str.indexOf( "
n1 ) return n1 + 6; pos = findEndOfCloseDiv( str, n2 + 1 ); if( pos <= 0 ) return pos; } } void ArticleRequest::bodyFinished() { if ( bodyDone ) return; GD_DPRINTF( "some body finished\n" ); bool wasUpdated = false; while ( bodyRequests.size() ) { // Since requests should go in order, check the first one first if ( bodyRequests.front()->isFinished() ) { // Good GD_DPRINTF( "one finished.\n" ); Dictionary::DataRequest & req = *bodyRequests.front(); QString errorString = req.getErrorString(); if ( req.dataSize() >= 0 || errorString.size() ) { sptr< Dictionary::Class > const & activeDict = activeDicts[ activeDicts.size() - bodyRequests.size() ]; string dictId = activeDict->getId(); string head; string gdFrom = "gdfrom-" + Html::escape( dictId ); if ( closePrevSpan ) { head += "
"; } else { // This is the first article head += ""; } bool collapse = false; if( articleSizeLimit >= 0 ) { try { Mutex::Lock _( dataMutex ); QString text = QString::fromUtf8( req.getFullData().data(), req.getFullData().size() ); if( !needExpandOptionalParts ) { // Strip DSL optional parts int pos = 0; for( ; ; ) { pos = text.indexOf( "
0 ) { int endPos = findEndOfCloseDiv( text, pos + 1 ); if( endPos > pos) text.remove( pos, endPos - pos ); else break; } else break; } } int size = QTextDocumentFragment::fromHtml( text ).toPlainText().length(); if( size > articleSizeLimit ) collapse = true; } catch(...) { } } string jsVal = Html::escapeForJavaScript( dictId ); head += ""; head += string( "
"; closePrevSpan = true; head += string( "
" + Html::escape( tr( "From " ).toUtf8().data() ) + "" + Html::escape( activeDict->getName().c_str() ) + "" + "" + "
"; head += "
"; head += "
getLangFrom() ).toLatin1().data(); head += "\" lang=\""; head += LangCoder::intToCode2( activeDict->getLangTo() ).toLatin1().data(); head += "\""; head += " style=\"display:"; head += collapse ? "none" : "inline"; head += string( "\" id=\"gdarticlefrom-" ) + Html::escape( dictId ) + "\">"; if ( errorString.size() ) { head += "
" + Html::escape( tr( "Query error: %1" ).arg( errorString ).toUtf8().data() ) + "
"; } Mutex::Lock _( dataMutex ); size_t offset = data.size(); data.resize( data.size() + head.size() + ( req.dataSize() > 0 ? req.dataSize() : 0 ) ); memcpy( &data.front() + offset, head.data(), head.size() ); try { if ( req.dataSize() > 0 ) bodyRequests.front()->getDataSlice( 0, req.dataSize(), &data.front() + offset + head.size() ); } catch( std::exception & e ) { gdWarning( "getDataSlice error: %s\n", e.what() ); } wasUpdated = true; foundAnyDefinitions = true; } GD_DPRINTF( "erasing..\n" ); bodyRequests.pop_front(); GD_DPRINTF( "erase done..\n" ); } else { GD_DPRINTF( "one not finished.\n" ); break; } } if ( bodyRequests.empty() ) { // No requests left, end the article bodyDone = true; { string footer; if ( closePrevSpan ) { footer += "
"; closePrevSpan = false; } if ( !foundAnyDefinitions ) { // No definitions were ever found, say so to the user. // Larger words are usually whole sentences - don't clutter the output // with their full bodies. footer += ArticleMaker::makeNotFoundBody( word.size() < 40 ? word : "", group ); // When there were no definitions, we run stemmed search. stemmedWordFinder = new WordFinder( this ); connect( stemmedWordFinder.get(), SIGNAL( finished() ), this, SLOT( stemmedSearchFinished() ), Qt::QueuedConnection ); stemmedWordFinder->stemmedMatch( word, activeDicts ); } else { footer += ""; } Mutex::Lock _( dataMutex ); size_t offset = data.size(); data.resize( data.size() + footer.size() ); memcpy( &data.front() + offset, footer.data(), footer.size() ); } if ( stemmedWordFinder.get() ) update(); else finish(); } else if ( wasUpdated ) update(); } void ArticleRequest::stemmedSearchFinished() { // Got stemmed matching results WordFinder::SearchResults sr = stemmedWordFinder->getResults(); string footer; bool continueMatching = false; if ( sr.size() ) { footer += "
" + Html::escape( tr( "Close words: " ).toUtf8().data() ) + ""; for( unsigned x = 0; x < sr.size(); ++x ) { footer += linkWord( sr[ x ].first ); if ( x != sr.size() - 1 ) { footer += ", "; } } footer += "
"; } splittedWords = splitIntoWords( word ); if ( splittedWords.first.size() > 1 ) // Contains more than one word { disconnect( stemmedWordFinder.get(), SIGNAL( finished() ), this, SLOT( stemmedSearchFinished() ) ); connect( stemmedWordFinder.get(), SIGNAL( finished() ), this, SLOT( individualWordFinished() ), Qt::QueuedConnection ); currentSplittedWordStart = -1; currentSplittedWordEnd = currentSplittedWordStart; firstCompoundWasFound = false; compoundSearchNextStep( false ); continueMatching = true; } if ( !continueMatching ) footer += ""; { Mutex::Lock _( dataMutex ); size_t offset = data.size(); data.resize( data.size() + footer.size() ); memcpy( &data.front() + offset, footer.data(), footer.size() ); } if ( continueMatching ) update(); else finish(); } void ArticleRequest::compoundSearchNextStep( bool lastSearchSucceeded ) { if ( !lastSearchSucceeded ) { // Last search was unsuccessful. First, emit what we had. string footer; if ( lastGoodCompoundResult.size() ) // We have something to append { // DPRINTF( "Appending\n" ); if ( !firstCompoundWasFound ) { // Append the beginning footer += "
" + Html::escape( tr( "Compound expressions: " ).toUtf8().data() ) + ""; firstCompoundWasFound = true; } else { // Append the separator footer += " / "; } footer += linkWord( lastGoodCompoundResult ); lastGoodCompoundResult.clear(); } // Then, start a new search for the next word, if possible if ( currentSplittedWordStart >= splittedWords.first.size() - 2 ) { // The last word was the last possible to start from if ( firstCompoundWasFound ) footer += ""; // Now add links to all the individual words. They conclude the result. footer += "
" + Html::escape( tr( "Individual words: " ).toUtf8().data() ) + "expressionMatch( currentSplittedWordCompound, activeDicts, 40, // Would one be enough? Leave 40 to be safe. Dictionary::SuitableForCompoundSearching ); } QString ArticleRequest::makeSplittedWordCompound() { QString result; result.clear(); for( int x = currentSplittedWordStart; x <= currentSplittedWordEnd; ++x ) { result.append( splittedWords.first[ x ] ); if ( x < currentSplittedWordEnd ) { wstring ws( gd::toWString( splittedWords.second[ x + 1 ] ) ); Folding::normalizeWhitespace( ws ); result.append( gd::toQString( ws ) ); } } return result; } void ArticleRequest::individualWordFinished() { WordFinder::SearchResults const & results = stemmedWordFinder->getResults(); if ( results.size() ) { wstring source = Folding::applySimpleCaseOnly( gd::toWString( currentSplittedWordCompound ) ); bool hadSomething = false; for( unsigned x = 0; x < results.size(); ++x ) { if ( results[ x ].second ) { // Spelling suggestion match found. No need to continue. hadSomething = true; lastGoodCompoundResult = currentSplittedWordCompound; break; } // Prefix match found. Check if the aliases are acceptable. wstring result( Folding::applySimpleCaseOnly( gd::toWString( results[ x ].first ) ) ); if ( source.size() <= result.size() && result.compare( 0, source.size(), source ) == 0 ) { // The resulting string begins with the source one hadSomething = true; if ( source.size() == result.size() ) { // Got the match. No need to continue. lastGoodCompoundResult = currentSplittedWordCompound; break; } } } if ( hadSomething ) { compoundSearchNextStep( true ); return; } } compoundSearchNextStep( false ); } void ArticleRequest::appendToData( std::string const & str ) { Mutex::Lock _( dataMutex ); size_t offset = data.size(); data.resize( data.size() + str.size() ); memcpy( &data.front() + offset, str.data(), str.size() ); } QPair< ArticleRequest::Words, ArticleRequest::Spacings > ArticleRequest::splitIntoWords( QString const & input ) { QPair< Words, Spacings > result; QChar const * ptr = input.data(); for( ; ; ) { QString spacing; for( ; ptr->unicode() && ( Folding::isPunct( ptr->unicode() ) || Folding::isWhitespace( ptr->unicode() ) ); ++ptr ) spacing.append( *ptr ); result.second.append( spacing ); QString word; for( ; ptr->unicode() && !( Folding::isPunct( ptr->unicode() ) || Folding::isWhitespace( ptr->unicode() ) ); ++ptr ) word.append( *ptr ); if ( word.isEmpty() ) break; result.first.append( word ); } return result; } string ArticleRequest::linkWord( QString const & str ) { QUrl url; url.setScheme( "gdlookup" ); url.setHost( "localhost" ); url.setPath( Qt4x5::Url::ensureLeadingSlash( str ) ); string escapedResult = Html::escape( str.toUtf8().data() ); return string( "" + escapedResult +""; } std::string ArticleRequest::escapeSpacing( QString const & str ) { QByteArray spacing = Html::escape( str.toUtf8().data() ).c_str(); spacing.replace( "\n", "
" ); return spacing.data(); } void ArticleRequest::cancel() { if( isFinished() ) return; if( !altSearches.empty() ) { for( list< sptr< Dictionary::WordSearchRequest > >::iterator i = altSearches.begin(); i != altSearches.end(); ++i ) { (*i)->cancel(); } } if( !bodyRequests.empty() ) { for( list< sptr< Dictionary::DataRequest > >::iterator i = bodyRequests.begin(); i != bodyRequests.end(); ++i ) { (*i)->cancel(); } } if( stemmedWordFinder.get() ) stemmedWordFinder->cancel(); finish(); } goldendict-1.5.0/article_maker.hh000066400000000000000000000151211443523320500167550ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __ARTICLE_MAKER_HH_INCLUDED__ #define __ARTICLE_MAKER_HH_INCLUDED__ #include #include #include #include #include "config.hh" #include "dictionary.hh" #include "instances.hh" #include "wordfinder.hh" /// This class generates the article's body for the given lookup request class ArticleMaker: public QObject { Q_OBJECT // We make it QObject to use tr() conveniently std::vector< sptr< Dictionary::Class > > const & dictionaries; std::vector< Instances::Group > const & groups; QString displayStyle, addonStyle; bool needExpandOptionalParts; bool collapseBigArticles; int articleLimitSize; public: /// On construction, a reference to all dictionaries and a reference all /// groups' instances are to be passed. Those references are kept stored as /// references, and as such, any changes to them would reflect on the results /// of the inquiries, although those changes are perfectly legal. ArticleMaker( std::vector< sptr< Dictionary::Class > > const & dictionaries, std::vector< Instances::Group > const & groups, QString const & displayStyle, QString const & addonStyle); /// Sets the display style to use for any new requests. This affects the /// choice of the stylesheet file. void setDisplayStyle( QString const &, QString const & addonStyle ); /// Looks up the given phrase within the given group, and creates a full html /// page text containing its definition. /// The result is returned as Dictionary::DataRequest just like dictionaries /// themselves do. The difference is that the result is a complete html page /// with all definitions from all the relevant dictionaries. /// Contexts is a map of context values to be passed to each dictionary, where /// the keys are dictionary ids. /// If mutedDicts is not empty, the search would be limited only to those /// dictionaries in group which aren't listed there. sptr< Dictionary::DataRequest > makeDefinitionFor( Config::InputPhrase const & phrase, unsigned groupId, QMap< QString, QString > const & contexts, QSet< QString > const & mutedDicts = QSet< QString >(), QStringList const & dictIDs = QStringList(), bool ignoreDiacritics = false ) const; /// Makes up a text which states that no translation for the given word /// was found. Sometimes it's better to call this directly when it's already /// known that there's no translation. sptr< Dictionary::DataRequest > makeNotFoundTextFor( QString const & word, QString const & group ) const; /// Creates an 'untitled' page. The result is guaranteed to be instant. sptr< Dictionary::DataRequest > makeEmptyPage() const; /// Create page with one picture sptr< Dictionary::DataRequest > makePicturePage( std::string const & url ) const; /// Set auto expanding optional parts of articles void setExpandOptionalParts( bool expand ); /// Add base path to file path if it's relative and file not found /// Return true if path successfully adjusted static bool adjustFilePath( QString & fileName ); /// Set collapse articles parameters void setCollapseParameters( bool autoCollapse, int articleSize ); private: /// Makes everything up to and including the opening body tag. std::string makeHtmlHeader( QString const & word, QString const & icon, bool expandOptionalParts ) const; /// Makes the html body for makeNotFoundTextFor() static std::string makeNotFoundBody( QString const & word, QString const & group ); friend class ArticleRequest; // Allow it calling makeNotFoundBody() }; /// The request specific to article maker. This should really be private, /// but we need it to be handled by moc. class ArticleRequest: public Dictionary::DataRequest { Q_OBJECT QString word, group; QMap< QString, QString > contexts; std::vector< sptr< Dictionary::Class > > activeDicts; std::set< gd::wstring > alts; // Accumulated main forms std::list< sptr< Dictionary::WordSearchRequest > > altSearches; bool altsDone, bodyDone; std::list< sptr< Dictionary::DataRequest > > bodyRequests; bool foundAnyDefinitions; bool closePrevSpan; // Indicates whether the last opened article span is to // be closed after the article ends. sptr< WordFinder > stemmedWordFinder; // Used when there're no results /// A sequence of words and spacings between them, including the initial /// spacing before the first word and the final spacing after the last word. typedef QList< QString > Words; typedef QList< QString > Spacings; /// Splits the given string into words and spacings between them. QPair< Words, Spacings > splitIntoWords( QString const & ); QPair< Words, Spacings > splittedWords; int currentSplittedWordStart; int currentSplittedWordEnd; QString currentSplittedWordCompound; QString lastGoodCompoundResult; bool firstCompoundWasFound; int articleSizeLimit; bool needExpandOptionalParts; bool ignoreDiacritics; public: ArticleRequest( Config::InputPhrase const & phrase, QString const & group, QMap< QString, QString > const & contexts, std::vector< sptr< Dictionary::Class > > const & activeDicts, std::string const & header, int sizeLimit, bool needExpandOptionalParts_, bool ignoreDiacritics = false ); virtual void cancel(); // { finish(); } // Add our own requests cancellation here private slots: void altSearchFinished(); void bodyFinished(); void stemmedSearchFinished(); void individualWordFinished(); private: /// Appends the given string to 'data', with locking its mutex. void appendToData( std::string const & ); /// Uses stemmedWordFinder to perform the next step of looking up word /// combinations. void compoundSearchNextStep( bool lastSearchSucceeded ); /// Creates a single word out of the [currentSplittedWordStart..End] range. QString makeSplittedWordCompound(); /// Makes an html link to the given word. std::string linkWord( QString const & ); /// Escapes the spacing between the words to include in html. std::string escapeSpacing( QString const & ); /// Find end of corresponding
tag int findEndOfCloseDiv( QString const &, int pos ); }; #endif goldendict-1.5.0/article_netmgr.cc000066400000000000000000000504241443523320500171450ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #if defined( _MSC_VER ) && _MSC_VER < 1800 // VS2012 and older #include #else #include #endif #include #include "article_netmgr.hh" #include "wstring_qt.hh" #include "gddebug.hh" #include "qt4x5.hh" using std::string; #if QT_VERSION >= 0x050300 // Qt 5.3+ // SecurityWhiteList SecurityWhiteList & SecurityWhiteList::operator=( SecurityWhiteList const & swl ) { swlDelete(); swlCopy( swl ); return *this; } QWebSecurityOrigin * SecurityWhiteList::setOrigin( QUrl const & url ) { swlDelete(); originUri = url.toString( QUrl::PrettyDecoded ); origin = new QWebSecurityOrigin( url ); return origin; } void SecurityWhiteList::swlCopy( SecurityWhiteList const & swl ) { if( swl.origin ) { hostsToAccess = swl.hostsToAccess; originUri = swl.originUri; origin = new QWebSecurityOrigin( QUrl( originUri ) ); for( QSet< QPair< QString, QString > >::iterator it = hostsToAccess.begin(); it != hostsToAccess.end(); ++it ) origin->addAccessWhitelistEntry( it->first, it->second, QWebSecurityOrigin::AllowSubdomains ); } } void SecurityWhiteList::swlDelete() { if( origin ) { for( QSet< QPair< QString, QString > >::iterator it = hostsToAccess.begin(); it != hostsToAccess.end(); ++it ) origin->removeAccessWhitelistEntry( it->first, it->second, QWebSecurityOrigin::AllowSubdomains ); delete origin; origin = 0; } hostsToAccess.clear(); originUri.clear(); } // AllowFrameReply AllowFrameReply::AllowFrameReply( QNetworkReply * _reply ) : baseReply( _reply ) { // Set base data setOperation( baseReply->operation() ); setRequest( baseReply->request() ); setUrl( baseReply->url() ); // Signals to own slots connect( baseReply, SIGNAL( metaDataChanged() ), this, SLOT( applyMetaData() ) ); connect( baseReply, SIGNAL( error( QNetworkReply::NetworkError) ), this, SLOT( applyError( QNetworkReply::NetworkError ) ) ); connect( baseReply, SIGNAL( readyRead() ), this, SLOT( readDataFromBase() ) ); // Redirect QNetworkReply signals connect( baseReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SIGNAL( downloadProgress( qint64, qint64 ) ) ); connect( baseReply, SIGNAL( encrypted() ), this, SIGNAL( encrypted() ) ); connect( baseReply, SIGNAL( finished() ), this, SIGNAL( finished() ) ); connect( baseReply, SIGNAL( preSharedKeyAuthenticationRequired( QSslPreSharedKeyAuthenticator * ) ), this, SIGNAL( preSharedKeyAuthenticationRequired( QSslPreSharedKeyAuthenticator * ) ) ); connect( baseReply, SIGNAL( redirected( const QUrl & ) ), this, SIGNAL( redirected( const QUrl & ) ) ); connect( baseReply, SIGNAL( sslErrors( const QList< QSslError > & ) ), this, SIGNAL( sslErrors( const QList< QSslError > & ) ) ); connect( baseReply, SIGNAL( uploadProgress( qint64, qint64 ) ), this, SIGNAL( uploadProgress( qint64, qint64 ) ) ); // Redirect QIODevice signals connect( baseReply, SIGNAL( aboutToClose() ), this, SIGNAL( aboutToClose() ) ); connect( baseReply, SIGNAL( bytesWritten( qint64 ) ), this, SIGNAL( bytesWritten( qint64 ) ) ); connect( baseReply, SIGNAL( readChannelFinished() ), this, SIGNAL( readChannelFinished() ) ); setOpenMode( QIODevice::ReadOnly ); } void AllowFrameReply::applyMetaData() { // Set raw headers except X-Frame-Options QList< QByteArray > rawHeaders = baseReply->rawHeaderList(); for( QList< QByteArray >::iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { if( it->toLower() != "x-frame-options" ) setRawHeader( *it, baseReply->rawHeader( *it ) ); } // Set known headers setHeader( QNetworkRequest::ContentDispositionHeader, baseReply->header( QNetworkRequest::ContentDispositionHeader ) ); setHeader( QNetworkRequest::ContentTypeHeader, baseReply->header( QNetworkRequest::ContentTypeHeader ) ); setHeader( QNetworkRequest::ContentLengthHeader, baseReply->header( QNetworkRequest::ContentLengthHeader ) ); setHeader( QNetworkRequest::LocationHeader, baseReply->header( QNetworkRequest::LocationHeader ) ); setHeader( QNetworkRequest::LastModifiedHeader, baseReply->header( QNetworkRequest::LastModifiedHeader ) ); setHeader( QNetworkRequest::CookieHeader, baseReply->header( QNetworkRequest::CookieHeader ) ); setHeader( QNetworkRequest::SetCookieHeader, baseReply->header( QNetworkRequest::SetCookieHeader ) ); setHeader( QNetworkRequest::UserAgentHeader, baseReply->header( QNetworkRequest::UserAgentHeader ) ); setHeader( QNetworkRequest::ServerHeader, baseReply->header( QNetworkRequest::ServerHeader ) ); // Set attributes setAttribute( QNetworkRequest::HttpStatusCodeAttribute, baseReply->attribute( QNetworkRequest::HttpStatusCodeAttribute ) ); setAttribute( QNetworkRequest::HttpReasonPhraseAttribute, baseReply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ) ); setAttribute( QNetworkRequest::RedirectionTargetAttribute, baseReply->attribute( QNetworkRequest::RedirectionTargetAttribute ) ); setAttribute( QNetworkRequest::ConnectionEncryptedAttribute, baseReply->attribute( QNetworkRequest::ConnectionEncryptedAttribute ) ); setAttribute( QNetworkRequest::SourceIsFromCacheAttribute, baseReply->attribute( QNetworkRequest::SourceIsFromCacheAttribute ) ); setAttribute( QNetworkRequest::HttpPipeliningWasUsedAttribute, baseReply->attribute( QNetworkRequest::HttpPipeliningWasUsedAttribute ) ); setAttribute( QNetworkRequest::BackgroundRequestAttribute, baseReply->attribute( QNetworkRequest::BackgroundRequestAttribute ) ); setAttribute( QNetworkRequest::SpdyWasUsedAttribute, baseReply->attribute( QNetworkRequest::SpdyWasUsedAttribute ) ); emit metaDataChanged(); } void AllowFrameReply::setReadBufferSize( qint64 size ) { QNetworkReply::setReadBufferSize( size ); baseReply->setReadBufferSize( size ); } qint64 AllowFrameReply::bytesAvailable() const { return buffer.size() + QNetworkReply::bytesAvailable(); } void AllowFrameReply::applyError( QNetworkReply::NetworkError code ) { setError( code, baseReply->errorString() ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 15, 0 ) emit errorOccurred( code ); #else emit error( code ); #endif } void AllowFrameReply::readDataFromBase() { QByteArray data; data.resize( baseReply->bytesAvailable() ); baseReply->read( data.data(), data.size() ); buffer += data; emit readyRead(); } qint64 AllowFrameReply::readData( char * data, qint64 maxSize ) { qint64 size = qMin( maxSize, qint64( buffer.size() ) ); memcpy( data, buffer.data(), size ); buffer.remove( 0, size ); return size; } #endif namespace { /// Uses some heuristics to chop off the first domain name from the host name, /// but only if it's not too base. Returns the resulting host name. QString getHostBase( QUrl const & url ) { QString host = url.host(); QStringList domains = host.split( '.' ); int left = domains.size(); // Skip last <=3-letter domain name if ( left && domains[ left - 1 ].size() <= 3 ) --left; // Skip another <=3-letter domain name if ( left && domains[ left - 1 ].size() <= 3 ) --left; if ( left > 1 ) { // We've got something like www.foobar.co.uk -- we can chop off the first // domain return host.mid( domains[ 0 ].size() + 1 ); } else return host; } } QNetworkReply * ArticleNetworkAccessManager::createRequest( Operation op, QNetworkRequest const & req, QIODevice * outgoingData ) { QNetworkRequest localReq( req ); if( ( localReq.url().scheme() == "gdlookup" || localReq.url().scheme() == "http" ) && localReq.url().host() == "upload.wikimedia.org" ) { // Handle some requests from offline wikipedia/wiktionary without scheme or with "http" scheme QUrl newUrl( req.url() ); newUrl.setScheme( "https" ); localReq.setUrl( newUrl ); } if ( op == GetOperation ) { if ( localReq.url().scheme() == "qrcx" ) { // We have to override the local load policy for the qrc scheme, hence // we use qrcx and redirect it here back to qrc QUrl newUrl( localReq.url() ); newUrl.setScheme( "qrc" ); newUrl.setHost( "" ); localReq.setUrl( newUrl ); return QNetworkAccessManager::createRequest( op, localReq, outgoingData ); } #if QT_VERSION >= 0x050300 // Qt 5.3+ // Workaround of same-origin policy if( ( localReq.url().scheme().startsWith( "http" ) || localReq.url().scheme() == "ftp" ) && localReq.hasRawHeader( "Referer" ) ) { QByteArray referer = localReq.rawHeader( "Referer" ); QUrl refererUrl = QUrl::fromEncoded( referer ); if( refererUrl.scheme().startsWith( "http") || refererUrl.scheme() == "ftp" ) { // Only for pages from network resources if ( !localReq.url().host().endsWith( refererUrl.host() ) ) { QUrl frameUrl; frameUrl.setScheme( refererUrl.scheme() ); frameUrl.setHost( refererUrl.host() ); QString frameStr = frameUrl.toString( QUrl::PrettyDecoded ); SecurityWhiteList & value = allOrigins[ frameStr ]; if( !value.origin ) value.setOrigin( frameUrl ); QPair< QString, QString > target( localReq.url().scheme(), localReq.url().host() ); if( value.hostsToAccess.find( target ) == value.hostsToAccess.end() ) { value.hostsToAccess.insert( target ); value.origin->addAccessWhitelistEntry( target.first, target.second, QWebSecurityOrigin::AllowSubdomains ); } } } } #endif QString contentType; sptr< Dictionary::DataRequest > dr = getResource( localReq.url(), contentType ); if ( dr.get() ) return new ArticleResourceReply( this, localReq, dr, contentType ); } // Check the Referer. If the user has opted-in to block elements from external // pages, we block them. if ( disallowContentFromOtherSites && localReq.hasRawHeader( "Referer" ) ) { QByteArray referer = localReq.rawHeader( "Referer" ); //DPRINTF( "Referer: %s\n", referer.data() ); QUrl refererUrl = QUrl::fromEncoded( referer ); //DPRINTF( "Considering %s vs %s\n", getHostBase( localReq.url() ).toUtf8().data(), // getHostBase( refererUrl ).toUtf8().data() ); if ( !localReq.url().host().endsWith( refererUrl.host() ) && getHostBase( localReq.url() ) != getHostBase( refererUrl ) && !localReq.url().scheme().startsWith("data") ) { gdWarning( "Blocking element \"%s\"\n", localReq.url().toEncoded().data() ); return new BlockedNetworkReply( this ); } } if( localReq.url().scheme() == "file" ) { // Check file presence and adjust path if necessary QString fileName = localReq.url().toLocalFile(); if( localReq.url().host().isEmpty() && articleMaker.adjustFilePath( fileName ) ) { QUrl newUrl( localReq.url() ); QUrl localUrl = QUrl::fromLocalFile( fileName ); newUrl.setHost( localUrl.host() ); newUrl.setPath( Qt4x5::Url::ensureLeadingSlash( localUrl.path() ) ); localReq.setUrl( newUrl ); return QNetworkAccessManager::createRequest( op, localReq, outgoingData ); } } QNetworkReply *reply = 0; // spoof User-Agent if ( hideGoldenDictHeader && localReq.url().scheme().startsWith("http", Qt::CaseInsensitive)) { QByteArray const userAgentHeader = "User-Agent"; localReq.setRawHeader( userAgentHeader, localReq.rawHeader( userAgentHeader ).replace( qApp->applicationName().toUtf8(), "" ) ); reply = QNetworkAccessManager::createRequest( op, localReq, outgoingData ); } if( !reply ) reply = QNetworkAccessManager::createRequest( op, localReq, outgoingData ); if( localReq.url().scheme() == "https") { #ifndef QT_NO_OPENSSL connect( reply, SIGNAL( sslErrors( QList< QSslError > ) ), reply, SLOT( ignoreSslErrors() ) ); #endif } #if QT_VERSION >= 0x050300 // Qt 5.3+ return op == QNetworkAccessManager::GetOperation || op == QNetworkAccessManager::HeadOperation ? new AllowFrameReply( reply ) : reply; #else return reply; #endif } sptr< Dictionary::DataRequest > ArticleNetworkAccessManager::getResource( QUrl const & url, QString & contentType ) { GD_DPRINTF( "getResource: %ls\n", url.toString().toStdWString().c_str() ); GD_DPRINTF( "scheme: %ls\n", url.scheme().toStdWString().c_str() ); GD_DPRINTF( "host: %ls\n", url.host().toStdWString().c_str() ); if ( url.scheme() == "gdlookup" ) { if( !url.host().isEmpty() && url.host() != "localhost" ) { // Strange request - ignore it return new Dictionary::DataRequestInstant( false ); } contentType = "text/html"; if ( Qt4x5::Url::queryItemValue( url, "blank" ) == "1" ) return articleMaker.makeEmptyPage(); Config::InputPhrase phrase ( Qt4x5::Url::queryItemValue( url, "word" ).trimmed(), Qt4x5::Url::queryItemValue( url, "punctuation_suffix" ) ); bool groupIsValid = false; unsigned group = Qt4x5::Url::queryItemValue( url, "group" ).toUInt( &groupIsValid ); QString dictIDs = Qt4x5::Url::queryItemValue( url, "dictionaries" ); if( !dictIDs.isEmpty() ) { // Individual dictionaries set from full-text search QStringList dictIDList = dictIDs.split( "," ); return articleMaker.makeDefinitionFor( phrase, 0, QMap< QString, QString >(), QSet< QString >(), dictIDList ); } // See if we have some dictionaries muted QStringList const mutedDictList = Qt4x5::Url::queryItemValue( url, "muted" ).split( ',' ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) QSet< QString > const mutedDicts( mutedDictList.cbegin(), mutedDictList.cend() ); #else QSet< QString > const mutedDicts = QSet< QString >::fromList( mutedDictList ); #endif // Unpack contexts QMap< QString, QString > contexts; QString contextsEncoded = Qt4x5::Url::queryItemValue( url, "contexts" ); if ( contextsEncoded.size() ) { QByteArray ba = QByteArray::fromBase64( contextsEncoded.toLatin1() ); QBuffer buf( & ba ); buf.open( QBuffer::ReadOnly ); QDataStream stream( &buf ); stream >> contexts; } // See for ignore diacritics bool ignoreDiacritics = Qt4x5::Url::queryItemValue( url, "ignore_diacritics" ) == "1"; if ( groupIsValid && phrase.isValid() ) // Require group and phrase to be passed return articleMaker.makeDefinitionFor( phrase, group, contexts, mutedDicts, QStringList(), ignoreDiacritics ); } if ( ( url.scheme() == "bres" || url.scheme() == "gdau" || url.scheme() == "gdvideo" || url.scheme() == "gico" ) && url.path().size() ) { //DPRINTF( "Get %s\n", req.url().host().toLocal8Bit().data() ); //DPRINTF( "Get %s\n", req.url().path().toLocal8Bit().data() ); string id = url.host().toStdString(); bool search = ( id == "search" ); if ( !search ) { for( unsigned x = 0; x < dictionaries.size(); ++x ) if ( dictionaries[ x ]->getId() == id ) { if( url.scheme() == "gico" ) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); dictionaries[ x ]->getIcon().pixmap( 16 ).save(&buffer, "PNG"); buffer.close(); sptr< Dictionary::DataRequestInstant > ico = new Dictionary::DataRequestInstant( true ); ico->getData().resize( bytes.size() ); memcpy( &( ico->getData().front() ), bytes.data(), bytes.size() ); return ico; } try { return dictionaries[ x ]->getResource( Qt4x5::Url::fullPath( url ).mid( 1 ).toUtf8().data() ); } catch( std::exception & e ) { gdWarning( "getResource request error (%s) in \"%s\"\n", e.what(), dictionaries[ x ]->getName().c_str() ); return sptr< Dictionary::DataRequest >(); } } } else { // We don't do search requests for now #if 0 for( unsigned x = 0; x < dictionaries.size(); ++x ) { if ( search || dictionaries[ x ]->getId() == id ) { try { dictionaries[ x ]->getResource( url.path().mid( 1 ).toUtf8().data(), data ); return true; } catch( Dictionary::exNoSuchResource & ) { if ( !search ) break; } } } #endif } } if ( url.scheme() == "gdpicture" ) { contentType = "text/html"; QUrl imgUrl ( url ); imgUrl.setScheme( "bres" ); return articleMaker.makePicturePage( imgUrl.toEncoded().data() ); } return sptr< Dictionary::DataRequest >(); } ArticleResourceReply::ArticleResourceReply( QObject * parent, QNetworkRequest const & netReq, sptr< Dictionary::DataRequest > const & req_, QString const & contentType ): QNetworkReply( parent ), req( req_ ), alreadyRead( 0 ) { setRequest( netReq ); setOpenMode( ReadOnly ); if ( contentType.size() ) setHeader( QNetworkRequest::ContentTypeHeader, contentType ); connect( req.get(), SIGNAL( updated() ), this, SLOT( reqUpdated() ) ); connect( req.get(), SIGNAL( finished() ), this, SLOT( reqFinished() ) ); if ( req->isFinished() || req->dataSize() > 0 ) { connect( this, SIGNAL( readyReadSignal() ), this, SLOT( readyReadSlot() ), Qt::QueuedConnection ); connect( this, SIGNAL( finishedSignal() ), this, SLOT( finishedSlot() ), Qt::QueuedConnection ); emit readyReadSignal(); if ( req->isFinished() ) { emit finishedSignal(); GD_DPRINTF( "In-place finish.\n" ); } } } ArticleResourceReply::~ArticleResourceReply() { req->cancel(); } void ArticleResourceReply::reqUpdated() { emit readyRead(); } void ArticleResourceReply::reqFinished() { emit readyRead(); finishedSlot(); } qint64 ArticleResourceReply::bytesAvailable() const { qint64 avail = req->dataSize(); if ( avail < 0 ) return 0; return avail - alreadyRead + QNetworkReply::bytesAvailable(); } qint64 ArticleResourceReply::readData( char * out, qint64 maxSize ) { // From the doc: "This function might be called with a maxSize of 0, // which can be used to perform post-reading operations". if ( maxSize == 0 ) return 0; GD_DPRINTF( "====reading %d bytes\n", (int)maxSize ); bool finished = req->isFinished(); qint64 avail = req->dataSize(); if ( avail < 0 ) return finished ? -1 : 0; qint64 left = avail - alreadyRead; qint64 toRead = maxSize < left ? maxSize : left; try { req->getDataSlice( alreadyRead, toRead, out ); } catch( std::exception & e ) { qWarning( "getDataSlice error: %s\n", e.what() ); } alreadyRead += toRead; if ( !toRead && finished ) return -1; else return toRead; } void ArticleResourceReply::readyReadSlot() { readyRead(); } void ArticleResourceReply::finishedSlot() { if ( req->dataSize() < 0 ) { #if QT_VERSION >= QT_VERSION_CHECK( 5, 15, 0 ) emit errorOccurred( ContentNotFoundError ); #else emit error( ContentNotFoundError ); #endif } finished(); } BlockedNetworkReply::BlockedNetworkReply( QObject * parent ): QNetworkReply( parent ) { setError( QNetworkReply::ContentOperationNotPermittedError, "Content Blocked" ); connect( this, SIGNAL( finishedSignal() ), this, SLOT( finishedSlot() ), Qt::QueuedConnection ); emit finishedSignal(); // This way we call readyRead()/finished() sometime later } void BlockedNetworkReply::finishedSlot() { emit readyRead(); emit finished(); } goldendict-1.5.0/article_netmgr.hh000066400000000000000000000140201443523320500171470ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __ARTICLE_NETMGR_HH_INCLUDED__ #define __ARTICLE_NETMGR_HH_INCLUDED__ #include #if QT_VERSION >= 0x050300 // Qt 5.3+ #include #include #include #include #endif #include "dictionary.hh" #include "article_maker.hh" using std::vector; /// A custom QNetworkAccessManager version which fetches images from the /// dictionaries when requested. #if QT_VERSION >= 0x050300 // Qt 5.3+ // White lists for QWebSecurityOrigin struct SecurityWhiteList { QWebSecurityOrigin * origin; QString originUri; QSet< QPair< QString, QString > > hostsToAccess; SecurityWhiteList() : origin( 0 ) {} ~SecurityWhiteList() { swlDelete(); } SecurityWhiteList( SecurityWhiteList const & swl ) : origin( 0 ) { swlCopy( swl ); } SecurityWhiteList & operator=( SecurityWhiteList const & swl ); QWebSecurityOrigin * setOrigin( QUrl const & url ); private: void swlCopy( SecurityWhiteList const & swl ); void swlDelete(); }; typedef QMap< QString, SecurityWhiteList > Origins; // Proxy class for QNetworkReply to remove X-Frame-Options header // It allow to show websites in "; dr->getData().resize( result.size() ); memcpy( &( dr->getData().front() ), result.data(), result.size() ); return dr; } // To load data from site return new WebSiteArticleRequest( urlString, netMgr, this ); } class WebSiteResourceRequest: public WebSiteDataRequestSlots { QNetworkReply * netReply; QString url; WebSiteDictionary * dictPtr; QNetworkAccessManager & mgr; public: WebSiteResourceRequest( QString const & url_, QNetworkAccessManager & _mgr, WebSiteDictionary * dictPtr_ ); ~WebSiteResourceRequest() {} virtual void cancel(); private: virtual void requestFinished( QNetworkReply * ); }; WebSiteResourceRequest::WebSiteResourceRequest( QString const & url_, QNetworkAccessManager & _mgr, WebSiteDictionary * dictPtr_ ): url( url_ ), dictPtr( dictPtr_ ), mgr( _mgr ) { connect( &mgr, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( requestFinished( QNetworkReply * ) ), Qt::QueuedConnection ); QUrl reqUrl( url ); netReply = mgr.get( QNetworkRequest( reqUrl ) ); #ifndef QT_NO_OPENSSL connect( netReply, SIGNAL( sslErrors( QList< QSslError > ) ), netReply, SLOT( ignoreSslErrors() ) ); #endif } void WebSiteResourceRequest::cancel() { finish(); } void WebSiteResourceRequest::requestFinished( QNetworkReply * r ) { if ( isFinished() ) // Was cancelled return; if ( r != netReply ) { // Well, that's not our reply, don't do anything return; } if ( netReply->error() == QNetworkReply::NoError ) { // Check for redirect reply QVariant possibleRedirectUrl = netReply->attribute( QNetworkRequest::RedirectionTargetAttribute ); QUrl redirectUrl = possibleRedirectUrl.toUrl(); if( !redirectUrl.isEmpty() ) { disconnect( netReply, 0, 0, 0 ); netReply->deleteLater(); netReply = mgr.get( QNetworkRequest( redirectUrl ) ); #ifndef QT_NO_OPENSSL connect( netReply, SIGNAL( sslErrors( QList< QSslError > ) ), netReply, SLOT( ignoreSslErrors() ) ); #endif return; } // Handle reply data QByteArray replyData = netReply->readAll(); QString cssString = QString::fromUtf8( replyData ); dictPtr->isolateWebCSS( cssString ); QByteArray cssData = cssString.toUtf8(); Mutex::Lock _( dataMutex ); size_t prevSize = data.size(); data.resize( prevSize + cssData.size() ); memcpy( &data.front() + prevSize, cssData.data(), cssData.size() ); hasAnyData = true; } else setErrorString( netReply->errorString() ); disconnect( netReply, 0, 0, 0 ); netReply->deleteLater(); finish(); } sptr< Dictionary::DataRequest > WebSiteDictionary::getResource( string const & name ) THROW_SPEC( std::exception ) { QString link = QString::fromUtf8( name.c_str() ); int pos = link.indexOf( '/' ); if( pos > 0 ) link.replace( pos, 1, "://" ); return new WebSiteResourceRequest( link, netMgr, this ); } void WebSiteDictionary::loadIcon() throw() { if ( dictionaryIconLoaded ) return; if( !iconFilename.isEmpty() ) { QFileInfo fInfo( QDir( Config::getConfigDir() ), iconFilename ); if( fInfo.isFile() ) loadIconFromFile( fInfo.absoluteFilePath(), true ); } if( dictionaryIcon.isNull() ) dictionaryIcon = dictionaryNativeIcon = QIcon(":/icons/internet.png"); dictionaryIconLoaded = true; } } vector< sptr< Dictionary::Class > > makeDictionaries( Config::WebSites const & ws, QNetworkAccessManager & mgr ) THROW_SPEC( std::exception ) { vector< sptr< Dictionary::Class > > result; for( int x = 0; x < ws.size(); ++x ) { if ( ws[ x ].enabled ) result.push_back( new WebSiteDictionary( ws[ x ].id.toUtf8().data(), ws[ x ].name.toUtf8().data(), ws[ x ].url, ws[ x ].iconFilename, ws[ x ].inside_iframe, mgr ) ); } return result; } } goldendict-1.5.0/website.hh000066400000000000000000000014541443523320500156210ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __WEBSITE_HH_INCLUDED__ #define __WEBSITE_HH_INCLUDED__ #include "dictionary.hh" #include "config.hh" #include #include /// Support for any web sites via a templated url. namespace WebSite { using std::vector; using std::string; vector< sptr< Dictionary::Class > > makeDictionaries( Config::WebSites const &, QNetworkAccessManager & ) THROW_SPEC( std::exception ); /// Exposed here for moc class WebSiteDataRequestSlots: public Dictionary::DataRequest { Q_OBJECT protected slots: virtual void requestFinished( QNetworkReply * ) {} }; } #endif goldendict-1.5.0/wildcard.cc000066400000000000000000000053721443523320500157410ustar00rootroot00000000000000#include #include "wildcard.hh" /* Modified function from Qt Translates a wildcard pattern to an equivalent regular expression pattern (e.g., *.cpp to .*\.cpp). */ QString wildcardsToRegexp( const QString & wc_str ) { const int wclen = wc_str.length(); QString rx; int i = 0; bool isEscaping = false; // the previous character is '\' const QChar *wc = wc_str.unicode(); while( i < wclen ) { const QChar c = wc[ i++ ]; switch( c.unicode() ) { case '\\': if( isEscaping ) { rx += QLatin1String( "\\\\" ); } // we insert the \\ later if necessary if( i == wclen ) { // the end rx += QLatin1String( "\\\\" ); } isEscaping = true; break; case '*': if( isEscaping ) { rx += QLatin1String( "\\*" ); isEscaping = false; } else { rx += QLatin1String( ".*" ); } break; case '?': if( isEscaping ) { rx += QLatin1String( "\\?" ); isEscaping = false; } else { rx += QLatin1Char( '.' ); } break; case '$': case '(': case ')': case '+': case '.': case '^': case '{': case '|': case '}': if( isEscaping ) { isEscaping = false; rx += QLatin1String( "\\\\" ); } rx += QLatin1Char( '\\' ); rx += c; break; case '[': if(isEscaping) { isEscaping = false; rx += QLatin1String( "\\[" ); } else { QString tmp; tmp += c; if( i < wclen && wc[ i ] == QLatin1Char( '!' ) ) { tmp += QLatin1Char( '^' ); ++i; } while( i < wclen && wc[ i ] != QLatin1Char( ']' ) ) { if( wc[ i ] == QLatin1Char( '\\' ) ) tmp += QLatin1Char( '\\' ); tmp += wc[ i++ ]; } if( i < wclen ) rx += tmp; else rx += QRegularExpression::escape( tmp ); } break; case ']': if( isEscaping ){ isEscaping = false; rx += QLatin1String( "\\" ); } rx += c; break; default: if( isEscaping ){ isEscaping = false; rx += QLatin1String( "\\\\" ); } rx += c; } } return rx; } goldendict-1.5.0/wildcard.hh000066400000000000000000000002221443523320500157400ustar00rootroot00000000000000#ifndef __WILCARD_HH_INCLUDED__ #define __WILCARD_HH_INCLUDED__ #include QString wildcardsToRegexp( const QString & wc_str ); #endif goldendict-1.5.0/wordbyauto.cc000066400000000000000000000057021443523320500163440ustar00rootroot00000000000000#include #include #include "wordbyauto.hh" #include "uiauto.hh" #include #include "gddebug.hh" class GDAutomationClient { public: GDAutomationClient(); ~GDAutomationClient(); bool getWordAtPoint( POINT pt ); WCHAR *getText() { return buffer; } private: WCHAR buffer[256]; IUIAutomation *pGDAutomation; IUIAutomationTreeWalker *pTree; }; GDAutomationClient gdAuto; GDAutomationClient::GDAutomationClient() { HRESULT hr; CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ); hr = CoCreateInstance( CLSID_CUIAutomation , NULL, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&pGDAutomation ); if( hr != S_OK ) pGDAutomation = NULL; pTree = NULL; if( pGDAutomation != NULL ) hr = pGDAutomation->get_RawViewWalker( &pTree ); memset( buffer, 0, sizeof(buffer) ); } GDAutomationClient::~GDAutomationClient() { if( pTree != NULL ) pTree->Release(); if( pGDAutomation != NULL ) pGDAutomation->Release(); CoUninitialize(); } bool GDAutomationClient::getWordAtPoint( POINT pt ) { HRESULT hr; IUIAutomationTextPattern *pTextPattern; IUIAutomationTextRange *pTextRange; IUIAutomationElement *pElement, *pParent; BSTR bstr; RECT r = { 0, 0, 0, 0 }; bool bGoUp; GD_DPRINTF("\nEntering getWordAtPoint\n"); if( pGDAutomation == NULL ) return false; buffer[0] = 0; pElement = NULL; hr = pGDAutomation->ElementFromPoint( pt, &pElement ); GD_DPRINTF("ElementFromPoint return hr=%08lX, ptr=%p\n", hr, pElement); if( hr != S_OK || pElement == NULL ) return false; pTextPattern = NULL; bGoUp = false; while( pElement != NULL ) { hr = pElement->GetCurrentPatternAs( UIA_TextPatternId, IID_IUIAutomationTextPattern, (void**)&pTextPattern ); if( hr == S_OK && pTextPattern != NULL ) break; if( pTree == NULL ) { pElement->Release(); return false; } pParent = NULL; hr = pTree->GetParentElement( pElement, &pParent ); pElement->Release(); pElement = pParent; bGoUp = TRUE; } if( pElement == NULL ) return false; if( !bGoUp ) { hr = pElement->get_CurrentBoundingRectangle( &r ); if( hr == S_OK) { pt.x -= r.left; pt.y -= r.top; } } pElement->Release(); pTextRange = NULL; hr = pTextPattern->RangeFromPoint( pt, &pTextRange ); pTextPattern->Release(); if( hr != S_OK || pTextRange == NULL ) return false; hr = pTextRange->ExpandToEnclosingUnit( TextUnit_Word ); if( hr == S_OK) { hr = pTextRange->GetText( 255, &bstr ); if (hr == S_OK) { wsprintfW( buffer, L"%s", (LPCWSTR)bstr ); SysFreeString( bstr ); } } pTextRange->Release(); return ( buffer[0] != 0 ); } WCHAR *gdGetWordAtPointByAutomation( POINT pt ) { if( gdAuto.getWordAtPoint( pt ) ) return gdAuto.getText(); else return NULL; } goldendict-1.5.0/wordbyauto.hh000066400000000000000000000002001443523320500163420ustar00rootroot00000000000000#ifndef __WORD_BY_AUTO_HH_INCLUDED #define __WORD_BY_AUTO_HH_INCLUDED WCHAR *gdGetWordAtPointByAutomation( POINT pt ); #endif goldendict-1.5.0/wordfinder.cc000066400000000000000000000370271443523320500163150ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "wordfinder.hh" #include "folding.hh" #include "wstring_qt.hh" #include #include #include "gddebug.hh" using std::vector; using std::list; using gd::wstring; using gd::wchar; using std::map; using std::pair; WordFinder::WordFinder( QObject * parent ): QObject( parent ), searchInProgress( false ), updateResultsTimer( this ), searchQueued( false ) { updateResultsTimer.setInterval( 1000 ); // We use a one second update timer updateResultsTimer.setSingleShot( true ); connect( &updateResultsTimer, SIGNAL( timeout() ), this, SLOT( updateResults() ), Qt::QueuedConnection ); } WordFinder::~WordFinder() { clear(); } void WordFinder::prefixMatch( QString const & str, std::vector< sptr< Dictionary::Class > > const & dicts, unsigned long maxResults, Dictionary::Features features ) { cancel(); searchQueued = true; searchType = PrefixMatch; inputWord = str; inputDicts = &dicts; requestedMaxResults = maxResults; requestedFeatures = features; resultsArray.clear(); resultsIndex.clear(); searchResults.clear(); if ( queuedRequests.empty() ) { // No requests are queued, no need to wait for them to finish. startSearch(); } // Else some requests are still queued, last one to finish would trigger // new search. This shouldn't take a lot of time, since they were all // cancelled, but still it could take some time. } void WordFinder::stemmedMatch( QString const & str, std::vector< sptr< Dictionary::Class > > const & dicts, unsigned minLength, unsigned maxSuffixVariation, unsigned long maxResults, Dictionary::Features features ) { cancel(); searchQueued = true; searchType = StemmedMatch; inputWord = str; inputDicts = &dicts; requestedMaxResults = maxResults; requestedFeatures = features; stemmedMinLength = minLength; stemmedMaxSuffixVariation = maxSuffixVariation; resultsArray.clear(); resultsIndex.clear(); searchResults.clear(); if ( queuedRequests.empty() ) startSearch(); } void WordFinder::expressionMatch( QString const & str, std::vector< sptr< Dictionary::Class > > const & dicts, unsigned long maxResults, Dictionary::Features features ) { cancel(); searchQueued = true; searchType = ExpressionMatch; inputWord = str; inputDicts = &dicts; requestedMaxResults = maxResults; requestedFeatures = features; resultsArray.clear(); resultsIndex.clear(); searchResults.clear(); if ( queuedRequests.empty() ) { // No requests are queued, no need to wait for them to finish. startSearch(); } } void WordFinder::startSearch() { if ( !searchQueued ) return; // Search was probably cancelled // Clear the requests just in case queuedRequests.clear(); finishedRequests.clear(); searchErrorString.clear(); searchResultsUncertain = false; searchQueued = false; searchInProgress = true; // Gather all writings of the word if ( allWordWritings.size() != 1 ) allWordWritings.resize( 1 ); allWordWritings[ 0 ] = gd::toWString( inputWord ); for( size_t x = 0; x < inputDicts->size(); ++x ) { vector< wstring > writings = (*inputDicts)[ x ]->getAlternateWritings( allWordWritings[ 0 ] ); allWordWritings.insert( allWordWritings.end(), writings.begin(), writings.end() ); } // Query each dictionary for all word writings for( size_t x = 0; x < inputDicts->size(); ++x ) { if ( ( (*inputDicts)[ x ]->getFeatures() & requestedFeatures ) != requestedFeatures ) continue; for( size_t y = 0; y < allWordWritings.size(); ++y ) { try { sptr< Dictionary::WordSearchRequest > sr = ( searchType == PrefixMatch || searchType == ExpressionMatch ) ? (*inputDicts)[ x ]->prefixMatch( allWordWritings[ y ], requestedMaxResults ) : (*inputDicts)[ x ]->stemmedMatch( allWordWritings[ y ], stemmedMinLength, stemmedMaxSuffixVariation, requestedMaxResults ); connect( sr.get(), SIGNAL( finished() ), this, SLOT( requestFinished() ), Qt::QueuedConnection ); queuedRequests.push_back( sr ); } catch( std::exception & e ) { gdWarning( "Word \"%s\" search error (%s) in \"%s\"\n", inputWord.toUtf8().data(), e.what(), (*inputDicts)[ x ]->getName().c_str() ); } } } // Handle any requests finished already requestFinished(); } void WordFinder::cancel() { searchQueued = false; searchInProgress = false; cancelSearches(); } void WordFinder::clear() { cancel(); queuedRequests.clear(); finishedRequests.clear(); } void WordFinder::requestFinished() { bool newResults = false; // See how many new requests have finished, and if we have any new results for( list< sptr< Dictionary::WordSearchRequest > >::iterator i = queuedRequests.begin(); i != queuedRequests.end(); ) { if ( (*i)->isFinished() ) { if ( searchInProgress && !(*i)->getErrorString().isEmpty() ) searchErrorString = tr( "Failed to query some dictionaries." ); if ( (*i)->isUncertain() ) searchResultsUncertain = true; if ( (*i)->matchesCount() ) { newResults = true; // This list is handled by updateResults() finishedRequests.splice( finishedRequests.end(), queuedRequests, i++ ); } else // We won't do anything with it anymore, so we erase it queuedRequests.erase( i++ ); } else ++i; } if ( !searchInProgress ) { // There is no search in progress, so we just wait until there's // no requests left if ( queuedRequests.empty() ) { // We got rid of all queries, queued search can now start finishedRequests.clear(); if ( searchQueued ) startSearch(); } return; } if ( newResults && queuedRequests.size() && !updateResultsTimer.isActive() ) { // If we have got some new results, but not all of them, we would start a // timer to update a user some time in the future updateResultsTimer.start(); } if ( queuedRequests.empty() ) { // Search is finished. updateResults(); } } namespace { unsigned saturated( unsigned x ) { return x < 255 ? x : 255; } /// Checks whether the first string has the second one inside, surrounded from /// both sides by either whitespace, punctuation or begin/end of string. /// If true is returned, pos holds the offset in the haystack. If the offset /// is larger than 255, it is set to 255. bool hasSurroundedWithWs( wstring const & haystack, wstring const & needle, wstring::size_type & pos ) { if ( haystack.size() < needle.size() ) return false; // Needle won't even fit into a haystack for( pos = 0; ; ++pos ) { pos = haystack.find( needle, pos ); if ( pos == wstring::npos ) return false; // Not found if ( ( !pos || Folding::isWhitespace( haystack[ pos - 1 ] ) || Folding::isPunct( haystack[ pos - 1 ] ) ) && ( ( pos + needle.size() == haystack.size() ) || Folding::isWhitespace( haystack[ pos + needle.size() ] ) || Folding::isPunct( haystack[ pos + needle.size() ] ) ) ) { pos = saturated( pos ); return true; } } } } void WordFinder::updateResults() { if ( !searchInProgress ) return; // Old queued signal if ( updateResultsTimer.isActive() ) updateResultsTimer.stop(); // Can happen when we were done before it'd expire wstring original = Folding::applySimpleCaseOnly( allWordWritings[ 0 ] ); for( list< sptr< Dictionary::WordSearchRequest > >::iterator i = finishedRequests.begin(); i != finishedRequests.end(); ) { for( size_t count = (*i)->matchesCount(), x = 0; x < count; ++x ) { wstring match = (**i)[ x ].word; int weight = (**i)[ x ].weight; wstring lowerCased = Folding::applySimpleCaseOnly( match ); if( searchType == ExpressionMatch ) { unsigned ws; for( ws = 0; ws < allWordWritings.size(); ws++ ) { if( ws == 0 ) { // Check for prefix match with original expression if( lowerCased.compare( 0, original.size(), original ) == 0 ) break; } else if( lowerCased == Folding::applySimpleCaseOnly( allWordWritings[ ws ] ) ) break; } if( ws >= allWordWritings.size() ) { // No exact matches found continue; } weight = ws; } pair< ResultsIndex::iterator, bool > insertResult = resultsIndex.insert( pair< wstring, ResultsArray::iterator >( lowerCased, resultsArray.end() ) ); if ( !insertResult.second ) { // Wasn't inserted since there was already an item -- check the case if ( insertResult.first->second->word != match ) { // The case is different -- agree on a lowercase version insertResult.first->second->word = lowerCased; } if ( !weight && insertResult.first->second->wasSuggested ) insertResult.first->second->wasSuggested = false; } else { resultsArray.push_back( OneResult() ); resultsArray.back().word = match; resultsArray.back().rank = INT_MAX; resultsArray.back().wasSuggested = ( weight != 0 ); insertResult.first->second = --resultsArray.end(); } } finishedRequests.erase( i++ ); } size_t maxSearchResults = 500; if ( resultsArray.size() ) { if ( searchType == PrefixMatch ) { /// Assign each result a category, storing it in the rank's field enum Category { ExactMatch, ExactNoFullCaseMatch, ExactNoDiaMatch, ExactNoPunctMatch, ExactNoWsMatch, ExactInsideMatch, ExactNoDiaInsideMatch, ExactNoPunctInsideMatch, PrefixMatch, PrefixNoDiaMatch, PrefixNoPunctMatch, PrefixNoWsMatch, WorstMatch, Multiplier = 256 // Categories should be multiplied by Multiplier }; for( unsigned wr = 0; wr < allWordWritings.size(); ++wr ) { wstring target = Folding::applySimpleCaseOnly( allWordWritings[ wr ] ); wstring targetNoFullCase = Folding::applyFullCaseOnly( target ); wstring targetNoDia = Folding::applyDiacriticsOnly( targetNoFullCase ); wstring targetNoPunct = Folding::applyPunctOnly( targetNoDia ); wstring targetNoWs = Folding::applyWhitespaceOnly( targetNoPunct ); wstring::size_type matchPos = 0; for( ResultsIndex::const_iterator i = resultsIndex.begin(), j = resultsIndex.end(); i != j; ++i ) { wstring resultNoFullCase, resultNoDia, resultNoPunct, resultNoWs; int rank; if ( i->first == target ) rank = ExactMatch * Multiplier; else if ( ( resultNoFullCase = Folding::applyFullCaseOnly( i->first ) ) == targetNoFullCase ) rank = ExactNoFullCaseMatch * Multiplier; else if ( ( resultNoDia = Folding::applyDiacriticsOnly( resultNoFullCase ) ) == targetNoDia ) rank = ExactNoDiaMatch * Multiplier; else if ( ( resultNoPunct = Folding::applyPunctOnly( resultNoDia ) ) == targetNoPunct ) rank = ExactNoPunctMatch * Multiplier; else if ( ( resultNoWs = Folding::applyWhitespaceOnly( resultNoPunct ) ) == targetNoWs ) rank = ExactNoWsMatch * Multiplier; else if ( hasSurroundedWithWs( i->first, target, matchPos ) ) rank = ExactInsideMatch * Multiplier + matchPos; else if ( hasSurroundedWithWs( resultNoDia, targetNoDia, matchPos ) ) rank = ExactNoDiaInsideMatch * Multiplier + matchPos; else if ( hasSurroundedWithWs( resultNoPunct, targetNoPunct, matchPos ) ) rank = ExactNoPunctInsideMatch * Multiplier + matchPos; else if ( i->first.size() > target.size() && i->first.compare( 0, target.size(), target ) == 0 ) rank = PrefixMatch * Multiplier + saturated( i->first.size() ); else if ( resultNoDia.size() > targetNoDia.size() && resultNoDia.compare( 0, targetNoDia.size(), targetNoDia ) == 0 ) rank = PrefixNoDiaMatch * Multiplier + saturated( i->first.size() ); else if ( resultNoPunct.size() > targetNoPunct.size() && resultNoPunct.compare( 0, targetNoPunct.size(), targetNoPunct ) == 0 ) rank = PrefixNoPunctMatch * Multiplier + saturated( i->first.size() ); else if ( resultNoWs.size() > targetNoWs.size() && resultNoWs.compare( 0, targetNoWs.size(), targetNoWs ) == 0 ) rank = PrefixNoWsMatch * Multiplier + saturated( i->first.size() ); else rank = WorstMatch * Multiplier; if ( i->second->rank > rank ) i->second->rank = rank; // We store the best rank of any writing } } resultsArray.sort( SortByRank() ); } else if( searchType == StemmedMatch ) { // Handling stemmed matches // We use two factors -- first is the number of characters strings share // in their beginnings, and second, the length of the strings. Here we assign // only the first one, storing it in rank. Then we sort the results using // SortByRankAndLength. for( unsigned wr = 0; wr < allWordWritings.size(); ++wr ) { wstring target = Folding::apply( allWordWritings[ wr ] ); for( ResultsIndex::const_iterator i = resultsIndex.begin(), j = resultsIndex.end(); i != j; ++i ) { wstring resultFolded = Folding::apply( i->first ); int charsInCommon = 0; for( wchar const * t = target.c_str(), * r = resultFolded.c_str(); *t && *t == *r; ++t, ++r, ++charsInCommon ) ; int rank = -charsInCommon; // Negated so the lesser-than // comparison would yield right // results. if ( i->second->rank > rank ) i->second->rank = rank; // We store the best rank of any writing } } resultsArray.sort( SortByRankAndLength() ); maxSearchResults = 15; } } searchResults.clear(); searchResults.reserve( resultsArray.size() < maxSearchResults ? resultsArray.size() : maxSearchResults ); for( ResultsArray::const_iterator i = resultsArray.begin(), j = resultsArray.end(); i != j; ++i ) { //DPRINTF( "%d: %ls\n", i->second, i->first.c_str() ); if ( searchResults.size() < maxSearchResults ) searchResults.push_back( std::pair< QString, bool >( gd::toQString( i->word ), i->wasSuggested ) ); else break; } if ( queuedRequests.size() ) { // There are still some unhandled results. emit updated(); } else { // That were all of them. searchInProgress = false; emit finished(); } } void WordFinder::cancelSearches() { for( list< sptr< Dictionary::WordSearchRequest > >::iterator i = queuedRequests.begin(); i != queuedRequests.end(); ++i ) (*i)->cancel(); } goldendict-1.5.0/wordfinder.hh000066400000000000000000000141471443523320500163250ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __WORDFINDER_HH_INCLUDED__ #define __WORDFINDER_HH_INCLUDED__ #include #include #include #include #include #include #include #include "dictionary.hh" /// This component takes care of finding words. The search is asynchronous. /// This means the GUI doesn't get blocked during the sometimes lenghtly /// process of finding words. class WordFinder: public QObject { Q_OBJECT public: typedef std::vector< std::pair< QString, bool > > SearchResults; // bool is a "was suggested" flag private: SearchResults searchResults; QString searchErrorString; bool searchResultsUncertain; std::list< sptr< Dictionary::WordSearchRequest > > queuedRequests, finishedRequests; bool searchInProgress; QTimer updateResultsTimer; // Saved search params bool searchQueued; QString inputWord; enum SearchType { PrefixMatch, StemmedMatch, ExpressionMatch } searchType; unsigned long requestedMaxResults; Dictionary::Features requestedFeatures; unsigned stemmedMinLength; unsigned stemmedMaxSuffixVariation; std::vector< sptr< Dictionary::Class > > const * inputDicts; std::vector< gd::wstring > allWordWritings; // All writings of the inputWord struct OneResult { gd::wstring word; int rank; bool wasSuggested; }; // Maps lowercased string to the original one. This catches all duplicates // without case sensitivity. Made as an array and a map indexing that array. typedef std::list< OneResult > ResultsArray; typedef std::map< gd::wstring, ResultsArray::iterator > ResultsIndex; ResultsArray resultsArray; ResultsIndex resultsIndex; public: WordFinder( QObject * parent ); ~WordFinder(); /// Do the standard prefix-match search in the given list of dictionaries. /// Some dictionaries might only support exact matches -- for them, only /// the exact matches would be found. All search results are put into a single /// list containing the exact matches first, then the prefix ones. Duplicate /// matches from different dictionaries are merged together. /// If a list of features is specified, the search will only be performed in /// the dictionaries which possess all the features requested. /// If there already was a prefixMatch operation underway, it gets cancelled /// and the new one replaces it. void prefixMatch( QString const &, std::vector< sptr< Dictionary::Class > > const &, unsigned long maxResults = 40, Dictionary::Features = Dictionary::NoFeatures ); /// Do a stemmed-match search in the given list of dictionaries. All comments /// from prefixMatch() generally apply as well. void stemmedMatch( QString const &, std::vector< sptr< Dictionary::Class > > const &, unsigned minLength = 3, unsigned maxSuffixVariation = 3, unsigned long maxResults = 30, Dictionary::Features = Dictionary::NoFeatures ); /// Do the expression-match search in the given list of dictionaries. /// Function find exact matches for one of spelling suggestions. void expressionMatch( QString const &, std::vector< sptr< Dictionary::Class > > const &, unsigned long maxResults = 40, Dictionary::Features = Dictionary::NoFeatures ); /// Returns the vector containing search results from the last operation. /// If it didn't finish yet, the result is not final and may be changing /// over time. SearchResults const & getResults() const { return searchResults; } /// Returns a human-readable error string for the last finished request. Empty /// string means it finished without any error. QString const & getErrorString() { return searchErrorString; } /// Returns true if the search was inconclusive -- that is, there may be more /// results than the ones returned. bool wasSearchUncertain() const { return searchResultsUncertain; } /// Cancels any pending search operation, if any. void cancel(); /// Cancels any pending search operation, if any, and makes sure no pending /// requests exist, and hence no dictionaries are used anymore. Unlike /// cancel(), this may take some time to finish. void clear(); signals: /// Indicates that the search has got some more results, and continues /// searching. void updated(); /// Indicates that the search has finished. void finished(); private slots: /// Called each time one of the requests gets finished void requestFinished(); /// Called by updateResultsTimer to update searchResults and signal updated() void updateResults(); private: // Starts the previously queued search. void startSearch(); // Cancels all searches. Useful to do before destroying them all, since they // would cancel in parallel. void cancelSearches(); /// Compares results based on their ranks struct SortByRank { bool operator () ( OneResult const & first, OneResult const & second ) { if ( first.rank < second.rank ) return true; if ( first.rank > second.rank ) return false; // Do any sort of collation here in the future. For now we just put the // strings sorted lexicographically. return first.word < second.word; } }; /// Compares results based on their ranks and lengths struct SortByRankAndLength { bool operator () ( OneResult const & first, OneResult const & second ) { if ( first.rank < second.rank ) return true; if ( first.rank > second.rank ) return false; if ( first.word.size() < second.word.size() ) return true; if ( first.word.size() > second.word.size() ) return false; // Do any sort of collation here in the future. For now we just put the // strings sorted lexicographically. return first.word < second.word; } }; }; #endif goldendict-1.5.0/wordlist.cc000066400000000000000000000072311443523320500160130ustar00rootroot00000000000000/* This file is (c) 2013 Tvangeste * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include #include "wordlist.hh" WordList::WordList( QWidget * parent ) : QListWidget( parent ) , listItemDelegate( itemDelegate() ) { wordFinder = 0; translateLine = 0; setItemDelegate( &listItemDelegate ); } void WordList::attachFinder( WordFinder * finder ) { // qDebug() << "Attaching the word finder..." << finder; if ( wordFinder == finder ) return; if ( wordFinder ) { disconnect( wordFinder, SIGNAL( updated() ), this, SLOT( prefixMatchUpdated() ) ); disconnect( wordFinder, SIGNAL( finished() ), this, SLOT( prefixMatchFinished() ) ); } wordFinder = finder; connect( wordFinder, SIGNAL( updated() ), this, SLOT( prefixMatchUpdated() ) ); connect( wordFinder, SIGNAL( finished() ), this, SLOT( prefixMatchFinished() ) ); } void WordList::prefixMatchUpdated() { updateMatchResults( false ); } void WordList::prefixMatchFinished() { updateMatchResults( true ); } void WordList::updateMatchResults( bool finished ) { WordFinder::SearchResults const & results = wordFinder->getResults(); setUpdatesEnabled( false ); for( unsigned x = 0; x < results.size(); ++x ) { QListWidgetItem * i = item( x ); if ( !i ) { i = new QListWidgetItem( results[ x ].first, this ); i->setToolTip( results[ x ].first ); if ( results[ x ].second ) { QFont f = i->font(); f.setItalic( true ); i->setFont( f ); } addItem( i ); } else { if ( i->text() != results[ x ].first ) { i->setText( results[ x ].first ); i->setToolTip( results[ x ].first ); } QFont f = i->font(); if ( f.italic() != results[ x ].second ) { f.setItalic( results[ x ].second ); i->setFont( f ); } } i->setTextAlignment(Qt::AlignLeft); } while ( count() > (int) results.size() ) { // Chop off any extra items that were there QListWidgetItem * i = takeItem( count() - 1 ); if ( i ) delete i; else break; } if ( count() ) { scrollToItem( item( 0 ), QAbstractItemView::PositionAtTop ); setCurrentItem( 0, QItemSelectionModel::Clear ); } setUpdatesEnabled( true ); if ( finished ) { unsetCursor(); refreshTranslateLine(); if ( !wordFinder->getErrorString().isEmpty() ) emit statusBarMessage( tr( "WARNING: %1" ).arg( wordFinder->getErrorString() ), 20000 , QPixmap( ":/icons/error.png" ) ); } if( !results.empty() && results.front().first.isRightToLeft() ) setLayoutDirection( Qt::RightToLeft ); else setLayoutDirection( Qt::LeftToRight ); emit contentChanged(); } void WordList::refreshTranslateLine() { if ( !translateLine ) return; // Visually mark the input line to mark if there's no results bool setMark = wordFinder->getResults().empty() && !wordFinder->wasSearchUncertain(); if ( translateLine->property( "noResults" ).toBool() != setMark ) { translateLine->setProperty( "noResults", setMark ); translateLine->setStyleSheet( translateLine->styleSheet() ); } } void WordList::resizeEvent( QResizeEvent * ev ) { // In some rare cases Qt start send QResizeEvent recursively // up to full stack depletion (tested on Qt 4.8.5, 4.8.6). // We use this trick to break such suicidal process. for( int x = 0; x < resizedSizes.size(); x++ ) if( resizedSizes.at( x ) == ev->size() ) return; resizedSizes.push_back( ev->size() ); QListWidget::resizeEvent( ev ); resizedSizes.pop_back(); } goldendict-1.5.0/wordlist.hh000066400000000000000000000017771443523320500160360ustar00rootroot00000000000000/* This file is (c) 2013 Tvangeste * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef WORDLIST_HH #define WORDLIST_HH #include #include #include "wordfinder.hh" #include "delegate.hh" class WordList : public QListWidget { Q_OBJECT public: explicit WordList(QWidget * parent = 0); void attachFinder(WordFinder * finder); virtual void setTranslateLine(QLineEdit * line) { translateLine = line; } protected: virtual void resizeEvent( QResizeEvent * ev ); signals: void statusBarMessage(QString const & message, int timeout = 0, QPixmap const & pixmap = QPixmap()); void contentChanged(); public slots: private slots: void prefixMatchUpdated(); void prefixMatchFinished(); void updateMatchResults( bool finished ); private: void refreshTranslateLine(); WordFinder * wordFinder; QLineEdit * translateLine; WordListItemDelegate listItemDelegate; QVector< QSize > resizedSizes; }; #endif // WORDLIST_HH goldendict-1.5.0/wstring.cc000066400000000000000000000003601443523320500156350ustar00rootroot00000000000000#ifdef __WIN32 #include "wstring.hh" #include "iconv.hh" #include namespace gd { wstring __nativeToWs( wchar_t const * str ) { return Iconv::toWstring( "WCHAR_T", str, wcslen( str ) * sizeof( wchar_t ) ); } } #endif goldendict-1.5.0/wstring.hh000066400000000000000000000057321443523320500156570ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __WSTRING_HH_INCLUDED__ #define __WSTRING_HH_INCLUDED__ #include /// While most systems feature a 4-byte wchar_t and an UCS-4 Unicode /// characters representation for it, Windows uses 2-byte wchar_t and an UTF-16 /// encoding. The use of UTF-16 on Windows is most probably a homeage to an /// ancient history dating back to when there was nothing but a BMP, and /// all Unicode chars were 2 bytes long. After the Unicode got expanded past /// two-byte representation, the guys at Microsoft had probably decided that /// the least painful way to go is to just switch to UTF-16. Or so's the theory. /// /// Now, the UTF family is an encoding, made for transit purposes -- is not a /// representation. While it's good for passthrough, it's not directly /// applicable for manipulation on Unicode symbols. It must be decoded first to /// a normal UCS. Think like this: UTF to UCS is something like Base64 to ASCII. /// /// The distinction between Microsoft platform and all other ones is that while /// the latters are stuck in an 8-bit era and use UTF-8 to pass unicode around /// through its venerable interfaces, the former one is stuck in a 16-bit era, /// and uses UTF-16 instead. Neither solution allows for direct processing of /// the symbols in those strings without decoding them first. And the 16-bit /// solution is even more ugly than the 8-bit one, because it doesn't have a /// benefit of ASCII compatibility, having a much more useless UCS-2 /// compatibility instead. It's stuck in the middle of nowhere, really. /// /// The question is, what are we going to do with all this? When we do Unicode /// processing in GoldenDict, we want to use real Unicode characters, not some /// UTF-16 encoded ones. To that end, we have two options under Windows: first, /// use QString, and second, use basic_string< unsigned int >. /// While we use QStrings for the GUI and other non-critical code, there is a /// serious doubt on the efficiency of QStrings for bulk text processing. And /// since a lot of code uses wstring already, it would be much easier to convert /// it to use basic_string< unsigned int > instead, since it shares the same /// template, and therefore the interface too, with wstring. That's why we /// introduce our own gd::wstring and gd::wchar types here. On all systems but /// Windows, they are equivalent to std::wstring and wchar_t. On Windows, they /// are basic_string< unsigned int > and unsigned int. namespace gd { #ifdef __WIN32 typedef unsigned int wchar; typedef std::basic_string< wchar > wstring; // GD_NATIVE_TO_WS is used to convert L"" strings to a const pointer to // wchar. wstring __nativeToWs( wchar_t const * ); #define GD_NATIVE_TO_WS( str ) ( gd::__nativeToWs( ( str ) ).c_str() ) #else typedef wchar_t wchar; using std::wstring; #define GD_NATIVE_TO_WS( str ) ( str ) #endif } #endif goldendict-1.5.0/wstring_qt.cc000066400000000000000000000016271443523320500163500ustar00rootroot00000000000000#include "wstring_qt.hh" #include namespace gd { #ifdef __WIN32 QString toQString( wstring const & in ) { return QString::fromUcs4( in.c_str() ); } #else QString toQString( wstring const & in ) { return QString::fromStdWString( in ); } #endif wstring toWString( QString const & in ) { QVector< unsigned int > v = in.toUcs4(); // Fix for QString instance which contains non-BMP characters // Qt will created unexpected null characters may confuse btree indexer. // Related: https://bugreports.qt-project.org/browse/QTBUG-25536 int n = v.size(); while ( n > 0 && v[ n - 1 ] == 0 ) n--; if ( n != v.size() ) v.resize( n ); return wstring( ( const wchar * ) v.constData(), v.size() ); } wstring normalize( const wstring & str ) { return gd::toWString( gd::toQString( str ).normalized( QString::NormalizationForm_C ) ); } } goldendict-1.5.0/wstring_qt.hh000066400000000000000000000010131443523320500163470ustar00rootroot00000000000000/* This file is (c) 2008-2012 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __WSTRING_QT_HH_INCLUDED__ #define __WSTRING_QT_HH_INCLUDED__ /// This file adds conversions between gd::wstring and QString. See wstring.hh /// for more details on gd::wstring. #include "wstring.hh" #include namespace gd { QString toQString( wstring const & ); wstring toWString( QString const & ); wstring normalize( wstring const & ); } #endif goldendict-1.5.0/x64.cc000066400000000000000000000035141443523320500145650ustar00rootroot00000000000000#include #include #ifndef _UNICODE #define _UNICODE #endif #include "x64.hh" #include #include typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); PROCESS_INFORMATION pInfo; #ifndef Q_OS_WIN64 bool isWow64() { static LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL bIsWow64 = FALSE; if( NULL == fnIsWow64Process ) fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress( GetModuleHandle( _T("kernel32") ), "IsWow64Process" ); if( NULL != fnIsWow64Process ) { if ( !fnIsWow64Process( GetCurrentProcess(), &bIsWow64 ) ) return false; } return bIsWow64; } #endif bool installx64Hooks() { STARTUPINFO startup; #ifndef Q_OS_WIN64 if( !isWow64() ) return false; #endif if( pInfo.hProcess != NULL ) removex64Hooks(); QDir dir = QCoreApplication::applicationDirPath(); #ifdef Q_OS_WIN64 if( !dir.cd("x86") ) return false; QString starterProc = QDir::toNativeSeparators( dir.filePath( "x86helper.exe" ) ); #else if( !dir.cd("x64") ) return false; QString starterProc = QDir::toNativeSeparators( dir.filePath( "x64helper.exe" ) ); #endif memset( &startup, 0, sizeof(startup) ); startup.cb = sizeof(startup); BOOL b = CreateProcess( starterProc.toStdWString().c_str(), NULL, NULL, NULL, FALSE, CREATE_NO_WINDOW | DETACHED_PROCESS, NULL, NULL, &startup, &pInfo ); if( !b ) pInfo.hProcess = NULL; return b; } void removex64Hooks() { if( pInfo.hProcess == NULL ) return; PostThreadMessage( pInfo.dwThreadId, WM_QUIT, 0, 0 ); DWORD res = WaitForSingleObject( pInfo.hProcess, 3000 ); if( res == WAIT_TIMEOUT ) TerminateProcess( pInfo.hProcess, 1 ); CloseHandle( pInfo.hProcess ); CloseHandle( pInfo.hThread ); pInfo.hProcess = NULL; } goldendict-1.5.0/x64.hh000066400000000000000000000002271443523320500145750ustar00rootroot00000000000000#ifndef __X64_HH_INCLUDED__ #define __X64_HH_INCLUDED__ bool isWow64(); bool installx64Hooks(); void removex64Hooks(); #endif // __X64_HH_INCLUDED__ goldendict-1.5.0/xdxf.cc000066400000000000000000001271101443523320500151140ustar00rootroot00000000000000/* This file is (c) 2008-2009 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "xdxf.hh" #include "btreeidx.hh" #include "folding.hh" #include "utf8.hh" #include "chunkedstorage.hh" #include "dictzip.h" #include "htmlescape.hh" #include "fsencoding.hh" #include #include #include #include #include #include #include #include "categorized_logging.hh" #include "gddebug.hh" #include "wstring_qt.hh" #include "xdxf2html.hh" #include "ufile.hh" #include "dictzip.h" #include "langcoder.hh" #include "indexedzip.hh" #include "filetype.hh" #include "tiff.hh" #include "ftshelpers.hh" #ifdef _MSC_VER #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "qt4x5.hh" namespace Xdxf { using std::map; using std::multimap; using std::pair; using std::set; using std::string; using gd::wstring; using std::vector; using std::list; using BtreeIndexing::WordArticleLink; using BtreeIndexing::IndexedWords; using BtreeIndexing::IndexInfo; quint32 getLanguageId( const QString & lang ) { QString lstr = lang.left( 3 ); if( lstr.endsWith( QChar( '-' ) ) ) lstr.chop( 1 ); switch( lstr.size() ) { case 2: return LangCoder::code2toInt( lstr.toLatin1().data() ); case 3: return LangCoder::findIdForLanguageCode3( lstr.toLatin1().data() ); } return 0; } namespace { DEF_EX_STR( exCantReadFile, "Can't read file", Dictionary::Ex ) DEF_EX_STR( exNotXdxfFile, "The file is not an XDXF file:", Dictionary::Ex ) DEF_EX( exCorruptedIndex, "The index file is corrupted", Dictionary::Ex ) DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex ) enum { Signature = 0x46584458, // XDXF on little-endian, FXDX on big-endian CurrentFormatVersion = 6 + BtreeIndexing::FormatVersion + Folding::Version }; enum ArticleFormat { Default = 0, Visual = 1, Logical = 2 }; struct IdxHeader { uint32_t signature; // First comes the signature, XDXF uint32_t formatVersion; // File format version (CurrentFormatVersion) uint32_t articleFormat; // ArticleFormat value, except that 0 = bad file uint32_t langFrom; // Source language uint32_t langTo; // Target language uint32_t articleCount; // Total number of articles uint32_t wordCount; // Total number of words uint32_t nameAddress; // Address of an utf8 name string, in chunks uint32_t nameSize; // And its size uint32_t descriptionAddress; // Address of an utf8 description string, in chunks uint32_t descriptionSize; // And its size uint32_t hasAbrv; // Non-zero means file has abrvs at abrvAddress uint32_t abrvAddress; // Address of abrv map in the chunked storage uint32_t chunksOffset; // The offset to chunks' storage uint32_t indexBtreeMaxElements; // Two fields from IndexInfo uint32_t indexRootOffset; uint32_t hasZipFile; // Non-zero means there's a zip file with resources // present uint32_t zipIndexBtreeMaxElements; // Two fields from IndexInfo of the zip // resource index. uint32_t zipIndexRootOffset; uint32_t revisionNumber; // Format revision } #ifndef _MSC_VER __attribute__((packed)) #endif ; bool indexIsOldOrBad( string const & indexFile ) { File::Class idx( indexFile, "rb" ); IdxHeader header; return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != Signature || header.formatVersion != CurrentFormatVersion || !header.articleFormat; } class XdxfDictionary: public BtreeIndexing::BtreeDictionary { Mutex idxMutex; File::Class idx; IdxHeader idxHeader; sptr< ChunkedStorage::Reader > chunks; Mutex dzMutex; dictData * dz; Mutex resourceZipMutex; IndexedZip resourceZip; string dictionaryName; map< string, string > abrv; public: XdxfDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ); ~XdxfDictionary(); virtual string getName() throw() { return dictionaryName; } virtual map< Dictionary::Property, string > getProperties() throw() { return map< Dictionary::Property, string >(); } virtual unsigned long getArticleCount() throw() { return idxHeader.articleCount; } virtual unsigned long getWordCount() throw() { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const { return idxHeader.langFrom; } inline virtual quint32 getLangTo() const { return idxHeader.langTo; } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ); virtual sptr< Dictionary::DataRequest > getResource( string const & name ) THROW_SPEC( std::exception ); virtual QString const& getDescription(); virtual QString getMainFilename(); virtual sptr< Dictionary::DataRequest > getSearchResults( QString const & searchString, int searchMode, bool matchCase, int distanceBetweenWords, int maxResults, bool ignoreWordsOrder, bool ignoreDiacritics, QThreadPool * ftsThreadPoolPtr ); virtual void getArticleText( uint32_t articleAddress, QString & headword, QString & text ); virtual void makeFTSIndex(QAtomicInt & isCancelled, bool firstIteration ); virtual void setFTSParameters( Config::FullTextSearch const & fts ) { can_FTS = fts.enabled && !fts.disabledTypes.contains( "XDXF", Qt::CaseInsensitive ) && ( fts.maxDictionarySize == 0 || getArticleCount() <= fts.maxDictionarySize ); } virtual uint32_t getFtsIndexVersion() { return 1; } protected: void loadIcon() throw(); private: // Loads the article, storing its headword and formatting article's data into an html. void loadArticle( uint32_t address, string & articleText, QString * headword = 0 ); friend class XdxfArticleRequest; friend class XdxfResourceRequest; }; XdxfDictionary::XdxfDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ): BtreeDictionary( id, dictionaryFiles ), idx( indexFile, "rb" ), idxHeader( idx.read< IdxHeader >() ) { // Read the dictionary name chunks = new ChunkedStorage::Reader( idx, idxHeader.chunksOffset ); if ( idxHeader.nameSize ) { vector< char > chunk; dictionaryName = string( chunks->getBlock( idxHeader.nameAddress, chunk ), idxHeader.nameSize ); } // Open the file DZ_ERRORS error; dz = dict_data_open( dictionaryFiles[ 0 ].c_str(), &error, 0 ); if ( !dz ) throw exDictzipError( string( dz_error_str( error ) ) + "(" + dictionaryFiles[ 0 ] + ")" ); // Read the abrv, if any if ( idxHeader.hasAbrv ) { vector< char > chunk; char * abrvBlock = chunks->getBlock( idxHeader.abrvAddress, chunk ); uint32_t total; memcpy( &total, abrvBlock, sizeof( uint32_t ) ); abrvBlock += sizeof( uint32_t ); while( total-- ) { uint32_t keySz; memcpy( &keySz, abrvBlock, sizeof( uint32_t ) ); abrvBlock += sizeof( uint32_t ); char * key = abrvBlock; abrvBlock += keySz; uint32_t valueSz; memcpy( &valueSz, abrvBlock, sizeof( uint32_t ) ); abrvBlock += sizeof( uint32_t ); abrv[ string( key, keySz ) ] = string( abrvBlock, valueSz ); abrvBlock += valueSz; } // Open a resource zip file, if there's one if ( idxHeader.hasZipFile && ( idxHeader.zipIndexBtreeMaxElements || idxHeader.zipIndexRootOffset ) ) { resourceZip.openIndex( IndexInfo( idxHeader.zipIndexBtreeMaxElements, idxHeader.zipIndexRootOffset ), idx, idxMutex ); QString zipName = QDir::fromNativeSeparators( FsEncoding::decode( getDictionaryFilenames().back().c_str() ) ); if ( zipName.endsWith( ".zip", Qt::CaseInsensitive ) ) // Sanity check resourceZip.openZipFile( zipName ); } } // Initialize the index openIndex( IndexInfo( idxHeader.indexBtreeMaxElements, idxHeader.indexRootOffset ), idx, idxMutex ); // Full-text search parameters can_FTS = true; ftsIdxName = indexFile + "_FTS"; if( !Dictionary::needToRebuildIndex( dictionaryFiles, ftsIdxName ) && !FtsHelpers::ftsIndexIsOldOrBad( ftsIdxName, this ) ) FTS_index_completed.ref(); } XdxfDictionary::~XdxfDictionary() { if ( dz ) dict_data_close( dz ); } void XdxfDictionary::loadIcon() throw() { if ( dictionaryIconLoaded ) return; QString fileName = QDir::fromNativeSeparators( FsEncoding::decode( getDictionaryFilenames()[ 0 ].c_str() ) ); QFileInfo baseInfo( fileName ); fileName = baseInfo.absoluteDir().absoluteFilePath( "icon32.png" ); QFileInfo info( fileName ); if( !info.isFile() ) { fileName = baseInfo.absoluteDir().absoluteFilePath( "icon16.png" ); info = QFileInfo( fileName ); } if ( info.isFile() ) loadIconFromFile( fileName, true ); if ( dictionaryIcon.isNull() ) { // Load failed -- use default icons dictionaryIcon = QIcon(":/icons/icon32_xdxf.png"); dictionaryNativeIcon = QIcon(":/icons/icon32_xdxf.png"); } dictionaryIconLoaded = true; } QString const& XdxfDictionary::getDescription() { if( !dictionaryDescription.isEmpty() ) return dictionaryDescription; if( idxHeader.descriptionAddress == 0 ) dictionaryDescription = "NONE"; else { try { vector< char > chunk; char * descr; { Mutex::Lock _( idxMutex ); descr = chunks->getBlock( idxHeader.descriptionAddress, chunk ); } dictionaryDescription = QString::fromUtf8( descr, idxHeader.descriptionSize ); } catch(...) { } } return dictionaryDescription; } QString XdxfDictionary::getMainFilename() { return FsEncoding::decode( getDictionaryFilenames()[ 0 ].c_str() ); } void XdxfDictionary::makeFTSIndex( QAtomicInt & isCancelled, bool firstIteration ) { if( !( Dictionary::needToRebuildIndex( getDictionaryFilenames(), ftsIdxName ) || FtsHelpers::ftsIndexIsOldOrBad( ftsIdxName, this ) ) ) FTS_index_completed.ref(); if( haveFTSIndex() ) return; if( ensureInitDone().size() ) return; if( firstIteration && getArticleCount() > FTS::MaxDictionarySizeForFastSearch ) return; gdDebug( "Xdxf: Building the full-text index for dictionary: %s\n", getName().c_str() ); try { FtsHelpers::makeFTSIndex( this, isCancelled ); FTS_index_completed.ref(); } catch( std::exception &ex ) { gdWarning( "Xdxf: Failed building full-text search index for \"%s\", reason: %s\n", getName().c_str(), ex.what() ); QFile::remove( FsEncoding::decode( ftsIdxName.c_str() ) ); } } void XdxfDictionary::getArticleText( uint32_t articleAddress, QString & headword, QString & text ) { try { string articleStr; loadArticle( articleAddress, articleStr, &headword ); wstring wstr = Utf8::decode( articleStr ); text = Html::unescape( gd::toQString( wstr ) ); } catch( std::exception &ex ) { gdWarning( "Xdxf: Failed retrieving article from \"%s\", reason: %s\n", getName().c_str(), ex.what() ); } } sptr< Dictionary::DataRequest > XdxfDictionary::getSearchResults( QString const & searchString, int searchMode, bool matchCase, int distanceBetweenWords, int maxResults, bool ignoreWordsOrder, bool ignoreDiacritics, QThreadPool * ftsThreadPoolPtr ) { return new FtsHelpers::FTSResultsRequest( *this, searchString,searchMode, matchCase, distanceBetweenWords, maxResults, ignoreWordsOrder, ignoreDiacritics, ftsThreadPoolPtr ); } /// XdxfDictionary::getArticle() class XdxfArticleRequest; class XdxfArticleRequestRunnable: public QRunnable { XdxfArticleRequest & r; QSemaphore & hasExited; public: XdxfArticleRequestRunnable( XdxfArticleRequest & r_, QSemaphore & hasExited_ ): r( r_ ), hasExited( hasExited_ ) {} ~XdxfArticleRequestRunnable() { hasExited.release(); } virtual void run(); }; class XdxfArticleRequest: public Dictionary::DataRequest { friend class XdxfArticleRequestRunnable; wstring word; vector< wstring > alts; XdxfDictionary & dict; bool ignoreDiacritics; QAtomicInt isCancelled; QSemaphore hasExited; public: XdxfArticleRequest( wstring const & word_, vector< wstring > const & alts_, XdxfDictionary & dict_, bool ignoreDiacritics_ ): word( word_ ), alts( alts_ ), dict( dict_ ), ignoreDiacritics( ignoreDiacritics_ ) { QThreadPool::globalInstance()->start( new XdxfArticleRequestRunnable( *this, hasExited ) ); } void run(); // Run from another thread by XdxfArticleRequestRunnable virtual void cancel() { isCancelled.ref(); } ~XdxfArticleRequest() { isCancelled.ref(); hasExited.acquire(); } }; void XdxfArticleRequestRunnable::run() { r.run(); } void XdxfArticleRequest::run() { if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } vector< WordArticleLink > chain = dict.findArticles( word, ignoreDiacritics ); for( unsigned x = 0; x < alts.size(); ++x ) { /// Make an additional query for each alt vector< WordArticleLink > altChain = dict.findArticles( alts[ x ], ignoreDiacritics ); chain.insert( chain.end(), altChain.begin(), altChain.end() ); } multimap< wstring, pair< string, string > > mainArticles, alternateArticles; set< uint32_t > articlesIncluded; // Some synonims make it that the articles // appear several times. We combat this // by only allowing them to appear once. wstring wordCaseFolded = Folding::applySimpleCaseOnly( word ); if( ignoreDiacritics ) wordCaseFolded = Folding::applyDiacriticsOnly( wordCaseFolded ); for( unsigned x = 0; x < chain.size(); ++x ) { if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } if ( articlesIncluded.find( chain[ x ].articleOffset ) != articlesIncluded.end() ) continue; // We already have this article in the body. // Now grab that article string headword, articleText; headword = chain[ x ].word; try { dict.loadArticle( chain[ x ].articleOffset, articleText ); // Ok. Now, does it go to main articles, or to alternate ones? We list // main ones first, and alternates after. // We do the case-folded comparison here. wstring headwordStripped = Folding::applySimpleCaseOnly( Utf8::decode( headword ) ); if( ignoreDiacritics ) headwordStripped = Folding::applyDiacriticsOnly( headwordStripped ); multimap< wstring, pair< string, string > > & mapToUse = ( wordCaseFolded == headwordStripped ) ? mainArticles : alternateArticles; mapToUse.insert( pair< wstring, pair< string, string > >( Folding::applySimpleCaseOnly( Utf8::decode( headword ) ), pair< string, string >( headword, articleText ) ) ); articlesIncluded.insert( chain[ x ].articleOffset ); } catch( std::exception &ex ) { gdWarning( "XDXF: Failed loading article from \"%s\", reason: %s\n", dict.getName().c_str(), ex.what() ); } } if ( mainArticles.empty() && alternateArticles.empty() ) { // No such word finish(); return; } string result; multimap< wstring, pair< string, string > >::const_iterator i; string cleaner = """""""""""" """""""""""" "" ""; for( i = mainArticles.begin(); i != mainArticles.end(); ++i ) { // result += "

"; // result += i->second.first; // result += "

"; result += i->second.second; result += cleaner; } for( i = alternateArticles.begin(); i != alternateArticles.end(); ++i ) { // result += "

"; // result += i->second.first; // result += "

"; result += i->second.second; result += cleaner; } Mutex::Lock _( dataMutex ); data.resize( result.size() ); memcpy( &data.front(), result.data(), result.size() ); hasAnyData = true; finish(); } sptr< Dictionary::DataRequest > XdxfDictionary::getArticle( wstring const & word, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ) { return new XdxfArticleRequest( word, alts, *this, ignoreDiacritics ); } void XdxfDictionary::loadArticle( uint32_t address, string & articleText, QString * headword ) { // Read the properties vector< char > chunk; char * propertiesData; { Mutex::Lock _( idxMutex ); propertiesData = chunks->getBlock( address, chunk ); } if ( &chunk.front() + chunk.size() - propertiesData < 9 ) { articleText = string( "
Index seems corrupted
" ); return; } unsigned char fType = (unsigned char) *propertiesData; uint32_t articleOffset, articleSize; memcpy( &articleOffset, propertiesData + 1, sizeof( uint32_t ) ); memcpy( &articleSize, propertiesData + 5, sizeof( uint32_t ) ); // Load the article char * articleBody; { Mutex::Lock _( dzMutex ); // Note that the function always zero-pads the result. articleBody = dict_data_read_( dz, articleOffset, articleSize, 0, 0 ); } if ( !articleBody ) { // throw exCantReadFile( getDictionaryFilenames()[ 0 ] ); articleText = string( "
DICTZIP error: " ) + dict_error_str( dz ) + "
"; return; } articleText = Xdxf2Html::convert( string( articleBody ), Xdxf2Html::XDXF, idxHeader.hasAbrv ? &abrv : NULL, this, &resourceZip, fType == Logical, idxHeader.revisionNumber, headword ); free( articleBody ); } class GzippedFile: public QIODevice { gzFile gz; public: GzippedFile( char const * fileName ) THROW_SPEC( exCantReadFile ); ~GzippedFile(); // size_t gzTell(); char * readDataArray( unsigned long startPos, unsigned long size ); protected: dictData *dz; virtual bool isSequential () const { return false; } // Which is a lie, but else pos() won't work bool waitForReadyRead ( int ) { return !gzeof( gz ); } qint64 bytesAvailable() const { return ( gzeof( gz ) ? 0 : 1 ) + QIODevice::bytesAvailable(); } virtual qint64 readData( char * data, qint64 maxSize ); virtual bool atEnd() const; virtual qint64 writeData ( const char * /*data*/, qint64 /*maxSize*/ ) { return -1; } }; GzippedFile::GzippedFile( char const * fileName ) THROW_SPEC( exCantReadFile ) { gz = gd_gzopen( fileName ); if ( !gz ) throw exCantReadFile( fileName ); DZ_ERRORS error; dz = dict_data_open( fileName, &error, 0 ); } GzippedFile::~GzippedFile() { gzclose( gz ); if( dz ) dict_data_close( dz ); } bool GzippedFile::atEnd() const { return gzeof( gz ); } /* size_t GzippedFile::gzTell() { return gztell( gz ); } */ qint64 GzippedFile::readData( char * data, qint64 maxSize ) { if ( maxSize > 1 ) maxSize = 1; // The returning value translates directly to QIODevice semantics int n = gzread( gz, data, maxSize ); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) // With QT 5.x QXmlStreamReader ask one byte instead of one UTF-8 char. // We read and return all bytes for char. if( n == 1 ) { char ch = *data; int addBytes = 0; if( ch & 0x80 ) { if( ( ch & 0xF8 ) == 0xF0 ) addBytes = 3; else if( ( ch & 0xF0 ) == 0xE0 ) addBytes = 2; else if( ( ch & 0xE0 ) == 0xC0 ) addBytes = 1; } if( addBytes ) n += gzread( gz, data + 1, addBytes ); } #endif return n; } char * GzippedFile::readDataArray( unsigned long startPos, unsigned long size ) { if( dz == NULL ) return NULL; return dict_data_read_( dz, startPos, size, 0, 0 ); } QString readXhtmlData( QXmlStreamReader & stream ) { QString result; while( !stream.atEnd() ) { stream.readNext(); if ( stream.isStartElement() ) { QString name = stream.name().toString(); result += "<" + Qt4x5::escape( name ) + " "; QXmlStreamAttributes attrs = stream.attributes(); for( int x = 0; x < attrs.size(); ++x ) { result += Qt4x5::escape( attrs[ x ].name().toString() ); result += "=\"" + Qt4x5::escape( attrs[ x ].value().toString() ) + "\""; } result += ">"; result += readXhtmlData( stream ); result += ""; } else if ( stream.isCharacters() || stream.isWhitespace() || stream.isCDATA() ) { result += stream.text(); } else if ( stream.isEndElement() ) break; } return result; } namespace { /// Deal with Qt 4.5 incompatibility QString readElementText( QXmlStreamReader & stream ) { #if QT_VERSION >= 0x040600 return stream.readElementText( QXmlStreamReader::SkipChildElements ); #else return stream.readElementText(); #endif } } void addAllKeyTags( QXmlStreamReader & stream, list< QString > & words ) { // todo implement support for tag , that overrides the article sorting order if ( stream.name() == "k" ) { words.push_back( readElementText( stream ) ); return; } while( !stream.atEnd() ) { stream.readNext(); if ( stream.isStartElement() ) addAllKeyTags( stream, words ); else if ( stream.isEndElement() ) return; } } void checkArticlePosition( GzippedFile & gzFile, uint32_t *pOffset, uint32_t *pSize ) { char * data = gzFile.readDataArray( *pOffset, *pSize ); if( data == NULL ) return; QString s = QString::fromUtf8( data ); free( data ); int n = s.lastIndexOf( " 0 ) *pSize -= s.size() - n; if( s.at( 0 ) == '>') { *pOffset += 1; *pSize -= 1; } } void indexArticle( GzippedFile & gzFile, QXmlStreamReader & stream, IndexedWords & indexedWords, ChunkedStorage::Writer & chunks, unsigned & articleCount, unsigned & wordCount, ArticleFormat defaultFormat ) { ArticleFormat format( Default ); QStringRef formatValue = stream.attributes().value( "f" ); if ( formatValue == "v" ) format = Visual; else if ( formatValue == "l" ) format = Logical; if( format == Default ) format = defaultFormat; size_t articleOffset = gzFile.pos() - 1; // stream.characterOffset() is loony // uint32_t lineNumber = stream.lineNumber(); // uint32_t columnNumber = stream.columnNumber(); list< QString > words; while( !stream.atEnd() ) { stream.readNext(); // Find any tags and index them if ( stream.isEndElement() ) { // End of the tag if ( words.empty() ) { // Nothing to index, this article didn't have any tags gdWarning( "No tags found in an article at offset 0x%x, article skipped.\n", (unsigned) articleOffset ); } else { // Add an entry uint32_t offset = chunks.startNewBlock(); uint32_t offs = articleOffset; uint32_t size = gzFile.pos() - 1 - articleOffset; checkArticlePosition( gzFile, &offs, &size ); unsigned char f = format; chunks.addToBlock( &f, 1 ); chunks.addToBlock( &offs, sizeof( offs ) ); chunks.addToBlock( &size, sizeof( size ) ); // Add also first header - it's needed for full-text search chunks.addToBlock( words.begin()->toUtf8().data(), words.begin()->toUtf8().length() + 1 ); // DPRINTF( "%x: %s\n", articleOffset, words.begin()->toUtf8().data() ); // Add words to index for( list< QString >::const_iterator i = words.begin(); i != words.end(); ++i ) indexedWords.addWord( gd::toWString( *i ), offset ); ++articleCount; wordCount += words.size(); } return; } else if ( stream.isStartElement() ) { addAllKeyTags( stream, words ); } } } //// XdxfDictionary::getResource() class XdxfResourceRequest; class XdxfResourceRequestRunnable: public QRunnable { XdxfResourceRequest & r; QSemaphore & hasExited; public: XdxfResourceRequestRunnable( XdxfResourceRequest & r_, QSemaphore & hasExited_ ): r( r_ ), hasExited( hasExited_ ) {} ~XdxfResourceRequestRunnable() { hasExited.release(); } virtual void run(); }; class XdxfResourceRequest: public Dictionary::DataRequest { friend class XdxfResourceRequestRunnable; XdxfDictionary & dict; string resourceName; QAtomicInt isCancelled; QSemaphore hasExited; public: XdxfResourceRequest( XdxfDictionary & dict_, string const & resourceName_ ): dict( dict_ ), resourceName( resourceName_ ) { QThreadPool::globalInstance()->start( new XdxfResourceRequestRunnable( *this, hasExited ) ); } void run(); // Run from another thread by XdxfResourceRequestRunnable virtual void cancel() { isCancelled.ref(); } ~XdxfResourceRequest() { isCancelled.ref(); hasExited.acquire(); } }; void XdxfResourceRequestRunnable::run() { r.run(); } void XdxfResourceRequest::run() { // Some runnables linger enough that they are cancelled before they start if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } if ( dict.ensureInitDone().size() ) { setErrorString( QString::fromUtf8( dict.ensureInitDone().c_str() ) ); finish(); return; } string n = FsEncoding::dirname( dict.getDictionaryFilenames()[ 0 ] ) + FsEncoding::separator() + FsEncoding::encode( resourceName ); GD_DPRINTF( "n is %s\n", n.c_str() ); try { try { Mutex::Lock _( dataMutex ); File::loadFromFile( n, data ); } catch( File::exCantOpen & ) { n = dict.getDictionaryFilenames()[ 0 ] + ".files" + FsEncoding::separator() + FsEncoding::encode( resourceName ); try { Mutex::Lock _( dataMutex ); File::loadFromFile( n, data ); } catch( File::exCantOpen & ) { // Try reading from zip file if ( dict.resourceZip.isOpen() ) { Mutex::Lock _( dict.resourceZipMutex ); Mutex::Lock __( dataMutex ); if ( !dict.resourceZip.loadFile( Utf8::decode( resourceName ), data ) ) throw; // Make it fail since we couldn't read the archive } else throw; } } if ( Filetype::isNameOfTiff( resourceName ) ) { // Convert it dataMutex.lock(); QImage img = QImage::fromData( (unsigned char *) &data.front(), data.size() ); #ifdef MAKE_EXTRA_TIFF_HANDLER if( img.isNull() ) GdTiff::tiffToQImage( &data.front(), data.size(), img ); #endif dataMutex.unlock(); if ( !img.isNull() ) { // Managed to load -- now store it back as BMP QByteArray ba; QBuffer buffer( &ba ); buffer.open( QIODevice::WriteOnly ); img.save( &buffer, "BMP" ); Mutex::Lock _( dataMutex ); data.resize( buffer.size() ); memcpy( &data.front(), buffer.data(), data.size() ); } } Mutex::Lock _( dataMutex ); hasAnyData = true; } catch( std::exception &ex ) { gdCWarning( dictionaryResourceLc, "XDXF: Failed loading resource \"%s\" for \"%s\", reason: %s\n", resourceName.c_str(), dict.getName().c_str(), ex.what() ); // Resource not loaded -- we don't set the hasAnyData flag then } finish(); } sptr< Dictionary::DataRequest > XdxfDictionary::getResource( string const & name ) THROW_SPEC( std::exception ) { return new XdxfResourceRequest( *this, name ); } } // anonymous namespace - this section of file is devoted to rebuilding of dictionary articles index vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing & initializing ) THROW_SPEC( std::exception ) { vector< sptr< Dictionary::Class > > dictionaries; for( vector< string >::const_iterator i = fileNames.begin(); i != fileNames.end(); ++i ) { // Only allow .xdxf and .xdxf.dz suffixes if ( ( i->size() < 5 || strcasecmp( i->c_str() + ( i->size() - 5 ), ".xdxf" ) != 0 ) && ( i->size() < 8 || strcasecmp( i->c_str() + ( i->size() - 8 ), ".xdxf.dz" ) != 0 ) ) continue; try { vector< string > dictFiles( 1, *i ); string baseName = ( (*i)[ i->size() - 5 ] == '.' ) ? string( *i, 0, i->size() - 5 ) : string( *i, 0, i->size() - 8 ); // See if there's a zip file with resources present. If so, include it. string zipFileName; if ( File::tryPossibleZipName( baseName + ".xdxf.files.zip", zipFileName ) || File::tryPossibleZipName( baseName + ".xdxf.dz.files.zip", zipFileName ) || File::tryPossibleZipName( baseName + ".XDXF.FILES.ZIP", zipFileName ) || File::tryPossibleZipName( baseName + ".XDXF.DZ.FILES.ZIP", zipFileName ) ) dictFiles.push_back( zipFileName ); string dictId = Dictionary::makeDictionaryId( dictFiles ); string indexFile = indicesDir + dictId; if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { // Building the index gdDebug( "Xdxf: Building the index for dictionary: %s\n", i->c_str() ); //initializing.indexingDictionary( nameFromFileName( dictFiles[ 0 ] ) ); File::Class idx( indexFile, "wb" ); IdxHeader idxHeader; map< string, string > abrv; memset( &idxHeader, 0, sizeof( idxHeader ) ); // We write a dummy header first. At the end of the process the header // will be rewritten with the right values. idx.write( idxHeader ); IndexedWords indexedWords; GzippedFile gzFile( dictFiles[ 0 ].c_str() ); if ( !gzFile.open( QIODevice::ReadOnly ) ) throw exCantReadFile( dictFiles[ 0 ] ); QXmlStreamReader stream( &gzFile ); QString dictionaryName, dictionaryDescription; ChunkedStorage::Writer chunks( idx ); // Wait for the first element, which must be xdxf bool hadXdxf = false; while( !stream.atEnd() ) { stream.readNext(); if ( stream.isStartElement() ) { if ( stream.name() != "xdxf" ) throw exNotXdxfFile( dictFiles[ 0 ] ); else { // Read the xdxf string str = stream.attributes().value( "lang_from" ).toString().toLatin1().data(); if( !str.empty() ) idxHeader.langFrom = getLanguageId( str.c_str() ); str = stream.attributes().value( "lang_to" ).toString().toLatin1().data(); if( !str.empty() ) idxHeader.langTo = getLanguageId( str.c_str() ); QRegExp regNum( "\\d+" ); regNum.indexIn( stream.attributes().value( "revision" ).toString() ); idxHeader.revisionNumber = regNum.cap().toUInt(); bool isLogical = ( stream.attributes().value( "format" ) == "logical" || idxHeader.revisionNumber >= 34 ); idxHeader.articleFormat = isLogical ? Logical : Visual; unsigned articleCount = 0, wordCount = 0; while( !stream.atEnd() ) { stream.readNext(); if ( stream.isStartElement() ) { // todo implement using short for denoting the dictionary in settings or dict list toolbar if ( stream.name() == "full_name" || stream.name() == "full_title" ) { // That's our name QString name = stream.readElementText(); if ( dictionaryName.isEmpty() ) { dictionaryName = name; initializing.indexingDictionary( dictionaryName.toUtf8().data() ); idxHeader.nameAddress = chunks.startNewBlock(); QByteArray n = dictionaryName.toUtf8(); idxHeader.nameSize = n.size(); chunks.addToBlock( n.data(), n.size() ); } else { GD_DPRINTF( "Warning: duplicate full_name in %s\n", dictFiles[ 0 ].c_str() ); } } else if ( stream.name() == "description" ) { // todo implement adding other information to the description like <publisher>, <authors>, <file_ver>, <creation_date>, <last_edited_date>, <dict_edition>, <publishing_date>, <dict_src_url> QString desc = readXhtmlData( stream ); if( isLogical ) { desc = desc.simplified(); desc.replace( QRegExp( "<br\\s*>\\s*</br>" ), QChar( '\n' ) ); } if ( dictionaryDescription.isEmpty() ) { dictionaryDescription = desc; idxHeader.descriptionAddress = chunks.startNewBlock(); QByteArray n = dictionaryDescription.toUtf8(); idxHeader.descriptionSize = n.size(); chunks.addToBlock( n.data(), n.size() ); } else { GD_DPRINTF( "Warning: duplicate description in %s\n", dictFiles[ 0 ].c_str() ); } } else if( stream.name() == "languages" ) { while( !( stream.isEndElement() && stream.name() == "languages" ) && !stream.atEnd() ) { if( !stream.readNext() ) break; if ( stream.isStartElement() ) { if( stream.name() == "from" ) { if( idxHeader.langFrom == 0 ) { QString lang = stream.attributes().value( "xml:lang" ).toString(); idxHeader.langFrom = getLanguageId( lang ); } } else if( stream.name() == "to" ) { if( idxHeader.langTo == 0 ) { QString lang = stream.attributes().value( "xml:lang" ).toString(); idxHeader.langTo = getLanguageId( lang ); } } } else if ( stream.isEndElement() && stream.name() == "languages" ) break; } } else if ( stream.name() == "abbreviations" ) { QString s; string value; list < wstring > keys; while( !( stream.isEndElement() && stream.name() == "abbreviations" ) && !stream.atEnd() ) { if( !stream.readNextStartElement() ) break; // abbreviations tag set switch at format revision = 30 if( idxHeader.revisionNumber >= 30 ) { while ( !( stream.isEndElement() && stream.name() == "abbr_def" ) || !stream.atEnd() ) { if ( stream.isStartElement() && stream.name() == "abbr_k" ) { s = readElementText( stream ); keys.push_back( gd::toWString( s ) ); } else if ( stream.isStartElement() && stream.name() == "abbr_v" ) { s = readElementText( stream ); value = Utf8::encode( Folding::trimWhitespace( gd::toWString( s ) ) ); for( list< wstring >::iterator i = keys.begin(); i != keys.end(); ++i ) { abrv[ Utf8::encode( Folding::trimWhitespace( *i ) ) ] = value; } keys.clear(); } else if ( stream.isEndElement() && stream.name() == "abbreviations" ) break; stream.readNext(); } } else { while ( !( stream.isEndElement() && stream.name() == "abr_def" ) || !stream.atEnd() ) { if ( stream.isStartElement() && stream.name() == "k" ) { s = readElementText( stream ); keys.push_back( gd::toWString( s ) ); } else if ( stream.isStartElement() && stream.name() == "v" ) { s = readElementText( stream ); value = Utf8::encode( Folding::trimWhitespace( gd::toWString( s ) ) ); for( list< wstring >::iterator i = keys.begin(); i != keys.end(); ++i ) { abrv[ Utf8::encode( Folding::trimWhitespace( *i ) ) ] = value; } keys.clear(); } else if ( stream.isEndElement() && stream.name() == "abbreviations" ) break; stream.readNext(); } } } } else if ( stream.name() == "ar" ) { indexArticle( gzFile, stream, indexedWords, chunks, articleCount, wordCount, isLogical ? Logical : Visual ); } } } // Write abbreviations if presented if( !abrv.empty() ) { idxHeader.hasAbrv = 1; idxHeader.abrvAddress = chunks.startNewBlock(); uint32_t sz = abrv.size(); chunks.addToBlock( &sz, sizeof( uint32_t ) ); for( map< string, string >::const_iterator i = abrv.begin(); i != abrv.end(); ++i ) { sz = i->first.size(); chunks.addToBlock( &sz, sizeof( uint32_t ) ); chunks.addToBlock( i->first.data(), sz ); sz = i->second.size(); chunks.addToBlock( &sz, sizeof( uint32_t ) ); chunks.addToBlock( i->second.data(), sz ); } } // Finish with the chunks idxHeader.chunksOffset = chunks.finish(); // Build index IndexInfo idxInfo = BtreeIndexing::buildIndex( indexedWords, idx ); idxHeader.indexBtreeMaxElements = idxInfo.btreeMaxElements; idxHeader.indexRootOffset = idxInfo.rootOffset; indexedWords.clear(); // Release memory -- no need for this data // If there was a zip file, index it too if ( zipFileName.size() ) { GD_DPRINTF( "Indexing zip file\n" ); idxHeader.hasZipFile = 1; IndexedWords zipFileNames; IndexedZip zipFile; if( zipFile.openZipFile( QDir::fromNativeSeparators( FsEncoding::decode( zipFileName.c_str() ) ) ) ) zipFile.indexFile( zipFileNames ); if( !zipFileNames.empty() ) { // Build the resulting zip file index IndexInfo idxInfo = BtreeIndexing::buildIndex( zipFileNames, idx ); idxHeader.zipIndexBtreeMaxElements = idxInfo.btreeMaxElements; idxHeader.zipIndexRootOffset = idxInfo.rootOffset; } else { // Bad zip file -- no index (though the mark that we have one // remains) idxHeader.zipIndexBtreeMaxElements = 0; idxHeader.zipIndexRootOffset = 0; } } else idxHeader.hasZipFile = 0; // That concludes it. Update the header. idxHeader.signature = Signature; idxHeader.formatVersion = CurrentFormatVersion; idxHeader.articleCount = articleCount; idxHeader.wordCount = wordCount; idx.rewind(); idx.write( &idxHeader, sizeof( idxHeader ) ); hadXdxf = true; } break; } } if ( !hadXdxf ) throw exNotXdxfFile( dictFiles[ 0 ] ); if ( stream.hasError() ) { gdWarning( "%s had a parse error %s at line %lu, and therefore was indexed only up to the point of error.", dictFiles[ 0 ].c_str(), stream.errorString().toUtf8().data(), (unsigned long) stream.lineNumber() ); } } dictionaries.push_back( new XdxfDictionary( dictId, indexFile, dictFiles ) ); } catch( std::exception & e ) { gdWarning( "Xdxf dictionary initializing failed: %s, error: %s\n", i->c_str(), e.what() ); } } return dictionaries; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/xdxf.hh����������������������������������������������������������������������������0000664�0000000�0000000�00000001260�14435233205�0015123�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2008-2009 Konstantin Isakov <ikm@goldendict.org> * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __XDXF_HH_INCLUDED__ #define __XDXF_HH_INCLUDED__ #include "dictionary.hh" /// Support for the XDXF (.xdxf(.dz)) files. namespace Xdxf { using std::vector; using std::string; quint32 getLanguageId( const QString & lang ); vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing & ) THROW_SPEC( std::exception ); } #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/xdxf2html.cc�����������������������������������������������������������������������0000664�0000000�0000000�00000054612�14435233205�0016071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org> * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "xdxf2html.hh" #include <QtXml> #include "gddebug.hh" #include "utf8.hh" #include "wstring_qt.hh" #include "folding.hh" #include "fsencoding.hh" #include "audiolink.hh" #include "file.hh" #include "filetype.hh" #include "htmlescape.hh" #include "qt4x5.hh" #include <QDebug> #include "xdxf.hh" #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) #include <QRegularExpression> #endif namespace Xdxf2Html { static void fixLink( QDomElement & el, string const & dictId, const char *attrName ) { QUrl url; url.setScheme( "bres" ); url.setHost( QString::fromStdString(dictId) ); url.setPath( Qt4x5::Url::ensureLeadingSlash( el.attribute(attrName) ) ); el.setAttribute( attrName, url.toEncoded().data() ); } // converting a number into roman representation string convertToRoman( int input, int lower_case ) { string romanvalue = ""; if( input >= 4000 ) { int x = ( input - input % 4000 ) / 1000; romanvalue = "(" + convertToRoman( x, lower_case ) + ")" ; input %= 4000; } const string roman[26] = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I", "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"}; const int decimal[13] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; for( int i = 0; i < 13; i++ ) { while( input >= decimal[ i ] ) { input -= decimal[ i ]; if ( lower_case == 1 ) romanvalue += roman[ i + 13 ]; else romanvalue += roman[ i ]; } } return romanvalue; } QDomElement fakeElement( QDomDocument & dom ) { // Create element which will be removed after // We will insert it to empty elements to avoid output ones in <xxx/> form return dom.createElement( "b" ); } string convert( string const & in, DICT_TYPE type, map < string, string > const * pAbrv, Dictionary::Class *dictPtr, IndexedZip * resourceZip, bool isLogicalFormat, unsigned revisionNumber, QString * headword ) { // DPRINTF( "Source>>>>>>>>>>: %s\n\n\n", in.c_str() ); // Convert spaces after each end of line to  s, and then each end of // line to a <br> string inConverted; inConverted.reserve( in.size() ); bool afterEol = false; for( string::const_iterator i = in.begin(), j = in.end(); i != j; ++i ) { switch( *i ) { case '\n': afterEol = true; if( !isLogicalFormat ) inConverted.append( "<br/>" ); break; case '\r': break; case ' ': if ( afterEol ) { if( !isLogicalFormat ) inConverted.append( " " ); break; } // Fall-through default: inConverted.push_back( *i ); afterEol = false; } } // Strip "<nu />" tags - QDomDocument don't handle it correctly string::size_type n; while( ( n = inConverted.find( "<nu />" ) ) != string::npos ) inConverted.erase( n, 6 ); // We build a dom representation of the given xml, then do some transforms QDomDocument dd; QString errorStr; int errorLine, errorColumn; string in_data; if( type == XDXF ) { in_data = "<div class=\"xdxf\""; if( dictPtr->isToLanguageRTL() ) in_data += " dir=\"rtl\""; in_data += ">"; } else in_data = "<div class=\"sdct_x\">"; in_data += inConverted + "</div>"; if( !dd.setContent( QByteArray( in_data.c_str() ), false, &errorStr, &errorLine, &errorColumn ) ) { qWarning( "Xdxf2html error, xml parse failed: %s at %d,%d\n", errorStr.toLocal8Bit().constData(), errorLine, errorColumn ); gdWarning( "The input was: %s\n", in.c_str() ); return in; } QDomNodeList nodes = dd.elementsByTagName( "ex" ); // Example while( nodes.size() ) { QString author, source; QDomElement el = nodes.at( 0 ).toElement(); author = el.attribute( "author", QString() ); source = el.attribute( "source", QString() ); if( el.hasChildNodes() ) { QDomNodeList lst = el.childNodes(); for( int i = 0; i < lst.count(); ++i ) { QDomElement el2 = el.childNodes().at( i ).toElement(); if( el2.tagName().compare( "ex_orig", Qt::CaseInsensitive ) == 0 ) { el2.setTagName( "span" ); el2.setAttribute( "class", "xdxf_ex_orig" ); } else if( el2.tagName().compare( "ex_tran", Qt::CaseInsensitive ) == 0 ) { el2.setTagName( "span" ); el2.setAttribute( "class", "xdxf_ex_tran" ); } } } if( ( !author.isEmpty() || !source.isEmpty() ) && ( !el.text().isEmpty() || !el.childNodes().isEmpty() ) ) { QDomElement el2 = dd.createElement( "span" ); el2.setAttribute( "class", "xdxf_ex_source" ); QString text = author; if( !source.isEmpty() ) { if( !text.isEmpty() ) text += ", "; text += source; } QDomText txtNode = dd.createTextNode( text ); el2.appendChild( txtNode ); el.appendChild( el2 ); } if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if( isLogicalFormat ) el.setAttribute( "class", "xdxf_ex" ); else el.setAttribute( "class", "xdxf_ex_old" ); } nodes = dd.elementsByTagName( "mrkd" ); // marked out words in translations/examples of usage while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); el.setAttribute( "class", "xdxf_ex_markd" ); } nodes = dd.elementsByTagName( "k" ); // Key if( headword ) headword->clear(); while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); if( type == STARDICT ) { el.setTagName( "span" ); el.setAttribute( "class", "xdxf_k" ); } else { if( headword && headword->isEmpty() ) *headword = el.text(); el.setTagName( "div" ); el.setAttribute( "class", "xdxf_headwords" ); bool isLanguageRtl = dictPtr->isFromLanguageRTL(); if( el.hasAttribute( "xml:lang" ) ) { // Change xml-attribute "xml:lang" to html-attribute "lang" QString lang = el.attribute( "xml:lang" ); el.removeAttribute( "xml:lang" ); el.setAttribute( "lang", lang ); quint32 langID = Xdxf::getLanguageId( lang ); if( langID ) isLanguageRtl = LangCoder::isLanguageRTL( langID ); } if( isLanguageRtl != dictPtr->isToLanguageRTL() ) el.setAttribute( "dir", isLanguageRtl ? "rtl" : "ltr" ); } } // processing of nested <def>s if( isLogicalFormat ) // in articles with visual format <def> tags do not effect the formatting. { nodes = dd.elementsByTagName( "def" ); // this is a logical type of XDXF, so we need to render proper numbering // we will do it this way: // 1. we compute the maximum nesting depth of the article int maxNestingDepth = 1; // maximum nesting depth of the article for( int i = 0; i < nodes.size(); i++ ) { QDomElement el = nodes.at( i ).toElement(); QDomElement nestingNode = el; int nestingCount = 0; while ( nestingNode.parentNode().toElement().tagName() == "def" ) { nestingCount++; nestingNode = nestingNode.parentNode().toElement(); } if ( nestingCount > maxNestingDepth ) maxNestingDepth = nestingCount; } // 2. in this loop we go layer-by-layer through all <def> and insert proper numbers according to its structure for( int j = maxNestingDepth; j > 0; j-- ) // j symbolizes special depth to be processed at this iteration { int siblingCount = 0; // this that counts the number of among all siblings of this depth QString numberText = ""; // the number to be inserted into the beginning of <def> (I,II,IV,1,2,3,a),b),c)...) for( int i = 0; i < nodes.size(); i++ ) { QDomElement el = nodes.at( i ).toElement(); QDomElement nestingNode = el; // computing the depth @nestingDepth of a current node @el int nestingDepth = 0; while( nestingNode.parentNode().toElement().tagName() == "def" ) { nestingDepth++; nestingNode=nestingNode.parentNode().toElement(); } // we process nodes on of current depth @j // we do this in order not to break the numbering at this depth level if (nestingDepth == j) { siblingCount++; if( maxNestingDepth == 1 ) { numberText = numberText.setNum( siblingCount ) + ". "; } else if( maxNestingDepth == 2 ) { if( nestingDepth == 1 ) numberText = numberText.setNum( siblingCount ) + ". "; if( nestingDepth == 2 ) numberText = numberText.setNum( siblingCount ) + ") "; } else { if( nestingDepth == 1 ) numberText = QString::fromStdString( convertToRoman(siblingCount,0) + ". " ); if( nestingDepth == 2 ) numberText = numberText.setNum( siblingCount ) + ". "; if( nestingDepth == 3 ) numberText = numberText.setNum( siblingCount ) + ") "; if( nestingDepth == 4 ) numberText = QString::fromStdString( convertToRoman(siblingCount,1) + ") " ); } QDomElement numberNode = dd.createElement( "span" ); numberNode.setAttribute( "class", "xdxf_num" ); QDomText text_num = dd.createTextNode( numberText ); numberNode.appendChild( text_num ); el.insertBefore( numberNode, el.firstChild() ); if ( el.hasAttribute( "cmt" ) ) { QDomElement cmtNode = dd.createElement( "span" ); cmtNode.setAttribute( "class", "xdxf_co" ); QDomText text_num = dd.createTextNode( el.attribute( "cmt" ) ); cmtNode.appendChild( text_num ); el.insertAfter( cmtNode, el.firstChild() ); } } else if( nestingDepth < j ) // if it goes one level up @siblingCount needs to be reset siblingCount = 0; } } // we finally change all <def> tags into 'xdxf_def' <span>s while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); el.setTagName( "span" ); el.setAttribute( "class", "xdxf_def" ); bool isLanguageRtl = dictPtr->isToLanguageRTL(); if( el.hasAttribute( "xml:lang" ) ) { // Change xml-attribute "xml:lang" to html-attribute "lang" QString lang = el.attribute( "xml:lang" ); el.removeAttribute( "xml:lang" ); el.setAttribute( "lang", lang ); quint32 langID = Xdxf::getLanguageId( lang ); if( langID ) isLanguageRtl = LangCoder::isLanguageRTL( langID ); } if( isLanguageRtl != dictPtr->isToLanguageRTL() ) el.setAttribute( "dir", isLanguageRtl ? "rtl" : "ltr" ); } } nodes = dd.elementsByTagName( "opt" ); // Optional headword part while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); el.setAttribute( "class", "xdxf_opt" ); } nodes = dd.elementsByTagName( "kref" ); // Reference to another word while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "a" ); el.setAttribute( "href", QString( "bword:" ) + el.text() ); el.setAttribute( "class", "xdxf_kref" ); if ( el.hasAttribute( "idref" ) ) { // todo implement support for referencing only specific parts of the article el.setAttribute( "href", QString( "bword:" ) + el.text() + "#" + el.attribute( "idref" )); } if ( el.hasAttribute( "kcmt" ) ) { QDomText kcmtText = dd.createTextNode( " " + el.attribute( "kcmt" ) ); el.parentNode().insertAfter( kcmtText, el ); } } nodes = dd.elementsByTagName( "iref" ); // Reference to internet site while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); QString ref = el.attribute( "href" ); if( ref.isEmpty() ) ref = el.text(); el.setAttribute( "href", ref ); el.setTagName( "a" ); } // Abbreviations if( revisionNumber < 29 ) nodes = dd.elementsByTagName( "abr" ); else nodes = dd.elementsByTagName( "abbr" ); while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); el.setAttribute( "class", "xdxf_abbr" ); if( type == XDXF && pAbrv != NULL ) { string val = Utf8::encode( Folding::trimWhitespace( gd::toWString( el.text() ) ) ); // If we have such a key, display a title map< string, string >::const_iterator i = pAbrv->find( val ); if ( i != pAbrv->end() ) { string title; if ( Utf8::decode( i->second ).size() < 70 ) { // Replace all spaces with non-breakable ones, since that's how Lingvo shows tooltips title.reserve( i->second.size() ); for( char const * c = i->second.c_str(); *c; ++c ) { if ( *c == ' ' || *c == '\t' ) { // u00A0 in utf8 title.push_back( 0xC2 ); title.push_back( 0xA0 ); } else if( *c == '-' ) // Change minus to non-breaking hyphen (uE28091 in utf8) { title.push_back( 0xE2 ); title.push_back( 0x80 ); title.push_back( 0x91 ); } else title.push_back( *c ); } } else title = i->second; el.setAttribute( "title", gd::toQString( Utf8::decode( title ) ) ); } } } nodes = dd.elementsByTagName( "dtrn" ); // Direct translation while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); el.setAttribute( "class", "xdxf_dtrn" ); } nodes = dd.elementsByTagName( "c" ); // Color while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if ( el.hasAttribute( "c" ) ) { el.setAttribute( "style", "color:" + el.attribute( "c" ) ); el.removeAttribute( "c" ); } else el.setAttribute( "style", "color:blue" ); } nodes = dd.elementsByTagName( "co" ); // Editorial comment while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if( isLogicalFormat ) el.setAttribute( "class", "xdxf_co" ); else el.setAttribute( "class", "xdxf_co_old" ); } /* grammar information */ nodes = dd.elementsByTagName( "gr" ); // proper grammar tag while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if( isLogicalFormat ) el.setAttribute( "class", "xdxf_gr" ); else el.setAttribute( "class", "xdxf_gr_old" ); } nodes = dd.elementsByTagName( "pos" ); // deprecated grammar tag while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if( isLogicalFormat ) el.setAttribute( "class", "xdxf_gr" ); else el.setAttribute( "class", "xdxf_gr_old" ); } nodes = dd.elementsByTagName( "tense" ); // deprecated grammar tag while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if( isLogicalFormat ) el.setAttribute( "class", "xdxf_gr" ); else el.setAttribute( "class", "xdxf_gr_old" ); } /* end of grammar generation */ nodes = dd.elementsByTagName( "tr" ); // Transcription while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); el.setTagName( "span" ); if( isLogicalFormat ) el.setAttribute( "class", "xdxf_tr" ); else el.setAttribute( "class", "xdxf_tr_old" ); } // Ensure that ArticleNetworkAccessManager can deal with XDXF images. // We modify the URL by using the dictionary ID as the hostname. // This is necessary to determine from which dictionary a requested // image originates. nodes = dd.elementsByTagName( "img" ); for( int i = 0; i < nodes.size(); i++ ) { QDomElement el = nodes.at( i ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); if ( el.hasAttribute( "src" ) ) { fixLink( el, dictPtr->getId(), "src" ); } if ( el.hasAttribute( "losrc" ) ) { fixLink( el, dictPtr->getId(), "losrc" ); } if ( el.hasAttribute( "hisrc" ) ) { fixLink( el, dictPtr->getId(), "hisrc" ); } } nodes = dd.elementsByTagName( "rref" ); // Resource reference while( nodes.size() ) { QDomElement el = nodes.at( 0 ).toElement(); if( el.text().isEmpty() && el.childNodes().isEmpty() ) el.appendChild( fakeElement( dd ) ); // if( type == XDXF && dictPtr != NULL && !el.hasAttribute( "start" ) ) if( dictPtr != NULL && !el.hasAttribute( "start" ) ) { string filename = Utf8::encode( gd::toWString( el.text() ) ); if ( Filetype::isNameOfPicture( filename ) ) { QUrl url; url.setScheme( "bres" ); url.setHost( QString::fromUtf8( dictPtr->getId().c_str() ) ); url.setPath( Qt4x5::Url::ensureLeadingSlash( QString::fromUtf8( filename.c_str() ) ) ); QDomElement newEl = dd.createElement( "img" ); newEl.setAttribute( "src", url.toEncoded().data() ); newEl.setAttribute( "alt", Html::escape( filename ).c_str() ); QDomNode parent = el.parentNode(); if( !parent.isNull() ) { parent.replaceChild( newEl, el ); continue; } } else if( Filetype::isNameOfSound( filename ) ) { QDomElement el_script = dd.createElement( "script" ); QDomNode parent = el.parentNode(); if( !parent.isNull() ) { bool search = false; if( type == STARDICT ) { string n = FsEncoding::dirname( dictPtr->getDictionaryFilenames()[ 0 ] ) + FsEncoding::separator() + string( "res" ) + FsEncoding::separator() + FsEncoding::encode( filename ); search = !File::exists( n ) && ( !resourceZip || !resourceZip->isOpen() || !resourceZip->hasFile( Utf8::decode( filename ) ) ); } else { string n = dictPtr->getDictionaryFilenames()[ 0 ] + ".files" + FsEncoding::separator() + FsEncoding::encode( filename ); search = !File::exists( n ) && !File::exists( FsEncoding::dirname( dictPtr->getDictionaryFilenames()[ 0 ] ) + FsEncoding::separator() + FsEncoding::encode( filename ) ) && ( !resourceZip || !resourceZip->isOpen() || !resourceZip->hasFile( Utf8::decode( filename ) ) ); } QUrl url; url.setScheme( "gdau" ); url.setHost( QString::fromUtf8( search ? "search" : dictPtr->getId().c_str() ) ); url.setPath( Qt4x5::Url::ensureLeadingSlash( QString::fromUtf8( filename.c_str() ) ) ); el_script.setAttribute( "type", "text/javascript" ); parent.replaceChild( el_script, el ); QDomText el_txt = dd.createTextNode( makeAudioLinkScript( string( "\"" ) + url.toEncoded().data() + "\"", dictPtr->getId() ).c_str() ); el_script.appendChild( el_txt ); QDomElement el_span = dd.createElement( "span" ); el_span.setAttribute( "class", "xdxf_wav" ); parent.insertAfter( el_span, el_script ); QDomElement el_a = dd.createElement( "a" ); el_a.setAttribute( "href", url.toEncoded().data() ); el_span.appendChild( el_a ); QDomElement el_img = dd.createElement( "img"); el_img.setAttribute( "src", "qrcx://localhost/icons/playsound.png" ); el_img.setAttribute( "border", "0" ); el_img.setAttribute( "align", "absmiddle" ); el_img.setAttribute( "alt", "Play" ); el_a.appendChild( el_img ); continue; } } } // We don't really know how to handle this at the moment, so we'll just // convert it to a span and leave it as is for now. el.setTagName( "span" ); el.setAttribute( "class", "xdxf_rref" ); } // GD_DPRINTF( "Result>>>>>>>>>>: %s\n\n\n", dd.toByteArray( 0 ).data() ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) return dd.toString( 1 ).remove('\n').remove( QRegularExpression( "<(b|blockquote|i)/>" ) ).toUtf8().data(); #else return dd.toString( 1 ).remove('\n').remove( QRegExp( "<(b|blockquote|i)/>" ) ).toUtf8().data(); #endif } } ����������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/xdxf2html.hh�����������������������������������������������������������������������0000664�0000000�0000000�00000001605�14435233205�0016075�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org> * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __XDXF2HTML_HH_INCLUDED__ #define __XDXF2HTML_HH_INCLUDED__ #include <string> #include <map> #include "dictionary.hh" #include "indexedzip.hh" /// Xdxf is an xml file format. Since we display html, we'd like to be able /// to convert articles with such a markup to an html. namespace Xdxf2Html { enum DICT_TYPE { STARDICT, XDXF }; using std::string; using std::map; /// Converts the given xdxf markup to an html one. This is currently used /// for Stardict's 'x' records. string convert( string const &, DICT_TYPE type, map < string, string > const * pAbrv, Dictionary::Class *dictPtr, IndexedZip * resourceZip, bool isLogicalFormat = false, unsigned revisionNumber = 0, QString * headword = 0 ); } #endif ���������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/zim.cc�����������������������������������������������������������������������������0000664�0000000�0000000�00000151516�14435233205�0014751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2012 Abs62 * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifdef MAKE_ZIM_SUPPORT #include "zim.hh" #include "btreeidx.hh" #include "fsencoding.hh" #include "folding.hh" #include "categorized_logging.hh" #include "gddebug.hh" #include "utf8.hh" #include "decompress.hh" #include "langcoder.hh" #include "wstring_qt.hh" #include "filetype.hh" #include "file.hh" #include "qt4x5.hh" #include "tiff.hh" #include "ftshelpers.hh" #include "htmlescape.hh" #include "splitfile.hh" #ifdef _MSC_VER #include <stub_msvc.h> #endif #include <QByteArray> #include <QFile> #include <QFileInfo> #include <QString> #include <QRunnable> #include <QSemaphore> #include <QAtomicInt> #include <QImage> #include <QDir> #include <QDebug> #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) #include <QRegularExpression> #endif #include <string> #include <set> #include <map> #include <algorithm> namespace Zim { #define CACHE_SIZE 3 using std::string; using std::map; using std::vector; using std::multimap; using std::pair; using std::set; using gd::wstring; using BtreeIndexing::WordArticleLink; using BtreeIndexing::IndexedWords; using BtreeIndexing::IndexInfo; DEF_EX_STR( exNotZimFile, "Not an Zim file", Dictionary::Ex ) DEF_EX_STR( exCantReadFile, "Can't read file", Dictionary::Ex ) DEF_EX_STR( exInvalidZimHeader, "Invalid Zim header", Dictionary::Ex ) DEF_EX( exUserAbort, "User abort", Dictionary::Ex ) //namespace { class ZimFile; #pragma pack( push, 1 ) enum CompressionType { Default = 0, None, Zlib, Bzip2, Lzma2, Zstd }; /// Zim file header struct ZIM_header { quint32 magicNumber; quint16 majorVersion; quint16 minorVersion; quint8 uuid[ 16 ]; quint32 articleCount; quint32 clusterCount; quint64 urlPtrPos; quint64 titlePtrPos; quint64 clusterPtrPos; quint64 mimeListPos; quint32 mainPage; quint32 layoutPage; quint64 checksumPos; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct ArticleEntry { quint16 mimetype; quint8 parameterLen; char nameSpace; quint32 revision; quint32 clusterNumber; quint32 blobNumber; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct RedirectEntry { quint16 mimetype; quint8 parameterLen; char nameSpace; quint32 revision; quint32 redirectIndex; } #ifndef _MSC_VER __attribute__((packed)) #endif ; enum { Signature = 0x584D495A, // ZIMX on little-endian, XMIZ on big-endian CurrentFormatVersion = 3 + BtreeIndexing::FormatVersion + Folding::Version }; struct IdxHeader { quint32 signature; // First comes the signature, ZIMX quint32 formatVersion; // File format version (CurrentFormatVersion) quint32 indexBtreeMaxElements; // Two fields from IndexInfo quint32 indexRootOffset; quint32 resourceIndexBtreeMaxElements; // Two fields from IndexInfo quint32 resourceIndexRootOffset; quint32 wordCount; quint32 articleCount; quint32 namePtr; quint32 descriptionPtr; quint32 langFrom; // Source language quint32 langTo; // Target language } #ifndef _MSC_VER __attribute__((packed)) #endif ; #pragma pack( pop ) // Class for support of split zim files struct Cache { char * data; quint32 clusterNumber; int stamp; int count, size; unsigned blobs_offset_size; Cache() : data( 0 ), clusterNumber( 0 ), stamp( -1 ), count( 0 ), size( 0 ), blobs_offset_size( 0 ) {} }; class ZimFile : public SplitFile::SplitFile { public: ZimFile(); ZimFile( const QString & name ); ~ZimFile(); virtual void setFileName( const QString & name ); bool open(); void close() { SplitFile::close(); clearCache(); } const ZIM_header & header() const { return zimHeader; } string getClusterData( quint32 cluster_nom, unsigned & blob_offset_size ); const QString getMimeType( quint16 nom ) { return mimeTypes.value( nom ); } bool isArticleMime( quint16 mime_type ) { return getMimeType( mime_type ).startsWith( "text/html", Qt::CaseInsensitive ) || getMimeType( mime_type ).startsWith( "text/plain", Qt::CaseInsensitive ); } quint16 redirectedMimeType( RedirectEntry const & redEntry ); private: ZIM_header zimHeader; Cache cache[ CACHE_SIZE ]; int stamp; QVector< QPair< quint64, quint32 > > clusterOffsets; QStringList mimeTypes; void clearCache(); }; ZimFile::ZimFile() : stamp( 0 ) { memset( &zimHeader, 0, sizeof( zimHeader ) ); } ZimFile::ZimFile( const QString & name ) { setFileName( name ); } ZimFile::~ZimFile() { clearCache(); } void ZimFile::setFileName( const QString & name ) { close(); memset( &zimHeader, 0, sizeof( zimHeader ) ); clearCache(); appendFile( name ); if( name.endsWith( ".zimaa", Qt::CaseInsensitive ) ) { QString fname = name; for( int i = 0; i < 26; i++ ) { fname[ fname.size() - 2 ] = (char)( 'a' + i ); int j; for( j = 1; j < 26; j++ ) { fname[ fname.size() - 1 ] = (char)( 'a' + j ); if( !QFileInfo( fname ).isFile() ) break; appendFile( fname ); } if( j < 26 ) break; } } } void ZimFile::clearCache() { for( int i = 0; i < CACHE_SIZE; i++ ) { if( cache[ i ].data ) { free( cache[ i ].data ); cache[ i ].data = 0; } cache[ i ].clusterNumber = 0; cache[ i ].stamp = -1; cache[ i ].count = 0; cache[ i ].size = 0; } stamp = 0; } bool ZimFile::open() { if( !SplitFile::open( QIODevice::ReadOnly ) ) return false; memset( &zimHeader, 0, sizeof( zimHeader ) ); if( read( reinterpret_cast< char * >( &zimHeader ), sizeof( zimHeader ) ) != sizeof( zimHeader ) ) return false; if( zimHeader.magicNumber != 0x44D495A || zimHeader.mimeListPos != sizeof( zimHeader ) ) return false; // Clusters in zim file may be placed in random order. // We create sorted offsets list to calculate clusters size. clusterOffsets.resize( zimHeader.clusterCount ); QVector< quint64 > offs; offs.resize( zimHeader.clusterCount ); seek( zimHeader.clusterPtrPos ); qint64 size = zimHeader.clusterCount * sizeof( quint64 ); if( read( reinterpret_cast< char * >( offs.data() ), size) != size ) { vector< string > names; getFilenames( names ); throw exCantReadFile( names[ 0 ] ); } for( quint32 i = 0; i < zimHeader.clusterCount; i++ ) clusterOffsets[ i ] = QPair< quint64, quint32 >( offs.at( i ), i ); std::sort( clusterOffsets.begin(), clusterOffsets.end() ); // Read mime types string type; char ch; seek( zimHeader.mimeListPos ); for( ; ; ) { type.clear(); while( getChar( &ch ) ) { if( ch == 0 ) break; type.push_back( ch ); } if( type.empty() ) break; QString s = QString::fromUtf8( type.c_str(), type.size() ); mimeTypes.append( s ); } return true; } string ZimFile::getClusterData( quint32 cluster_nom, unsigned & blobs_offset_size ) { // Check cache int target = 0; bool found = false; int lastStamp = INT_MAX; for( int i = 0; i < CACHE_SIZE; i++ ) { if( cache[ i ].clusterNumber == cluster_nom && cache[ i ].count ) { found = true; target = i; break; } if( cache[ i ].stamp < lastStamp ) { lastStamp = cache[ i ].stamp; target = i; } } cache[ target ].stamp = ++stamp; if( stamp < 0 ) { stamp = 0; for (int i = 0; i < CACHE_SIZE; i++) cache[ i ].stamp = -1; } if( found ) { // Cache hit blobs_offset_size = cache[ target ].blobs_offset_size; return string( cache[ target ].data, cache[ target ].count ); } // Cache miss, read data from file // Calculate cluster size quint64 clusterSize; quint32 nom; for( nom = 0; nom < zimHeader.clusterCount; nom++ ) if( clusterOffsets.at( nom ).second == cluster_nom ) break; if( nom >= zimHeader.clusterCount ) // Invalid cluster nom return string(); if( nom < zimHeader.clusterCount - 1 ) clusterSize = clusterOffsets.at( nom + 1 ).first - clusterOffsets.at( nom ).first; else clusterSize = size() - clusterOffsets.at( nom ).first; // Read cluster data seek( clusterOffsets.at( nom ).first ); char compressionType, cluster_info; if( !getChar( &cluster_info ) ) return string(); compressionType = cluster_info & 0x0F; blobs_offset_size = cluster_info & 0x10 && zimHeader.majorVersion >= 6 ? 8 : 4; string decompressedData; QByteArray data = read( clusterSize ); if( compressionType == Default || compressionType == None ) decompressedData = string( data.data(), data.size() ); else if( compressionType == Zlib ) decompressedData = decompressZlib( data.constData(), data.size() ); else if( compressionType == Bzip2 ) decompressedData = decompressBzip2( data.constData(), data.size() ); else if( compressionType == Lzma2 ) decompressedData = decompressLzma2( data.constData(), data.size() ); else if( compressionType == Zstd ) decompressedData = decompressZstd( data.constData(), data.size() ); else return string(); if( decompressedData.empty() ) return string(); // Check BLOBs number in the cluster // We cache multi-element clusters only quint32 firstOffset32; quint64 firstOffset; if( blobs_offset_size == 8 ) memcpy( &firstOffset, decompressedData.data(), sizeof(firstOffset) ); else { memcpy( &firstOffset32, decompressedData.data(), sizeof(firstOffset32) ); firstOffset = firstOffset32; } quint32 blobCount = ( firstOffset - blobs_offset_size ) / blobs_offset_size; if( blobCount > 1 ) { // Fill cache int size = decompressedData.size(); if( cache[ target ].count < size ) { if( cache[ target ].data ) free( cache[ target ].data ); cache[ target ].data = ( char * )malloc( size ); if( cache[ target ].data ) cache[ target ].size = size; else { cache[ target ].size = 0; cache[ target ].count = 0; } } if( cache[ target ].size ) { memcpy( cache[ target ].data, decompressedData.c_str(), size ); cache[ target ].count = size; cache[ target ].clusterNumber = cluster_nom; cache[ target ].blobs_offset_size = blobs_offset_size; } } return decompressedData; } quint16 ZimFile::redirectedMimeType( RedirectEntry const & redEntry ) { RedirectEntry current_entry = redEntry; quint64 current_pos = pos(); quint16 mimetype = 0xFFFF; for( ; ; ) { quint32 current_nom = current_entry.redirectIndex; seek( zimHeader.urlPtrPos + (quint64)current_nom * 8 ); quint64 new_pos; if( read( reinterpret_cast< char * >( &new_pos ), sizeof(new_pos) ) != sizeof(new_pos) ) break; seek( new_pos ); quint16 new_mimetype; if( read( reinterpret_cast< char * >( &new_mimetype ), sizeof(new_mimetype) ) != sizeof(new_mimetype) ) break; if( new_mimetype == 0xFFFF ) // Redirect to other article { if( read( reinterpret_cast< char * >( ¤t_entry ) + 2, sizeof( current_entry ) - 2 ) != sizeof( current_entry ) - 2 ) break; if( current_nom == current_entry.redirectIndex ) break; } else { mimetype = new_mimetype; break; } } seek( current_pos ); return mimetype; } // Some supporting functions bool indexIsOldOrBad( string const & indexFile ) { File::Class idx( indexFile, "rb" ); IdxHeader header; return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != Signature || header.formatVersion != CurrentFormatVersion; } quint32 getArticleCluster( ZimFile & file, quint32 articleNumber ) { while( 1 ) { ZIM_header const & header = file.header(); if( articleNumber >= header.articleCount ) break; file.seek( header.urlPtrPos + (quint64)articleNumber * 8 ); quint64 pos; if( file.read( reinterpret_cast< char * >( &pos ), sizeof(pos) ) != sizeof(pos) ) break; // Read article info quint16 mimetype; file.seek( pos ); if( file.read( reinterpret_cast< char * >( &mimetype ), sizeof(mimetype) ) != sizeof(mimetype) ) break; if( mimetype == 0xFFFF ) // Redirect to other article { RedirectEntry redEntry; if( file.read( reinterpret_cast< char * >( &redEntry ) + 2, sizeof(redEntry) - 2 ) != sizeof(redEntry) - 2 ) break; if( articleNumber == redEntry.redirectIndex ) break; articleNumber = redEntry.redirectIndex; continue; } ArticleEntry artEntry; artEntry.mimetype = mimetype; if( file.read( reinterpret_cast< char * >( &artEntry ) + 2, sizeof(artEntry) - 2 ) != sizeof(artEntry) - 2 ) break; return artEntry.clusterNumber; } return 0xFFFFFFFF; } quint32 readArticle( ZimFile & file, quint32 articleNumber, string & result, set< quint32 > * loadedArticles = NULL ) { result.clear(); while( 1 ) { ZIM_header const & header = file.header(); if( articleNumber >= header.articleCount ) break; file.seek( header.urlPtrPos + (quint64)articleNumber * 8 ); quint64 pos; if( file.read( reinterpret_cast< char * >( &pos ), sizeof(pos) ) != sizeof(pos) ) break; // Read article info quint16 mimetype; file.seek( pos ); if( file.read( reinterpret_cast< char * >( &mimetype ), sizeof(mimetype) ) != sizeof(mimetype) ) break; if( mimetype == 0xFFFF ) // Redirect to other article { RedirectEntry redEntry; if( file.read( reinterpret_cast< char * >( &redEntry ) + 2, sizeof(redEntry) - 2 ) != sizeof(redEntry) - 2 ) break; if( articleNumber == redEntry.redirectIndex ) break; articleNumber = redEntry.redirectIndex; continue; } if( loadedArticles && loadedArticles->find( articleNumber ) != loadedArticles->end() ) break; ArticleEntry artEntry; artEntry.mimetype = mimetype; if( file.read( reinterpret_cast< char * >( &artEntry ) + 2, sizeof(artEntry) - 2 ) != sizeof(artEntry) - 2 ) break; // Read cluster data unsigned offset_size = 0; string decompressedData = file.getClusterData( artEntry.clusterNumber, offset_size ); if( decompressedData.empty() ) break; // Take article data from cluster quint32 firstOffset32; quint64 firstOffset; if( offset_size == 8 ) memcpy( &firstOffset, decompressedData.data(), sizeof(firstOffset) ); else { memcpy( &firstOffset32, decompressedData.data(), sizeof(firstOffset32) ); firstOffset = firstOffset32; } quint32 blobCount = ( firstOffset - offset_size ) / offset_size; if( artEntry.blobNumber > blobCount ) break; quint32 size; if( offset_size == 8 ) { quint64 offsets[ 2 ]; memcpy( offsets, decompressedData.data() + artEntry.blobNumber * 8, sizeof(offsets) ); size = offsets[ 1 ] - offsets[ 0 ]; result.append( decompressedData, offsets[ 0 ], size ); } else { quint32 offsets[ 2 ]; memcpy( offsets, decompressedData.data() + artEntry.blobNumber * 4, sizeof(offsets) ); size = offsets[ 1 ] - offsets[ 0 ]; result.append( decompressedData, offsets[ 0 ], size ); } return articleNumber; } return 0xFFFFFFFF; } // ZimDictionary class ZimDictionary: public BtreeIndexing::BtreeDictionary { enum LINKS_TYPE { UNKNOWN, SLASH, NO_SLASH }; Mutex idxMutex; Mutex zimMutex, idxResourceMutex; File::Class idx; BtreeIndex resourceIndex; IdxHeader idxHeader; string dictionaryName; ZimFile df; set< quint32 > articlesIndexedForFTS; LINKS_TYPE linksType; public: ZimDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ); ~ZimDictionary(); virtual string getName() throw() { return dictionaryName; } virtual map< Dictionary::Property, string > getProperties() throw() { return map< Dictionary::Property, string >(); } virtual unsigned long getArticleCount() throw() { return idxHeader.articleCount; } virtual unsigned long getWordCount() throw() { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const { return idxHeader.langFrom; } inline virtual quint32 getLangTo() const { return idxHeader.langTo; } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ); virtual sptr< Dictionary::DataRequest > getResource( string const & name ) THROW_SPEC( std::exception ); virtual QString const& getDescription(); /// Loads the resource. void loadResource( std::string &resourceName, string & data ); virtual sptr< Dictionary::DataRequest > getSearchResults( QString const & searchString, int searchMode, bool matchCase, int distanceBetweenWords, int maxResults, bool ignoreWordsOrder, bool ignoreDiacritics, QThreadPool * ftsThreadPoolPtr ); virtual void getArticleText( uint32_t articleAddress, QString & headword, QString & text ); quint32 getArticleText( uint32_t articleAddress, QString & headword, QString & text, set< quint32 > * loadedArticles ); virtual void makeFTSIndex(QAtomicInt & isCancelled, bool firstIteration ); virtual void setFTSParameters( Config::FullTextSearch const & fts ) { can_FTS = fts.enabled && !fts.disabledTypes.contains( "ZIM", Qt::CaseInsensitive ) && ( fts.maxDictionarySize == 0 || getArticleCount() <= fts.maxDictionarySize ); } virtual void sortArticlesOffsetsForFTS( QVector< uint32_t > & offsets, QAtomicInt & isCancelled ); protected: virtual void loadIcon() throw(); private: /// Loads the article. quint32 loadArticle( quint32 address, string & articleText, set< quint32 > * loadedArticles, bool rawText = false ); string convert( string const & in_data ); friend class ZimArticleRequest; friend class ZimResourceRequest; }; ZimDictionary::ZimDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ): BtreeDictionary( id, dictionaryFiles ), idx( indexFile, "rb" ), idxHeader( idx.read< IdxHeader >() ), df( FsEncoding::decode( dictionaryFiles[ 0 ].c_str() ) ), linksType( UNKNOWN ) { // Open data file df.open(); // Initialize the indexes openIndex( IndexInfo( idxHeader.indexBtreeMaxElements, idxHeader.indexRootOffset ), idx, idxMutex ); resourceIndex.openIndex( IndexInfo( idxHeader.resourceIndexBtreeMaxElements, idxHeader.resourceIndexRootOffset ), idx, idxResourceMutex ); // Read dictionary name if( idxHeader.namePtr == 0xFFFFFFFF ) { QString name = QDir::fromNativeSeparators( FsEncoding::decode( dictionaryFiles[ 0 ].c_str() ) ); int n = name.lastIndexOf( '/' ); dictionaryName = string( name.mid( n + 1 ).toUtf8().constData() ); } else { readArticle( df, idxHeader.namePtr, dictionaryName ); } // Full-text search parameters can_FTS = true; ftsIdxName = indexFile + "_FTS"; if( !Dictionary::needToRebuildIndex( dictionaryFiles, ftsIdxName ) && !FtsHelpers::ftsIndexIsOldOrBad( ftsIdxName, this ) ) FTS_index_completed.ref(); } ZimDictionary::~ZimDictionary() { df.close(); } void ZimDictionary::loadIcon() throw() { if ( dictionaryIconLoaded ) return; QString fileName = QDir::fromNativeSeparators( FsEncoding::decode( getDictionaryFilenames()[ 0 ].c_str() ) ); // Remove the extension fileName.chop( 3 ); if( !loadIconFromFile( fileName ) ) { // Load failed -- use default icons dictionaryNativeIcon = dictionaryIcon = QIcon(":/icons/icon32_zim.png"); } dictionaryIconLoaded = true; } quint32 ZimDictionary::loadArticle( quint32 address, string & articleText, set< quint32 > * loadedArticles, bool rawText ) { quint32 ret; { Mutex::Lock _( zimMutex ); ret = readArticle( df, address, articleText, loadedArticles ); } if( !rawText ) articleText = convert( articleText ); return ret; } string ZimDictionary::convert( const string & in ) { QString text = QString::fromUtf8( in.c_str() ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) // replace background text.replace( QRegularExpression( "<\\s*body\\s+([^>]*)(background(|-color)):([^;\"]*(;|))" ), QString( "<body \\1" ) ); // pattern of img and script text.replace( QRegularExpression( "<\\s*(img|script)\\s+([^>]*)src=(\"|)(\\.\\.|)/" ), QString( "<\\1 \\2src=\\3bres://%1/").arg( getId().c_str() ) ); // Fix links without '"' text.replace( QRegularExpression( "href=(\\.\\.|)/([^\\s>]+)" ), QString( "href=\"\\1/\\2\"" ) ); // pattern <link... href="..." ...> text.replace( QRegularExpression( "<\\s*link\\s+([^>]*)href=\"(\\.\\.|)/" ), QString( "<link \\1href=\"bres://%1/").arg( getId().c_str() ) ); // localize the http://en.wiki***.com|org/wiki/<key> series links // excluding those keywords that have ":" in it QString urlWiki = "\"http(s|)://en\\.(wiki(pedia|books|news|quote|source|voyage|versity)|wiktionary)\\.(org|com)/wiki/([^:\"]*)\""; text.replace( QRegularExpression( "<\\s*a\\s+(class=\"external\"\\s+|)href=" + urlWiki ), QString( "<a href=\"gdlookup://localhost/\\6\"" ) ); #else // replace background text.replace( QRegExp( "<\\s*body\\s+([^>]*)(background(|-color)):([^;\"]*(|;))" ), QString( "<body \\1" ) ); // pattern of img and script text.replace( QRegExp( "<\\s*(img|script)\\s+([^>]*)src=(\"|)(\\.\\.|)/" ), QString( "<\\1 \\2src=\\3bres://%1/").arg( getId().c_str() ) ); // Fix links without '"' text.replace( QRegExp( "href=(\\.\\.|)/([^\\s>]+)" ), QString( "href=\"\\1/\\2\"" ) ); // pattern <link... href="..." ...> text.replace( QRegExp( "<\\s*link\\s+([^>]*)href=\"(\\.\\.|)/" ), QString( "<link \\1href=\"bres://%1/").arg( getId().c_str() ) ); // localize the http://en.wiki***.com|org/wiki/<key> series links // excluding those keywords that have ":" in it QString urlWiki = "\"http(s|)://en\\.(wiki(pedia|books|news|quote|source|voyage|versity)|wiktionary)\\.(org|com)/wiki/([^:\"]*)\""; text.replace( QRegExp( "<\\s*a\\s+(class=\"external\"\\s+|)href=" + urlWiki ), QString( "<a href=\"gdlookup://localhost/\\6\"" ) ); #endif // pattern <a href="..." ...>, excluding any known protocols such as http://, mailto:, #(comment) // these links will be translated into local definitions #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QRegularExpression rxLink( "<\\s*a\\s+([^>]*)href=\"(?!(?:\\w+://|#|mailto:|tel:))(/|)([^\"]*)\"\\s*(title=\"[^\"]*\")?[^>]*>" ); QRegularExpressionMatchIterator it = rxLink.globalMatch( text ); int pos = 0; QString newText; while( it.hasNext() ) { QRegularExpressionMatch match = it.next(); newText += text.midRef( pos, match.capturedStart() - pos ); pos = match.capturedEnd(); QStringList list = match.capturedTexts(); // Add empty strings for compatibility with QRegExp behaviour for( int i = match.lastCapturedIndex() + 1; i < 5; i++ ) list.append( QString() ); #else QRegExp rxLink( "<\\s*a\\s+([^>]*)href=\"(?!(\\w+://|#|mailto:|tel:))(/|)([^\"]*)\"\\s*(title=\"[^\"]*\")?[^>]*>", Qt::CaseSensitive, QRegExp::RegExp2 ); int pos = 0; while( (pos = rxLink.indexIn( text, pos )) >= 0 ) { QStringList list = rxLink.capturedTexts(); #endif QString tag = list[3]; // a url, ex: Precambrian_Chaotian.html if ( !list[4].isEmpty() ) // a title, ex: title="Precambrian/Chaotian" tag = list[4].split("\"")[1]; // Check type of links inside articles if( linksType == UNKNOWN && tag.indexOf( '/' ) >= 0 ) { QString word = QUrl::fromPercentEncoding( tag.toLatin1() ); word.remove( QRegExp( "\\.(s|)htm(l|)$", Qt::CaseInsensitive ) ). replace( "_", " " ); vector< WordArticleLink > links; links = findArticles( gd::toWString( word ) ); if( !links.empty() ) { linksType = SLASH; } else { word.remove( QRegExp(".*/") ); links = findArticles( gd::toWString( word ) ); if( !links.empty() ) { linksType = NO_SLASH; links.clear(); } } } if( linksType == SLASH || linksType == UNKNOWN ) { tag.remove( QRegExp( "\\.(s|)htm(l|)$", Qt::CaseInsensitive ) ). replace( "_", "%20" ). prepend( "<a href=\"gdlookup://localhost/" ). append( "\" " + list[4] + ">" ); } else { tag.remove( QRegExp(".*/") ). remove( QRegExp( "\\.(s|)htm(l|)$", Qt::CaseInsensitive ) ). replace( "_", "%20" ). prepend( "<a href=\"gdlookup://localhost/" ). append( "\" " + list[4] + ">" ); } #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) newText += tag; } if( pos ) { newText += text.midRef( pos ); text = newText; } newText.clear(); #else text.replace( pos, list[0].length(), tag ); pos += tag.length() + 1; } #endif // Occasionally words needs to be displayed in vertical, but <br/> were changed to <br\> somewhere // proper style: <a href="gdlookup://localhost/Neoptera" ... >N<br/>e<br/>o<br/>p<br/>t<br/>e<br/>r<br/>a</a> #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QRegularExpression rxBR( "(<a href=\"gdlookup://localhost/[^\"]*\"\\s*[^>]*>)\\s*((\\w\\s*<br(\\\\|/|)>\\s*)+\\w)\\s*</a>", QRegularExpression::UseUnicodePropertiesOption ); pos = 0; QRegularExpressionMatchIterator it2 = rxLink.globalMatch( text ); while( it2.hasNext() ) { QRegularExpressionMatch match = it.next(); newText += text.midRef( pos, match.capturedStart() - pos ); pos = match.capturedEnd(); QStringList list = match.capturedTexts(); // Add empty strings for compatibility with QRegExp behaviour for( int i = match.lastCapturedIndex() + 1; i < 3; i++ ) list.append( QString() ); QString tag = list[2]; tag.replace( QRegExp( "<br( |)(\\\\|/|)>", Qt::CaseInsensitive ) , "<br/>" ). prepend( list[1] ). append( "</a>" ); newText += tag; } if( pos ) { newText += text.midRef( pos ); text = newText; } newText.clear(); #else QRegExp rxBR( "(<a href=\"gdlookup://localhost/[^\"]*\"\\s*[^>]*>)\\s*((\\w\\s*<br(\\\\|/|)>\\s*)+\\w)\\s*</a>", Qt::CaseSensitive, QRegExp::RegExp2 ); pos = 0; while( (pos = rxBR.indexIn( text, pos )) >= 0 ) { QStringList list = rxBR.capturedTexts(); QString tag = list[2]; tag.replace( QRegExp( "<br( |)(\\\\|/|)>", Qt::CaseInsensitive ) , "<br/>" ). prepend( list[1] ). append( "</a>" ); text.replace( pos, list[0].length(), tag ); pos += tag.length() + 1; } #endif // // output all links in the page - only for analysis // QRegExp rxPrintAllLinks( "<\\s*a\\s+[^>]*href=\"[^\"]*\"[^>]*>", // Qt::CaseSensitive, // QRegExp::RegExp2 ); // pos = 0; // while( (pos = rxPrintAllLinks.indexIn( text, pos )) >= 0 ) // { // QStringList list = rxPrintAllLinks.capturedTexts(); // qDebug() << "\n--Alllinks--" << list[0]; // pos += list[0].length() + 1; // } // Fix outstanding elements text += "<br style=\"clear:both;\" />"; return text.toUtf8().data(); } void ZimDictionary::loadResource( std::string & resourceName, string & data ) { vector< WordArticleLink > link; string resData; link = resourceIndex.findArticles( Utf8::decode( resourceName ) ); if( link.empty() ) return; { Mutex::Lock _( zimMutex ); readArticle( df, link[ 0 ].articleOffset, data ); } } QString const& ZimDictionary::getDescription() { if( !dictionaryDescription.isEmpty() || idxHeader.descriptionPtr == 0xFFFFFFFF ) return dictionaryDescription; string str; { Mutex::Lock _( zimMutex ); readArticle( df, idxHeader.descriptionPtr, str ); } if( !str.empty() ) dictionaryDescription = QString::fromUtf8( str.c_str(), str.size() ); return dictionaryDescription; } void ZimDictionary::makeFTSIndex( QAtomicInt & isCancelled, bool firstIteration ) { if( !( Dictionary::needToRebuildIndex( getDictionaryFilenames(), ftsIdxName ) || FtsHelpers::ftsIndexIsOldOrBad( ftsIdxName, this ) ) ) FTS_index_completed.ref(); if( haveFTSIndex() ) return; if( ensureInitDone().size() ) return; if( firstIteration ) return; gdDebug( "Zim: Building the full-text index for dictionary: %s\n", getName().c_str() ); try { Mutex::Lock _( getFtsMutex() ); File::Class ftsIdx( ftsIndexName(), "wb" ); FtsHelpers::FtsIdxHeader ftsIdxHeader; memset( &ftsIdxHeader, 0, sizeof( ftsIdxHeader ) ); // We write a dummy header first. At the end of the process the header // will be rewritten with the right values. ftsIdx.write( ftsIdxHeader ); ChunkedStorage::Writer chunks( ftsIdx ); BtreeIndexing::IndexedWords indexedWords; QSet< uint32_t > setOfOffsets; setOfOffsets.reserve( getWordCount() ); findArticleLinks( 0, &setOfOffsets, 0, &isCancelled ); if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); // We should sort articles order by cluster number // to effective use clusters data caching QVector< QPair< quint32, uint32_t > > offsetsWithClusters; offsetsWithClusters.reserve( setOfOffsets.size() ); for( QSet< uint32_t >::ConstIterator it = setOfOffsets.constBegin(); it != setOfOffsets.constEnd(); ++it ) { if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); Mutex::Lock _( zimMutex ); offsetsWithClusters.append( QPair< uint32_t, quint32 >( getArticleCluster( df, *it ), *it ) ); } // Free memory setOfOffsets.clear(); if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); std::sort( offsetsWithClusters.begin(), offsetsWithClusters.end() ); QVector< uint32_t > offsets; offsets.resize( offsetsWithClusters.size() ); for( int i = 0; i < offsetsWithClusters.size(); i++ ) offsets[ i ] = offsetsWithClusters.at( i ).second; // Free memory offsetsWithClusters.clear(); if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); QMap< QString, QVector< uint32_t > > ftsWords; set< quint32 > indexedArticles; quint32 articleNumber; // index articles for full-text search for( int i = 0; i < offsets.size(); i++ ) { if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); QString headword, articleStr; articleNumber = getArticleText( offsets.at( i ), headword, articleStr, &indexedArticles ); if( articleNumber == 0xFFFFFFFF ) continue; indexedArticles.insert( articleNumber ); FtsHelpers::parseArticleForFts( offsets.at( i ), articleStr, ftsWords ); } // Free memory offsets.clear(); QMap< QString, QVector< uint32_t > >::iterator it = ftsWords.begin(); while( it != ftsWords.end() ) { if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); uint32_t offset = chunks.startNewBlock(); uint32_t size = it.value().size(); chunks.addToBlock( &size, sizeof(uint32_t) ); chunks.addToBlock( it.value().data(), size * sizeof(uint32_t) ); indexedWords.addSingleWord( gd::toWString( it.key() ), offset ); it = ftsWords.erase( it ); } // Free memory ftsWords.clear(); if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); ftsIdxHeader.chunksOffset = chunks.finish(); ftsIdxHeader.wordCount = indexedWords.size(); if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); BtreeIndexing::IndexInfo ftsIdxInfo = BtreeIndexing::buildIndex( indexedWords, ftsIdx ); // Free memory indexedWords.clear(); ftsIdxHeader.indexBtreeMaxElements = ftsIdxInfo.btreeMaxElements; ftsIdxHeader.indexRootOffset = ftsIdxInfo.rootOffset; ftsIdxHeader.signature = FtsHelpers::FtsSignature; ftsIdxHeader.formatVersion = FtsHelpers::CurrentFtsFormatVersion + getFtsIndexVersion(); ftsIdx.rewind(); ftsIdx.writeRecords( &ftsIdxHeader, sizeof(ftsIdxHeader), 1 ); FTS_index_completed.ref(); } catch( std::exception &ex ) { gdWarning( "Zim: Failed building full-text search index for \"%s\", reason: %s\n", getName().c_str(), ex.what() ); QFile::remove( FsEncoding::decode( ftsIdxName.c_str() ) ); } } void ZimDictionary::sortArticlesOffsetsForFTS( QVector< uint32_t > & offsets, QAtomicInt & isCancelled ) { QVector< QPair< quint32, uint32_t > > offsetsWithClusters; offsetsWithClusters.reserve( offsets.size() ); for( QVector< uint32_t >::ConstIterator it = offsets.constBegin(); it != offsets.constEnd(); ++it ) { if( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) return; Mutex::Lock _( zimMutex ); offsetsWithClusters.append( QPair< uint32_t, quint32 >( getArticleCluster( df, *it ), *it ) ); } std::sort( offsetsWithClusters.begin(), offsetsWithClusters.end() ); for( int i = 0; i < offsetsWithClusters.size(); i++ ) offsets[ i ] = offsetsWithClusters.at( i ).second; } void ZimDictionary::getArticleText( uint32_t articleAddress, QString & headword, QString & text ) { try { headword.clear(); string articleText; loadArticle( articleAddress, articleText, 0, true ); text = Html::unescape( QString::fromUtf8( articleText.data(), articleText.size() ) ); } catch( std::exception &ex ) { gdWarning( "Zim: Failed retrieving article from \"%s\", reason: %s\n", getName().c_str(), ex.what() ); } } quint32 ZimDictionary::getArticleText( uint32_t articleAddress, QString & headword, QString & text, set< quint32 > * loadedArticles ) { quint32 articleNumber = 0xFFFFFFFF; try { headword.clear(); string articleText; articleNumber = loadArticle( articleAddress, articleText, loadedArticles, true ); text = Html::unescape( QString::fromUtf8( articleText.data(), articleText.size() ) ); } catch( std::exception &ex ) { gdWarning( "Zim: Failed retrieving article from \"%s\", reason: %s\n", getName().c_str(), ex.what() ); } return articleNumber; } sptr< Dictionary::DataRequest > ZimDictionary::getSearchResults( QString const & searchString, int searchMode, bool matchCase, int distanceBetweenWords, int maxResults, bool ignoreWordsOrder, bool ignoreDiacritics, QThreadPool * ftsThreadPoolPtr ) { return new FtsHelpers::FTSResultsRequest( *this, searchString,searchMode, matchCase, distanceBetweenWords, maxResults, ignoreWordsOrder, ignoreDiacritics, ftsThreadPoolPtr ); } /// ZimDictionary::getArticle() class ZimArticleRequest; class ZimArticleRequestRunnable: public QRunnable { ZimArticleRequest & r; QSemaphore & hasExited; public: ZimArticleRequestRunnable( ZimArticleRequest & r_, QSemaphore & hasExited_ ): r( r_ ), hasExited( hasExited_ ) {} ~ZimArticleRequestRunnable() { hasExited.release(); } virtual void run(); }; class ZimArticleRequest: public Dictionary::DataRequest { friend class ZimArticleRequestRunnable; wstring word; vector< wstring > alts; ZimDictionary & dict; bool ignoreDiacritics; QAtomicInt isCancelled; QSemaphore hasExited; public: ZimArticleRequest( wstring const & word_, vector< wstring > const & alts_, ZimDictionary & dict_, bool ignoreDiacritics_ ): word( word_ ), alts( alts_ ), dict( dict_ ), ignoreDiacritics( ignoreDiacritics_ ) { QThreadPool::globalInstance()->start( new ZimArticleRequestRunnable( *this, hasExited ) ); } void run(); // Run from another thread by ZimArticleRequestRunnable virtual void cancel() { isCancelled.ref(); } ~ZimArticleRequest() { isCancelled.ref(); hasExited.acquire(); } }; void ZimArticleRequestRunnable::run() { r.run(); } void ZimArticleRequest::run() { if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } vector< WordArticleLink > chain = dict.findArticles( word, ignoreDiacritics ); for( unsigned x = 0; x < alts.size(); ++x ) { /// Make an additional query for each alt vector< WordArticleLink > altChain = dict.findArticles( alts[ x ], ignoreDiacritics ); chain.insert( chain.end(), altChain.begin(), altChain.end() ); } multimap< wstring, pair< string, string > > mainArticles, alternateArticles; set< quint32 > articlesIncluded; // Some synonims make it that the articles // appear several times. We combat this // by only allowing them to appear once. wstring wordCaseFolded = Folding::applySimpleCaseOnly( word ); if( ignoreDiacritics ) wordCaseFolded = Folding::applyDiacriticsOnly( wordCaseFolded ); for( unsigned x = 0; x < chain.size(); ++x ) { if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } // Now grab that article string headword, articleText; headword = chain[ x ].word; quint32 articleNumber = 0xFFFFFFFF; try { articleNumber = dict.loadArticle( chain[ x ].articleOffset, articleText, &articlesIncluded ); } catch(...) { } if( articleNumber == 0xFFFFFFFF ) continue; // No article loaded if ( articlesIncluded.find( articleNumber ) != articlesIncluded.end() ) continue; // We already have this article in the body. // Ok. Now, does it go to main articles, or to alternate ones? We list // main ones first, and alternates after. // We do the case-folded comparison here. wstring headwordStripped = Folding::applySimpleCaseOnly( Utf8::decode( headword ) ); if( ignoreDiacritics ) headwordStripped = Folding::applyDiacriticsOnly( headwordStripped ); multimap< wstring, pair< string, string > > & mapToUse = ( wordCaseFolded == headwordStripped ) ? mainArticles : alternateArticles; mapToUse.insert( pair< wstring, pair< string, string > >( Folding::applySimpleCaseOnly( Utf8::decode( headword ) ), pair< string, string >( headword, articleText ) ) ); articlesIncluded.insert( articleNumber ); } if ( mainArticles.empty() && alternateArticles.empty() ) { // No such word finish(); return; } string result; // See Issue #271: A mechanism to clean-up invalid HTML cards. string cleaner = "</font>""</font>""</font>""</font>""</font>""</font>" "</font>""</font>""</font>""</font>""</font>""</font>" "</b></b></b></b></b></b></b></b>" "</i></i></i></i></i></i></i></i>" "</a></a></a></a></a></a></a></a>"; multimap< wstring, pair< string, string > >::const_iterator i; for( i = mainArticles.begin(); i != mainArticles.end(); ++i ) { result += "<div class=\"zimdict\">"; result += "<h2 class=\"zimdict_headword\">"; result += i->second.first; result += "</h2>"; result += i->second.second; result += cleaner + "</div>"; } for( i = alternateArticles.begin(); i != alternateArticles.end(); ++i ) { result += "<div class=\"zimdict\">"; result += "<h2 class=\"zimdict_headword\">"; result += i->second.first; result += "</h2>"; result += i->second.second; result += cleaner + "</div>"; } Mutex::Lock _( dataMutex ); data.resize( result.size() ); memcpy( &data.front(), result.data(), result.size() ); hasAnyData = true; finish(); } sptr< Dictionary::DataRequest > ZimDictionary::getArticle( wstring const & word, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ) { return new ZimArticleRequest( word, alts, *this, ignoreDiacritics ); } //// ZimDictionary::getResource() class ZimResourceRequest; class ZimResourceRequestRunnable: public QRunnable { ZimResourceRequest & r; QSemaphore & hasExited; public: ZimResourceRequestRunnable( ZimResourceRequest & r_, QSemaphore & hasExited_ ): r( r_ ), hasExited( hasExited_ ) {} ~ZimResourceRequestRunnable() { hasExited.release(); } virtual void run(); }; class ZimResourceRequest: public Dictionary::DataRequest { friend class ZimResourceRequestRunnable; ZimDictionary & dict; string resourceName; QAtomicInt isCancelled; QSemaphore hasExited; public: ZimResourceRequest( ZimDictionary & dict_, string const & resourceName_ ): dict( dict_ ), resourceName( resourceName_ ) { QThreadPool::globalInstance()->start( new ZimResourceRequestRunnable( *this, hasExited ) ); } void run(); // Run from another thread by ZimResourceRequestRunnable virtual void cancel() { isCancelled.ref(); } ~ZimResourceRequest() { isCancelled.ref(); hasExited.acquire(); } }; void ZimResourceRequestRunnable::run() { r.run(); } void ZimResourceRequest::run() { // Some runnables linger enough that they are cancelled before they start if ( Qt4x5::AtomicInt::loadAcquire( isCancelled ) ) { finish(); return; } try { string resource; dict.loadResource( resourceName, resource ); if( resource.empty() ) throw File::Ex(); if( Filetype::isNameOfCSS( resourceName ) ) { QString css = QString::fromUtf8( resource.data(), resource.size() ); dict.isolateCSS( css, ".zimdict" ); QByteArray bytes = css.toUtf8(); Mutex::Lock _( dataMutex ); data.resize( bytes.size() ); memcpy( &data.front(), bytes.constData(), bytes.size() ); } else if ( Filetype::isNameOfTiff( resourceName ) ) { // Convert it dataMutex.lock(); QImage img = QImage::fromData( reinterpret_cast< const uchar * >( resource.data() ), resource.size() ); #ifdef MAKE_EXTRA_TIFF_HANDLER if( img.isNull() ) GdTiff::tiffToQImage( &data.front(), data.size(), img ); #endif dataMutex.unlock(); if ( !img.isNull() ) { // Managed to load -- now store it back as BMP QByteArray ba; QBuffer buffer( &ba ); buffer.open( QIODevice::WriteOnly ); img.save( &buffer, "BMP" ); Mutex::Lock _( dataMutex ); data.resize( buffer.size() ); memcpy( &data.front(), buffer.data(), data.size() ); } } else { Mutex::Lock _( dataMutex ); data.resize( resource.size() ); memcpy( &data.front(), resource.data(), data.size() ); } Mutex::Lock _( dataMutex ); hasAnyData = true; } catch( std::exception &ex ) { gdCWarning( dictionaryResourceLc, "ZIM: Failed loading resource \"%s\" from \"%s\", reason: %s\n", resourceName.c_str(), dict.getName().c_str(), ex.what() ); // Resource not loaded -- we don't set the hasAnyData flag then } finish(); } sptr< Dictionary::DataRequest > ZimDictionary::getResource( string const & name ) THROW_SPEC( std::exception ) { return new ZimResourceRequest( *this, name ); } //} // anonymous namespace vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing & initializing, unsigned maxHeadwordsToExpand ) THROW_SPEC( std::exception ) { vector< sptr< Dictionary::Class > > dictionaries; for( vector< string >::const_iterator i = fileNames.begin(); i != fileNames.end(); ++i ) { // Skip files with the extensions different to .zim to speed up the // scanning QString firstName = QDir::fromNativeSeparators( FsEncoding::decode( i->c_str() ) ); if( !firstName.endsWith( ".zim") && !firstName.endsWith( ".zimaa" ) ) continue; // Got the file -- check if we need to rebuid the index ZimFile df( firstName ); vector< string > dictFiles; df.getFilenames( dictFiles ); string dictId = Dictionary::makeDictionaryId( dictFiles ); string indexFile = indicesDir + dictId; try { if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { gdDebug( "Zim: Building the index for dictionary: %s\n", i->c_str() ); unsigned articleCount = 0; unsigned wordCount = 0; df.open(); ZIM_header const & zh = df.header(); if( zh.magicNumber != 0x44D495A ) throw exNotZimFile( i->c_str() ); if( zh.mimeListPos != sizeof( ZIM_header ) ) throw exInvalidZimHeader( i->c_str() ); bool new_namespaces = ( zh.majorVersion >= 6 && zh.minorVersion >= 1 ); { int n = firstName.lastIndexOf( '/' ); initializing.indexingDictionary( firstName.mid( n + 1 ).toUtf8().constData() ); } File::Class idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); idxHeader.namePtr = 0xFFFFFFFF; idxHeader.descriptionPtr = 0xFFFFFFFF; // We write a dummy header first. At the end of the process the header // will be rewritten with the right values. idx.write( idxHeader ); IndexedWords indexedWords, indexedResources; QByteArray artEntries; df.seek( zh.urlPtrPos ); artEntries = df.read( (quint64)zh.articleCount * 8 ); QVector< quint64 > clusters; clusters.reserve( zh.clusterCount ); df.seek( zh.clusterPtrPos ); { QByteArray data = df.read( (quint64)zh.clusterCount * 8 ); for( unsigned n = 0; n < zh.clusterCount; n++ ) clusters.append( *( reinterpret_cast< const quint64 * >( data.constData() ) + n ) ); } const quint64 * ptr; quint16 mimetype, redirected_mime = 0xFFFF; ArticleEntry artEntry; RedirectEntry redEntry; string url, title; char nameSpace; for( unsigned n = 0; n < zh.articleCount; n++ ) { ptr = reinterpret_cast< const quint64 * >( artEntries.constData() ) + n; df.seek( *ptr ); df.read( reinterpret_cast< char * >( &mimetype ), sizeof(mimetype) ); if( mimetype == 0xFFFF ) { redEntry.mimetype = mimetype; qint64 ret = df.read( reinterpret_cast< char * >( &redEntry ) + 2, sizeof(RedirectEntry) - 2 ); if( ret != sizeof(RedirectEntry) - 2 ) throw exCantReadFile( i->c_str() ); redirected_mime = df.redirectedMimeType( redEntry ); nameSpace = redEntry.nameSpace; } else { artEntry.mimetype = mimetype; qint64 ret = df.read( reinterpret_cast< char * >( &artEntry ) + 2, sizeof(ArticleEntry) - 2 ); if( ret != sizeof(ArticleEntry) - 2 ) throw exCantReadFile( i->c_str() ); nameSpace = artEntry.nameSpace; if( ( nameSpace == 'A' || ( nameSpace == 'C' && new_namespaces ) ) && df.isArticleMime( mimetype ) ) articleCount++; } // Read article url and title char ch; url.clear(); while( df.getChar( &ch ) ) { if( ch == 0 ) break; url.push_back( ch ); } title.clear(); while( df.getChar( &ch ) ) { if( ch == 0 ) break; title.push_back( ch ); } if( nameSpace == 'A' || ( nameSpace == 'C' && new_namespaces && ( df.isArticleMime( mimetype ) || ( mimetype == 0xFFFF && df.isArticleMime( redirected_mime ) ) ) ) ) { wstring word; if( !title.empty() ) word = Utf8::decode( title ); else word = Utf8::decode( url ); if( df.isArticleMime( mimetype ) || ( mimetype == 0xFFFF && df.isArticleMime( redirected_mime ) ) ) { if( maxHeadwordsToExpand && zh.articleCount >= maxHeadwordsToExpand ) indexedWords.addSingleWord( word, n ); else indexedWords.addWord( word, n ); wordCount++; } else { url.insert( url.begin(), '/' ); url.insert( url.begin(), nameSpace ); indexedResources.addSingleWord( Utf8::decode( url ), n ); } } else if( nameSpace == 'M' ) { if( url.compare( "Title" ) == 0 ) { idxHeader.namePtr = n; string name; readArticle( df, n, name ); initializing.indexingDictionary( name ); } else if( url.compare( "Description" ) == 0 ) idxHeader.descriptionPtr = n; else if( url.compare( "Language" ) == 0 ) { string lang; readArticle( df, n, lang ); if( lang.size() == 2 ) idxHeader.langFrom = LangCoder::code2toInt( lang.c_str() ); else if( lang.size() == 3 ) idxHeader.langFrom = LangCoder::findIdForLanguageCode3( lang.c_str() ); idxHeader.langTo = idxHeader.langFrom; } } else if( nameSpace == 'X' ) { continue; } else { url.insert( url.begin(), '/' ); url.insert( url.begin(), nameSpace ); indexedResources.addSingleWord( Utf8::decode( url ), n ); } } // Build index { IndexInfo idxInfo = BtreeIndexing::buildIndex( indexedWords, idx ); idxHeader.indexBtreeMaxElements = idxInfo.btreeMaxElements; idxHeader.indexRootOffset = idxInfo.rootOffset; indexedWords.clear(); // Release memory -- no need for this data } { IndexInfo idxInfo = BtreeIndexing::buildIndex( indexedResources, idx ); idxHeader.resourceIndexBtreeMaxElements = idxInfo.btreeMaxElements; idxHeader.resourceIndexRootOffset = idxInfo.rootOffset; indexedResources.clear(); // Release memory -- no need for this data } idxHeader.signature = Signature; idxHeader.formatVersion = CurrentFormatVersion; idxHeader.articleCount = articleCount; idxHeader.wordCount = wordCount; idx.rewind(); idx.write( &idxHeader, sizeof( idxHeader ) ); } dictionaries.push_back( new ZimDictionary( dictId, indexFile, dictFiles ) ); } catch( std::exception & e ) { gdWarning( "Zim dictionary initializing failed: %s, error: %s\n", i->c_str(), e.what() ); continue; } catch( ... ) { qWarning( "Zim dictionary initializing failed\n" ); continue; } } return dictionaries; } } // namespace Zim #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/zim.hh�����������������������������������������������������������������������������0000664�0000000�0000000�00000001106�14435233205�0014750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef __ZIM_HH_INCLUDED__ #define __ZIM_HH_INCLUDED__ #ifdef MAKE_ZIM_SUPPORT #include "dictionary.hh" /// Support for the Zim dictionaries. namespace Zim { using std::vector; using std::string; vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing &, unsigned maxHeadwordsToExpand ) THROW_SPEC( std::exception ); } #endif #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/zipfile.cc�������������������������������������������������������������������������0000664�0000000�0000000�00000015140�14435233205�0015604�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org> * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "zipfile.hh" #include <QtEndian> #include <QByteArray> #include <QFileInfo> namespace ZipFile { #pragma pack( push, 1 ) /// End-of-central-directory record, as is struct EndOfCdirRecord { quint32 signature; quint16 numDisk, numDiskCd, totalEntriesDisk, totalEntries; quint32 size, offset; quint16 commentLength; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct CentralFileHeaderRecord { quint32 signature; quint16 verMadeBy, verNeeded, gpBits, compressionMethod, fileTime, fileDate; quint32 crc32, compressedSize, uncompressedSize; quint16 fileNameLength, extraFieldLength, fileCommentLength, diskNumberStart, intFileAttrs; quint32 externalFileAttrs, offsetOfLocalHeader; } #ifndef _MSC_VER __attribute__((packed)) #endif ; struct LocalFileHeaderRecord { quint32 signature; quint16 verNeeded, gpBits, compressionMethod, fileTime, fileDate; quint32 crc32, compressedSize, uncompressedSize; quint16 fileNameLength, extraFieldLength; } #ifndef _MSC_VER __attribute__((packed)) #endif ; #pragma pack( pop ) static quint32 const endOfCdirRecordSignatureValue = qToLittleEndian( 0x06054b50 ); static quint32 const centralFileHeaderSignature = qToLittleEndian( 0x02014b50 ); static quint32 const localFileHeaderSignature = qToLittleEndian( 0x04034b50 ); static CompressionMethod getCompressionMethod( quint16 compressionMethod ) { switch( qFromLittleEndian( compressionMethod ) ) { case 0: return Uncompressed; case 8: return Deflated; default: return Unsupported; } } bool positionAtCentralDir( SplitZipFile & zip ) { // Find the end-of-central-directory record int maxEofBufferSize = 65535 + sizeof( EndOfCdirRecord ); if ( zip.size() > maxEofBufferSize ) zip.seek( zip.size() - maxEofBufferSize ); else if ( (size_t) zip.size() < sizeof( EndOfCdirRecord ) ) return false; else zip.seek( 0 ); QByteArray eocBuffer = zip.read( maxEofBufferSize ); if ( eocBuffer.size() < (int)sizeof( EndOfCdirRecord ) ) return false; int lastIndex = eocBuffer.size() - sizeof( EndOfCdirRecord ); QByteArray endOfCdirRecordSignature( (char const *)&endOfCdirRecordSignatureValue, sizeof( endOfCdirRecordSignatureValue ) ); EndOfCdirRecord endOfCdirRecord; quint32 cdir_offset; for( ; ; --lastIndex ) { lastIndex = eocBuffer.lastIndexOf( endOfCdirRecordSignature, lastIndex ); if ( lastIndex == -1 ) return false; /// We need to copy it due to possible alignment issues on ARM etc memcpy( &endOfCdirRecord, eocBuffer.data() + lastIndex, sizeof( endOfCdirRecord ) ); /// Sanitize the record by checking the offset cdir_offset = zip.calcAbsoluteOffset( qFromLittleEndian( endOfCdirRecord.offset ), qFromLittleEndian( endOfCdirRecord.numDiskCd ) ); if ( !zip.seek( cdir_offset ) ) continue; quint32 signature; if ( zip.read( (char *)&signature, sizeof( signature ) ) != sizeof( signature ) ) continue; if ( signature == centralFileHeaderSignature ) break; } // Found cdir -- position the file on the first header return zip.seek( cdir_offset ); } bool readNextEntry( SplitZipFile & zip, CentralDirEntry & entry ) { CentralFileHeaderRecord record; if ( zip.read( (char *)&record, sizeof( record ) ) != sizeof( record ) ) return false; if ( record.signature != centralFileHeaderSignature ) return false; // Read file name int fileNameLength = qFromLittleEndian( record.fileNameLength ); entry.fileName = zip.read( fileNameLength ); if ( entry.fileName.size() != fileNameLength ) return false; // Skip extra fields if ( !zip.seek( ( zip.pos() + qFromLittleEndian( record.extraFieldLength ) ) + qFromLittleEndian( record.fileCommentLength ) ) ) return false; entry.localHeaderOffset = zip.calcAbsoluteOffset( qFromLittleEndian( record.offsetOfLocalHeader ), qFromLittleEndian( record.diskNumberStart ) ); entry.compressedSize = qFromLittleEndian( record.compressedSize ); entry.uncompressedSize = qFromLittleEndian( record.uncompressedSize ); entry.compressionMethod = getCompressionMethod( record.compressionMethod ); entry.fileNameInUTF8 = ( qFromLittleEndian( record.gpBits ) & 0x800 ) != 0; return true; } bool readLocalHeader( SplitZipFile & zip, LocalFileHeader & entry ) { LocalFileHeaderRecord record; if ( zip.read( (char *)&record, sizeof( record ) ) != sizeof( record ) ) return false; if ( record.signature != localFileHeaderSignature ) return false; // Read file name int fileNameLength = qFromLittleEndian( record.fileNameLength ); entry.fileName = zip.read( fileNameLength ); if ( entry.fileName.size() != fileNameLength ) return false; // Skip extra field if ( !zip.seek( zip.pos() + qFromLittleEndian( record.extraFieldLength ) ) ) return false; entry.compressedSize = qFromLittleEndian( record.compressedSize ); entry.uncompressedSize = qFromLittleEndian( record.uncompressedSize ); entry.compressionMethod = getCompressionMethod( record.compressionMethod ); return true; } SplitZipFile::SplitZipFile( const QString & name ) { setFileName( name ); } void SplitZipFile::setFileName( const QString & name ) { { QString lname = name.toLower(); if( lname.endsWith( ".zips" ) ) { appendFile( name ); return; } if( !lname.endsWith( ".zip" ) ) return; } if( QFileInfo( name ).isFile() ) { for( int i = 1; i < 100; i++ ) { QString name2 = name.left( name.size() - 2 ) + QString( "%1" ).arg( i, 2, 10, QChar( '0' ) ); if( QFileInfo( name2 ).isFile() ) appendFile( name2 ); else break; } appendFile( name ); } else { for( int i = 1; i < 1000; i++ ) { QString name2 = name + QString( ".%1" ).arg( i, 3, 10, QChar( '0' ) ); if( QFileInfo( name2 ).isFile() ) appendFile( name2 ); else break; } } } QDateTime SplitZipFile::lastModified() const { unsigned long ts = 0; for( QVector< QFile * >::const_iterator i = files.begin(); i != files.end(); ++i ) { unsigned long t = QFileInfo( (*i)->fileName() ).lastModified().toTime_t(); if( t > ts ) ts = t; } return QDateTime::fromTime_t( ts ); } qint64 SplitZipFile::calcAbsoluteOffset( qint64 offset, quint16 partNo ) { if( partNo >= offsets.size() ) return 0; return offsets.at( partNo ) + offset; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/zipfile.hh�������������������������������������������������������������������������0000664�0000000�0000000�00000004172�14435233205�0015621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org> * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef __ZIPFILE_HH_INCLUDED__ #define __ZIPFILE_HH_INCLUDED__ #include <QFile> #include <QDateTime> #include "splitfile.hh" /// Support for zip files in GoldenDict. Note that the implementation is /// strictly tailored to GoldenDict needs only. namespace ZipFile { // Support for split zip files class SplitZipFile : public SplitFile::SplitFile { public: SplitZipFile() {} SplitZipFile( const QString & name ); virtual void setFileName( const QString & name ); // Latest modified time for all parts QDateTime lastModified() const; // Calc absolute offset by relative offset in part and part nom qint64 calcAbsoluteOffset( qint64 offset, quint16 partNo ); }; enum CompressionMethod { Uncompressed, Deflated, Unsupported }; /// Entry from central dir struct CentralDirEntry { QByteArray fileName; quint32 localHeaderOffset, compressedSize, uncompressedSize; CompressionMethod compressionMethod; bool fileNameInUTF8; }; /// Represents contents of the local file header -- that what CentralDirEntry:: /// localHeaderOffset points at. struct LocalFileHeader { QByteArray fileName; quint32 compressedSize, uncompressedSize; CompressionMethod compressionMethod; }; /// Finds the central directory in the given file and positions it at its /// beginning. Returns true if the file is positioned, false otherwise (not a /// zip file or other error). /// Once the file is positioned, entries may be read by constructing Entry /// objects. bool positionAtCentralDir( SplitZipFile & ); /// Reads entry from the zip at its current offset. The file gets advanced /// by the size of entry, so it points to the next entry. /// Returns true on success, false otherwise. bool readNextEntry( SplitZipFile &, CentralDirEntry & ); /// Reads loca file header from the zip at its current offset. The file gets /// advanced by the size of entry and starts pointing to file data. /// Returns true on success, false otherwise. bool readLocalHeader( SplitZipFile &, LocalFileHeader & ); } #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������goldendict-1.5.0/zipsounds.cc�����������������������������������������������������������������������0000664�0000000�0000000�00000035653�14435233205�0016213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org> * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "zipsounds.hh" #include "file.hh" #include "folding.hh" #include "utf8.hh" #include "btreeidx.hh" #include "fsencoding.hh" #include "audiolink.hh" #include "indexedzip.hh" #include "filetype.hh" #include "gddebug.hh" #include "chunkedstorage.hh" #include "htmlescape.hh" #include <set> #include <string> #include <QFile> #include <QDir> #include <QDebug> #ifdef _MSC_VER #include <stub_msvc.h> #endif #include "qt4x5.hh" namespace ZipSounds { using std::string; using gd::wstring; using std::map; using std::multimap; using std::set; using BtreeIndexing::WordArticleLink; using BtreeIndexing::IndexedWords; using BtreeIndexing::IndexInfo; namespace { DEF_EX( exInvalidData, "Invalid data encountered", Dictionary::Ex ) enum { Signature = 0x5350495a, // ZIPS on little-endian, SPIZ on big-endian CurrentFormatVersion = 6 + BtreeIndexing::FormatVersion }; struct IdxHeader { uint32_t signature; // First comes the signature, ZIPS uint32_t formatVersion; // File format version, currently 1. uint32_t soundsCount; // Total number of sounds, for informative purposes only uint32_t indexBtreeMaxElements; // Two fields from IndexInfo uint32_t indexRootOffset; uint32_t chunksOffset; // The offset to chunks' storage } #ifndef _MSC_VER __attribute__((packed)) #endif ; bool indexIsOldOrBad( string const & indexFile ) { File::Class idx( indexFile, "rb" ); IdxHeader header; return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != Signature || header.formatVersion != CurrentFormatVersion; } wstring stripExtension( string const & str ) { wstring name; try { name = Utf8::decode( str ); } catch( Utf8::exCantDecode & ) { return name; } if( Filetype::isNameOfSound( str ) ) { wstring::size_type pos = name.rfind( L'.' ); if ( pos != wstring::npos ) name.erase( pos ); // Strip spaces at the end of name string::size_type n = name.length(); while( n && name.at( n - 1 ) == L' ' ) n--; if( n != name.length() ) name.erase( n ); } return name; } class ZipSoundsDictionary: public BtreeIndexing::BtreeDictionary { Mutex idxMutex; File::Class idx; IdxHeader idxHeader; sptr< ChunkedStorage::Reader > chunks; IndexedZip zipsFile; public: ZipSoundsDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ); virtual string getName() throw(); virtual map< Dictionary::Property, string > getProperties() throw() { return map< Dictionary::Property, string >(); } virtual unsigned long getArticleCount() throw() { return idxHeader.soundsCount; } virtual unsigned long getWordCount() throw() { return getArticleCount(); } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ); virtual sptr< Dictionary::DataRequest > getResource( string const & name ) THROW_SPEC( std::exception ); protected: virtual void loadIcon() throw(); }; ZipSoundsDictionary::ZipSoundsDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ): BtreeDictionary( id, dictionaryFiles ), idx( indexFile, "rb" ), idxHeader( idx.read< IdxHeader >() ) { chunks = new ChunkedStorage::Reader( idx, idxHeader.chunksOffset ); // Initialize the index openIndex( IndexInfo( idxHeader.indexBtreeMaxElements, idxHeader.indexRootOffset ), idx, idxMutex ); QString zipName = QDir::fromNativeSeparators( FsEncoding::decode( getDictionaryFilenames()[ 0 ].c_str() ) ); zipsFile.openZipFile( zipName ); zipsFile.openIndex( IndexInfo( idxHeader.indexBtreeMaxElements, idxHeader.indexRootOffset ), idx, idxMutex ); } string ZipSoundsDictionary::getName() throw() { string result = FsEncoding::basename( getDictionaryFilenames()[ 0 ] ); // Strip the extension result.erase( result.rfind( '.' ) ); return result; } sptr< Dictionary::DataRequest > ZipSoundsDictionary::getArticle( wstring const & word, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) THROW_SPEC( std::exception ) { vector< WordArticleLink > chain = findArticles( word, ignoreDiacritics ); for( unsigned x = 0; x < alts.size(); ++x ) { /// Make an additional query for each alt vector< WordArticleLink > altChain = findArticles( alts[ x ], ignoreDiacritics ); chain.insert( chain.end(), altChain.begin(), altChain.end() ); } multimap< wstring, uint32_t > mainArticles, alternateArticles; set< uint32_t > articlesIncluded; // Some synonims make it that the articles // appear several times. We combat this // by only allowing them to appear once. wstring wordCaseFolded = Folding::applySimpleCaseOnly( word ); if( ignoreDiacritics ) wordCaseFolded = Folding::applyDiacriticsOnly( wordCaseFolded ); for( unsigned x = 0; x < chain.size(); ++x ) { if ( articlesIncluded.find( chain[ x ].articleOffset ) != articlesIncluded.end() ) continue; // We already have this article in the body. // Ok. Now, does it go to main articles, or to alternate ones? We list // main ones first, and alternates after. // We do the case-folded comparison here. wstring headwordStripped = Folding::applySimpleCaseOnly( Utf8::decode( chain[ x ].word ) ); if( ignoreDiacritics ) headwordStripped = Folding::applyDiacriticsOnly( headwordStripped ); multimap< wstring, uint32_t > & mapToUse = ( wordCaseFolded == headwordStripped ) ? mainArticles : alternateArticles; mapToUse.insert( std::pair< wstring, uint32_t >( Folding::applySimpleCaseOnly( Utf8::decode( chain[ x ].word ) ), chain[ x ].articleOffset ) ); articlesIncluded.insert( chain[ x ].articleOffset ); } if ( mainArticles.empty() && alternateArticles.empty() ) return new Dictionary::DataRequestInstant( false ); // No such word string result; multimap< wstring, uint32_t >::const_iterator i; result += "<table class=\"lsa_play\">"; vector< char > chunk; char * nameBlock; for( i = mainArticles.begin(); i != mainArticles.end(); ++i ) { try { Mutex::Lock _( idxMutex ); nameBlock = chunks->getBlock( i->second, chunk ); if ( nameBlock >= &chunk.front() + chunk.size() ) { // chunks reader thinks it's okay since zero-sized records can exist, // but we don't allow that. throw ChunkedStorage::exAddressOutOfRange(); } } catch( ChunkedStorage::exAddressOutOfRange & ) { // Bad address continue; } uint16_t sz; memcpy( &sz, nameBlock, sizeof( uint16_t ) ); nameBlock += sizeof( uint16_t ); string name( nameBlock, sz ); nameBlock += sz; string displayedName = mainArticles.size() + alternateArticles.size() > 1 ? name : Utf8::encode( stripExtension( name ) ); result += "<tr>"; QUrl url; url.setScheme( "gdau" ); url.setHost( QString::fromUtf8( getId().c_str() ) ); url.setPath( Qt4x5::Url::ensureLeadingSlash( QString::fromUtf8( name.c_str() ) ) ); string ref = string( "\"" ) + url.toEncoded().data() + "\""; result += addAudioLink( ref, getId() ); result += "<td><a href=" + ref + "><img src=\"qrcx://localhost/icons/playsound.png\" border=\"0\" alt=\"Play\"/></a></td>"; result += "<td><a href=" + ref + ">" + Html::escape( displayedName ) + "</a></td>"; result += "</tr>"; } for( i = alternateArticles.begin(); i != alternateArticles.end(); ++i ) { try { Mutex::Lock _( idxMutex ); nameBlock = chunks->getBlock( i->second, chunk ); if ( nameBlock >= &chunk.front() + chunk.size() ) { // chunks reader thinks it's okay since zero-sized records can exist, // but we don't allow that. throw ChunkedStorage::exAddressOutOfRange(); } } catch( ChunkedStorage::exAddressOutOfRange & ) { // Bad address continue; } uint16_t sz; memcpy( &sz, nameBlock, sizeof( uint16_t ) ); nameBlock += sizeof( uint16_t ); string name( nameBlock, sz ); nameBlock += sz; string displayedName = mainArticles.size() + alternateArticles.size() > 1 ? name : Utf8::encode( stripExtension( name ) ); result += "<tr>"; QUrl url; url.setScheme( "gdau" ); url.setHost( QString::fromUtf8( getId().c_str() ) ); url.setPath( Qt4x5::Url::ensureLeadingSlash( QString::fromUtf8( name.c_str() ) ) ); string ref = string( "\"" ) + url.toEncoded().data() + "\""; result += addAudioLink( ref, getId() ); result += "<td><a href=" + ref + "><img src=\"qrcx://localhost/icons/playsound.png\" border=\"0\" alt=\"Play\"/></a></td>"; result += "<td><a href=" + ref + ">" + Html::escape( displayedName ) + "</a></td>"; result += "</tr>"; } result += "</table>"; Dictionary::DataRequestInstant * ret = new Dictionary::DataRequestInstant( true ); ret->getData().resize( result.size() ); memcpy( &(ret->getData().front()), result.data(), result.size() ); return ret; } sptr< Dictionary::DataRequest > ZipSoundsDictionary::getResource( string const & name ) THROW_SPEC( std::exception ) { // Remove extension for sound files (like in sound dirs) wstring strippedName = stripExtension( name ); vector< WordArticleLink > chain = findArticles( strippedName ); if ( chain.empty() ) return new Dictionary::DataRequestInstant( false ); // No such resource // Find sound uint32_t dataOffset = 0; for( int x = chain.size() - 1; x >= 0 ; x-- ) { vector< char > chunk; char * nameBlock = chunks->getBlock( chain[ x ].articleOffset, chunk ); uint16_t sz; memcpy( &sz, nameBlock, sizeof( uint16_t ) ); nameBlock += sizeof( uint16_t ); string fileName( nameBlock, sz ); nameBlock += sz; memcpy( &dataOffset, nameBlock, sizeof( uint32_t ) ); if( name.compare( fileName ) == 0 ) break; } sptr< Dictionary::DataRequestInstant > dr = new Dictionary::DataRequestInstant( true ); if ( zipsFile.loadFile( dataOffset, dr->getData() ) ) return dr; return new Dictionary::DataRequestInstant( false ); } void ZipSoundsDictionary::loadIcon() throw() { if ( dictionaryIconLoaded ) return; QString fileName = QDir::fromNativeSeparators( FsEncoding::decode( getDictionaryFilenames()[ 0 ].c_str() ) ); // Remove the extension fileName.chop( 4 ); if( !loadIconFromFile( fileName ) ) { // Load failed -- use default icons dictionaryNativeIcon = dictionaryIcon = QIcon(":/icons/playsound.png"); } dictionaryIconLoaded = true; } } vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing & initializing ) THROW_SPEC( std::exception ) { (void) initializing; vector< sptr< Dictionary::Class > > dictionaries; for( vector< string >::const_iterator i = fileNames.begin(); i != fileNames.end(); ++i ) { /// Only allow .zips extension if ( i->size() < 5 || strcasecmp( i->c_str() + ( i->size() - 5 ), ".zips" ) != 0 ) continue; try { vector< string > dictFiles( 1, *i ); string dictId = Dictionary::makeDictionaryId( dictFiles ); string indexFile = indicesDir + dictId; if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { gdDebug( "Zips: Building the index for dictionary: %s\n", i->c_str() ); File::Class idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); // We write a dummy header first. At the end of the process the header // will be rewritten with the right values. idx.write( idxHeader ); IndexedWords names, zipFileNames; ChunkedStorage::Writer chunks( idx ); quint32 namesCount; IndexedZip zipFile; if( zipFile.openZipFile( QDir::fromNativeSeparators( FsEncoding::decode( i->c_str() ) ) ) ) zipFile.indexFile( zipFileNames, &namesCount ); if( !zipFileNames.empty() ) { for( IndexedWords::iterator i = zipFileNames.begin(); i != zipFileNames.end(); ++i ) { vector< WordArticleLink > links = i->second; for( unsigned x = 0; x < links.size(); x++ ) { // Save original name uint32_t offset = chunks.startNewBlock(); uint16_t sz = links[ x ].word.size(); chunks.addToBlock( &sz, sizeof(uint16_t) ); chunks.addToBlock( links[ x ].word.c_str(), sz ); chunks.addToBlock( &links[ x ].articleOffset, sizeof( uint32_t ) ); // Remove extension for sound files (like in sound dirs) wstring word = stripExtension( links[ x ].word ); if( !word.empty() ) names.addWord( word, offset ); } } // Finish with the chunks idxHeader.chunksOffset = chunks.finish(); // Build the resulting zip file index IndexInfo idxInfo = BtreeIndexing::buildIndex( names, idx ); // That concludes it. Update the header. idxHeader.indexBtreeMaxElements = idxInfo.btreeMaxElements; idxHeader.indexRootOffset = idxInfo.rootOffset; idxHeader.signature = Signature; idxHeader.formatVersion = CurrentFormatVersion; idxHeader.soundsCount = namesCount; idx.rewind(); idx.write( &idxHeader, sizeof( idxHeader ) ); } else { idx.close(); QFile::remove( QDir::fromNativeSeparators( FsEncoding::decode( indexFile.c_str() ) ) ); throw exInvalidData(); } } dictionaries.push_back( new ZipSoundsDictionary( dictId, indexFile, dictFiles ) ); } catch( std::exception & e ) { gdWarning( "Zipped sounds pack reading failed: %s, error: %s\n", i->c_str(), e.what() ); } } return dictionaries; } } �������������������������������������������������������������������������������������goldendict-1.5.0/zipsounds.hh�����������������������������������������������������������������������0000664�0000000�0000000�00000001116�14435233205�0016210�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* This file is part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #ifndef ZIPSOUNDS_HH #define ZIPSOUNDS_HH #include "dictionary.hh" /// Support for compresses audio packs (.zips). namespace ZipSounds { using std::vector; using std::string; vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames, string const & indicesDir, Dictionary::Initializing & ) THROW_SPEC( std::exception ); } #endif // ZIPSOUNDS_HH ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������