././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1406815 css-parser-1.0.7/0000755000175000017500000000000000000000000012614 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559562852.0 css-parser-1.0.7/COPYING0000644000175000017500000010575500000000000013664 0ustar00kovidkovid 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 . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559562852.0 css-parser-1.0.7/COPYING.LESSER0000644000175000017500000001717200000000000014653 0ustar00kovidkovid GNU LESSER 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. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559562852.0 css-parser-1.0.7/MANIFEST.in0000644000175000017500000000013200000000000014346 0ustar00kovidkovidrecursive-include css_parser_tests *.py *.css include run_tests.py include COPYING.LESSER ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1406815 css-parser-1.0.7/PKG-INFO0000644000175000017500000000325000000000000013711 0ustar00kovidkovidMetadata-Version: 2.1 Name: css-parser Version: 1.0.7 Summary: A CSS Cascading Style Sheets library for Python Home-page: https://github.com/ebook-utils/css-parser Author: Various People Author-email: redacted@anonymous.net License: LGPL 3.0 or later Keywords: CSS,Cascading Style Sheets,CSSParser,DOM Level 2 Stylesheets,DOM Level 2 CSS Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Text Processing :: Markup :: HTML Description-Content-Type: text/markdown License-File: COPYING License-File: COPYING.LESSER css-parser ================ [![Build Status](https://github.com/ebook-utils/css-parser/workflows/CI/badge.svg)](https://github.com/ebook-utils/css-parser/actions?query=workflow%3ACI) A fork of the cssutils project based on version 1.0.2. This fork includes general bug fixes and extensions specific to editing and working with ebooks. The main python source code has been modified so that it will run without further conversion on both Python >= 2.7 and Python 3.X without any further modules required. All required modifications are handled local to each file For more information on usage, please see the [cssutils documentation](https://cssutils.readthedocs.io). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1624688637.0 css-parser-1.0.7/README.md0000644000175000017500000000124200000000000014072 0ustar00kovidkovidcss-parser ================ [![Build Status](https://github.com/ebook-utils/css-parser/workflows/CI/badge.svg)](https://github.com/ebook-utils/css-parser/actions?query=workflow%3ACI) A fork of the cssutils project based on version 1.0.2. This fork includes general bug fixes and extensions specific to editing and working with ebooks. The main python source code has been modified so that it will run without further conversion on both Python >= 2.7 and Python 3.X without any further modules required. All required modifications are handled local to each file For more information on usage, please see the [cssutils documentation](https://cssutils.readthedocs.io). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1140146 css-parser-1.0.7/css_parser_tests/0000755000175000017500000000000000000000000016202 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/__init__.py0000644000175000017500000000010300000000000020305 0ustar00kovidkovid"""css_parser unittests""" from __future__ import unicode_literals././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1611677859.0 css-parser-1.0.7/css_parser_tests/basetest.py0000644000175000017500000002534400000000000020376 0ustar00kovidkovidfrom __future__ import absolute_import, print_function, unicode_literals import logging import os import re import sys import unittest from contextlib import contextmanager from io import StringIO import css_parser """Base class for all tests""" TEST_HOME = os.path.dirname(os.path.abspath(__file__)) PY2x = sys.version_info < (3, 0) def msg3x(msg): """msg might contain unicode repr `u'...'` which in py3 is `u'...` needed by tests using ``assertRaisesMsg``""" if not PY2x and msg.find("u'"): msg = msg.replace("u'", "'") return msg def get_resource_filename(resource_name): """Get the resource filename. """ return os.path.join(TEST_HOME, *resource_name.split('/')) def get_sheet_filename(sheet_name): """Get the filename for the given sheet.""" # Extract all sheets since they might use @import sheet_dir = get_resource_filename('sheets') return os.path.join(sheet_dir, sheet_name) class BaseTestCase(unittest.TestCase): def _tempSer(self): "Replace default ser with temp ser." self._ser = css_parser.ser css_parser.ser = css_parser.serialize.CSSSerializer() def _restoreSer(self): "Restore the default ser." css_parser.ser = self._ser @staticmethod def _setHandler(): "sets log's new handler and returns StringIO instance to getvalue" s = StringIO() h = logging.StreamHandler(s) h.setFormatter(logging.Formatter('%(levelname)s %(message)s')) # remove if present already css_parser.log.removeHandler(h) css_parser.log.addHandler(h) return s @staticmethod def captureLog(log_level, callable, *args, **kwargs): """returns the output of an ad hoc created log (which doesn't affect the standard one). Example usage: warning = self.captureLog(logging.WARNING, css_parser.stylesheets.MediaQuery, 'unknown-media') self.assertEqual(warning, 'WARNING [...]') """ old_log = css_parser.log._log old_level = css_parser.log.getEffectiveLevel() css_parser.log.setLog(logging.getLogger('CSS_PARSER-IGNORE')) css_parser.log.setLevel(log_level) s = BaseTestCase._setHandler() callable(*args, **kwargs) result = s.getvalue() css_parser.log.setLog(old_log) css_parser.log.setLevel(old_level) return result def setUp(self): # a raising parser!!! css_parser.log.raiseExceptions = True css_parser.log.setLevel(logging.FATAL) self.p = css_parser.CSSParser(raiseExceptions=True) @contextmanager def patch_default_fetcher(self, return_value): import css_parser.util as cu orig = cu._defaultFetcher def defaultFetcher(*a): return return_value if callable(return_value): cu._defaultFetcher = return_value else: cu._defaultFetcher = defaultFetcher try: yield finally: cu._defaultFetcher = orig def tearDown(self): if hasattr(self, '_ser'): self._restoreSer() def assertRaisesEx(self, exception, callable, *args, **kwargs): """ from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/307970 """ if "exc_args" in kwargs: exc_args = kwargs["exc_args"] del kwargs["exc_args"] else: exc_args = None if "exc_pattern" in kwargs: exc_pattern = kwargs["exc_pattern"] del kwargs["exc_pattern"] else: exc_pattern = None argv = [repr(a) for a in args]\ + ["%s=%r" % (k, v) for k, v in kwargs.items()] callsig = "%s(%s)" % (callable.__name__, ", ".join(argv)) try: callable(*args, **kwargs) except exception as exc: if exc_args is not None: self.failIf(exc.args != exc_args, "%s raised %s with unexpected args: " "expected=%r, actual=%r" % (callsig, exc.__class__, exc_args, exc.args)) if exc_pattern is not None: self.assertTrue(exc_pattern.search(str(exc)), "%s raised %s, but the exception " "does not match '%s': %r" % (callsig, exc.__class__, exc_pattern.pattern, str(exc))) except Exception: exc_info = sys.exc_info() self.fail("%s raised an unexpected exception type: " "expected=%s, actual=%s" % (callsig, exception, exc_info[0])) else: self.fail("%s did not raise %s" % (callsig, exception)) def _assertRaisesMsgSubstring(self, excClass, msg, substring_match, callableObj, *args, **kwargs): try: callableObj(*args, **kwargs) except excClass as exc: excMsg = str(exc) if not msg: # No message provided: any message is fine. return elif (msg in excMsg if substring_match else msg == excMsg): # Message provided, and we got the right message: passes. return else: # Message provided, and it didn't match: fail! raise self.failureException( "Right exception, wrong message: got '%s' instead of '%s'" % (excMsg, msg)) else: if hasattr(excClass, '__name__'): excName = excClass.__name__ else: excName = str(excClass) raise self.failureException( "Expected to raise %s, didn't get an exception at all" % excName ) def assertRaisesMsg(self, excClass, msg, callableObj, *args, **kwargs): """ Just like unittest.TestCase.assertRaises, but checks that the message is right too. Usage:: self.assertRaisesMsg( MyException, "Exception message", my_function, arg1, arg2, kwarg1=val, kwarg2=val) from http://www.nedbatchelder.com/blog/200609.html#e20060905T064418 """ return self._assertRaisesMsgSubstring(excClass, msg, False, callableObj, *args, **kwargs) def assertRaisesMsgSubstring(self, excClass, msg, callableObj, *args, **kwargs): """ Just like assertRaisesMsg, but looks for substring in the message. """ return self._assertRaisesMsgSubstring(excClass, msg, True, callableObj, *args, **kwargs) def do_equal_p(self, tests, att='cssText', debug=False, raising=True): """ if raising self.p is used for parsing, else self.pf """ p = css_parser.CSSParser(raiseExceptions=raising) # parses with self.p and checks att of result for test, expected in tests.items(): if debug: print(('"%s"' % test)) s = p.parseString(test) if expected is None: expected = test ans = s.__getattribute__(att) if isinstance(ans, bytes): ans = ans.decode('utf-8') self.assertEqual(expected, ans) def do_raise_p(self, tests, debug=False, raising=True): # parses with self.p and expects raise p = css_parser.CSSParser(raiseExceptions=raising) for test, expected in tests.items(): if debug: print(('"%s"' % test)) self.assertRaises(expected, p.parseString, test) def do_equal_r(self, tests, att='cssText', debug=False): # sets attribute att of self.r and asserts Equal for test, expected in tests.items(): if debug: print(('"%s"' % test)) self.r.__setattr__(att, test) if expected is None: expected = test self.assertEqual(expected, self.r.__getattribute__(att)) def do_raise_r(self, tests, att='_setCssText', debug=False): # sets self.r and asserts raise for test, expected in tests.items(): if debug: print(('"%s"' % test)) self.assertRaises(expected, self.r.__getattribute__(att), test) def do_raise_r_list(self, tests, err, att='_setCssText', debug=False): # sets self.r and asserts raise for test in tests: if debug: print(('"%s"' % test)) self.assertRaises(err, self.r.__getattribute__(att), test) class GenerateTests(type): """Metaclass to handle a parametrized test. This works by generating many test methods from a single method. To generate the methods, you need the base method with the prefix "gen_test_", which takes the parameters. Then you define the attribute "cases" on this method with a list of cases. Each case is a tuple, which is unpacked when the test is called. Example:: def gen_test_length(self, string, expected): self.assertEquals(len(string), expected) gen_test_length.cases = [ ("a", 1), ("aa", 2), ] """ def __new__(cls, name, bases, attrs): new_attrs = {} for aname, aobj in attrs.items(): if not aname.startswith("gen_test_"): new_attrs[aname] = aobj continue # Strip off the gen_ test_name = aname[4:] cases = aobj.cases for case_num, case in enumerate(cases): stringed_case = cls.make_case_repr(case) case_name = "%s_%s_%s" % (test_name, case_num, stringed_case) # Force the closure binding def make_wrapper(case=case, aobj=aobj): def wrapper(self): aobj(self, *case) return wrapper wrapper = make_wrapper() wrapper.__name__ = str(case_name) wrapper.__doc__ = "%s(%s)" % (test_name, ", ".join(map(repr, case))) if aobj.__doc__ is not None: wrapper.__doc__ += "\n\n" + aobj.__doc__ new_attrs[case_name] = wrapper return type(name, bases, new_attrs) @classmethod def make_case_repr(cls, case): if isinstance(case, type('')): value = case else: try: iter(case) except TypeError: value = repr(case) else: value = '_'.join(cls.make_case_repr(x) for x in case) value = re.sub('[^A-Za-z_]', '_', value) value = re.sub('_{2,}', '_', value) return value ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1306815 css-parser-1.0.7/css_parser_tests/sheets/0000755000175000017500000000000000000000000017475 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/096.css0000644000175000017500000001023100000000000020522 0ustar00kovidkovid/* default stylesheet for all variations */ .jsonly { display: none } .stylenav { text-align: right; margin-top: -1em } html, body { padding: 0; margin: 0 } body { font: normal 90%/1.1 Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; color: #fff; background-color: #344 } h1, h2, h3, h4, h5, h6 { font: normal 2.4em Verdana, "Lucida Grande", Arial, Helvetica, sans-serif } h1, h2, h3, caption { color: #a00; margin: 0.3em -0.4em 0.5em -0.5em } h1, h2 { letter-spacing: -0.05em } h2, h3, caption { margin: 0.3em -1.3em 0.3em 0 } h2 { font-size: 1.6em; border-right: 1.3em solid #677; border-bottom: 1px dotted #677; padding-left: 0.1em; padding-bottom: 0.1em; margin-top: 2.1em; margin-bottom: 0.4em } h3 { font-size: 0.9em; text-transform: uppercase; margin-top: 1.2em; margin-bottom: 0.1em } caption { font-size: 1.05em; text-align: left; margin-bottom: 0.2em } h4 { font-size: 0.9em; font-weight: bold; margin: 1.2em 0 0 } h5, h6 { font-size: 1em; margin: 0 } h6 { font-size: 0.9em } p, ol, ul, dl { line-height: 1.3; margin-top: 0; margin-bottom: 1em } ul { list-style-type: square } ul.code { line-height: 1.3 } li, dd { margin-bottom: 0.3em } dt { font-weight: bold } pre, code { color: #00a; line-height: 1.4; font-size: 1.1em } pre { border: 1px solid #eee; overflow: auto } table code, table pre { font-size: 1.3em } .deprecated { color: #888 } table { font-size: 0.9em; border-collapse: collapse } tr { vertical-align: top; line-height: 1.3 } td, th { text-align: left; padding: 0.4em 0.5em; border-bottom: 1px dotted #667 } td.center { text-align: center } tr:hover, li:hover { background-color: #f8f8f8 } acronym, .explain { border-bottom: 1px dotted #344 } a { text-decoration: none; color: #fff; border-bottom: 1px solid #aaa } #main a { color: #a00 } a:visited { color: #eee } #main a:visited { color: #344 } a:hover { text-decoration: underline; color: #fff } #main a:hover { background-color: #f5f8ff } #main a:active { color: #fff; background-color: #abb } label { display: block; padding: 0.5em 0 0.1em } input, textarea { font: bold 1em Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; background-color: #eee; width: 100%; border: 1px inset } #submit, textarea { margin-bottom: 1.5em } #submit { font-weight: bold; color: #00a; background-color: #fff; border: 1px outset; margin-top: 2em } input:focus, input:hover, textarea:focus, textarea:hover { font-weight: bold; background-color: #fff; border-style: solid } #submit:hover, #submit:focus { background-color: #eee; border-style: solid } #submit:active { border-style: inset } #header { padding: 1.1em 5% 0; padding: 40px 5% 0; color: #334; background: #fff url(../static/img/header_r.jpg) no-repeat; border-bottom: 1px solid #344; height: 90px; he\ight: 50px } #header dt, #header p { font-family: Arial, Helvetica, sans-serif; letter-spacing: 0.3em } #header a { color: #334 } #main .nav { padding-bottom: 0.5em; border-bottom: 3px solid #eee; margin-bottom: 1em; margin-left: -8% } .nav dt, .nav dd, .nav dd ul, .nav li { display: inline } .nav dt { font-weight: bold } .nav li { font-weight: bold; padding-right: 0.5em } .nav a { font-weight: normal } #footer { padding: 1em 5% 1em 10%; border-top: 3px double #fff; text-align: right } #main { color: #000; background-color: #fff; padding: 1em 26% 1em 12% } .ext { margin-top: 2em } .ext a { font-size: 0.8em }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/097.css0000644000175000017500000001023100000000000020523 0ustar00kovidkovid/* default stylesheet for all variations */ .jsonly { display: none } .stylenav { text-align: right; margin-top: -1em } html, body { padding: 0; margin: 0 } body { font: normal 90%/1.1 Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; color: #fff; background-color: #344 } h1, h2, h3, h4, h5, h6 { font: normal 2.4em Verdana, "Lucida Grande", Arial, Helvetica, sans-serif } h1, h2, h3, caption { color: #a00; margin: 0.3em -0.4em 0.5em -0.5em } h1, h2 { letter-spacing: -0.05em } h2, h3, caption { margin: 0.3em -1.3em 0.3em 0 } h2 { font-size: 1.6em; border-right: 1.3em solid #677; border-bottom: 1px dotted #677; padding-left: 0.1em; padding-bottom: 0.1em; margin-top: 2.1em; margin-bottom: 0.4em } h3 { font-size: 0.9em; text-transform: uppercase; margin-top: 1.2em; margin-bottom: 0.1em } caption { font-size: 1.05em; text-align: left; margin-bottom: 0.2em } h4 { font-size: 0.9em; font-weight: bold; margin: 1.2em 0 0 } h5, h6 { font-size: 1em; margin: 0 } h6 { font-size: 0.9em } p, ol, ul, dl { line-height: 1.3; margin-top: 0; margin-bottom: 1em } ul { list-style-type: square } ul.code { line-height: 1.3 } li, dd { margin-bottom: 0.3em } dt { font-weight: bold } pre, code { color: #00a; line-height: 1.4; font-size: 1.1em } pre { border: 1px solid #eee; overflow: auto } table code, table pre { font-size: 1.3em } .deprecated { color: #888 } table { font-size: 0.9em; border-collapse: collapse } tr { vertical-align: top; line-height: 1.3 } td, th { text-align: left; padding: 0.4em 0.5em; border-bottom: 1px dotted #667 } td.center { text-align: center } tr:hover, li:hover { background-color: #f8f8f8 } acronym, .explain { border-bottom: 1px dotted #344 } a { text-decoration: none; color: #fff; border-bottom: 1px solid #aaa } #main a { color: #a00 } a:visited { color: #eee } #main a:visited { color: #344 } a:hover { text-decoration: underline; color: #fff } #main a:hover { background-color: #f5f8ff } #main a:active { color: #fff; background-color: #abb } label { display: block; padding: 0.5em 0 0.1em } input, textarea { font: bold 1em Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; background-color: #eee; width: 100%; border: 1px inset } #submit, textarea { margin-bottom: 1.5em } #submit { font-weight: bold; color: #00a; background-color: #fff; border: 1px outset; margin-top: 2em } input:focus, input:hover, textarea:focus, textarea:hover { font-weight: bold; background-color: #fff; border-style: solid } #submit:hover, #submit:focus { background-color: #eee; border-style: solid } #submit:active { border-style: inset } #header { padding: 1.1em 5% 0; padding: 40px 5% 0; color: #334; background: #fff url(../static/img/header_r.jpg) no-repeat; border-bottom: 1px solid #344; height: 90px; he\ight: 50px } #header dt, #header p { font-family: Arial, Helvetica, sans-serif; letter-spacing: 0.3em } #header a { color: #334 } #main .nav { padding-bottom: 0.5em; border-bottom: 3px solid #eee; margin-bottom: 1em; margin-left: -8% } .nav dt, .nav dd, .nav dd ul, .nav li { display: inline } .nav dt { font-weight: bold } .nav li { font-weight: bold; padding-right: 0.5em } .nav a { font-weight: normal } #footer { padding: 1em 5% 1em 10%; border-top: 3px double #fff; text-align: right } #main { color: #000; background-color: #fff; padding: 1em 26% 1em 12% } .ext { margin-top: 2em } .ext a { font-size: 0.8em }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1.css0000644000175000017500000000007300000000000020347 0ustar00kovidkovid@charset "utf-8"; /*äöüß*/ /* κουÏος */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1ascii.css0000644000175000017500000000010700000000000021356 0ustar00kovidkovid@charset "ascii"; @import "1inherit-ascii.css"; @import "1utf.css"; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1import.css0000644000175000017500000000006700000000000021605 0ustar00kovidkovid@charset "iso-8859-1"; @import "1inherit-iso.css"; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1inherit-ascii.css0000644000175000017500000000004000000000000023012 0ustar00kovidkovid/* inherited encoding ascii */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1inherit-iso.css0000644000175000017500000000010400000000000022515 0ustar00kovidkovid@import "2inherit-iso.css"; /* 1 inherited encoding iso-8859-1 */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1inherit-utf8.css0000644000175000017500000000003600000000000022615 0ustar00kovidkovid/* inherit encoding utf-8 */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/1utf.css0000644000175000017500000000010100000000000021056 0ustar00kovidkovid@charset "utf-8"; @import "1inherit-utf8.css"; /* € */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/2inherit-iso.css0000644000175000017500000000004700000000000022524 0ustar00kovidkovid/* 2 inherited encoding iso-8859-1 */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/2resolve.css0000644000175000017500000000004400000000000021746 0ustar00kovidkovid@charset "ascii"; @namespace "x"; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/acid2.css0000644000175000017500000002243600000000000021200 0ustar00kovidkovid /* section numbers refer to CSS2.1 */ /* page setup */ html { font: 12px sans-serif; margin: 0; padding: 0; overflow: hidden; /* hides scrollbars on viewport, see 11.1.1:3 */ background: white; color: red; } body { margin: 0; padding: 0; } /* introduction message */ .intro { font: 2em sans-serif; margin: 3.5em 2em; padding: 0.5em; border: solid thin; background: white; color: black; position: relative; z-index: 2; /* should cover the black and red bars that are fixed-positioned */ } .intro * { font: inherit; margin: 0; padding: 0; } .intro h1 { font-size: 1em; font-weight: bolder; margin: 0; padding: 0; } .intro :link { color: blue; } .intro :visited { color: purple; } /* picture setup */ #top { margin: 100em 3em 0; padding: 2em 0 0 .5em; text-align: left; font: 2em/24px sans-serif; color: navy; white-space: pre; } /* "Hello World!" text */ .picture { position: relative; border: 1em solid transparent; margin: 0 0 100em 3em; } /* containing block for face */ .picture { background: red; } /* overriden by preferred stylesheet below */ /* top line of face (scalp): fixed positioning and min/max height/width */ .picture p { position: fixed; margin: 0; padding: 0; border: 0; top: 9em; left: 11em; width: 140%; max-width: 4em; height: 8px; min-height: 1em; max-height: 2mm; /* min-height overrides max-height, see 10.7 */ background: black; border-bottom: 0.5em yellow solid; } /* bits that shouldn't be part of the top line (and shouldn't be visible at all): HTML parsing, "+" combinator, stacking order */ .picture p.bad { border-bottom: red solid; /* shouldn't matter, because the "p + table + p" rule below should match it too, thus hiding it */ } .picture p + p { background: maroon; z-index: 1; } /* shouldn't match anything */ .picture p + table + p { margin-top: 3em; /* should end up under the absolutely positioned table below, and thus not be visible */ } /* second line of face: attribute selectors, float positioning */ [class~=one].first.one { position: absolute; top: 0; margin: 36px 0 0 60px; padding: 0; border: black 2em; border-style: none solid; /* shrink wraps around float */ } [class~=one][class~=first] [class=second\ two][class="second two"] { float: right; width: 48px; height: 12px; background: yellow; margin: 0; padding: 0; } /* only content of abs pos block */ /* third line of face: width and overflow */ .forehead { margin: 4em; width: 8em; border-left: solid black 1em; border-right: solid black 1em; background: red url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC); /* that's a 1x1 yellow pixel PNG */ } .forehead * { width: 12em; line-height: 1em; } /* class selectors headache */ .two.error.two { background: maroon; } /* shouldn't match */ .forehead.error.forehead { background: red; } /* shouldn't match */ [class=second two] { background: red; } /* this should be ignored (invalid selector -- grammar says it only accepts IDENTs or STRINGs) */ /* fourth and fifth lines of face, with eyes: paint order test (see appendix E) and fixed backgrounds */ /* the two images are identical: 2-by-2 squares with the top left and bottom right pixels set to yellow and the other two set to transparent. Since they are offset by one pixel from each other, the second one paints exactly over the transparent parts of the first one, thus creating a solid yellow block. */ .eyes { position: absolute; top: 5em; left: 3em; margin: 0; padding: 0; background: red; } #eyes-a { height: 0; line-height: 2em; text-align: right; } /* contents should paint top-most because they're inline */ #eyes-a object { display: inline; vertical-align: bottom; } #eyes-a object[type] { width: 7.5em; height: 2.5em; } /* should have no effect since that object should fallback to being inline (height/width don't apply to inlines) */ #eyes-a object object object { border-right: solid 1em black; padding: 0 12px 0 11px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D) fixed 1px 0; } #eyes-b { float: left; width: 10em; height: 2em; background: fixed url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D); border-left: solid 1em black; border-right: solid 1em red; } /* should paint in the middle layer because it is a float */ #eyes-c { display: block; background: red; border-left: 2em solid yellow; width: 10em; height: 2em; } /* should paint bottom most because it is a block */ /* lines six to nine, with nose: auto margins */ .nose { float: left; margin: -2em 2em -1em; border: solid 1em black; border-top: 0; min-height: 80%; height: 60%; max-height: 3em; /* percentages become auto (see 10.5 and 10.7) and intrinsic height is more than 3em, so 3em wins */ padding: 0; width: 12em; } .nose > div { padding: 1em 1em 3em; height: 0; background: yellow; } .nose div div { width: 2em; height: 2em; background: red; margin: auto; } .nose :hover div { border-color: blue; } .nose div:hover :before { border-bottom-color: inherit; } .nose div:hover :after { border-top-color: inherit; } .nose div div:before { display: block; border-style: none solid solid; border-color: red yellow black yellow; border-width: 1em; content: ''; height: 0; } .nose div :after { display: block; border-style: solid solid none; border-color: black yellow red yellow; border-width: 1em; content: ''; height: 0; } /* between lines nine and ten: margin collapsing with 'float' and 'clear' */ .empty { margin: 6.25em; height: 10%; /* computes to auto which makes it empty per 8.3.1:7 (own margins) */ } .empty div { margin: 0 2em -6em 4em; } .smile { margin: 5em 3em; clear: both; /* clearance is negative (see 8.3.1 and 9.5.1) */ } /* line ten and eleven: containing block for abs pos */ .smile div { margin-top: 0.25em; background: black; width: 12em; height: 2em; position: relative; bottom: -1em; } .smile div div { position: absolute; top: 0; right: 1em; width: auto; height: 0; margin: 0; border: yellow solid 1em; } /* smile (over lines ten and eleven): backgrounds behind borders, inheritance of 'float', nested floats, negative heights */ .smile div div span { display: inline; margin: -1em 0 0 0; border: solid 1em transparent; border-style: none solid; float: right; background: black; height: 1em; } .smile div div span em { float: inherit; border-top: solid yellow 1em; border-bottom: solid black 1em; } /* zero-height block; width comes from (zero-height) child. */ .smile div div span em strong { width: 6em; display: block; margin-bottom: -1em; /* should have no effect, since parent has top&bottom borders, so this margin doesn't collapse */ } /* line twelve: line-height */ .chin { margin: -4em 4em 0; width: 8em; line-height: 1em; border-left: solid 1em black; border-right: solid 1em black; background: yellow url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D) /* 64x64 red square */ no-repeat fixed /* shouldn't be visible unless the smiley is moved to the top left of the viewport */; } .chin div { display: inline; font: 2px/4px serif; } /* line thirteen: cascade and selector tests */ .parser-container div { color: maroon; border: solid; color: orange; } /* setup */ div.parser-container * { border-color: black; /* overrides (implied) border-color on previous line */ } /* setup */ * div.parser { border-width: 0 2em; /* overrides (implied) declarations on earlier line */ } /* setup */ /* line thirteen continued: parser tests */ .parser { /* comment parsing test -- comment ends before the end of this line, the backslash should have no effect: \*/ } .parser { margin: 0 5em 1em; padding: 0 1em; width: 2em; height: 1em; error: \}; background: yellow; } /* setup with parsing test */ * html .parser { background: gray; } \.parser { padding: 2em; } .parser { m\argin: 2em; }; .parser { height: 3em; } .parser { width: 200; } .parser { border: 5em solid red ! error; } .parser { background: red pink; } /* line fourteen (last line of face): table */ ul { display: table; padding: 0; margin: -1em 7em 0; background: red; } ul li { padding: 0; margin: 0; } ul li.first-part { display: table-cell; height: 1em; width: 1em; background: black; } ul li.second-part { display: table; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */ ul li.third-part { display: table-cell; height: 0.5em; /* gets stretched to fit row */ width: 1em; background: black; } ul li.fourth-part { list-style: none; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */ /* bits that shouldn't appear: inline alignment in cells */ .image-height-test { height: 10px; overflow: hidden; font: 20em serif; } /* only the area between the top of the line box and the top of the image should be visible */ table { margin: 0; border-spacing: 0; } td { padding: 0; }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/all.css0000644000175000017500000000041000000000000020752 0ustar00kovidkovid/* this sheet should be used in the HTML, deploy.py does resolve * all imports so that only a single CSS is deployed to cut down * the number of HTTP requests */ @import url(1.css); @import url(atrule.css); @import url(simple.css); @namespace a 'x';././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/atrule.css0000644000175000017500000000021100000000000021475 0ustar00kovidkovid@charset "UTF-8"; @three-dee { @background-lighting { azimuth: 30deg; elevation: 190deg; } H1 { color: red } } @page { foo: bar } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/bad.css0000644000175000017500000000011100000000000020726 0ustar00kovidkovidP { color: yellow; background-color: blue; 1rogue: catch me if you can } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/basic.css0000644000175000017500000000014200000000000021265 0ustar00kovidkovid{ foo: 1.5; bogus: 3, 2, 1; bar-color: #0FEED0; background: #abc; foreground: rgb( 10, 20, 30 ) } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/bundle.css0000644000175000017500000062665700000000000021506 0ustar00kovidkovid@charset "UTF-8";html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}table{border-collapse:separate;border-spacing:0}caption,th,td{text-align:left;font-weight:normal}blockquote:before,blockquote:after,q:before,q:after{content:""}blockquote,q{quotes:"" ""}.clearfix:after,#content:after{content:".";display:block;height:0;clear:both;visibility:hidden}.clearfix{zoom:1;display:block}body{font:13px/1.231 sans-serif;*font-size:small;*font:x-small}select,input,button,textarea,button{font:99% sans-serif}table{font-size:inherit;font:100%}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%}div{position:relative}section{position:relative;display:block}.sane-defaults code{border:1px solid #ddd;background:#f7f7f7;padding:0 2px;font-size:12px}.sane-defaults pre code{border:0;background:0;padding:0;font-size:inherit}.sane-defaults h2,#wrapper .sane-defaults h2{margin:6px 0;padding:10px 0 0;font-weight:normal;font-size:18px;line-height:24px;color:#666}.sane-defaults h3,#wrapper .sane-defaults h3{margin:6px 0;padding:10px 0 0;font-weight:normal;font-size:16px;line-height:21px;color:#666}.sane-defaults h1{margin-bottom:9px;line-height:24px;color:#333}.sane-defaults h4{font-weight:normal;color:#666}.sane-defaults ol{list-style:decimal inside}.sane-defaults p{margin:0 0 9px}.sane-defaults pre{margin:0 0 1em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:1px solid #ddd;background:#f7f7f7;padding:3px 6px;font-size:12px;line-height:16px;white-space:pre-wrap}.sane-defaults ul{list-style:disc inside}.sane-defaults-2{line-height:18px;counter-reset:ordered-list-item}.sane-defaults-2 h1,.sane-defaults-2 h2,.sane-defaults-2 h3,.sane-defaults-2 h4,.sane-defaults-2 h5,.sane-defaults-2 h6{color:#666}.sane-defaults-2 ol,.sane-defaults-2 ul{margin:0;padding:0 0 0 36px;list-style:none}.sane-defaults-2 ol{counter-reset:ordered-list-item}.sane-defaults-2 ol>li:before{position:absolute;margin:0 0 0 -21px;content:counter(ordered-list-item) ".";counter-increment:ordered-list-item}.sane-defaults-2 ul>li:before{position:absolute;margin:-1px 0 0 -12px;font-size:18px;content:"•"}.sane-defaults-2 li{margin:9px 0}.sane-defaults-2 p{margin:9px 0}.sane-defaults-2 pre{margin:9px 0;padding:9px 12px}.sane-defaults-2 img{display:block;max-width:100%}.ie8 .sane-defaults-2 img{max-width:none}@media print{*{color:#000!important}body{background:#fff!important}#tabs,#footer,#search,#repo-menu{display:none}}body{margin:0;background:#fff;padding:0;line-height:16px;font-family:Helvetica,Arial,sans-serif;color:#393939}p{line-height:18px}a{text-decoration:none;color:#2b547d}a:hover{text-decoration:underline}table{border-spacing:3px}h3,strong{font-weight:bold}em{font-style:italic}.leading{line-height:1.4em}.hidden{display:none}table input,select{margin-bottom:1em}.column ul{margin-top:10px;margin-left:15px;list-style:disc outside none}#introduction_{background:#035;background:-webkit-gradient(linear,left top,left bottom,from(#036),to(#035));background:-webkit-linear-gradient(#036,#035);background:-moz-linear-gradient(top,#036,#035);background:-o-linear-gradient(#036,#035);overflow:hidden}#header,#content,#footer,#introduction_>div{margin:0 auto;width:930px}#introduction_>div>div{float:left}#introduction_ li{line-height:19px}#header-wrap{background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABMCAIAAAAjsUpsAAAALUlEQVQY063JsREAMAzCQB37r8ZCNGQCu4kLNS+SCFBbAWPTv/Afu/Tl01a2H1C2LMMtdA10AAAAAElFTkSuQmCC) repeat-x 0 -6px}#header{position:relative;height:70px}#logo{display:block;float:left;margin-top:17px;width:127px;height:40px;background:url(../images/logo.png) 0 0;line-height:99em;overflow:hidden}#global-nav{position:absolute;right:0;top:5px}#global-nav li{float:left;margin-left:8px}#global-nav li a{background-image:url(../images/global-header-icons.png);background-repeat:no-repeat;padding-left:18px;font-size:85%;color:#8a8a8a}#global-nav li a:hover{text-decoration:none;color:#2b547d}#global-nav a.home{background-position:0 0}#global-nav a.home:hover{background-position:0 -28px}#global-nav a.docs{background-position:0 -57px}#global-nav a.docs:hover{background-position:0 -85px}#global-nav a.support{background-position:0 -117px}#global-nav a.support:hover{background-position:0 -145px}#global-nav a.blog{background-position:0 -177px}#global-nav a.blog:hover{background-position:0 -205px}#global-nav a.plugins{background-position:0 -236px}#global-nav a.plugins:hover{background-position:0 -264px}#global-nav a.forums{background-position:0 -296px}#global-nav a.forums:hover{background-position:0 -324px}#main-nav{float:right;margin-top:43px}#main-nav li{float:left;margin-right:4px;position:relative}#main-nav li a{padding:10px;font-size:108%}#main-nav>ul>li>a{color:#393939}#main-nav li.active,#main-nav #repositories-dropdown.active .drop-arrow{font-weight:bold}#main-nav #repositories-dropdown.active{font-weight:inherit}#main-nav li a span{font-size:85%;color:#6f6f6f}#main-nav>ul>li>a:hover,#main-nav>ul>li>a:hover>span{text-decoration:none;color:#036}#repositories-dropdown>div{position:absolute;width:256px;top:26px;left:-11px;padding:0 4px 4px 4px;overflow:hidden}#repositories-dropdown>div>div{-webkit-border-bottom-left-radius:5px;-webkit-border-bottom-right-radius:5px;-moz-border-bottom-left-radius:5px;-moz-border-bottom-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border:1px solid #ccc;border-top:0;background:#fff;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.2);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.2);box-shadow:0 1px 3px rgba(0,0,0,0.2);font-size:13px;line-height:16px;color:#333}#repositories-dropdown>div>div a{position:relative;display:block;padding:0 0 0 44px;font-size:inherit;line-height:28px;white-space:nowrap}#repositories-dropdown>div>div a:hover{background-color:#fff6c2;text-decoration:none}#repositories-dropdown>div>div a:after{position:absolute;right:0;top:0;bottom:0;display:block;width:16px;border-right:8px solid #fff;background:-webkit-gradient(linear,0 0,100% 0,from(rgba(255,255,255,0)),to(rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0),rgba(255,255,255,1));background:-moz-linear-gradient(left,rgba(255,255,255,0),rgba(255,255,255,1));background:-o-linear-gradient(left,rgba(255,255,255,0),rgba(255,255,255,1));content:""}#repositories-dropdown>div>div a:hover:after{border-right-color:#fff6c2;background:-webkit-gradient(linear,0 0,100% 0,from(rgba(255,246,194,0)),to(rgba(255,246,194,1)));background:-webkit-linear-gradient(left,rgba(255,246,194,0),rgba(255,246,194,1));background:-moz-linear-gradient(left,rgba(255,246,194,0),rgba(255,246,194,1));background:-o-linear-gradient(left,rgba(255,246,194,0),rgba(255,246,194,1))}#repositories-dropdown>div>div a>img{position:absolute;left:16px;top:4px;display:block;width:16px;height:16px;border:1px solid #ddd;background:#fff;padding:1px}#main-nav ul{float:right}#repositories-dropdown{margin-right:-4px}#repositories-dropdown a.drop-arrow{background:url(../images/drop-arrows.png) no-repeat 99px 14px;padding-right:25px}#repositories-dropdown.active a.drop-arrow{background-position:107px 14px}#repositories-dropdown.inertial-hover>a{margin-left:-6px;margin-right:-6px;padding-left:16px;padding-right:31px}#repositories-dropdown.inertial-hover.hovering>a{margin:-1px -7px;-webkit-border-top-left-radius:5px;-webkit-border-top-right-radius:5px;-moz-border-top-left-radius:5px;-moz-border-top-right-radius:5px;border-top-left-radius:5px;border-top-right-radius:5px;border:1px solid #ccc;background-color:#fff}#repo-overview h4{margin:0;padding:3px 0 6px;font-size:inherit;color:inherit}#repo-overview ol{margin:0 -16px}#repo-overview ol li{float:none;margin:0;height:28px;overflow:hidden}#repo-overview ol li a span{font-size:inherit;color:#999}#main-nav .inertial-hover>div{display:none}#main-nav .inertial-hover>div:hover,#main-nav .inertial-hover.hovering>div{display:block;z-index:3}#repositories-dropdown .group{border-top:1px solid #ddd;padding:12px 16px}#repositories-dropdown .new-repository{margin:0 -16px;background:url(../img/icons/fugue/plus.png) no-repeat 18px;padding:0 16px 0 44px;text-transform:lowercase}#repositories-dropdown .import-repository{margin:0 -16px;background:url(../img/icons/fugue/import.png) no-repeat 18px;padding:0 16px 0 44px;text-transform:lowercase}#main-nav li.search-box{margin:0;width:146px;height:20px}#main-nav #searchbox{position:relative;top:-3px;width:117px;border:1px solid #cfcfcf;background:#fff url(../images/search-icon.png) no-repeat 4px;color:#393939}body.non-webkit #main-nav #searchbox{padding:3px 0 3px 23px}#main-nav #searchbox.placeholder{color:#999}h1{font-weight:bold;font-size:20px;line-height:28px;color:#2b547d}#introduction_{position:relative}#introduction_ h1{margin:0;margin-bottom:8px;border:0;padding:28px 0 15px;text-align:left;font-weight:bold;font-size:300%;line-height:30px;color:#c5efff}#introduction_ h2{padding-bottom:10px;font-weight:bold;font-size:200%;margin-bottom:20px;color:#c5efff}#introduction_ ul{margin-top:0;margin-left:0;list-style:none}#introduction_ li{margin-bottom:7px;background:url(../images/yellow-bullet.png) no-repeat 0 50%;padding-left:15px;font-size:123.1%;color:#fff}#introduction_ .split-column{margin-bottom:10px}#introduction_ .button-wrap{margin:0 auto 15px;width:230px}#introduction_ .column{margin-right:0;margin-bottom:50px;width:650px}#introduction_ .last{width:250px}#introduction_ h2,#plans-and-billing h2{border-bottom:0}#introduction_ #signup-button{margin-top:0}#introduction_ #signup-button a{display:block;margin-left:-100px;width:250px;height:46px;background:url(../images/yellow-button-large.png) no-repeat 0 -75px;padding:29px 0 0 48px;font:bold 24px/16px Helvetica,Arial,sans-serif;color:#173562}#introduction_ #signup-button a:hover{background-position:0 0;text-decoration:none;color:inherit}.atl-button{display:block;background:url(../images/small-blue-button-left.png) no-repeat left top}.atl-button:hover{text-decoration:none}.atl-button>span{display:block;background:url(../images/small-blue-button-right.png) no-repeat right top;padding:0 16px 0 0;line-height:22px}.atl-button>span>span{background:url(../images/blue-arrow.png) no-repeat right 60%;padding:0 12px 0 0;color:#fff}.two-column,.three-column{overflow:visible}#content .three-column{margin-bottom:20px}#content{padding:30px 25px 180px}#content h2{font-weight:bold;font-size:146.5%;color:#2b547d}#content #headline{border-bottom:0}#content h3{color:#2b547d}.two-column .column{display:inline;float:left;margin-right:25px;width:450px}.column.last,.three-column .column.last{float:right;margin-right:0}.three-column .column{display:inline;float:left;margin-right:30px;width:286px}.split-column{overflow:hidden}.split-column .first,.split-column .last{float:left;width:50%}.plan-choices{margin-top:18px;overflow:hidden}.plan-choices .plan{position:relative;display:inline;float:left;margin-right:18px;width:140px;min-height:135px;background:url(../images/plan-box-bottom.png) no-repeat center bottom;text-align:center}#content .plan-choices .featured{margin-top:-10px;min-height:167px;background:url(../images/featured-plan-bottom.png) no-repeat center bottom}.plan-choices .featured h3{background:url(../images/featured-plan-top.png) no-repeat center top}.plan-choices span.offer-terms{position:relative;top:-6px;display:block;font-size:93%;font-size:11px}.plan-choices span.date{font-weight:bold;color:#2b547d}#content .plan h3{margin-top:0;background:url(../images/plan-box-top.png) no-repeat center top;padding-top:10px;font-weight:bold;font-size:161.6%;color:#065a9b}#content .plan-choices .last h3{font-size:124.5%}#content .plan p{color:#414141}#content .plan p.free{position:absolute;left:46px;bottom:51px;display:inline;padding-left:10px;font-size:123.1%}#content .plan p.price{position:absolute;left:46px;bottom:51px;display:inline;background:url(../images/dollar-sign.png) no-repeat left top;padding-left:10px;font-size:146.5%}#content .plan p.price span{font-size:12px}.plan a.atl-button{position:absolute;left:26px;bottom:10px;background:url(../images/small-blue-button-left.png) no-repeat top left;padding-left:16px}.plan a.atl-button:hover{background-position:left -37px}#content .featured a.atl-button{bottom:20px}.plan a.atl-button:hover>span{background-position:right -37px}.plan .current-plan{position:absolute;left:10px;bottom:11px;width:118px;background:url(../images/current-plan-bg.png) no-repeat scroll 0 -1px;padding-bottom:2px;font-weight:bold;font-size:85%}.featured .current-plan{bottom:21px}#content .plan h3 span{display:block;font-weight:normal;font-size:93%}.plan-choices .plan.last{margin-right:0}.plans-included{float:right;font-size:116%;color:#065a9b}#footer{clear:both;margin-top:-101px;height:80px;border-top:1px solid #c1c1c1;padding:10px 0;color:#414141}#footer li{display:inline;height:16px;line-height:16px}#footer-nav li+li:before{padding:0 6px;content:"\0020|\0020"}#footer-nav a{color:inherit}#social-nav{margin:-16px 0 12px 730px;width:200px;text-align:right}#social-nav li{margin-left:6px}#social-nav a:hover{text-decoration:none}#social-nav li.blog a{background:url(../images/rss-icon.png) no-repeat right 1px;padding-right:15px}#social-nav li.twitter a{background:url(../images/twitter-bird-small.png) no-repeat right 3px;padding-right:20px}#footer h5,#technologies{display:inline;font-size:11px;color:#888}#technologies li+li:before{padding:0 4px;content:"\0020/\0020"}#technologies a{color:inherit}table th{vertical-align:top}body.plans #content #features div.first{margin-left:0}.site-message{background:#e7f9e0;text-align:center;height:30px}.site-message p{display:inline-block;background:url(../images/green-upgrade-arrow-icon.png) no-repeat 0 50%;padding-left:26px;line-height:30px;color:#2d6b00}.site-message a,.site-message a:hover,.site-message a:visited{color:#2d6b00;text-decoration:underline}.site-message a:hover{text-decoration:none}.center-blocks{margin:0 auto;width:465px}#search-documentation{width:396px;border:1px solid #c3dfea;background:url(../images/search-docs-bg.png) no-repeat left;padding:8px 0 9px 40px;font-size:153.9%}.delete-list ul.errorlist li{border-bottom:0}.login-box{min-height:150px}.login-box .standard div{padding:5px 0}.login-box .standard .error.message{margin-top:-14px;padding:10px 20px 10px 50px}.login-box .standard label{display:inline-block;width:120px;padding-right:6px;line-height:30px}.login-box .standard label:after{content:":"}.login-box form div ul.errorlist,.login-box .standard .primary-button{margin-left:132px}.login-box .standard th{width:120px}#content .all-plans-include ul li{margin-bottom:12px;font-size:18px;color:#1378d5}.all-plans-include ul li span{color:#393939}.signup .standard .extra-info{margin-top:-14px;margin-left:-20px;margin-bottom:18px;width:418px;border-bottom:1px solid #ccc;background:#dff7d5;padding:9px 15px 9px 20px;color:#2d6b00}.signup .standard .extra-info p{line-height:17px}.redirect-to-atl{background:url(../images/redirect-bb-to-atl.jpg) no-repeat 50%;text-align:center}.redirect-to-atl .redirection{padding:125px 0 100px}.redirect-to-atl .redirection p{font-weight:bold;font-size:20px;line-height:1.5em;color:#2b547d}.follow{background:url(../img/icons/fugue/heart__plus.png) no-repeat 0;padding-left:20px}.follow.following{background-image:url(../img/icons/fugue/heart.png)}.follow.following:hover{background-image:url(../img/icons/fugue/heart_break.png)}.follow.loading{background-image:url(../img/loading.gif)}.tabs{margin-bottom:-1px;padding:0 4px;font-family:"Lucida Grande","Lucida Sans Unicode","Lucida Sans",sans-serif;font-size:12px;color:#525252}.tabs>li{display:block;float:left;margin:0 1px;min-width:60px;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;border:1px solid #ddd;background:#fafafa;background:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f5f5f5));background:-webkit-linear-gradient(#fff,#f5f5f5);background:-moz-linear-gradient(top,#fff,#f5f5f5);background:-o-linear-gradient(#fff,#f5f5f5)}.tabs>li+li{margin-left:0}.tabs a{display:block;padding:1px 8px;text-align:center;font-weight:bold;font-size:11px;line-height:21px;text-decoration:none;color:inherit;outline:0}.tabs>.selected,.tabs li:hover{background:#fff}.tabs>.selected>a{margin-bottom:-1px;border-bottom:1px solid #fff}.tabs .secondary{float:right}#tabs{position:relative;margin:0 -2px;height:24px;padding:0 2px;z-index:2}#content.repo-desc-hidden #tabs{margin:0;padding:0}.dropdown{position:relative}.dropdown ul{display:none;width:150px}.dropdown.inertial-hover.hovering>ul,.dropdown:hover ul{display:block;position:absolute;top:24px;left:-1px;z-index:10}.dropdown li:first-child a{background:#fff url(../img/icons/fugue/plus.png) no-repeat 123px}.dropdown li a{display:block;margin-top:-1px;border:1px solid #ddd;background:#fff;padding:0 10px;text-align:left;font-weight:normal;line-height:24px}.dropdown li a:hover{text-decoration:underline}html,body{height:100%}#wrapper{min-height:100%}form{margin-bottom:0}input{line-height:16px}input[type="email"],input[type="password"],input[type="search"],input[type="text"],textarea{background:#fff;color:#525252;padding:3px}input[type="radio"],input[type="checkbox"],input[type="submit"],input[type="reset"],input[type="button"],input[type="file"],select{font-size:1em}select{background:#fff;line-height:16px;color:#525252}h1,h2,h3,h4,h5,h6{margin-top:0;padding-top:0;margin-bottom:3px}hr{padding:0;margin:5px 0;height:1px;border:0;border-bottom:1px solid #ddd}code,pre,textarea{font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace}pre,textarea{font-size:11px}a img{border:0}fieldset{margin:0;border:0;padding:0}h1 a{color:#525252}div{margin:0;padding:0}p{margin:0;padding:0}table{padding:0;margin:0}th{vertical-align:top;text-align:left}.layout-box{border:1px solid #ddd;padding:15px 24px}.primary-button{border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #bbb;border-right:1px solid #bbb;background:#2b547d;padding:3px 10px;color:#fff;cursor:default}.primary-button:hover{text-decoration:none}.delete-button{border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #bbb;border-right:1px solid #bbb;background:#f44;padding:2px 8px;color:#fff}.secondary-button{border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #bbb;border-right:1px solid #bbb;padding:2px 8px;background:#eee;color:#000}.secondary-button-darkbg{background:#fff;color:#000}.bump-button{float:right;margin-right:14px}table.maintable{margin:12px 0;width:100%;border:1px solid #ddd;border-collapse:collapse}table.maintable th,table.maintable td{border-style:solid;border-color:#ddd;padding:.75em 10px .75em 20px}table.maintable th+th,table.maintable td+td{padding-left:10px}table.maintable td{font:12px/1.5em "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace}table.maintable th{font-weight:bold;line-height:18px;text-shadow:0 1px 0 #fff}table.maintable tbody tr:nth-child(even){background:#fcfcfc}table.maintable th,table.maintable td{border-bottom:1px solid #ddd}#wiki .highlight,.highlighttable .highlight{overflow-x:auto;padding-left:3px;padding-right:3px;width:882px}#wiki .highlight pre a:target,.highlighttable .highlight pre a:target{position:absolute;left:0;display:block;width:100%;height:15px;background:#ffffbe;z-index:-1}ul.bulleted{list-style:disc outside}#id_image+ul.bulleted{margin-bottom:10px}.cancel{display:inline-block;line-height:24px}.def-only>dt{position:absolute;left:-9999px}.editable-title:hover{padding:2px 20px 2px 0;background:url(../img/icons/fugue/pencil_small.png) no-repeat right}.editable-icon{background:url(../img/icons/fugue/pencil_small.png) no-repeat left;padding:2px 0 2px 16px;font-size:.8em;color:#525252}.is-spam{background:#eee url(../img/diagonal_transparent_bg.png) repeat left top}.spam{background:url(../img/icons/fugue/minus_small_circle.png) no-repeat left 65%;padding:2px 0 2px 14px;font-size:.8em;color:#525252}.ham{background:url(../img/icons/fugue/plus_small_circle.png) no-repeat left 65%;padding:2px 0 2px 14px;font-size:.8em;color:#525252}.delete{background:url(../img/icons/fugue/cross_small_circle.png) no-repeat left 65%;padding:2px 0 2px 14px;font-size:.8em;color:#525252}.delete[data-comment-is-spam="false"]{display:none}div.cb{clear:both}.noborder{border:0}.nomargin-bottom{margin-bottom:0}.nowrap{white-space:nowrap}.file-icon{background:url(../img/icons/fugue/document.png) no-repeat right;padding:2px 20px 2px 0}.global-event-wrapper{padding:6px 7px;border:1px solid #ddd;background:#fff}img.avatar{display:block;width:32px;height:32px;border:1px solid #e7e7e7;background:#fff;padding:2px}.left{float:left;margin-right:10px}.right{float:right;margin-left:10px}.info-box-lightgrey{margin-bottom:10px;border:1px solid #ddd;background:#eee;padding:15px 20px;font-size:1em;line-height:1.4em}.inset{margin:0 25px}.scroll-x{overflow-x:auto}a.img{text-decoration:none}#repo-access-pane .warning.message,#repo-group-access-pane .warning.message,#plans-and-billing .warning.message,#account-settings-email-pane .warning.message{position:relative;margin:-11px -20px 0;border-bottom:1px solid #ccc;background:#fff6c2 url(../img/icons/atl/yellow-warning-icon.png) no-repeat 20px;padding:10px 20px 10px 60px;text-align:left}#repo-access-pane .warning.message,#repo-group-access-pane .warning.message{margin-bottom:10px;padding:12px 45px 12px 60px}#account-settings-email-pane .warning.message{margin-bottom:13px}#repo-access-pane .warning.message p,#repo-group-access-pane .warning.message p{line-height:17px}#repo-access-pane .warning.message a[href="#hide"],#repo-group-access-pane .warning.message a[href="#hide"]{position:absolute;right:20px;top:12px;display:block;width:17px;height:17px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAQElEQVQY02OYOXPmmZkQwIAHg8AZOAOPBrg8hgAuhSA+Ax4NGAYw4DEJwyZcbnyNzQ9km0y0m4kODZLCmegYBAAbGv9TWnWYEgAAAABJRU5ErkJggg==) no-repeat 50%;line-height:99px;overflow:hidden;cursor:default}.warning{margin-bottom:10px;border:1px solid #ddd;background:url(../img/icons/orange/warning.gif) no-repeat 15px 15px;padding:15px 40px}.errors{margin-bottom:10px;border:1px solid #ddd;background:#eee url(../img/icons/fugue/exclamation.png) no-repeat 15px 15px;padding:15px 40px}.errors .error{display:block;padding-top:3px}.errorlist{margin:0;padding:0;list-style-type:none}.errorlist ul{padding:0;margin:0;list-style-type:none}.column ul.errorlist{margin-left:0}.errorlist li{margin:5px 0 0;background:url(../img/icons/red/warning.gif) no-repeat left;padding:2px 0 2px 20px;list-style:none}.info-paragraph{padding-bottom:0}#twocols1,#twocols2{float:left;width:455px}#twocols1{margin-right:20px}#repo-menu{margin:0 -2px -1px;height:22px;padding:8px 20px}#content.repo-desc-hidden #repo-menu{display:none}.repo-menu{border:1px solid #ddd;color:#525252}.repo-menu h3{line-height:24px}.repo-menu .share{background-image:url(../img/icons/fugue/card_address.png)}.repo-menu .revoke{background-image:url(../img/icons/fugue/cross.png)}.repo-menu .revoke-group{background-image:url(../img/icons/silver/close.gif)}.repo-menu .rss{background-image:url(../img/icons/fugue/feed.png)}.repo-menu .atom{background-image:url(../img/icons/fugue/atom.png)}.repo-menu .pull-request{background-image:url(../img/icons/fugue/arrow_180.png)}.repo-menu .compare-link{background-image:url(../img/arrow_switch.png)}.repo-menu .fork{background-image:url(../img/icons/fugue/arrow_045.png)}.repo-menu .patch-queue{background-image:url(../img/icons/fugue/bandaid.png)}.repo-menu .source{background-image:url(../img/icons/fugue/download-arrow.png)}.repo-menu li{position:relative;float:left}.repo-menu li ul{position:absolute;left:11px;top:20px;display:none;width:115px;border:1px solid #ddd;z-index:2}.repo-menu li:hover ul{display:block}.repo-menu li li{display:block;background:#fff url(../img/layout/bg_lists_small.png) repeat-x 0 0;padding:0;font-size:13px;line-height:16px}.repo-menu li li:hover{background:#fff}.repo-menu li li+li{border-top:1px solid #ddd}.repo-menu li li a{display:block;padding:5px 8px}.repo-menu>ul>li>a,.repo-menu .metadata>li{background-repeat:no-repeat;background-position:0 50%;padding-left:20px;line-height:22px;color:inherit}.repo-menu>ul>li>a{display:block;margin-left:15px;text-decoration:none}.repo-menu .metadata>li{margin-right:12px}.repo-menu .branches{background-image:url(../img/icons/fugue/arrow_135.png)}.repo-menu .tags{background-image:url(../img/icons/fugue/tags.png)}.repo-menu .metadata ul{width:148px}.repo-menu .branches ul{width:172px}.repo-menu .branches li{width:172px}.repo-menu .tags li{width:148px}#repo-menu .source:after,.repo-menu .metadata>li:after{content:"\0020»"}.repo-menu .metadata a{width:106px;overflow:hidden}.repo-menu .branches a{width:130px}.repo-menu a.menu-compare{position:absolute;right:0;top:0;width:16px;height:16px;background:url(../img/arrow_switch.png) no-repeat 50%;padding:5px;text-indent:-9999px}.repo-menu .downloads a{width:82px;background:url(../img/icons/fugue/document_zipper.png) no-repeat 3px;padding-left:25px}#repo-menu-links{float:right}.repo-menu .inertial-hover.hovering.branches>ul,.repo-menu .inertial-hover.hovering.tags>ul,.repo-menu .inertial-hover.hovering.get-source>ul{display:block}#repo-menu-links-mini{display:none}#content.repo-desc-hidden #repo-menu-links-mini{display:block;float:right}#content.repo-desc-hidden #repo-menu-links-mini.nosource{width:168px}#repo-menu-links-mini>li>a{float:left;margin-left:8px;width:16px;height:22px;background-repeat:no-repeat;background-position:0 50%;padding:0}#repo-menu-links-mini li:hover ul{left:4px}#repo-menu-links-mini .follow,#repo-menu-links-mini a[href="#share"],#repo-menu-links-mini .revoke,#repo-menu-links-mini .revoke-group{text-indent:-9999px}#content #repo-heading{margin:0 0 10px;padding:0;font-weight:normal;font-size:16px;line-height:1.5;color:#999}#content #repo-heading.private{margin-left:-1px;background:url(../img/icons/fugue/lock.png) no-repeat;padding-left:21px}#content #repo-heading .owner-username{color:inherit}#content #repo-heading .repo-name{color:#2b547d}.repo-avatar{margin-left:20px;padding-top:2px}#content.repo-desc-hidden .repo-avatar{display:none}#repo-desc{position:relative;margin-bottom:10px;min-height:28px;background:#fff;padding:8px 24px;font-size:1.1em;line-height:1.4em}#repo-desc pre{padding:0;margin:3px 0}#repo-desc h3{display:inline}#repo-desc h3 span{font-size:.8em}.repo-desc-description{margin:0 0 .5em;width:750px}#content.repo-desc-hidden .repo-desc-description{display:none}.repo-desc-private{background:#fff}#repo-desc-cloneinfo{margin:8px 0 0;border-top:1px solid #ddd;padding:8px 0 4px}#content.repo-desc-hidden #repo-desc-cloneinfo{display:none}#repo-desc-cloneinfo.https a.https,#repo-desc-cloneinfo.ssh a.ssh{font-weight:bold;text-decoration:none;cursor:default}#repo-desc-cloneinfo.https #clone-url-ssh,#repo-desc-cloneinfo.ssh #clone-url-https{display:none}#clone-url-https:before,#clone-url-ssh:before,#clone-url-ssh-git:before{content:"$\0020"}#repo-desc .repo-avatar{position:absolute;top:8px;right:0}#toggle-repo-content{position:absolute;left:0;bottom:0;display:block;width:928px;height:5px;border-top:1px solid #ddd;background:#eee url(../img/repo-toggle-up.png)}#content.repo-desc-hidden #toggle-repo-content{background-image:url(../img/repo-toggle-down.png)}#toggle-repo-content:hover{background-color:#ddd}#sourcelist .name{width:275px}#sourcelist .size{width:100px}#sourcelist .text{width:375px}#sourcelist.mq .text{width:300px}#sourcelist .mq{width:75px;padding-right:0}#sourcelist td.name{width:254px;background:url(../img/icons/fugue/document_text.png) no-repeat 0;padding-left:21px;font-size:12px;font-family:Monaco,"Courier New",monospace}#sourcelist td.dirname{background-image:url(../img/icons/fugue/folder.png)}#sourcelist td.subreponame{background-image:url(../img/icons/fugue/folder__arrow.png)}#source-path h1{font-weight:normal;font-size:18px;color:#999}#view-at-form{float:right}#view-at-input{margin-top:4px;width:140px;background:url(../images/search-icon.png) no-repeat 4px}body.non-webkit #view-at-input{margin-top:3px;padding:3px 0 3px 23px}.info-table{width:100%;border-bottom:1px solid #ddd;background:#fafafa}.info-table th{vertical-align:middle}#shortlogs-changes img{float:left;margin:0 9px 0 0;width:16px;height:16px;border:1px solid #ddd}#shortlogs-changes .newtable .files-added,#shortlogs-changes .newtable .files-modified,#shortlogs-changes .newtable .files-deleted{width:36px;padding:0;text-align:center;color:#282}#shortlogs-changes .newtable .files-modified{color:#b70}#shortlogs-changes .newtable .files-deleted{color:#c33}#shortlogs-changes .newtable .files-deleted{padding-right:24px}#shortlogs-changes .newtable th.files-added,#shortlogs-changes .newtable th.files-modified,#shortlogs-changes .newtable th.files-deleted{background:url(../img/icons/green/add.gif) no-repeat 10px;text-indent:-9999px}#shortlogs-changes .newtable th.files-modified{background-image:url(../img/icons/orange/edit.gif)}#shortlogs-changes .newtable th.files-deleted{background-image:url(../img/icons/red/remove.gif)}#shortlogs-changes .newtable .text{width:478px}#shortlogs-changes .newtable .text>a{color:inherit}#shortlogs-changes .commented{background:url(../img/icons/fugue/balloon_left.png) no-repeat 50%}#shortlogs-changes input[type="radio"]{float:left;margin:3px 3px 3px -16px}.diff-radio input{margin:0;padding:0}#changeset-changed{clear:both;font-size:14px;line-height:24px}#changeset-changed>li{padding-left:25px;background:url(../img/icons/orange/edit.gif) no-repeat 4px}#changeset-changed>li.added{background-image:url(../img/icons/green/add.gif)}#changeset-changed>li.removed{background-image:url(../img/icons/red/remove.gif)}#changeset-changed>li:nth-child(odd){background-color:#f7f7f7}#changeset-changed .diffstat{float:right}#changeset-changed .diffstat>ul{float:right;width:70px;padding:4px;overflow:auto}#changeset-changed .diffstat>ul>li{display:block;float:left;width:14px;background:inherit;text-align:center;font-weight:bold;line-height:14px}#changeset-changed .diffstat>ul>li.added{color:#282}#changeset-changed .diffstat>ul>li.removed{color:#c33}#changeset-changed .diffstat>span{display:block;float:right;font-size:12px;font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace}#changeset-diff{margin-top:10px;font-size:1.1em}#changeset-diff div.diff{margin:0 0 10px;border:1px solid #ddd}#changeset-diff p{padding:15px 25px}#changeset-diff .changeset-top-link{background:url(../img/icons/fugue/arrow_skip_090.png) no-repeat left;padding:2px 0 2px 20px}#changeset-diff table{width:100%;border-collapse:collapse;border:1px solid #ddd;font-size:1em}#changeset-diff table.jsudiff{border:0;font-size:11px!important}#changeset-diff table td{margin:0;padding:0 5px}#changeset-diff table td.linenr{width:25px;background:#eee;border-right:1px solid #ddd;text-align:right}#changeset-diff table td:target+td.code,#changeset-diff table td:target+td+td.code{background:#ffffbe}#changeset-diff table td.hashnr{width:80px;border-right:1px solid #ddd;background:#fafafa}#changeset-diff table td.linenr pre{width:25px;text-align:right;letter-spacing:-1px;color:#747474}#changeset-diff table td code{margin:0;padding:0;font-size:11px;line-height:1.5em}#changeset-diff table td.linenr a{color:inherit}#changeset-diff tr.ellipsis{background:#efefef;text-align:center}#changeset-diff tr.linecontext{margin:3px;background:#dde7ef;text-align:left;font:11px "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace;letter-spacing:-1px;color:#747474}#changeset-diff tr.del{background:#fdd}#changeset-diff tr.add,#changeset-diff tr.ins{background:#dfd}#changeset-diff tr.marker{background:#eee}#changeset-diff tr.conflict-a,#changeset-diff tr.conflict-b{background:#fe9}#changeset-diff ins,#changeset-diff del{display:inline-block;text-decoration:none}#changeset-diff ins{background:#afa}#changeset-diff del{background:#faa}strong.conflicts{font-weight:normal;color:#c00}.changesets-date{margin:0 auto;border-bottom:0;background:#fff url(../img/layout/bg_general.png) repeat-x left top;padding:10px 20px;font-weight:bold;font-size:1.1em}.changeset-description{margin-bottom:10px;width:710px;white-space:pre-wrap}.files-affected{float:right;margin:0 -100px 0 0;width:96px;overflow:hidden}.files-affected dt{position:absolute;left:-9999px}.files-affected dd{float:left;width:32px;text-align:center}.files-affected dd[title="Added"]{color:#282}.files-affected dd[title="Modified"]{color:#b70}.files-affected dd[title="Removed"]{color:#c33}#source-summary>p{white-space:pre-wrap}#source-summary+p{margin:0 0 5px 25px}.highlighttable{width:928px;border-collapse:collapse}.highlighttable .linenos{border-right:1px solid #ddd;background:#eee;padding-left:5px;padding-right:5px;text-align:right}.highlighttable .linenodiv{width:30px}.highlighttable .code{width:938px}.highlighttable .code-diff{width:885px;border:1px solid #ddd;padding:0}.highlighttable pre{line-height:1.4em}.removed,.added,.command{margin:0;padding:0}.removed{background:#fdd}.added{background:#dfd}.command{background:#dde7ef}#source-view{margin-top:10px;border:1px solid #ddd}#source-view .header{border-bottom:1px solid #ddd;background:#fafafa;padding:4px 10px;line-height:24px;overflow:auto}#source-view .metadata>li{float:left;margin-right:10px}#source-view .source-view-links{float:right}#source-view .source-view-links>li{display:inline}#source-view .source-view-links>li+li:before{content:"/\0020"}#source-view .source-view-form{display:inline}#source-view .source-view-form select,#source-view .source-view-form input[type="submit"]{margin:auto}#inline-image{padding:14px}#inline-image img{margin-bottom:5px}#source-summary{margin:10px 0;border:1px solid #ddd;background:#f7f7f7;padding:10px 300px 10px 20px;font:12px/1.5em "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace;overflow:hidden}#source-summary p{margin:.25em 0 .5em;width:600px;line-height:inherit}.relations{float:right;margin:0 -280px 0 0;text-align:right}.relations dt{position:relative;left:auto;clear:left;float:left;width:100px;text-align:right;color:#747474}.relations dt:after{content:":"}.relations dd{float:left;margin:0 -0.5em 0 0;padding:0 .5em}#source-summary .metadata{position:relative;margin:.5em 0 0;min-height:34px;padding:2px 0 2px 45px;font-family:Helvetica,Arial,sans-serif}#source-summary .metadata dt{position:absolute;left:-9999px}#source-summary .metadata .avatar{position:absolute;left:-3px;top:0}#source-summary .metadata dd:last-child{color:#747474}#source-summary span{font-family:"Lucida Grande","Trebuchet MS",Tahoma,Arial,sans-serif}#source-summary table{border-collapse:collapse}#source-summary table td{padding:0 4px 4px}.tags{display:inline;float:right;margin:0 5px 0 0}.tags li{display:inline;padding:2px 4px}.button-default,.button-tip{margin-right:3px;padding:2px;white-space:normal;font:11px "Lucida Grande","Trebuchet MS",Tahoma,Arial,sans-serif;color:#000}.paginator{float:right;margin:0;padding:0}#wrapper .paginator{list-style:none}.paginator li{float:left;margin-left:2px}.paginator li:hover{background:#eee}.paginator li a{display:block;padding:2px 5px;font-size:1em;text-decoration:none;color:#525252}.paginator li a.current{color:#454b6c;font-weight:bold}.paginator-bottom{margin-top:5px}.repos-all-pages-bottom .paginator{margin-top:15px}li.hellip{width:1.4em;text-align:center;font-size:.8em}.ac_results{border:1px solid black;background-color:white;padding:0;overflow:hidden;z-index:99999}.ac_results ul{margin:0;width:100%;padding:0;list-style:none}.ac_results li{display:block;margin:0;padding:2px 5px;font:menu;font-size:.85em;line-height:16px;overflow:hidden;cursor:default}.ac_odd{background-color:#eee}.ac_over{background-color:#0a246a;color:#fff}.onecol-form,.onecol-info,.pane{position:relative;margin-bottom:10px;background:#fff;padding:43px 20px 15px}.onecol-form{background:#dde7ef}.onecol-info{width:920px}.pane{border:1px solid #ccc;padding:0 20px 12px}.onecol-form h2,.onecol-info h2{position:absolute;left:0;top:0;width:906px;border:1px solid #ccc;padding:7px 0 5px 20px;text-align:left;font-weight:normal;font-size:14px}.pane h2{margin:0 -20px;padding:7px 0 5px 20px;font-weight:normal;font-size:14px}.onecol-info h2,.pane h2{background:#fff url(../img/wiki/bg-th.png) no-repeat left top}.onecol-form h2{background:#fff url(../img/wiki/bg-th-blue.png) no-repeat left top}.twocol-form,.twocol-info{margin-bottom:10px;width:413px;border:1px solid #ddd;background:#f5f5f5;padding:0 20px 10px}.twocol-form{background:#dde7ef;padding-bottom:15px}.twocol-form>.standard{position:static}.twocol-form textarea{width:280px}.twocol-form table{width:100%;border-collapse:collapse}.twocol-form th{padding-right:10px;font-weight:normal}.twocol-form-right td{text-align:right}.twocol-info.access ul{line-height:24px}.twocol-info.access ul a:before{content:"»\0020"}.twocol-info.api p+p{margin:8px 0 0}.twocol-info p:last-child{padding-bottom:0}.twocol-form h3,.twocol-info h3{margin:0 -20px 11px;border-width:1px 0 1px 1px;border-style:solid;border-color:#fff;border-bottom:1px solid #ccc;padding:6px 0 5px 19px;font-weight:normal;font-size:14px}.twocol-form h3{margin-bottom:14px;background:-webkit-gradient(linear,0 0,0 100%,from(#dde7ef),to(#cfdbe8));background:-webkit-linear-gradient(#dde7ef,#cfdbe8);background:-moz-linear-gradient(top,#dde7ef,#cfdbe8);background:-o-linear-gradient(#dde7ef,#cfdbe8)}.twocol-info h3{background:-webkit-gradient(linear,0 0,0 100%,from(#eee),to(#fff));background:-webkit-linear-gradient(#eee,#fff);background:-moz-linear-gradient(top,#eee,#fff);background:-o-linear-gradient(#eee,#fff)}.feeds{margin-bottom:-10px;padding-left:10px;overflow:auto}.feeds li{float:left}.feeds img{display:block;margin:2px}#newsfeed .feeds+p{clear:both;margin-top:0;font-weight:inherit;line-height:18px}#repo-style-admin-preview{border:1px solid #ddd}#explanation p{font-size:13px;line-height:inherit}#explanation{float:right;width:300px;font-size:14px;line-height:18px}#explanation h2{margin-bottom:4px;font-size:16px;line-height:20px;color:#2b547d}#relevant-repos h2{float:none}#relevant-repos h2+a+p{clear:both;font-size:13px;line-height:inherit}#relevant-repos li.private{background-image:none}#relevant-repos li li a{color:#747474}#relevant-repos .follow.following{background-image:url(../img/icons/fugue/heart.png)}ol.users .label{position:relative;left:auto;clear:left;float:left}ol.users .label:after{margin-right:.25em;content:":"}ol.users .label+dd{float:left}.repositories{clear:both}.repositories li{padding-left:20px}.repositories .private{background:url(../img/icons/fugue/lock.png) no-repeat 0 0}.repositories .public{background:url(../img/icons/fugue/lock_disable_unlock.png) no-repeat 0 0}.repositories h4{margin:0;font-weight:normal;font-size:13px;line-height:18px}.repositories p{line-height:18px;color:#666}.thumbnails{clear:both;overflow:auto}.thumbnails li{display:block;float:left;border:1px solid #ccc}.thumbnails li+li{margin-left:2px}.thumbnails a{display:block}.thumbnails img{display:block;width:35px;height:35px}#delete-repository{border:1px solid #ddd;background:#fff;padding:0 20px 15px}#delete-repository h2{margin:0 -20px 15px;border:0;background:#fff url(../img/wiki/bg-th.png) no-repeat 0 0;padding:4px 20px;font-weight:normal;font-size:14px;line-height:24px}#delete-repository td{margin:0}#delete-repository .errorlist{position:absolute;margin:0 0 0 190px}#delete-repository label{line-height:27px}#account-settings-email-pane .status{float:right;margin-right:25px;width:inherit}#account-settings-email-pane .status.sent,#account-settings-email-pane .status.confirmed{color:#666}#account-settings-add-email div{display:inline}#account-settings-add-email label{position:absolute;left:-9999px}#add-private-user #username,#account-settings-email-input{width:15em}#account-settings-cname{margin-top:15px}#account-settings-sshkeys .mapping-widget{font-family:monospace}#newsfeed{float:left;width:555px;line-height:16px}#newsfeed h2{margin-bottom:4px;font-size:16px;line-height:20px;color:#2b547d}#dashboard #newsfeed h2{float:left}#newsfeed>ol>li{position:relative;border-bottom:1px dashed #ddd;padding:9px 0 9px 54px;font-size:1em}#newsfeed>ol>li:last-child{border:0}#newsfeed li li{background-repeat:no-repeat;background-position:0 5px;padding:2px 0 2px 24px}#newsfeed .commit{background-image:url(../img/icons/fugue/puzzle.png)}#newsfeed .create{background-image:url(../img/icons/fugue/plus.png)}#newsfeed .delete,#newsfeed .pullrequest_comment_deleted,#newsfeed .cset_comment_deleted{background-image:url(../img/icons/fugue/slash.png);font-size:inherit;color:inherit}#newsfeed .file_uploaded{background-image:url(../img/icons/fugue/clipboard__plus.png)}#newsfeed .fork{background-image:url(../img/icons/fugue/arrow_045.png)}#newsfeed .issue_comment{background-image:url(../img/icons/fugue/ticket_pencil.png)}#newsfeed .issue_update{background-image:url(../img/icons/fugue/ticket.png)}#newsfeed .report_issue{background-image:url(../img/icons/fugue/ticket_plus.png)}#newsfeed .start_follow_issue{background-image:url(../img/icons/fugue/issue_following.png)}#newsfeed .start_follow_pullrequest,#newsfeed .start_follow_repo,#newsfeed .start_follow_user{background-image:url(../img/icons/fugue/heart.png)}#newsfeed .stop_follow_issue{background-image:url(../img/icons/fugue/issue_follow.png)}#newsfeed .stop_follow_pullrequest,#newsfeed .stop_follow_repo,#newsfeed .stop_follow_user{background-image:url(../img/icons/fugue/heart_break.png)}#newsfeed .strip{background-image:url(../img/icons/fugue/slash.png)}#newsfeed .wiki_created{background-image:url(../img/icons/fugue/clipboard__plus.png)}#newsfeed .wiki_updated{background-image:url(../img/icons/fugue/clipboard__pencil.png)}#newsfeed .pullrequest_created{background-image:url(../img/icons/fugue/envelope_plus.png)}#newsfeed .pullrequest_updated{background-image:url(../img/icons/fugue/envelope_pencil.png)}#newsfeed .pullrequest_comment_created,#newsfeed .pullrequest_comment_updated,#newsfeed .cset_comment_updated,#newsfeed .cset_comment_created{background-image:url(../img/icons/fugue/balloon_left.png)}#newsfeed .pullrequest_fulfilled{background-image:url(../img/icons/fugue/envelope_arrow.png)}#newsfeed .pullrequest_rejected{background-image:url(../img/icons/fugue/envelope_minus.png)}#newsfeed .pullrequest_superseded{background-image:url(../img/icons/fugue/envelope_exclamation.png)}#newsfeed code{font-size:12px}#newsfeed h3{clear:both;margin:1em 0 0;border-bottom:1px solid #ddd;padding-bottom:5px;font-size:14px;line-height:18px;color:#747474}#newsfeed h4{margin:0;line-height:21px;color:#747474}#newsfeed h4:after{content:"…"}#newsfeed h4 a{color:inherit}#newsfeed p{margin-top:3px;font-weight:bold;line-height:inherit}#newsfeed p code{font-weight:normal}#newsfeed p~p{margin-top:0;font-weight:inherit}#newsfeed .commit p~p{margin:3px 0 0 -11px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAD0lEQVQI12OYiQQYiOMAAOoyHLGNzRLrAAAAAElFTkSuQmCC) no-repeat 0 6px;padding-left:11px}#newsfeed .avatar{position:absolute;left:0;top:10px}#newsfeed time{display:block;font-size:11px;line-height:14px;color:#747474}#newsfeed .paginator{float:none;margin:10px 0 0 76px}.newsfeed-header{margin-bottom:0;padding-bottom:0}.newsfeed-segment-grouper{margin:10px 0 0;border-bottom:1px solid #ddd;font-size:.9em}.newsfeed-avatar{margin-top:12px}table.newsfeed,table.repository-list{width:100%;line-height:21px}table.repository-list .lined-separator{border-top:1px solid #ddd}table.newsfeed{margin-top:-10px;width:428px}table.newsfeed td{vertical-align:top}table.newsfeed td b{background:#dde7ef;font-weight:bold;color:#333}table.newsfeed a{text-decoration:none}table.newsfeed td.newsfeed-icon{width:20px;padding:4px;vertical-align:top}table.newsfeed td.newsfeed-avatar{padding:4px;width:38px;vertical-align:top}td.lined-separator{border-top:1px dashed #ddd}#search label{font-weight:bold;line-height:24px}#search label[for="q"]:after{content:":"}#sign-in .newform>.error_.message_{margin-top:16px}#sign-in .error_{width:258px}#sign-in input[type="email"],#sign-in input[type="password"],#sign-in input[type="text"],#sign-in input[type="url"]{width:286px}.opensocial,.show-standard-signin{display:none}#openid_form .badges{overflow:auto}#openid_form li{display:block;float:left;margin-right:4px}#openid_form a{display:block;width:24px;height:24px;border:1px solid #ddd;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAAAYCAYAAADEQnB9AAAKjUlEQVR42u2cC2wUxx3G9+6gKogGjJRIEUmKLAgpLX2ItBiMgQBBAZVCmkCKJYIaWqQ2IUkbKW2p24IxUCgQHgnEDjgBxS6mKdjGBi6AA7aTOGDxEphigo0JEL9fIJvDZ3+db/Bcl+POt7vna0y7I33szt7ueG52f/f957FoADRbtmzdm7IbwZYtG2BbtmzZANuyZSt8gNu8QPGFyxg8Lx29frwNyz84hXZ44WlqQMH4MTjr0HDL6US7uLxdc6JF5OtlUT3oi2nar4S485nQJqH5QiO6oewuk/dMWqhTQv6N4tsJJlVspOzJkydH/3x5NtJPXpOaHL8BA0cukFse70q81mDbRwuNNKloo/dgyMI1sCITz05E659SOAxWZAhgbxvQIXZmLt0P7Tk3nPOLcd8LB/H5tWbxgRctZ87iy359JbwdDhfaBbwEuFbrhUvOngOxSGc7AfaXR6hAaJ1QvNCw7gKY8LZufCQsgAliyZkSWJERiAnqbzbl+wDWQ8zjizPPBtTS1GIJsZGHf8mSJbMaGhqKYTDxXF5jBAKCOHTpP/GTzZ+aEq8xAnGk608QM4/F4njlClPiNYEgDviAtrW1YchLe6HNL4K24AS0OUewdPdpSbZgGJemTIFHc8Dr0uBxONCouVAjIC7vIS4s0pNB4A2mRqGccAEmvN0AcEA4ExMTsXr1at82CMAhy3/x/RN49i85UoEgnpvwngTZX4TYIMAjzTz8egh4rRGAFZBmxGsMAuyrf21VZUhdKKtAfn4+Lp8qM9T+hDDtTLQlGQK4o51WAsQm5ME59xM4FxTBGV+I59YeJ9qysS8+MxM3hOPCwRDahetie1UUVep0GQb4YEIxdr2Qh8SBqXco/acfgp/pz13kTIGSQYCzTAKs1K8nAkxoFbhdQWwUYCsQmwEYFpMVgLnPusVvPYrx6z+RefUdrQIcCN7qYjcK3lotVZKZ7TuenJys2p3b+q8cYIAEtyMx4zS0Z9zo9YujcP7sEOasKxJHhQXfBD791mPIf/BryHo0CnsHR6F4QG9UiPCZfWOj8PqDqxdB1UNsBmCRhgh5p0+fLhuV22DAsvE7Hxyl4T0RYAWt/zF/iI0A/ODEZFiBuCcCTGBT8i/KvIKXW35GZ+T3sQrwjdZ6qevXLuLD115B7oxJ2LcySQLMPPfVfeF5RgFmKKyHUoXTp2pP+0LlsAC+UFEn+8CVTS147KVcaM8ewH1z3XCfuCKPJ2e9g4dfHQztrVFwbYqDtjEWfVb+CGN+OQKrvt0fRuEty7vmu3Hc3/DdXdCDqodY5VMnGnqA1hDG/fv349atW3KrAN2zZ4/8e9wGAfgpMwDX1dWhsLAQWVlZdwHMY0eOHEFNTY3Msy4ej8dyCB0IaisAE9BAEKu+LmElwKpPbBVg95mr6L2wxCfmVWK4CS3JJ5m3ADDhbW1t9blvWVWDPMbP1N9jva0A3O65KR2W4BJa/h0eU/rt2Ncw84GBEmTmrQLcdPPzO37EmA8GsSGAhz3/Lj7IKxHfoAMNzR6891EZSi/XSfddtj8ZWuJ34EqJhevtMdA2jRIaDcd6sb9xPBwrJoZsIIbIymkZQjMRVALa5vFKVZ+ux+vOVRJqPcD+oXWQG9A4YcKEOxqFeT28ahsA4AVGAPZ6vTh48CBSU1OxZcsWuN3uuwDmD8fWrVvlOceOHUN9fT2am5u7DWAVVpsFmBDSXf0hJtj8QeDDynLDBVgPr9J/rDbpblkAmHUitApSOi7z6jNCrIA2CzATQ2UCSnj1ie0UFRUlP6MbE3QrAHPfH2AmOrFlgO+fvQMDZmzBxFfSkbQ97/aolej7flT6GZy/+ya0d34ILVmAmzIKjjfHSmlvxMDxtzgB8LiQDeQfLh9PLZXQEmxuCSnh/bXzZbnlNcqdDQKctnjxYh+Y8le4M6+cV0EcAOCkUOUT3h07dkgwd+/ezcGXoCH04cOHkZ6ejtzcXOTl5aGqqsoUwMpl/fu+evE7mAWYD7cKlQkwHYxlV13cLHXzyLv3BMCsNwElqApa3g818qyiCqsAE06G0DnZGbKtmThgRXhVft+r8bJ/bAXgvEvzfOG3PvG4dQeesx0PzXofX5+Wiqin3kZ5ZRM6OjpQUXsFj6Y8De3NEXBuHi0UK/Zj4NwQC8cbcXAKgLWVMaYBpui4yokJrpLq8zJ0NjGANai0tLSDIatK58+fD+nATEVFRV+E7AJ0Oi/h7aoPzDarrq5GeXk5Dh06hJ07d+Lq1aumAPYHlnnW2R9mKwATShVO8+FmOQreewVgFUHQdfX7ql+sALcKMMNnrxj04Y/C0KFDZRsxdCbAypXpwnRqKwDTaVl+twIcPXUtYp7fitl/zsbL6w7jRGm9DJ87xL81zTWYsuNFONaMEKHzWNn/lRIAa6uEkuJMhdBKnc56B7yUCqEZaqt9o1M9/m6r9umggfrAnY5ZG6pswksRzEgPYulBDTYSHQ7ACmLCyvIIrt6B9RD3xD6wih5YNqVcVw+0Go226sAqwmL7ElxKPTeRcGDmwwI4/cBJ1NTL8WbcajyLyouZKDuxCa3Xq28PxoiVHlPTFwqIhwt4Y6CtixXuOxraXwXQy8YaGsTSD1RxAEvf91UubDRkDnIT+gldsTCNVGYUYLr6fxPgriAOB2A9xIHU00ehCSjhVX1fHqM78hjhtjoKzaSmjFSi+9KJFXTNFSUSYIbZ3TWIxR/Q8KaRxCzS9aqTOJ4Zg/LMvvgiV8PlXU6UZAxHU+152Sc+V3kJ31g+XsA7Gq7VY+FcKdx32ThoS8YZAs4/VPZXoGkkIyPQfjci3gLABaHKFWG2BJh9W32YHimAjfSDwwXYX/cKwPqpJA5gEVgFdLjzwCpSYBitIgROTbGd1Si1mk5SyQrAnD4y4r6GAb7ZdgPF6aPQkCOWSn7sRFtBX7Tk9UH1Tg0XMqfJvh3HtYaunyX6vI9DWyNC6OVx6L1kEhx/GmcYMrWQY210ug/cYAs5eI7JEFrdjAID0NYJbTSxTho5OTkS4m3btt0RSkcSYL37+kPc3QCHuxLLCrzsC/aUlVj6ejE8Jqh0Yzou3VaOTgvnVdNLqv5mF3IQVr0Dcz/shRzNtWX41/ZBaHUPQJO7H5pz+6Mxuz9u7BmEL//xPcBzHS1tHXh42TQBrhjMWvaE2AoXXjwB2h/jLIW8EVxS+X0u6ggC7uFOl+5jZS00YVHTSBkZGUhLS4voUkqDa6ENA8y1zUbh5bn/j0sp9U6sFnAo6UPrzvobApjQcvCKobJ//5c/AjweVh+4zduB8n3zUPv3fmjMegBN2fdLeJt2PoL6wgTZN844ug+u138Ax1IxH5woQmnhvNoioT9M6lEAd96Q7Tpoq4VWcLVWd7yNxBD63LlzOHDggATYs2u2oUjxq3yZgRB29dJCMPGaSLzM0DnKe8++zGC2/vqQOVgi2P4gB3sjKfDrhDdqUfNxAhr2zkBT1pNiOx0txzfC09aCUxVleOj3UwWwj8OZIOBNoPOKAaxFT/Q4eNW0Uufa6KeFXJF+nTBcgCP9OiEftFCvDdqvE0a2/oSY4bIR8VxTrxPasmXL/h85bNmyZQNsy5YtG2Bbtv5H9W8pN1H4BZcrNgAAAABJRU5ErkJggg==) no-repeat 0;text-indent:-9999px;overflow:hidden}#openid_form a.selected{border-color:#2b547d}#openid_form a[title="Yahoo!"]{background-position:-24px}#openid_form a[title="AOL"]{background-position:-48px}#openid_form a[title="OpenID"]{background-position:-72px}#openid_form a[title="MyOpenID"]{background-position:-96px}#openid_form a[title="LiveJournal"]{background-position:-120px}#openid_form a[title="Flickr"]{background-position:-144px}#openid_form a[title="WordPress"]{background-position:-168px}#openid_form a[title="VeriSign"]{background-position:-192px}#openid_form a[title="ClaimID"]{background-position:-216px}#openid_form .inputs li{display:none}#openid_form .inputs li.selected{display:block}#openid_submit{display:none}#associate-openid #openid_form>.badges>li{background:#fff}#associate-openid #openid_form>.inputs>li>input{width:286px}.search-results-result{margin-bottom:10px;border:1px solid #ddd;padding:15px 20px;font:12px/16px "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace}.search-results-result h4{margin-bottom:3px;font-weight:bold;font-size:1em}.search-results-result ul{margin:0;padding:0;list-style-type:none}.search-results-result ul li{padding:2px 0 2px 20px}.search-results-files li{background:url(../img/icons/silver/document.gif) no-repeat left}.search-results-messages li{background:url(../img/icons/silver/comment.gif) no-repeat left}.search-results-authors li{background:url(../img/icons/silver/user.gif) no-repeat left}.search-results-rev{border:1px solid #ddd;border-bottom:0;background:#eee;padding:10px 20px;font-weight:bold;font-size:1.1em}.repository-description{margin:0;padding:0 0 0 20px}.cset-merge{background:url(../img/merge.png) no-repeat bottom right}div#captcha{border-bottom:1px solid #ddd;padding:10px 0 0 20px}#captcha input{width:80px;border:1px solid #ddd}#captcha p{font-size:11px;padding-top:4px}#captcha h4{margin-bottom:6px}#captcha img{float:left;margin:2px 5px 0 0}.markItUpEditor{border:1px solid #ddd;font:14px/18px monospace}.markItUp .markItUpHeader{margin:5px 0;overflow:auto}#welcome .two-thirds-primary h2{margin-top:18px;font-size:16px}#welcome p{margin-top:9px;margin-bottom:9px}#welcome ul{margin:9px 0 9px 9px;line-height:18px;list-style:disc inside}#welcome .two-thirds-secondary h2{font-size:20px;line-height:28px}#welcome .two-thirds-secondary a.img{position:relative;left:-1px}#filters{border-collapse:collapse}#filters td{padding:4px 8px 4px 0}#filters td img.filter-remove{vertical-align:text-bottom}#filters td.filter-header{width:80px}#filters input[type="text"]{width:150px;padding-top:0;padding-bottom:0;font-size:13px}#filters select{margin:2px 0;width:160px}.delete-list ul{margin:0 0 10px;padding:0;list-style-type:none}.delete-list ul li{border-bottom:1px solid #ddd;padding:4px 2px}.delete-list ul li:hover{background:#ddd}.delete-list ul li form{float:right;margin:0;padding:0}#embedded-link{margin:0 0 0 5px;width:120px}.readonly{margin:7px 0 0;background:#fee4a1;border:1px solid pink;padding:3px 8px}.plans-and-billing h4{margin-bottom:8px;font-weight:bold;font-size:18px}#use-your-own-domain form div{margin:10px 0}label[for="id_cname"]{font-weight:bold}label[for="id_cname"]:after{content:":"}.mapping-widget{margin:10px 0 20px;border-top:1px solid #ccc;background:#fff;line-height:16px;cursor:default}.mapping-widget li{position:relative;border-width:0 1px 1px;border-style:solid;border-color:#ccc;padding:4px 10px}.mapping-widget li:after{content:".";display:block;height:0;clear:both;visibility:hidden}.mapping-widget li div{position:static}.mapping-widget>li>span{display:inline-block;width:236px;overflow:hidden}.mapping-widget a[href="#delete"]{position:absolute;right:10px;top:4px;display:block;width:16px;background:url(../img/icons/fugue/minus_circle.png) no-repeat 0;text-indent:-9999px;cursor:inherit;overflow:hidden}.mapping-widget a[href="#delete"].loading{background-image:url(../img/loading.gif)}.mapping-widget a[href="#delete"].removed{background-image:none}.mapping-widget .children{margin:0 -11px -1px;border-top:1px solid #ccc;background:#f5f5f5}.mapping-widget .children li{padding-left:40px}.mapping-widget .access{position:absolute;right:36px;top:4px;color:#888}.mapping-widget li li .access{top:0;height:24px}.mapping-widget .access li{display:inline-block;border:0;padding:0 4px;-webkit-transition:color .25s linear}.mapping-widget .access li:hover,.mapping-widget .access li.selected{color:#333}.expandable.mapping-widget{line-height:24px}.expandable.mapping-widget b{display:inline-block;padding-left:22px}.expandable.mapping-widget li{padding-top:0;padding-bottom:0}.expandable.mapping-widget li[data-username] b{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAASElEQVQY02OYOXMmKxDfA2Lf////MyBjoBgYM0AV/YfiDUCsSEjR/9mzZ38H0l1QceyKkPAVILYmpAiGfckziRg34fUd3nACAAYTxCEbLP7rAAAAAElFTkSuQmCC) no-repeat 3px}.expandable.mapping-widget li.loading b{background-image:url(../img/loading.gif);background-position:0 50%}.expandable.mapping-widget li.revealed b{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAXUlEQVQY02OYOXMmKyHMACTuAfF/PPgeSJEvAUW+DP///wcp3IBDwQaQPEyR4uzZs78jK4DyFeGKoAq70EzpgskhKwL55ApUAYhmhSsCcpCxNVSRNbI4uiKYb1HEAMbLxCHmLf4fAAAAAElFTkSuQmCC)}.expandable.mapping-widget a[href="#delete"]{top:0}#plans-and-billing-div{position:static}#plans-and-billing-div>p{margin-top:10px}#plans-and-billing-div>.access>p{margin-top:6px;line-height:18px}#plans-and-billing-div>.access>div[id]>h4{margin:15px 0 0;font-size:13px}#plans-and-billing .expandable.mapping-widget{margin-bottom:0}#plans-and-billing .spinner{display:none;height:32px;background:url(../img/loading.gif) no-repeat 50% 100%}#plans-and-billing .spinner.loading{display:block}#add-private-user{padding-top:20px;height:27px}#add-private-user .errorlist li{display:block;margin:0;padding-top:0;padding-bottom:0;line-height:21px}#add-private-user div{display:inline}#add-private-user label{position:absolute;left:-9999px}#add-private-user-input{width:15em}#username-mappings-div{margin-left:auto;margin-right:auto}#username-mappings-div p.link{margin-top:5px}#username-mappings .children span:before{content:"alias:\0020";color:#666}#add-username-mapping{margin-bottom:10px;overflow:auto}#add-username-mapping div{float:left}#add-username-mapping div+div{margin-left:20px}#add-username-mapping label{display:block}#add-username-mapping input{margin-left:0;margin-right:0}#add-username-mapping .primary-button{display:block;margin-top:18px}.split-box{overflow:hidden}.split-box h4{margin-top:3px}#name-form input#name{width:265px}input#id_cname{width:140px}#name-form #choose-write-admin{margin-right:4px;width:72px}.plans-and-billing .split-box{margin:10px 0}.input-row{clear:both;margin-bottom:10px;overflow:hidden}.input-row input[type="checkbox"]{margin-left:0}.site-message{border-bottom:1px solid #ccc;background:#e7f9e0;text-align:center}#delete-user{text-align:center}form .actions{text-align:center}form .actions .primary-action{display:inline;margin:10px auto;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:0;background:#147;background:-webkit-gradient(linear,left top,left bottom,from(#147),to(#036));background:-webkit-linear-gradient(#147,#036);background:-moz-linear-gradient(top,#147,#036);background:-o-linear-gradient(#147,#036);padding:5px 10px;font-weight:bold;font-size:14px;text-shadow:1px 1px 0 #333;color:#fff;cursor:pointer}form .actions .primary-action:hover{background:#036;background:-webkit-gradient(linear,left top,left bottom,from(#036),to(#025));background:-webkit-linear-gradient(#036,#025);background:-moz-linear-gradient(top,#036,#025);background:-o-linear-gradient(#036,#025)}form .actions .primary-action:active{background:-webkit-gradient(linear,left top,left bottom,from(#025),to(#036));background:-webkit-linear-gradient(#025,#036);background:-moz-linear-gradient(top,#025,#036);background:-o-linear-gradient(#025,#036)}.message_{border:1px solid #ccc;padding:10px 0 10px 36px}.ui-dialog .message_,.twocol-form .message_,.mapping-widget li.messages .message_{border-width:0 0 1px 0}.ui-dialog .message_{margin:-20px -13px 20px}.message_.error_{background-color:#ffeae8}.message_.success_{background-color:#e7f9e0}.message_.warning_{background-color:#fff6c2}.message_.note_{background-color:#eafaff}.twocol-form .message_{margin:-14px -20px 20px;padding-left:56px;padding-right:20px}.message_ h4{margin:0 0 0 -15px;background-repeat:no-repeat;background-position:0 0;padding:4px 0 4px 40px;font-weight:bold;line-height:19px}.twocol-form .message_ h4{margin-left:-40px}.ui-dialog .message_ h4{margin-left:-20px}.message_.error_ h4{background-image:url(../img/icons/atl/red-warning-icon.png);color:#b50000}.message_.warning_ h4{background-image:url(../img/icons/atl/yellow-warning-icon.png);background-position:0 2px}.message_.success_ h4{background-image:url(../img/icons/atl/green-upgrade-arrow-icon.png);color:#2d6b00}.message_.note_ h4{background-image:url(../img/icons/info.png);background-position:6px 4px}.message_.warning_ ol{list-style:decimal outside;padding:0 0 0 18px}.message_.warning_ li+li{margin-top:3px}.message_.tooltip_{position:absolute;right:0;margin-top:5px;width:350px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;box-shadow:0 2px 4px rgba(0,0,0,0.3);padding-top:5px;padding-bottom:5px;z-index:2}.message_.tooltip_ h4{font-size:11px;font-weight:normal;color:#333}.tooltip-anchor{position:relative}.tooltip-anchor:after{content:"";position:absolute;left:-7px;bottom:-6px;margin-left:50%;width:15px;height:8px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAICAYAAAAm06XyAAAAPElEQVQY02P4//8/Ay68e/fu//jk8Wp89ev/f3wG4NUIw7gMIKgRnwFEacRlANEasRlAkkZ0A0jWiGwAAHSelXrfvXkCAAAAAElFTkSuQmCC);z-index:3}.message{padding:10px 0;text-align:center;border-width:0 0 1px;border-style:solid}.success.message{border-color:#ccc;background:#e7f9e0;color:#2d6b00}.info.message{border-color:#ddd;background:#dde7ef;color:#2b547d}.info.message p{display:inline;background:url(../img/icons/info.png) no-repeat 6px;padding:5px 0 5px 36px}.success.message p{display:inline;background:url(../img/icons/atl/green-upgrade-arrow-icon.png) no-repeat 6px;padding:5px 0 5px 36px}.error.message{margin:-11px -20px 14px;border-bottom:1px solid #ccc;background:#ffeae8 url(../img/icons/atl/red-warning-icon.png) no-repeat 14px;padding:10px 20px 10px 50px;text-align:left;color:#b50000}#header-messages .message{text-align:center;margin:0}#header-messages .error.message{background:#ffeae8}#header-messages .warning.message{background:#fff6c2}#header-messages .message p{display:inline;padding:10px 0 10px 36px}#header-messages .error.message p{background:url(../img/icons/atl/red-warning-icon.png) no-repeat 6px}#header-messages .warning.message p{background:url(../img/icons/atl/yellow-warning-icon.png) no-repeat 6px}.host-domain p{margin-top:8px}#fork-pane p{margin:0 0 10px}#fork-settings-advanced label[for="id_inherit"]{margin-left:22px;line-height:18px}#fork-settings-advanced #id_inherit{position:absolute;left:0}#boxError{width:500px;margin:0 222px}#boxError div{width:160px;float:left}#boxError li{font-size:14px;color:#35638e;font-weight:normal;line-height:1.5em}#imageError{margin:0 auto}h4{font-size:14px;font-weight:bold;color:#2b547d;margin:25px 0 7px}h4 a.hover{text-decoration:underline}.button-group ::selection{background:transparent}.button-group li{float:left;font-weight:bold;font-size:11px}.ui-dialog .ui-dialog-buttonpane button{font-weight:bold;font-size:11px}.button-group li+li{margin-left:-1px}.ui-dialog .ui-dialog-buttonpane button,.button-group li>a{display:block;height:22px;border:1px solid #ccc;filter:progid:DXImageTransform.Microsoft.Gradient(StartColorStr=#FFFFFF,EndColorStr=#DDDDDD);background:#eee;background:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ddd));background:-webkit-linear-gradient(#fff,#ddd);background:-moz-linear-gradient(top,#fff,#ddd);background:-o-linear-gradient(#fff,#ddd)!important;padding:0 9px;line-height:23px;text-shadow:0 1px 0 #fff}.ui-dialog.bb-dialog .ui-dialog-buttonpane button{color:#2b547d;height:24px}.ui-dialog .ui-dialog-buttonpane button:first-child,.button-group li:first-child>a{-webkit-border-radius:5px 0 0 5px;-moz-border-radius:5px 0 0 5px;border-radius:5px 0 0 5px;padding-left:10px}.ui-dialog .ui-dialog-buttonpane button:last-child,.button-group li:last-child>a{-webkit-border-radius:0 5px 5px 0;-moz-border-radius:0 5px 5px 0;border-radius:0 5px 5px 0;padding-right:10px}.ui-dialog .ui-dialog-buttonpane button:only-child,.button-group li:only-child>a{-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.ui-dialog .ui-dialog-buttonpane button:focus,.ui-dialog .ui-dialog-buttonpane button:hover,.button-group li>a:focus,.button-group li>a:hover{text-decoration:none;color:#393939;cursor:default}.ui-dialog.bb-dialog .ui-dialog-buttonpane button:active,.button-group>.selected>a,.button-group>.selected>a.icon,.button-group>li>a:active,.button-group>li>a.icon:active{filter:progid:DXImageTransform.Microsoft.Gradient(StartColorStr=#CCCCCC,EndColorStr=#EEEEEE);background:#ddd;background:-webkit-gradient(linear,0 0,0 100%,from(#ccc),to(#eee));background:-webkit-linear-gradient(#ccc,#eee);background:-moz-linear-gradient(#ccc,#eee);background:-o-linear-gradient(#ccc,#eee)!important;color:#393939}.ui-dialog.bb-dialog .ui-dialog-title{margin-left:6px}.ui-dialog.bb-dialog .newform{margin-left:7px}.ui-dialog.bb-dialog .ui-dialog-buttonpane button{margin:-1px 7px 14px}.repo-desc-hidden #fork-actions{display:none}#fork-actions.button-group{float:right}.ui-dialog.bb-dialog .ui-dialog-buttonpane button,.button-group>li>a.icon{background:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ddd));background:-webkit-linear-gradient(#fff,#ddd);background:-moz-linear-gradient(top,#fff,#ddd);background:-o-linear-gradient(#fff,#ddd);position:relative;cursor:pointer;padding-left:28px}.ui-dialog.bb-dialog .ui-dialog-buttonpane button:before,.button-group>li>a.icon:before{position:absolute;display:block;width:16px;height:16px;left:9px;top:3px;content:""}@-moz-document url-prefix(){.ui-dialog.bb-dialog .ui-dialog-buttonpane button:before{left:-22px;top:-3px}}#fork-actions.button-group>li>a.compare-link:before{background:url(../img/arrow_switch.png)}#fork-actions.button-group>li>a.pull-request:before,#pullrequest-actions.button-group>li>a.pull-request:before{left:8px;top:4px;background:url(../img/icons/fugue/arrow_180.png)}.ui-dialog.pr-fulfill-dialog .ui-dialog-buttonpane button:before,#pullrequest-actions.button-group>li>a.fulfill:before{background:url(../img/icons/fugue/tick-sprite.png)}.ui-dialog.pr-reject-dialog .ui-dialog-buttonpane button:before,#pullrequest-actions.button-group>li>a.reject:before{background:url(../img/icons/fugue/cross.png)}#pullrequest-actions.button-group>li>a.loading:before{background:url(../img/loading.gif)}#changeset-diff .reply td{padding:0}label[for="id_content"]{position:absolute;text-indent:-9999px}#id_content,#id_message{margin:0 0 6px;width:400px;height:90px;border:1px solid #e7e7e7;padding:3px 3px 3px 6px;font:13px/18px Helvetica,Arial,sans-serif;color:#393939}#id_message{margin-left:40px}.pull-request label[for="id_message"]{display:block;margin-top:10px}.pull-request #id_message{margin-left:0}form.comment{margin:0 0 0 102px;padding:0 0 0 6px}form.comment table{border-spacing:0}tr.disable-row{background:#ccc;font-style:italic}tr.disable-row td,tr.disable-row td a{color:gray}table.info{width:100%;border-collapse:collapse;font-size:14px;line-height:1.5em}table.info th,table.info td{border:1px solid #ddd;padding:3px 9px}table.info th{background:#eee;background:-webkit-gradient(linear,left top,left bottom,from(#f7f7f7),to(#e7e7e7));background:-webkit-linear-gradient(#f7f7f7,#e7e7e7);background:-moz-linear-gradient(top,#f7f7f7,#e7e7e7);background:-o-linear-gradient(#f7f7f7,#e7e7e7);font-weight:bold;text-shadow:0 1px 0 #fff}table.info tr:nth-child(odd){background:#f7f7f7}a.action{display:inline;margin:10px auto;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:0;background:#147;background:-webkit-gradient(linear,left top,left bottom,from(#147),to(#036));background:-webkit-linear-gradient(#147,#036);background:-moz-linear-gradient(top,#147,#036);background:-o-linear-gradient(#147,#036);padding:5px 10px;font-weight:bold;font-size:14px;text-shadow:1px 1px 0 #333;color:#fff;cursor:pointer}a.action:hover{background:#036;background:-webkit-gradient(linear,left top,left bottom,from(#036),to(#025));background:-webkit-linear-gradient(#036,#025);background:-moz-linear-gradient(top,#036,#025);background:-o-linear-gradient(#036,#025);text-decoration:none}a.action:active{background:-webkit-gradient(linear,left top,left bottom,from(#025),to(#036));background:-webkit-linear-gradient(#025,#036);background:-moz-linear-gradient(top,#025,#036);background:-o-linear-gradient(#025,#036)}td.code a{display:block;padding:0 3px;color:inherit}td.code a:hover,#changeset-diff td.code a:hover del,#changeset-diff td.code a:hover ins{background:#bdf;text-decoration:none}.comments-link{position:relative;top:3px;display:inline-block;margin:3px 0 0 5px;width:16px;height:16px;background:url(../img/icons/fugue/balloon_left.png);text-indent:-99999px;overflow:hidden}td.comments .comments-link{margin-left:0}.cset-comment-count{float:right;margin:0 5px 0 0}td.fulfilled,td.unfulfilled{background:url(../img/icons/fugue/cross.png) no-repeat 50%;text-indent:-9999px}td.fulfilled{background-image:url(../img/icons/fugue/tick.png)}.progress{position:relative;top:-1px;margin:0 -4px;border:1px solid #ccc}.progress div[aria-valuenow]{background:#fff6c2;text-align:center;font-size:11px;line-height:15px;letter-spacing:2px}.progress span{padding:0 3px}#search-form{float:left;margin-bottom:10px}#search-form input{display:block;float:left;margin:0;padding:4px 5px 4px 30px;width:400px;height:20px;font-size:18px;line-height:20px;border:1px solid #ddd;background:url(../images/search-docs-bg-30px.png) 0 -1px no-repeat}#search-form button{display:block;float:left;margin-left:3px;border:1px solid #ddd;height:30px;font-size:15px;padding:0 10px}#search-form+p{clear:both}.subtitle{clear:both;font-size:11px;line-height:20px}#content .repositories{border-bottom:0}.detailed>li{position:relative;min-height:41px;padding:10px 10px 10px 61px;overflow:auto;-webkit-transition:background .2s linear}.detailed>li:nth-child(even){background:#fcfcfc}.detailed>li:hover{background:#fff6c2}dd.name{float:left;font-size:18px;line-height:22px;color:#999}dd.name a:first-child{color:inherit}dd.name a:hover{text-decoration:none}dd.metadata{display:block;clear:left;float:left;color:#999}dd.metadata a{font-weight:bold;color:inherit}dd.avatar{position:absolute;left:10px;top:10px;width:auto;height:auto;background:#fff}dd.avatar a{display:block}dd.avatar img{display:block;width:35px;height:35px}.detailed p{clear:left;float:left;margin-top:6px;line-height:16px}dd.private.repository,dd.public.repository{position:absolute;right:251px;top:14px;width:16px;height:16px;background:url(../img/icons/fugue/lock.png) no-repeat;text-indent:-9999px}dd.public.repository{top:13px;background-image:url(../img/icons/fugue/lock_disable_unlock.png)}dd.commits,dd.forks,dd.followers{position:absolute;top:10px}dd.commits{right:213px}dd.followers{right:124px}dd.forks{right:47px}.info-button{height:22px;font-size:12px;line-height:22px}.info-button .info-button-action{display:block;-webkit-border-radius:5px 0 0 5px;-moz-border-radius:5px 0 0 5px;border-radius:5px 0 0 5px;border:1px solid #ccc;background:#eee;background:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#ddd));background:-webkit-linear-gradient(#fff,#ddd);background:-moz-linear-gradient(top,#fff,#ddd);background:-o-linear-gradient(#fff,#ddd)}.info-button .info-button-action:active{background:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#eee),color-stop(0.25,#ccc));background:-webkit-linear-gradient(#aaa,#ccc 25%,#eee);background:-moz-linear-gradient(top,#aaa,#ccc 25%,#eee);background:-o-linear-gradient(#aaa,#ccc 25%,#eee)}.info-button-readonly .info-button-action,.info-button-readonly .info-button-action:hover{background:#fff}.info-button .info-button-action a{display:block;width:22px;background-repeat:no-repeat;background-position:6px;padding:0 6px 0 0;text-indent:-9999px;cursor:default}.info-button .info-button-action a{background-image:url(../img/icons/fugue/puzzle.png)}.followers .info-button-readonly .info-button-action a,.followers-not-following .info-button .info-button-action a{background-image:url(../img/icons/fugue/heart_empty.png)}.followers-not-following .info-button .info-button-action a:hover,.followers-following .info-button .info-button-action a{background-image:url(../img/icons/fugue/heart.png)}.followers-following .info-button .info-button-action a:hover{background-image:url(../img/icons/fugue/heart_break.png)}.forks .info-button .info-button-action a{background-image:url(../img/icons/fugue/arrow_045.png)}.followers .info-button-requesting .info-button-action a,.followers .info-button-requesting .info-button-action a:hover{background-image:url(../img/loading.gif)}.info-button .info-button-label{position:absolute;left:29px;display:block;width:36px;-webkit-border-radius:0 5px 5px 0;-moz-border-radius:0 5px 5px 0;border-radius:0 5px 5px 0;border:1px solid #ccc;background:#fff;text-align:center}.commits .info-button .info-button-label{width:48px}button{background:#eee;background:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#ddd));background:-webkit-linear-gradient(#fff,#ddd);background:-moz-linear-gradient(top,#fff,#ddd);background:-o-linear-gradient(#fff,#ddd)}button:active{background:#ccc;background:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#eee),color-stop(0.25,#ccc));background:-webkit-linear-gradient(#aaa,#ccc 25%,#eee);background:-moz-linear-gradient(top,#aaa,#ccc 25%,#eee);background:-o-linear-gradient(#aaa,#ccc 25%,#eee)}dl.menu{line-height:20px}dl.menu dd{margin-left:10px}dl.menu dd+dt{margin-top:5px}ol.code{margin:8px 0 0;border:1px solid #ddd;background:#eee;padding:4px 12px;font-size:12px;line-height:1.5em}ol.code li{line-height:inherit}.container_{margin:0 25px}p.link a:after{content:"\0020»"}#grant-access.loading,#grant-group-access.loading{background:url(../img/loading.gif) no-repeat 386px}#grant-access div,#grant-access input,#grant-group-access div,#grant-group-access input{display:inline}#grant-access label,#grant-group-access label{position:absolute;left:-9999px}#grant-access-user-input{width:15em}label[for="access-level"],label[for="group-access-level"]{position:absolute;left:-9999px}#access-level,#group-access-level{margin:2px}#repo-group-access-pane>p{margin-bottom:5px}#group-access{margin-bottom:10px}#grant-group-access.loading{background:url(../img/loading.gif) no-repeat 386px 6px}#grant-group-access #groupname{width:200px}#what-is-bitbucket{display:none}.new-to-bitbucket{position:relative;margin-bottom:30px;border:1px solid #adf;background:#f2faff;background:-webkit-gradient(linear,left top,left bottom,from(#fafaff),to(#eafaff));background:-webkit-linear-gradient(#fafaff,#eafaff);background:-moz-linear-gradient(top,#fafaff,#eafaff);background:-o-linear-gradient(#fafaff,#eafaff);padding:20px 20px 20px 75px;color:#414141}.new-to-bitbucket .avatar{position:absolute;left:20px;top:20px}#content .new-to-bitbucket h2{margin:0;padding:0;font-size:16px;line-height:20px;color:#065a9b}.new-to-bitbucket p{margin-top:2px;line-height:16px}.primary-action-link{display:inline-block;*display:inline;zoom:1;height:36px;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;background:#147;padding:1px}.new-to-bitbucket .primary-action-link{position:absolute;right:20px;top:20px}.new-to-bitbucket .actions .primary-action-link{position:static;right:0;top:0;margin:20px 20px 0}.primary-action-link a{display:block;height:34px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;border-width:1px;border-style:solid;border-color:#369;border-top-color:#47a;background:#258;background:-webkit-gradient(linear,left top,left bottom,from(#369),to(#147));background:-webkit-linear-gradient(#369,#147);background:-moz-linear-gradient(top,#369,#147);background:-o-linear-gradient(#369,#147);padding:0 12px 0 18px;font-weight:bold;font-size:16px;line-height:32px;color:#fff;text-shadow:0 -1px 0 #333}.primary-action-link a:after{position:relative;top:1px;font-size:20px;font-family:Arial,sans-serif;content:"\0020»";color:rgba(255,255,255,0.8)}.primary-action-link a:hover{border-top-color:#369;background:#147;background:-webkit-gradient(linear,left top,left bottom,from(#258),to(#036));background:-webkit-linear-gradient(#258,#036);background:-moz-linear-gradient(top,#258,#036);background:-o-linear-gradient(#258,#036);text-decoration:none}.primary-action-link a:active{border-color:#258;background:#036;background:-webkit-gradient(linear,left top,left bottom,from(#036),to(#258));background:-webkit-linear-gradient(#036,#258);background:-moz-linear-gradient(top,#036,#258);background:-o-linear-gradient(#036,#258);text-decoration:none}.new-to-bitbucket a[href="#hide"]{position:absolute;left:3px;top:3px;display:none;width:17px;height:17px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAQElEQVQY02OYOXPmmZkQwIAHg8AZOAOPBrg8hgAuhSA+Ax4NGAYw4DEJwyZcbnyNzQ9km0y0m4kODZLCmegYBAAbGv9TWnWYEgAAAABJRU5ErkJggg==) no-repeat 50%;text-indent:-9999px;cursor:default}.jira{background:url(../img/icons/fugue/sticky_notes__arrow.png) no-repeat 0;padding-left:20px}#invitation-login{display:none}#invitation-dialog{padding-top:20px}#invitation-dialog .error_.message_,#invitation-dialog .success_.message_{display:none}#invitation-dialog input{width:265px}#invitation-dialog select{margin-left:15px}#invitation-blurb a[href="#share"]{background:url(../img/icons/fugue/card_address.png) no-repeat 0;padding-left:20px}#repo-access-pane #invitation-blurb{margin-top:10px}#toggle-email-notifications{float:right;margin-top:3px;margin-right:6px}#toggle-email-notifications .loading{background:url(../img/loading.gif) no-repeat 0 0;padding-left:20px}input.inline-field{border:1px solid #ddd;margin:5px -5px;height:16px;width:100%}input.inline-field.loading{background:url(../img/loading.gif) no-repeat 99% 50%}input.inline-field.placeholder,input.placeholder{color:#999}.mapping-widget li.messages .message_{margin:0 -10px;padding:5px}.mapping-widget li.messages .message_ h4{margin-left:5px;padding-left:30px;font-size:13px;line-height:17px}.unmapped-user{border-bottom:1px dotted #aaa}.unmapped-user a{font-size:.95em}.jsudiff{white-space:pre;font:11px/16px "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace;color:#747474}.jsudiff .context{background:#eee}.jsudiff td{display:block;float:left;height:16px}.jsudiff .number{width:25px;border-right:1px solid #ddd;background:#eee;padding:0 5px;text-align:right}.jsudiff a{color:inherit}.jsudiff .code{color:#393939}.jsudiff .context .code{color:inherit}a.compare-link img{position:relative;top:3px}#groups{position:relative;margin:0 auto;width:413px}#groups .link{margin-top:5px}#groups-container>ol>li{margin:10px -10px 0;padding:0 10px 10px}#groups h2{float:left;margin:0;padding:10px 0 0;font-weight:normal;font-size:18px;line-height:22px;color:#999}#groups h2 span{color:#2b547d}.group-members>.mapping-widget{clear:both;margin:0 0 10px}.group-members>ol{margin-bottom:0}.group-members>.errorlist{clear:both}.group-members form div{float:left}.group-members form div+div{float:right}.group-members label{position:absolute;left:-9999px}.group-members input[type="text"]{width:320px}.group-members input[type="submit"]{margin-right:0}#add-group-link{position:absolute;right:20px;top:0;background:url(../img/icons/fugue/plus.png) no-repeat 0;padding-left:20px;line-height:28px;color:#525252}#add-group-form{display:none;padding-top:20px}#groups .editable-title form{display:inline-block;margin:-4px -1px}#groups .editable-title input{width:220px;padding:0}#groups .delete-group{display:block;clear:both;margin-bottom:10px;color:#999;text-decoration:underline}#groups h2+div{float:right;padding-top:11px}#groups h2+div label{padding-right:2px}#groups h2+div select{margin-bottom:0}#groups-container .group-members{margin:10px -10px 0;padding:0 10px;overflow:auto}#add-group-input{width:265px}td.cset-delimiter{color:gray;text-align:center;font-family:monospace}#loadmoar{display:block;margin-top:16px;border:1px solid #ddd;padding:4px;text-align:center;color:#747474}#loadmoar:hover{background:#f7f7f7;text-decoration:none}#loadmoar.spinner{background:url(../img/loading.gif) no-repeat 50%;text-indent:-9999px}#infobox{position:fixed;right:4px;top:4px;height:10px;width:10px;text-indent:-9999px;border:2px solid #000;-moz-border-radius:8px;border-radius:8px}#infobox:hover{text-indent:0;width:300px;height:65px;border:1px solid #ece4ea;background:rgba(255,255,255,0.8);padding:4px;text-align:right}#infobox h4{margin:0 2px 0 0;border-bottom:1px solid #ece4ea}#infobox dl{margin:8px 0 0 0}.if-orange{background:#ffbf00}.if-red{background:red}.if-green{background:#dfd}#infobox dt,#infobox dd{float:right;margin-right:8px;height:18px}#infobox input{height:8px}.newtable{border-collapse:collapse;line-height:28px;color:#333}.newtable tr{display:block;clear:both}.newtable th,.newtable td{display:block;float:left;border-bottom:1px solid #ddd;height:28px;padding:0 20px 0 0;overflow:hidden}.newtable th{border-bottom-width:2px;font-weight:bold}.newtable th.date,.newtable td.date{width:100px}.newtable td.hash{font-size:12px}.newtable td.hash code{border:0;background:0;padding:0}.newtable th.person,.newtable td.person{width:160px}.newtable td.person{position:relative;width:132px;padding-left:28px}.newtable td.person img,.newtable td.repo img{position:absolute;left:0;top:4px;display:block;width:16px;height:16px;border:1px solid #ddd;padding:1px}.newtable td.repo{position:relative;padding-left:28px;color:#999}.newtable td.repo.deleted{padding-left:0}.newtable td.repo a,.newtable td.repo a:hover{color:inherit}.newtable td.repo a~a,.newtable td.repo a~a:hover{color:#2b547d}.newtable th.labels{position:absolute;left:-9999px}.newtable td.labels{position:absolute;right:140px;background:#fff;padding-right:0;overflow:visible}.newtable td.labels:before{position:absolute;left:-32px;top:0;bottom:0;display:block;width:32px;background:#fff;background:-webkit-gradient(linear,0 0,100% 0,from(rgba(255,255,255,0)),to(rgba(255,255,255,1)),color-stop(0.75,rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0),rgba(255,255,255,1));background:-moz-linear-gradient(left,rgba(255,255,255,0),rgba(255,255,255,1));background:-o-linear-gradient(left,rgba(255,255,255,0),rgba(255,255,255,1));content:""}.newtable .labels dt{position:absolute;left:-9999px}.newtable .labels dd{display:block;float:left;margin:4px 0 0 4px;max-width:150px;height:18px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:1px solid #ddd;background:#eee;padding:0 6px;text-align:right;white-space:nowrap;font:11px/18px Monaco,monospace;color:#555;overflow:hidden;text-overflow:ellipsis;text-overflow:ellipsis-word}.newtable .labels .tag{background:url(../img/icons/fugue/tags.png) no-repeat 4px;padding-left:24px}.newtable .labels.primary-branch:before,.newtable .labels.primary-branch .branch{display:none}.two-thirds-primary{float:left;width:555px}.two-thirds-secondary{float:right;width:300px}.two-thirds-secondary>h3{padding-top:16px}.two-thirds-secondary>p{margin:9px 20px 9px 0}#repo-create{margin-top:-6px}#first-time{padding-top:10px}#repo-import #old-repo{padding-top:6px}#clone-url-https,#clone-url-ssh{margin:0;font-size:12px;line-height:1.5}.diff-too-large{padding:8px}.newform{width:560px}.newform fieldset{margin:0;border:0;padding:0}.newform .checkbox.related-to-preceding{margin-top:4px}.newform label,.newform legend{display:block;padding:16px 0 4px;font-weight:bold;color:#666}.newform fieldset label,.newform .checkbox label{padding:inherit;font-weight:inherit}.newform .required label:after,.newform .required legend:after{content:"\0020(required)";font-weight:normal;font-style:italic;font-size:11px;color:#888}.newform div,.newform fieldset{position:relative;clear:both}.newform fieldset.no-legend,.newform .submit{padding-top:16px}.newform .submit{margin-bottom:20px}.newform input[type="file"],.newform input[type="submit"]{margin:0}.newform .left,.newform .right{width:270px}.newform .left{clear:left;float:left;margin-right:0}.newform .right{clear:right;float:right;margin-left:0}.newform input[type="url"],.newform input[type="text"],.newform input[type="password"],.newform input[type="email"]{margin:0;width:550px;font-size:16px;line-height:20px;color:#222}.newform input[type="url"],.newform input[type="text"],.newform input[type="password"],.newform input[type="email"],textarea{border:1px solid #ccc;border-bottom-color:#999;padding:4px}.newform input[type="url"]:focus,.newform input[type="text"]:focus,.newform input[type="password"]:focus,.newform input[type="email"]:focus,textarea:focus{-moz-box-shadow:1px 1px 3px #6ae,1px -1px 3px #6ae,-1px 1px 3px #6ae,-1px -1px 3px #6ae}.newform input[type="url"][disabled],.newform input[type="text"][disabled],.newform input[type="password"][disabled],.newform input[type="email"][disabled],.newform input[type="url"][readonly],.newform input[type="text"][readonly],.newform input[type="password"][readonly],.newform input[type="email"][readonly]{background-color:#f8f8f8;cursor:default}.newform .left input[type="url"],.newform .left input[type="text"],.newform .left input[type="password"],.newform .left input[type="email"],.newform .right input[type="url"],.newform .right input[type="text"],.newform .right input[type="password"],.newform .right input[type="email"]{width:260px}.newform textarea,.ui-dialog .newform textarea{margin:0;width:550px;padding:4px;font:16px/20px Helvetica,Arial,sans-serif;color:#222}.newform select{margin:0;width:270px}.newform option{font-size:13px}.newform legend.singleton{visibility:hidden}.newform .checkbox legend,.newform .radio legend{padding-bottom:6px}.newform .checkbox label,.newform .radio label{margin:4px 0 4px 20px;color:#555}.newform .checkbox input,.newform .radio input{position:absolute;left:0;top:1px;margin:0}.newform .checkbox input{top:5px}.newform h1,.newform h2,.newform h3,.newform h4,.newform h5,.newform h6{clear:both;padding-top:20px;font-size:16px;line-height:20px}.newform>.message_{margin:10px 0;border:1px solid #ccc;padding:10px 0 10px 16px}.newform>.message_ h4{margin:0;padding:4px 0 4px 36px;line-height:19px;font-size:14px}.newform .field_.message_{border:1px solid #ccc;padding:5px 0 5px 36px;margin:5px 0;list-style:disc}.tooltip_.message_.note_>*:first-child,.newform .field_.message_>*:first-child{margin:0 0 0 -30px;background-repeat:no-repeat;background-position:0 0;padding:4px 0 4px 30px;list-style:none;font-weight:bold}.newform .field_.message_.error_>*:first-child{background-image:url(../img/icons/atl/red-warning-icon.png);color:#b50000}.newform .field_.message_.warning_>*:first-child{background-image:url(../img/icons/atl/yellow-warning-icon.png);background-position:0 2px}.newform .field_.message_.success_>*:first-child{background-image:url(../img/icons/atl/green-upgrade-arrow-icon.png);color:#2d6b00}.tooltip_.message_.note_>*:first-child,.newform .field_.message_.note_>*:first-child{background-image:url(../img/icons/info.png);background-position:6px 4px}.newform .description{font-size:11px}.newform .checkbox label,.newform .radio label{display:block;padding-left:20px;text-indent:-20px;margin-left:0}.newform .checkbox label input,.newform .radio label input{width:13px;height:13px;padding:0;margin:0 3px 0 0;vertical-align:bottom;position:relative;top:-1px;*overflow:hidden}.newform .action{clear:both;float:left;margin:10px 0 0}.newform .action input[type=submit]{margin:0 0 10px}.newform.repo-create #id_description{height:100px}#repo-import .source-fields{display:none}#repo-import #id_url{width:534px;padding-right:20px}#repo-import #id_url.spinner,#repo-import #id_url.success,#repo-import #id_url.warning,#repo-import #id_url.error{background-image:url(../img/loading.gif);background-position:532px 5px;background-repeat:no-repeat}#repo-import #id_url.success{background-image:url(../img/icons/fugue/tick.png)}#repo-import #id_url.warning{background-image:url(../img/icons/fugue/exclamation.png)}#repo-import #id_url.error{background-image:url(../img/icons/fugue/slash.png)}#repo-import #url-auth-fields{display:none}.newform.repo-create #id_name{background:url(../img/icons/fugue/lock.png) no-repeat scroll 249px 50%;padding-right:20px;width:244px}.newform.repo-create #id_name.public{background-image:url(../img/icons/fugue/lock_disable_unlock.png)}.newform .warning_{width:275px;float:left;clear:both;margin:0;padding:0}.newform .warning_ p{margin:15px 0 0;padding:0 0 0 35px;color:#333;background:url("../img/icons/atl/warning-icon.png") 0 5px no-repeat}.newform .warning_{width:275px;float:left;clear:both;margin:0;padding:0}.newform .warning_ p{margin:15px 0 0;padding:0 0 0 35px;color:#333;background:url("../img/icons/atl/warning-icon.png") 0 5px no-repeat}.page-buttons{margin:0 0 20px 0;overflow:auto}.page-buttons-left,.page-buttons-right{border:1px solid #adf;font-size:18px;text-align:center;outline:0}.page-buttons-left{float:left;-webkit-border-radius:10px 0 0 10px;-moz-border-radius:10px 0 0 10px;border-radius:10px 0 0 10px;border-right:0;width:464px}.page-buttons-right{float:right;-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;width:463px}.page-buttons-selected,.page-buttons-unselected:hover{background:#147;background:-webkit-gradient(linear,left top,left bottom,from(#147),to(#036));background:-webkit-linear-gradient(#147,#036);background:-moz-linear-gradient(top,#147,#036);background:-o-linear-gradient(#147,#036);border-color:#036}.page-buttons-unselected{background:#f2faff;background:-webkit-gradient(linear,left top,left bottom,from(#fafaff),to(#eafaff));background:-webkit-linear-gradient(#fafaff,#eafaff);background:-moz-linear-gradient(top,#fafaff,#eafaff);background:-o-linear-gradient(#fafaff,#eafaff)}.page-buttons a,.page-buttons span{display:block;text-decoration:none}.page-buttons-selected{cursor:default}.page-buttons-link{padding:16px 10px 0 20px}.page-buttons-unselected:hover .page-buttons-link,.page-buttons-selected .page-buttons-link{color:white}.page-buttons-description{padding:2px 10px 12px 20px;font-size:12px;color:#999}.bb-dropdown{position:relative;height:28px;background-color:#cde;background-color:rgba(187,204,221,0.75);background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAOCAYAAADNGCeJAAAAQ0lEQVQ4y2P8//8/AxqACTAykAgY0QxDN5mRXMP+41JDqmH/Cakj1rD/xFpMaphRBEaQYVSPAKolDaonWqpnJ4ozOgBNPDPl3XpStwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:100%;padding:0 28px 0 0;cursor:default}.bb-dropdown.singleton{padding:0}.bb-dropdown:hover{background-color:#bcd}.bb-dropdown ::-moz-selection{background-color:transparent}.bb-dropdown ::selection{background-color:transparent}.bb-dropdown,.bb-dropdown>ul{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:2px solid #bcd}.bb-dropdown>ul{position:relative;margin:-2px;background:#fff;line-height:25px;z-index:1}.bb-dropdown>ul>li{display:none;margin:1px;height:24px;border:1px solid #ddd;padding:0 10px;overflow:hidden}.bb-dropdown>ul>li.selected{display:block;border-color:#bcd}.bb-dropdown.open>ul>li{display:block}.bb-dropdown.open>ul>li.selected{background:#fffbe1!important}.bb-dropdown.open>ul>li:hover{background:#fff6c2!important}.bb-button{display:inline;margin:10px auto;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:0;background:#147;background:-webkit-gradient(linear,0 0,0 100%,from(#147),to(#036));background:-webkit-linear-gradient(#147,#036);background:-moz-linear-gradient(top,#147,#036);background:-o-linear-gradient(#147,#036);padding:5px 10px;font-size:14px;text-shadow:1px 1px 0 #333;color:#fff;cursor:pointer}.bb-button:hover{background:#036;background:-webkit-gradient(linear,0 0,0 100%,from(#036),to(#025));background:-webkit-linear-gradient(#036,#025);background:-moz-linear-gradient(top,#036,#025);background:-o-linear-gradient(#036,#025)}.bb-button:active{background:#025;background:-webkit-gradient(linear,0 0,0 100%,from(#025),to(#036));background:-webkit-linear-gradient(#025,#036);background:-moz-linear-gradient(top,#025,#036);background:-o-linear-gradient(#025,#036)}.bb-button.accept{background:#282;background:-webkit-gradient(linear,0 0,0 100%,from(#282),to(#171));background:-webkit-linear-gradient(#282,#171);background:-moz-linear-gradient(top,#282,#171);background:-o-linear-gradient(#282,#171)}.bb-button.accept:hover{background:#171;background:-webkit-gradient(linear,0 0,0 100%,from(#171),to(#060));background:-webkit-linear-gradient(#171,#060);background:-moz-linear-gradient(top,#171,#060);background:-o-linear-gradient(#171,#060)}.bb-button.accept:active{background:#060;background:-webkit-gradient(linear,0 0,0 100%,from(#060),to(#171));background:-webkit-linear-gradient(#060,#171);background:-moz-linear-gradient(top,#060,#171);background:-o-linear-gradient(#060,#171)}.bb-button.reject{background:#c00;background:-webkit-gradient(linear,0 0,0 100%,from(#c00),to(#b00));background:-webkit-linear-gradient(#c00,#b00);background:-moz-linear-gradient(top,#c00,#b00);background:-o-linear-gradient(#c00,#b00)}.bb-button.reject:hover{background:#b00;background:-webkit-gradient(linear,0 0,0 100%,from(#b00),to(#a00));background:-webkit-linear-gradient(#b00,#a00);background:-moz-linear-gradient(top,#b00,#a00);background:-o-linear-gradient(#b00,#a00)}.bb-button.reject:active{background:#a00;background:-webkit-gradient(linear,0 0,0 100%,from(#a00),to(#b00));background:-webkit-linear-gradient(#a00,#b00);background:-moz-linear-gradient(top,#a00,#b00);background:-o-linear-gradient(#a00,#b00)}#svn-import{padding:0 25px}#svn-import>#masking-box{margin:0 -25px}#svn-import>#masking-box *:hover{cursor:default}#svn-import>#masking-box a:hover{text-decoration:none}#svn-import>#masking-box #toggle-repo-content{background-color:#eee}#svn-import>#masking-box>#mask{position:absolute;left:-2px;top:0;right:-2px;bottom:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#80FFFFFF,endColorStr=#80FFFFFF);background:rgba(255,255,255,0.5);z-index:99}#svn-import>h1{margin-top:20px}#svn-import>p{margin:1em 0;font-size:14px;line-height:1.5}#svn-import #progress{margin:10px 0;height:24px;padding:1px}#svn-import.error-state h1+p,#svn-import.error-state #progress,#svn-import.warning-state #progress,#svn-import.success-state #progress{display:none}#svn-import #progress,#svn-import #progress>.ui-progressbar-value{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}#svn-import #progress>.ui-progressbar-value{margin:0;background:#36b;background:-webkit-gradient(linear,0 0,0 100%,from(#69e),to(#36b));background:-webkit-linear-gradient(#69e,#36b);background:-moz-linear-gradient(top,#69e,#36b);background:-o-linear-gradient(#69e,#36b)}#svn-import #progress>.ui-progressbar-value.static-progress{background:url(../img/progressbar.gif) repeat}#svn-import #progress>.ui-progressbar-value>span{position:absolute;top:0;left:0;bottom:0;right:0;display:block;background:-webkit-gradient(linear,0 0,100% 100%,from(rgba(255,255,255,0.15)),color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:38px 38px;-moz-background-size:38px 38px;background-size:38px 38px;padding:0 10px;font-weight:bold;font-size:16px;line-height:26px;color:#999;cursor:default}#svn-import #progress>.ui-progressbar-value>span::-moz-selection{background:transparent}#svn-import #progress>.ui-progressbar-value>span::selection{background:transparent}#svn-import h2{position:absolute;left:-9999px}#svn-import #log{margin:10px 0;border:solid 1px #e7e7e7;padding:1px}#svn-import #log>ol{height:288px;background:#000;padding:4px 8px;line-height:18px;font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace;color:#fff;overflow:auto}#svn-import #log>ol ::-moz-selection{background:rgba(187,187,187,0.5)}#svn-import #log>ol ::selection{background:#bbb}#svn-import #log time{color:#6ff}#svn-import #status{display:none;margin:10px 0 0;width:876px;border:1px solid #e7e7e7;background:0;padding:1px;font-weight:bold}#svn-import.error-state #status,#svn-import.warning-state #status,#svn-import.success-state #status{display:block}#svn-import #status>p{border-width:1px;border-style:solid;background-repeat:no-repeat;background-position:10px 7px;padding:0;text-align:center;font-size:14px;line-height:22px;text-transform:uppercase}#svn-import.error-state #status>p{border-color:#b50000;background:#ffeae8;color:#b50000}#svn-import.warning-state #status>p{border-color:#fed;background-color:#fff6c2;color:#666}#svn-import.success-state #status>p{border-color:#480;background:#e7f9e0;color:#2d6b00}#consumers table{margin-top:10px;border-spacing:0}#consumers th{border-bottom:2px solid #ddd;padding:6px 0 6px 24px;font-weight:bold;color:#666}#consumers th.creation-date{width:100px}#consumers th.revoke-access{text-indent:-9999px;overflow:hidden}#consumers table td{border-bottom:1px solid #ddd;padding:6px 0 6px 24px}#consumers th.consumer,#consumers td.consumer{width:180px;padding-left:0;font-weight:bold}#consumers th.description,#consumers td.description{width:558px}#consumers td.creation-date{width:100px;color:#666}#consumers td.revoke-access>a{position:relative;top:-2px;display:block;margin-bottom:-20px;width:20px;height:20px;background:url(../img/icons/fugue/cross-sprite.png) no-repeat;text-indent:-9999px;overflow:hidden}#consumers td.revoke-access>a:focus,#consumers td.revoke-access>a:hover{background-position:0 -20px}#changeset h1{margin:0;font-weight:normal;font-size:18px;line-height:2;color:#666}#changeset h1>span>a{font-size:16px;color:#aaa}#changeset #compare h3>a{color:inherit}#changeset #compare h3>a:hover{color:#333}#changesets h1{margin:0;font-weight:normal;font-size:18px;line-height:2;color:#666}#changesets h1>span{color:#aaa}#changesets #commit-filter-help{display:none;padding:0 0 30px}#changesets #commit-filter-help h2{margin:0;padding:0;line-height:2}#changesets #commit-filter-help dl{border-bottom:1px solid #ddd;overflow:auto}#changesets #commit-filter-help dt,#changesets #commit-filter-help dd{border-top:1px solid #ddd;padding:6px 0;line-height:18px}#changesets #commit-filter-help dt{display:block;clear:both;float:left;width:300px;font-weight:bold;font-family:monospace}#changesets #commit-filter-help dd{display:block;float:right;width:630px}#changesets #commit-filter-help dl p{margin:0}#changesets #commit-filter-help dl p+p{margin-top:9px}#changesets #commit-filter-help code{border:1px solid #ddd;background:#eee;padding:0 2px;font-family:monospace;line-height:1}#changesets #shortlogs-changes .newtable td.text a{color:inherit}#changesets>#commit-history{clear:both}#changesets #commit-history .newtable{width:100%}#changesets #commit-history .newtable .hash{width:95px;padding-right:0}#changesets #commit-history .newtable .text{width:490px}#changesets #commit-history .newtable .comments{width:25px;padding-right:0}#changesets #commit-history .newtable .comments a{position:relative;top:3px}#changesets #commit-history .newtable .comments img{border:0;margin:0;float:none}#changesets #commit-history .newtable .comments span{display:none}#changesets #commit-history .newtable th.comments{text-indent:-9999px}#changesets #shortlogs-changes{position:relative}#changesets #changesets-graph{position:absolute;top:-40px;left:-400px;width:400px}#changesets #changesets-inner{position:static;margin:0 0 10px}#changesets #changesets-inner h3{margin:-1px 0 0;border:1px solid #ddd;background:#eee;padding:10px 24px;font-size:15px}#changesets .paginator{margin-top:9px}#changesets #changesets-inner>ol{font:12px/1.5em "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace}#changesets #changesets-inner>ol>li{margin:-1px 0 0;border:1px solid #ddd;padding:10px 300px 10px 24px}#changesets #changesets-inner>ol>li:nth-child(even){background:#fcfcfc}#changesets #changesets-inner h4{position:absolute;left:-9999px}#changesets #changesets-inner p{margin:.25em 0 .5em;width:600px;line-height:inherit}#changesets #changesets-inner .metadata{position:relative;margin:.5em 0 0;min-height:34px;padding:2px 0 2px 45px;font-family:Helvetica,Arial,sans-serif}#changesets #changesets-inner .metadata .avatar{position:absolute;left:-3px;top:0}#changesets #changesets-inner .metadata dt{position:absolute;left:-9999px}#changesets #changesets-inner .metadata time,#changesets #changesets-inner .metadata dd[title]{color:#747474}#changesets #changesets-inner li{position:relative}#changesets #changesets-inner .metadata+p{position:absolute;right:220px;bottom:12px;display:block;width:auto}#changesets #commit-history #file-history .hash{width:120px}#changesets #commit-history #file-history .added,#changesets #commit-history #file-history .removed{width:26px;background:0;padding-right:4px;text-align:right;color:#282}#changesets #commit-history #file-history .removed{color:#c33}#changesets #commit-history #file-history th.added,#changesets #commit-history #file-history th.removed{background:url(../img/icons/green/add.gif) no-repeat 100%;line-height:999px;overflow:hidden}#changesets #commit-history #file-history th.removed{background-image:url(../img/icons/red/remove.gif)}#changesets #commit-history #file-history .text{width:410px}#changesets #commit-history #file-history.no-diffstat .text{width:470px}#changesets #commit-history #file-history .text a{color:inherit}#changesets #commit-history #file-history td.labels{right:210px}#changesets #commit-history #file-history td.branch{right:140px}#changesets #commit-history #file-history td.branch>span{float:right;margin-left:4px}#changesets #commit-history #file-history td.branch>span.tag{background:#fff url(../img/icons/fugue/tags.png) no-repeat 4px;padding-left:24px}#changesets #commit-history #file-history .date{padding-left:20px}#changesets #commit-history #file-history .link{width:60px;padding-right:0}#changesets #commit-history #file-history th.link{margin-top:-6px;padding-bottom:6px;line-height:1}#commit-filter-control{float:right;margin-top:4px;-webkit-border-radius:13px;-moz-border-radius:13px;border-radius:13px;border:1px solid #ccc;background:#eee;background:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ddd));background:-webkit-linear-gradient(#fff,#ddd);background:-moz-linear-gradient(top,#fff,#ddd);background:-o-linear-gradient(#fff,#ddd);padding:0 4px 0 0;position:relative;z-index:1}#commit-filter-control:before{position:absolute;left:-9999px;top:5px;display:block;width:16px;height:16px;background:url(../img/loading.gif);content:""}#commit-filter-control.loading:before{left:-24px}#commit-filter-control div,#commit-filter-control ul{float:left}#commit-filter-control li{float:left;width:23px;height:26px;text-indent:-9999px}#commit-filter-control a{display:block;width:100%;height:100%;border-left:1px solid #ccc;background:no-repeat 50%}#commit-filter-control a[href="#help"]{border-left:none;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAALCAYAAABcUvyWAAAANElEQVQY02NIS0v7jwUzMOCQ+A+SQMcEJP7/R2AUo7AJgi3HJohVAsqHS4Axsn1YdeCVAADeXJAKhUuM8wAAAABJRU5ErkJggg==)}#commit-filter-control a[rel="prev"]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAALCAYAAABcUvyWAAAAK0lEQVQY033JMQEAAAyDMPxrw1NnoOvBQ1Appc4GaZAGaZAPWMACFrAANQeWWMGlEemaWAAAAABJRU5ErkJggg==)}#commit-filter-control.no-help a[rel="prev"]{border-left:none}#commit-filter-control a[rel="prev"][href]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAALCAYAAABcUvyWAAAAM0lEQVQY02P4//8/AzJOS0sDYeyCKBLIgnAJdEGwBDZBmMR/XBIM+CQY8EngdBVef8AlAcYvihBc6BRgAAAAAElFTkSuQmCC)}#commit-filter-control a[rel="next"]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAALCAYAAABcUvyWAAAAM0lEQVQYV2M8c+bMfwYGBkYGNMAIlQCzcUmA+bgkwGK4JECAEZcEaTqw2oHVVVj9geFzAIGmJVexAkzYAAAAAElFTkSuQmCC)}#commit-filter-control a[rel="next"][href]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAALCAYAAABcUvyWAAAAMklEQVQY02NMS0v7z8DAwMiABhihEgzoksgSKJLoEnBJbBJgcVwSpOlgJNpVWP2B4XMA5zYYMQRYttMAAAAASUVORK5CYII=)}#commit-filter-control a[rel="last"]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAJCAYAAAALpr0TAAAANklEQVQY02M4c+bMfyBmQMMYYjDB/1gU/sel8D8Whf9xKfyPReF/XAqxYrJNJMqNRPmaYDgCAMIsCQYwqXRDAAAAAElFTkSuQmCC)}#commit-filter-control a[rel="last"][href]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAJCAYAAAALpr0TAAAAPElEQVQY02NIS0v7DwQMQJoBREPZGGJgQXQJbGJwQWQJbGIogkgSGGIYgrgweSYS5UYkRVjFGLAowioGAFM/xNULoBOpAAAAAElFTkSuQmCC)}#commit-filter-control input{margin:2px;width:300px;font-size:16px;border:1px solid #cfcfcf;background:#fff url(../images/search-icon.png) no-repeat 4px;border-radius:12px}body.non-webkit #commit-filter-control input{font-size:13px;padding:3px 0 3px 23px}#compare #interdiff-heading{font-weight:normal;font-size:18px;color:#999}#compare h2,#compare h3{margin:0;padding-top:10px;font-weight:normal;font-size:18px;line-height:2;color:#666}#compare h2>span{color:#aaa}#compare h3>a{color:inherit}#compare #repo-heading>a{color:#2b547d}#compare .incoming-outgoing{position:absolute;right:0;margin-top:-30px}#compare>p,#compare #compare-header>p{margin:.5em 0 1em}#compare>pre,#compare #compare-header>pre{margin:1em 0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:1px solid #ddd;background:#eee;padding:.5em 1em;font-size:12px;line-height:16px}#compare .newtable .hash{width:100px}#compare .newtable .text{width:490px}#compare .newtable .date{width:100px}#compare .newtable .file{width:778px;background-repeat:no-repeat;background-position:2px}#compare .newtable td.file{width:750px;padding-left:28px}#compare .newtable td.file strong.conflicts{color:#c00;font-weight:normal}#compare .newtable .file-added{background-image:url(../img/icons/green/add.gif)}#compare .newtable .file-modified{background-image:url(../img/icons/orange/edit.gif)}#compare .newtable .file-removed{background-image:url(../img/icons/red/remove.gif)}#compare .newtable .file-conflicts{background-image:url(../img/icons/red/warning.gif)}#compare .newtable .lines-added,#compare .newtable .lines-removed{width:36px;padding:0;text-align:center}#compare .newtable th.lines-added,#compare .newtable th.lines-removed{background:url(../img/icons/green/add.gif) no-repeat 10px;text-indent:-9999px}#compare .newtable th.lines-removed{background-image:url(../img/icons/red/remove.gif);padding-right:60px}#compare .newtable td.lines-added{color:#282}#compare .newtable td.lines-removed{padding-right:60px;color:#c33}#compare a[href="#files"]{position:absolute;right:0;margin-top:20px;width:15px;height:15px;background:url(../img/up-arrow-sprite.png);text-indent:-9999px}#compare a[href="#files"]:focus,#compare a[href="#files"]:hover{background-position:-15px}#compare a[href="#files"]~a[href="#files"]{margin-top:30px}#compare #changesets-graph{position:absolute;top:-40px;left:-400px;width:400px}#compare #shortlogs-changes .newtable td.text a{color:inherit}#compare #shortlogs-changes .newtable{width:100%}#compare #shortlogs-changes .newtable .hash{width:95px;padding-right:0}#compare #shortlogs-changes .newtable .text{width:490px}#compare #shortlogs-changes .newtable .comments{width:25px;padding-right:0}#compare #shortlogs-changes .newtable .comments a{position:relative;top:3px}#compare #shortlogs-changes .newtable .comments img{border:0;margin:0;float:none}#compare #shortlogs-changes .newtable .comments span{display:none}#compare #shortlogs-changes .newtable th.comments{text-indent:-9999px}#compare #shortlogs-changes{position:relative}#cross-changeset-file-diff h1{padding:16px 0 6px;font-weight:normal;font-size:18px;line-height:24px;color:#666}#cross-changeset-file-diff h1>a{color:#2b547d}#cross-changeset-file-diff .newtable .file{width:778px}#cross-changeset-file-diff .newtable .lines-added,#cross-changeset-file-diff .newtable .lines-removed{width:36px;padding:0;text-align:center}#cross-changeset-file-diff .newtable th.lines-added,#cross-changeset-file-diff .newtable th.lines-removed{background:url(../img/icons/green/add.gif) no-repeat 10px;text-indent:-9999px}#cross-changeset-file-diff .newtable th.lines-removed{background-image:url(../img/icons/red/remove.gif);padding-right:60px}#cross-changeset-file-diff .newtable td{background-repeat:no-repeat;background-position:2px}#cross-changeset-file-diff .newtable td.file{width:750px;padding-left:28px}#cross-changeset-file-diff .newtable td.lines-added{color:#282}#cross-changeset-file-diff .newtable td.lines-removed{padding-right:60px;color:#c33}#cross-changeset-file-diff .newtable .file-added{background-image:url(../img/icons/green/add.gif)}#cross-changeset-file-diff .newtable .file-modified{background-image:url(../img/icons/orange/edit.gif)}#cross-changeset-file-diff .newtable .file-removed{background-image:url(../img/icons/red/remove.gif)}#cross-changeset-file-diff #changeset-diff h3,#cross-changeset-file-diff #changeset-diff a[href="#files"]{display:none}.user-comments{clear:both}.user-comments #comments-list{padding:0}.user-comments #comments-list:not(:empty){margin-bottom:20px}.user-comments #comments-list>li{margin:0}.user-comments #comments-list>li:before{content:""}.user-comments #comment-header{margin:6px 0;padding:10px 0 0;font-weight:normal;font-size:18px;line-height:24px;color:#666}.user-comments .comment-not-spam-link{display:none;position:absolute;right:40px;bottom:10px}.user-comments .user-comment .comment-not-spam-link a span{background:url(../img/icons/fugue/plus_small_circle.png) no-repeat left 65%;padding:2px 0 2px 14px;font-size:90%}.user-comments .user-comment.is-spam{background:#eee url(../img/diagonal_transparent_bg.png) repeat left top}.user-comments .comment-permalink{position:absolute;right:16px;bottom:10px}.user-comments .comment-permalink a span{background:url(../img/icons/permalink_light_16.png) no-repeat 50% 50%;width:16px;height:16px;display:block;text-indent:-9999px;cursor:pointer;margin-top:1px}.user-comments .comment-permalink a:hover span{background-image:url(../img/icons/permalink_dark_16.png)}.user-comments h4{margin-top:0}.user-comments blockquote,.user-comments pre{margin:9px 0;border:1px solid #ddd;padding:9px 12px}.user-comments blockquote{background:#fff;padding-top:0;padding-bottom:0}.user-comments textarea{font:12px/1.5 "Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace}.user-comments .user-comment{position:relative;margin:-1px 0 0;border-bottom:1px solid #ddd;border-top:1px solid #ddd;background:#fff;padding:10px 16px 10px 58px;font-size:13px}.user-comments .deleted{position:relative;margin:-1px 0 0;border-bottom:1px solid #ddd;border-top:1px solid #ddd;background:#fff;padding:10px 16px 10px 5px}.user-comments .user-comment img.avatar{width:32px;height:32px}#changeset .user-comment p{margin:9px 0;padding:0}.user-comments .user-comment dt{position:absolute;text-indent:-9999px}.user-comments .username{display:inline;font-weight:bold;color:#2b547d}.user-comments .user-comment .newform{width:auto}.user-comments .user-comment .changeset{display:none}.user-comments .user-comment .date{position:absolute;right:16px;top:10px;color:#999;font-size:90%}.user-comments .user-comment .comment-actions{position:relative;height:14px;border:0;padding:0;line-height:1;color:#666}.user-comments .user-comment .comment-actions a{font-size:90%}.user-comments .user-comment .comment-actions>li{float:left;margin:0}.user-comments .user-comment .comment-actions>li+li{margin-left:8px;border-left:1px solid #999;padding-left:8px}.user-comments .user-comment .comment-actions>li:before{content:""}.user-comments .child-comments{padding:0}.user-comments .child-comments>li{margin:0}.user-comments .child-comments>li:before{content:""}.user-comments .child-comments .user-comment{margin-left:48px}.user-comments .child-comments .child-comments .user-comment{margin-left:96px}.user-comments .child-comments .deleted{margin-left:48px}.user-comments dd.comment{margin:-5px 0 0}.user-comments .comment-reply{display:none;padding-top:10px}.user-comments #comment-reply-template{display:none}.user-comments ul .comment-reply{margin-left:58px}.user-comments ul .child-comments .child-comments .comment-reply{margin-left:106px}.user-comments .comment-reply .username{position:relative;padding:0 12px 0 48px}.user-comments .comment-reply img.avatar{position:absolute;left:0;top:10px}.user-comments .comment-reply .comment-form-container{margin-left:48px}.user-comments .comment-reply .comment-form-container .newform{width:100%}.user-comments .comment-reply .comment-form-container .newform .textarea-container{padding:0 0 5px}.user-comments .comment-reply .comment-form-container .newform textarea{margin:0;width:100%;height:108px;padding:3px 6px}.user-comments .comment-reply .comment-form-container .newform .submit{padding-top:0}.user-comments .new-comment-link{display:block;margin:10px 0}.composing-comment .user-comments .new-comment-link{display:none}.user-comments .markItUpContainer{margin-right:14px}.user-comments .comment-edit{display:none}.user-comments .user-comment .newform{position:relative;width:100%;padding-right:15px}.user-comments .comment-edit textarea{position:relative;margin:0;height:108px;width:100%}#dashboard #newsfeed h2{margin-bottom:11px}#dashboard .detailed p{width:600px}#dashboard #relevant-repos{float:right;width:300px;font-size:14px;line-height:18px}#dashboard #relevant-repos h2{float:left;margin-bottom:4px;font-size:16px;line-height:20px;color:#2b547d}#dashboard #relevant-repos .create-repo,#dashboard #relevant-repos #following-only{position:relative;top:2px;display:inline-block;margin:0 -4px -10px 8px;width:16px;height:16px;background:url(../img/icons/fugue/plus.png);text-indent:-9999px}#dashboard #relevant-repos #following-only{display:none!important}#dashboard #relevant-repos #following-only,#dashboard #relevant-repos.following-only #following-only:hover{background-image:url(../img/icons/fugue/heart_sprite.png);background-position:0 -96px}#dashboard #relevant-repos #following-only:hover,#dashboard #relevant-repos.following-only #following-only{background-position:0 -32px}#dashboard #relevant-repos ol{margin-top:4px}#dashboard #relevant-repos>ol>li{margin-top:13px}#dashboard #relevant-repos h3{clear:both;margin:0;font-size:14px;color:#747474}#dashboard #relevant-repos h3 a{color:inherit}#dashboard #relevant-repos li li{position:relative;background:url(../img/icons/fugue/lock_disable_unlock.png) no-repeat 22px 4px;padding:4px 0 4px 44px}#dashboard #relevant-repos li.private{background-image:url(../img/icons/fugue/lock.png)}#dashboard #relevant-repos .fork{display:inline-block;width:16px;background:url(../img/icons/fork_right.png) no-repeat 50%;text-indent:-9999px}#dashboard #relevant-repos .follow{position:absolute;left:0;top:5px;display:block;width:16px;height:16px;background:url(../img/icons/fugue/heart_sprite.png) no-repeat;padding:0;text-indent:-9999px;overflow:hidden}#dashboard #relevant-repos .follow:hover,#dashboard #relevant-repos .follow.following{background-position:0 -32px}#dashboard #relevant-repos .follow.following:hover{background-position:0 -64px}#dashboard #relevant-repos.following-only li li{display:none}#dashboard #relevant-repos.following-only li.following{display:inherit}#repobox a.user-repos{display:block}#repobox li.selected{margin-left:5px}#repobox li.selected a{text-decoration:underline;color:#2b547d}#repobox li.selected a:after{content:'\00A0\2192'}#repobox .unfolded:before{content:"\25BC\00A0"}#repobox .folded:before{content:"\25B6\00A0"}#dashboard #repobox{padding:8px;width:250px;background:rgba(249,249,249,1.0);overflow:auto}#dashboard #repobox #repolist{clear:both}#repobox #repo-actions{border:1px solid #ddd;background:#eee;display:block;height:24px;padding:2px;margin:6px 0 6px 0}#repo-actions input{float:right;width:98%;margin-right:1%}#descendants{padding:0 25px}#descendants #tabs,#descendants #repo-menu,#descendants #repo-desc{margin-left:-25px;margin-right:-25px}#descendants h2{margin-top:25px;color:#333}#descendants ol.detailed{margin:0 -10px}#descendants ol.detailed dd.compare a{display:block;float:left;margin:2px 0 0 4px;width:20px;height:20px;background:url(../img/arrow_switch.png) no-repeat 50%;text-indent:-9999px}#descendants ol.detailed dd.avatar{border:1px solid #e7e7e7;padding:2px}#descendants ol.detailed dt.url{position:inherit;left:inherit;clear:left;float:left}#descendants ol.detailed dt.url:after{content:":"}#descendants ol.detailed dt.url+dd{float:left;margin:0 4px}#descendants ol.detailed dd.size{float:left;color:#999}#descendants ol.detailed dd.size:before{content:"("}#descendants ol.detailed dd.size:after{content:")"}#descendants ol.detailed p{width:600px}#downloads #uploaded-files .name{width:550px}#downloads #uploaded-files .count{width:78px}#downloads #uploaded-files .delete{width:22px;background:url(../img/loading.gif) no-repeat -9999px;padding-right:0}#downloads #uploaded-files .delete>a{display:block;margin:6px 0;width:16px;height:16px;background:url(../img/icons/fugue/minus_circle.png);text-indent:-9999px;overflow:hidden}#downloads #uploaded-files .delete>a.deleting{background:url(../img/loading.gif)}#downloads .name{width:220px}#downloads td.name{font-family:Courier,"Courier New",monospace}#downloads .text{width:310px}#downloads .text>a{color:inherit}#downloads .size,#downloads .hash,#downloads .type{width:100px}#downloads .type ul{list-style:none}#downloads .type li{display:inline}#downloads .type li:after{content:","}#downloads .type li+li+li:after{content:""}#downloads #new-download label{position:absolute;left:-9999px}#downloads #new-download .file{padding-top:5px}#downloads #new-download .submit{display:none}#explore #explore-h1 h1{float:left}#explore #explore-h1:after{display:block;clear:both;height:0;content:".";visibility:hidden}#explore #explore-h1 p{position:relative;left:5px;top:7px;color:#777}#explore #tweet-buzz{float:right;margin-top:15px;width:31%}#explore #tweet-buzz-loading{color:#999}#explore #tweet-buzz-results{margin-top:11px}#explore #tweet-buzz-results p{margin:0 0 10px 46px;font-size:12px}#explore #tweet-buzz-results p.tweet a{word-wrap:break-word}#explore #tweet-buzz-results a.img{float:left}#explore #tweet-buzz-results p.name{margin-bottom:2px;font-size:15px}#explore #tweet-buzz-results a.time{font-size:12px;color:#999}#explore .short-repo-list{position:relative;margin:15px 0 25px;width:66%}#explore .short-repo-list .detailed{margin-bottom:3px}#explore .short-repo-list .detailed>li{margin-left:-8px}#explore .short-repo-list h2,#explore #tweet-buzz h2{font-size:16px;color:#555}#explore .short-repo-list .week .info-button-label{width:32px}#explore .short-repo-list .week dd.commits{right:201px}#explore .short-repo-list dd.public.repository{display:none}#explore .trending-repo-group.month,#explore .trending-repo-group.all{display:none}#explore ul.trending-periods{position:absolute;right:10px;top:0}#explore ul.trending-periods li{display:inline;padding:0 2px}#explore ul.trending-periods li.selected{font-weight:bold}#explore #search-form{float:left;margin-bottom:10px}#explore #search-form #search-text{display:block;float:left;margin:0;width:400px;height:20px;padding:4px 5px 4px 30px;font-size:18px;line-height:20px;border:1px solid #ddd;background:url(../images/search-docs-bg-30px.png) no-repeat 0 -1px}#explore .searchInput .selectors-wrap{clear:both;float:left;margin-right:.5em}.actions-wrap{float:left;margin-left:.5em}#explore #search-form button{display:block;float:left;margin-left:3px;height:30px;border:1px solid #ddd;padding:0 10px;font-size:15px}#explore #search-form+p{clear:both}#explore #sort-repos{float:right;padding-top:3px;font-weight:bold}#explore #sort-repos ul{float:left;margin-left:9px}#explore #sort-repos h4{float:left;margin:0 6px 0 0;width:50px;font-weight:bold;font-size:13px;line-height:24px;color:#393939}#explore #repositories{clear:both;margin:0 -10px}#explore #repositories p{width:600px}#explore dd.avatar img{width:32px;height:32px;border:1px solid #e7e7e7;padding:2px}#explore #repositories em{background-color:#faffa6}#explore .snippet{font-family:Monaco,"Courier New","DejaVu Sans Mono","Bitstream Vera Sans Mono",monospace;background-color:ghostWhite;border:1px solid #dedede;padding:.5em;line-height:1.5em;color:#444}#issues #search{position:relative;float:left;z-index:1}#issues #search label{position:absolute;left:-9999px}#issues #search #q{margin-top:1px;width:200px;font-size:16px}body.non-webkit #issues #search #q{margin-top:0;height:14px;border:1px solid #ccc;background:url(../images/search-icon.png) no-repeat 5px;padding:4px 0 4px 24px;font-size:13px}#issues #filters-filters{margin-top:-10px}#issues #filters-filters input[type="text"]{margin-bottom:2px}#issues #filters-filters #addfilter{margin-top:10px}#issues h1{margin-top:20px}#issues h2{font-size:16px;line-height:20px}#issues #issues-new-form select{max-width:250px}#issues #issues-new-form .markItUpContainer{width:442px}#issues #issues-main-form{padding:0 25px}#issues #issues-issue{margin:0 -25px;border:1px solid #ddd;background:#fff url(../img/layout/bg_general.png) repeat-x;padding:10px 24px}#issues #issues-main-form .avatar{position:absolute;left:24px;top:12px}#issues #issues-issue h1{margin:0 0 0 48px}#issues #issues-issue pre{white-space:pre-wrap}#issues .issues-issue-infotable{margin:0 -25px 10px;border:1px solid #ddd;border-top:0;padding:10px 24px}#issues .issues-issue-infotable td{padding-right:6px}#issues .issues-issue-infotable td.attr{font-weight:bold}#issues .issues-issue-infotable td.val{padding-right:90px}#issues #issues-issue-attachments{margin:0 -25px 10px;border:1px solid #ddd;padding:10px 24px}#issues #attachment_link{background:url(../img/icons/fugue/document__plus.png) no-repeat 0;padding:2px 0 2px 20px}#issues #issues-comments>li{position:relative;margin:-1px -25px 0;border:1px solid #ddd;padding:12px 24px 12px 72px}#issues .issues-comment-form{margin:-1px -25px 0;border:1px solid #ddd;padding:10px 24px}#issues .issues-action-form{margin:-1px -25px 0;border:1px solid #ddd;padding:10px 24px}#issues .buttons{margin:-1px -25px 0;border:1px solid #ddd;padding:9px 21px;overflow:auto}#issues .buttons li input{float:left;margin:4px;border-width:1px;border-style:solid;border-top-color:#ddd;border-left-color:#ddd;border-right-color:#bbb;border-bottom-color:#bbb;background:#eee;padding:2px 8px}#issues .buttons li:first-child input{margin:3px;background:#257;padding:3px 10px;color:#fff}#issues .buttons .cancel{margin:3px}#issues .issues-comments-permalink{float:right}#issues #issues-issue-attachments a.file-icon{margin-right:20px;background:url(../img/icons/fugue/document.png) no-repeat left;padding:2px 0 2px 20px}#issues #issues-issue-attachments table{margin-top:10px;border-collapse:collapse}#issues #issues-issue-attachments td{padding-left:0}#issues #issues-issue-state{float:right;margin-left:30px;margin-bottom:10px}#issues #issues-issue-state h3{margin-top:0;margin-bottom:8px;padding-top:0;text-align:right}#issues #issues-issue-state p{text-align:right}#issues .issues-change-indicator{background:url(../img/icons/silver/new.gif) no-repeat left;padding:2px 0 2px 20px}#issues p.issues-nocomments{padding:10px 0 0 20px}#issues .issues-action-form div.changes{float:right;margin-left:30px;width:380px;line-height:30px}#issues .issues-action-form div.changes dt{display:block;float:left;width:75px}#issues .issues-action-form div.changes select{min-width:175px}#issues .issues-comment-form #id_content{width:690px;height:162px}#issues .issues-comment-form #preview{clear:both;width:651px;border:1px solid #e7e7e7;background:#fcfcfc;padding:9px 24px}#issues #issues-new-form #preview{clear:both;width:400px;border:1px solid #ddd;background:#fcfcfc;padding:0 12px 12px 28px}#issues .issues-issue-description{padding-top:10px;padding-bottom:5px;font-size:1em}#issues .issues-issue-description p{margin-top:1em}#issues .issues-issue-description ul{margin:1.33em 0}#issues .issues-issue-description ol{margin:1.33em 0;padding-left:0;list-style-type:decimal;list-style-position:inside}#issues .issues-issue-description ul li>ul{margin-left:13px}#issues .issues-issue-description ol li>ol{margin-left:18px}#issues .issues-issue-description ol ul,#issues .issues-issue-description ul ol,#issues .issues-issue-description ul ul,#issues .issues-issue-description ol ol{margin-top:0;margin-bottom:0}#issues .issues-comment-form h2,#issues .issues-action-form h2{padding:0;line-height:40px}#issues .issues-action-form p{line-height:27px}#issues .issues-action-form select{margin:0 3px}#issues .issues-change-title{width:885px;border-bottom:1px solid #ddd;padding:10px 20px}#issues .issues-change-title span{font-size:1.3em}#issues .issues-change-title input{width:500px}#issues .issue-follow{background:url(../img/icons/fugue/heart__plus.png) no-repeat left;padding:2px 0 2px 19px}#issues .issue-following{padding:2px 0 2px 19px;background:url(../img/icons/fugue/heart.png) no-repeat left}#issues .button-groups{padding:5px 0 10px;overflow:auto}#issues .button-groups+.sane-defaults{margin-top:-52px}#issues .button-groups .button-group{position:relative;float:left;margin:0 10px 0 0;z-index:1}#issues #create-issue{float:right;margin-right:0}#issues #create-issue a.icon:before{left:8px;background:url(../img/icons/fugue/ticket_plus.png)}#issues #create-query a.icon:before{left:8px;background:url(../img/icons/fugue/gear.png)}#issues #issues-filter-criteria{position:relative;top:60px;margin:0}#issues #issues-list{margin:70px 0 10px;border-bottom:1px solid #ddd;line-height:18px}#issues #issues-filter-criteria:empty+#issues-list{margin-top:54px}#issues #issues-list th>a{color:inherit;outline:0}#issues #issues-list[data-sort-by] th>a:after{position:relative;left:3px;top:3px;display:inline-block;height:15px;content:""}#issues #issues-list[data-sort-by="id"] a[href$="sort=-id"]:after,#issues #issues-list[data-sort-by="created_on"] a[href$="sort=-created_on"]:after,#issues #issues-list[data-sort-by="status"] a[href$="sort=-status"]:after,#issues #issues-list[data-sort-by="responsible"] a[href$="sort=-responsible"]:after,#issues #issues-list[data-sort-by="kind"] a[href$="sort=-kind"]:after,#issues #issues-list[data-sort-by="version"] a[href$="sort=-version"]:after,#issues #issues-list[data-sort-by="milestone"] a[href$="sort=-milestone"]:after,#issues #issues-list[data-sort-by="component"] a[href$="sort=-component"]:after,#issues #issues-list[data-sort-by="priority"] a[href$="sort=-priority"]:after{width:15px;background:url(../img/arrows-sprite.png) -5px -30px}#issues #issues-list[data-sort-by="-id"] a[href$="sort=id"]:after,#issues #issues-list[data-sort-by="-created_on"] a[href$="sort=created_on"]:after,#issues #issues-list[data-sort-by="-status"] a[href$="sort=status"]:after,#issues #issues-list[data-sort-by="-responsible"] a[href$="sort=responsible"]:after,#issues #issues-list[data-sort-by="-kind"] a[href$="sort=kind"]:after,#issues #issues-list[data-sort-by="-version"] a[href$="sort=version"]:after,#issues #issues-list[data-sort-by="-milestone"] a[href$="sort=milestone"]:after,#issues #issues-list[data-sort-by="-component"] a[href$="sort=component"]:after,#issues #issues-list[data-sort-by="-priority"] a[href$="sort=priority"]:after{width:15px;background:url(../img/arrows-sprite.png) -5px -5px}#issues #issues-list th,#issues #issues-list td{height:auto;padding-top:5px;padding-bottom:5px}#issues #issues-list th{margin-bottom:-1px}#issues #issues-list td{border-top:1px solid #ddd;border-bottom:0}#issues #issues-list .text{width:516px}#issues #issues-list.optcols_1 .text{width:411px}#issues #issues-list.optcols_2 .text{width:306px}#issues #issues-list.optcols_3 .text{width:201px}#issues #issues-list .type,#issues #issues-list .priority{width:32px;padding-right:0;text-align:center}#issues #issues-list .state,#issues #issues-list .person{width:60px;padding-left:10px;padding-right:10px}#issues #issues-list .person{width:115px}#issues #issues-list .date{width:105px;padding-left:10px;padding-right:0}#issues #issues-list .component,#issues #issues-list .milestone,#issues #issues-list .version{width:95px;padding-left:10px;padding-right:0}#issues #issues-list td.type a,#issues #issues-list td.priority a{display:block;margin:-1px auto;width:20px;height:20px;background:url(../img/issue-types.png) no-repeat 0 0;text-indent:-9999px}#issues #issues-list td.type .enhancement{background-position:0 -20px}#issues #issues-list td.type .proposal{background-position:0 -40px}#issues #issues-list td.type .task{background-position:0 -60px}#issues #issues-list td.priority a{background-image:url(../img/priorities.png)}#issues #issues-list td.priority .critical{background-position:0 -20px}#issues #issues-list td.priority .major{background-position:0 -40px}#issues #issues-list td.priority .minor{background-position:0 -60px}#issues #issues-list td.priority .trivial{background-position:0 -80px}#issues #issues-list td.person a,#issues #issues-list td.component a,#issues #issues-list td.milestone a,#issues #issues-list td.version a{color:inherit}#issues #issues-list td.person .unassigned{color:#999}#issues #issues-list tr[data-state="resolved"] .state>a{color:#171}#issues #issues-list tr[data-state="on hold"] .state>a{color:#888}#issues #issues-list tr[data-state="invalid"] .state>a{color:#c33}#issues #issues-list tr[data-state="duplicate"] .state>a{color:#606}#issues #issues-list tr[data-state="wontfix"] .state>a{color:#c90}#issues #issues-list td[colspan="6"],#issues #issues-list td[colspan="7"],#issues #issues-list td[colspan="8"],#issues #issues-list td[colspan="9"]{width:910px;font-style:italic}#issues #issues-new-form{border:1px solid #ddd;background:#eee;padding:15px 24px}#issues #issues-new-form #id_title{width:430px}#issues #issues-new-form #id_content{margin-left:0;width:430px;height:162px}#issues #issues-new-form h3{margin-top:20px}#issues div.issue-status{clear:both;margin-top:-4px;margin-bottom:-6px;width:100px}#issues #issues-new-form input[type="text"],#issues #issues-issue input[type="text"],#issues #issues-issue select{min-width:200px}#issues #issues-new-responsible{position:absolute;right:0;top:15px;margin-left:30px;width:380px}#issues #issues-new-responsible td{padding:2px 2px 2px 0;text-align:right}#issues #issues-new-responsible th{padding:2px 10px 2px 0}#issues p.issues-issue-reportedby{margin:0 0 0 48px;padding-bottom:0;color:#747474}#issues .issue-changes li{margin:0}#issues .issue-changes li:before{content:"→\0020"}#issues .issue-changes span{background:#dde7ef;font-weight:bold;color:#333}#issues #issues-comments{line-height:18px}#issues #issues-comments .timestamps{color:#747474}#issues #issues-comments .highlighttable{width:auto}#issues .moderation-controls{text-align:right}#issues .moderation-controls li{display:inline}#issues .moderation-controls a{padding:0 0 0 16px;font-size:inherit}#issues .edit-comment-link{display:block;margin:12px 0 0;padding:0 0 0 18px;font-size:inherit;color:inherit}#issues #filters a[href="#remove"]{display:block;width:16px;height:16px;background:url(../img/icons/fugue/minus_circle.png);text-indent:-9999px;overflow:hidden}#messages #notification-controls{height:42px;border:1px solid #ddd;padding:16px 0 0}#messages #notification-controls input[type="checkbox"]{display:block;float:left;margin:7px 20px}#messages #notification-controls .paginator{margin:2px 20px}#messages #notifications{font-size:13px;line-height:16px}#messages #notifications li{position:relative;margin-top:-1px;min-height:36px;border:1px solid #ddd;padding:12px 20px 10px 300px}#messages #notifications li:nth-child(odd){background:#fcfcfc}#messages #notifications a[data-username],#messages #notifications span.deleted-user{position:absolute;left:105px;top:12px}#messages #notifications time{position:absolute;left:105px;top:32px;font-size:11px}#messages #notifications .avatar{position:absolute;left:52px;top:10px}#messages #notifications h4{margin:0 0 4px;font-weight:normal;font-size:inherit}#messages #notifications .unread h4{font-weight:bold}#messages #notifications input[type="checkbox"]{position:absolute;left:20px;top:23px;margin:0}#messages #no-notifications{border:1px solid #ddd;padding:20px;line-height:18px}#messages #inbox{border:1px solid #ddd;padding:20px}#messages #inbox h1{margin:-8px 0 0 52px;border-bottom:0;padding-bottom:8px;text-align:left}#messages #inbox h2{margin:0;padding:0;font-size:inherit;line-height:38px;color:inherit}#messages #inbox .sane-defaults h2{margin:6px 0;padding:10px 0 0;font-size:18px;line-height:24px;color:#666}#messages #inbox p{margin-bottom:8px;line-height:16px}#messages #inbox ol{margin:0 -20px;border-top:1px solid #ddd}#messages #inbox li{position:relative;min-height:38px;border-bottom:1px solid #ddd;padding:10px 20px 10px 72px}#messages #inbox li:nth-child(odd){background:#fcfcfc}#messages #inbox .avatar{position:absolute;left:20px;top:10px}#messages #inbox form{padding-left:52px}#messages #inbox #id_content{margin-left:0}#messages #inbox .primary-button{margin-right:10px}#messages #compose{border:1px solid #ddd;padding:20px}#messages #compose label{display:block;margin:4px 0 8px;padding:6px}#messages #compose input[type="text"],#messages #compose textarea{margin:4px 0 8px;width:400px;border:1px solid #ddd;padding:6px}#messages #compose input[type="text"]:focus,#messages #compose textarea:focus{border-color:#2b547d}#messages #compose .primary-button{margin-right:10px}#overview>#repo-desc{margin-bottom:15px}#overview>h1{position:absolute;left:-9999px;margin:20px 25px;font-weight:normal;font-size:38px;line-height:1;color:#999}#overview>h1>span{color:#2b547d}#overview>h2{margin:0;font-weight:normal;font-size:18px;line-height:2;color:#666}#overview>h2>span>a{font-size:16px;color:#aaa}#overview>#shortlogs-changes{margin-bottom:25px}#overview>#shortlogs-changes>.maintable th:first-child,#overview>#shortlogs-changes>.maintable td:first-child{padding-left:25px}#overview>.readme{margin:30px 0 0;border:0;padding:0}#overview>.readme>.plaintext{margin:0;border:0;background:0;padding:0;white-space:pre-wrap}#profile{position:relative}#profile h1{display:inline-block;margin:0 0 15px 50px;border:0;padding:0;text-align:left;font-weight:normal;font-size:32px;line-height:38px;color:#2b547d}#profile h1 span{color:#999}#profile>.avatar{position:absolute;left:0;top:0;display:block;border:1px solid #e7e7e7;background:#fff;padding:2px}#profile .actions{float:right;padding-top:18px}#profile .actions li{float:left}#profile .actions li+li{margin-left:20px}#profile .actions a{display:block;background:url(../img/icons/fugue/heart_sprite.png) no-repeat 100% 0;padding:0 24px 0 0;text-decoration:none;color:#525252}#profile .actions .follow:hover,#profile .actions .follow.following{background-position:100% -32px}#profile .actions .follow.following:hover{background-position:100% -64px}#profile .actions .send-message{background-image:url(../img/icons/fugue/mail__arrow_sprite.png)}#profile .actions .send-message:hover{background-position:100% -32px}#profile #metadata{border-width:1px 0;border-style:solid;border-color:#ddd;padding:15px 0;overflow:auto}#profile #metadata .userinfo{float:left;width:380px;line-height:20px}#profile #metadata .userinfo dt{position:relative;left:0;float:left;width:100px;color:#999}#profile #metadata #people{width:430px;float:right;overflow:auto}#profile #metadata #people>div{float:left;width:206px}#profile #metadata #people>div+div{float:right}#profile #metadata #people h2{font-size:14px;color:#747474}#profile #metadata #people li{display:block;float:left}#profile #metadata #people li+li{margin-left:4px}#profile #metadata #people a{display:block}#profile h2{font-size:16px;line-height:20px;color:#2b547d}#profile #newsfeed{width:430px;padding:30px 35px 0 0}#profile #newsfeed>p{margin-top:6px;font-weight:inherit;line-height:18px}#profile #newsfeed>ol>li{padding-left:0}#profile #newsfeed>ol>li>h4{position:absolute;left:-9999px}#profile #newsfeed>ol>li>h4+a{display:none}#profile #relevant-repos{float:right;width:430px;padding:30px 0 0 35px;font-size:13px}#profile #relevant-repos>ol{margin:10px -10px 0 -12px}#profile #relevant-repos>ol>li{position:relative;padding:10px 10px 10px 12px}#profile #relevant-repos h3{margin-right:100px;font-weight:normal;font-size:18px;line-height:22px;overflow:hidden}#profile #relevant-repos .metadata{margin:0;font-size:11px;line-height:20px;color:#999}#profile #relevant-repos .repository{right:85px}#profile #relevant-repos .followers{right:47px}#profile #relevant-repos p{margin-top:6px}#promo>.two-column{padding-top:10px}#promo>.two-column h3{margin:18px 0 0;line-height:18px}#pullrequest h1{margin-top:20px;line-height:24px;color:#333}#pullrequest .create-pr-description{margin:1em 0}#pullrequest p.metadata{font-size:11px;color:#999}#pullrequest p.metadata>a{text-decoration:underline;color:inherit}#pullrequest p.info{margin-top:10px}#pullrequest #id_description{height:120px}#pullrequest #pr-create{width:auto}#pullrequest #pr-create>div{width:560px}#pullrequest #pull-from,#pullrequest #pull-into{margin:12px 0;min-height:62px;border:1px solid #ddd;background:#f9f9f9;padding:10px 12px 12px 58px}#pullrequest #pull-from.deleted{padding-left:12px;padding-right:58px}#pullrequest div#pull-from{float:left;width:488px}#pullrequest div#pull-into{clear:none;float:right;margin-left:38px;width:260px}#pullrequest #pull-into:before{position:absolute;left:-29px;top:54px;width:19px;height:19px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAbklEQVQ4y83UsQ0AIQwDwOw/W1bwIK78FQghhc8LF1+4s04ISEJSvAWAOr3oYpmpFkbyGAAiqcxU1fmMnUBJEQDUycAqcGJrsZsdvMJ28BpbQQs2QBs2wH+dzHZnttcs/5ltAqyzad8a1n3m2rQPaRPU7lbOKrkAAAAASUVORK5CYII=);content:""}#pullrequest #pull-from h3,#pullrequest #pull-into h3{margin:0;padding:0;font-weight:normal;font-size:16px;color:#999}#pullrequest #pull-from h3 a,#pullrequest #pull-into h3 a{line-height:22px;color:inherit}#pullrequest #pull-from h3 a~a,#pullrequest #pull-into h3 a~a{color:#2b547d}#pullrequest #pull-from img.avatar,#pullrequest #pull-into img.avatar{position:absolute;left:10px;top:10px}#pullrequest #pull-from p.metadata,#pullrequest #pull-into p.metadata{margin-bottom:0;font-size:11px;line-height:14px;color:#999}#pullrequest #pull-from p.metadata+p{margin-top:8px}#pullrequest .bb-dropdown.open li[data-exists="false"]{display:none}#pullrequest .bb-dropdown.open li[data-exists="false"].new-branch{display:block}#pullrequest .bb-dropdown.open li[data-exists="false"].new-branch>h4:after{content:"\0020+ new branch";color:#999}#pullrequest .manual-merge-required{display:none}#pullrequest #pull-from.suppress-errors .error_.field_.message_,#pullrequest #pull-into.suppress-errors .error_.field_.message_{display:none}#pullrequest .newform #pull-from .field_.message_,#pullrequest .newform #pull-into .field_.message_{margin-top:10px;margin-bottom:0}#pullrequest .branch-summary{display:block;margin:3px 0 0;height:39px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:1px solid #ddd;background:#eee;padding:9px 12px;line-height:30px;overflow:hidden}#pullrequest #dest-branch.loading{background:#fff6c2}#pullrequest .branch-dropdown{color:#999}#pullrequest .branch-dropdown>li{padding-top:0}#pullrequest .branch-summary>h4,#pullrequest .branch-dropdown>li>h4{display:inline;margin:0;padding:0;font-weight:normal;font-size:13px;color:#666}#pullrequest .branch-summary>h4>a~a,#pullrequest .branch-dropdown>li>h4>a~a{color:inherit}#pullrequest .branch-summary>h4 code,#pullrequest .branch-dropdown>li>h4 code{border:0;background:0;padding:0 1px}#pullrequest .branch-summary>p,#pullrequest .branch-dropdown>li>p{display:inline}#pullrequest .branch-summary>p>a,#pullrequest .branch-dropdown>li>p>a{color:inherit}#pullrequest .branch-summary>p:before,#pullrequest .branch-dropdown>li>p:before{content:"~\0020"}#pullrequest .branch-summary>p:after,#pullrequest .branch-dropdown>li>p:after{content:":"}#pullrequest .branch-summary>blockquote,#pullrequest .branch-dropdown>li>blockquote{display:inline}#pullrequest #summary{clear:both}#pullrequest #compare{position:relative}#pullrequest #compare #compare-header{display:none}#pullrequest #compare.loading+#mask{position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(255,255,255,0.5)}#pullrequest #compare>hr{clear:both;margin:0;height:0;border:0;border-top:2px solid #ddd}#pullrequest[class] .list>h1{text-transform:lowercase}#pullrequest .list>h1:before{text-transform:none}#pullrequest .view #compare>hr{display:none}#pullrequest #compare .cross-repo-merge{display:none}#pullrequest .view .followers{position:absolute;right:37px;top:19px}#pullrequest .view.can-fulfill .followers{right:181px}#pullrequest .view .followers .info-button-label{font-weight:bold;color:#666}#pullrequest #pr-statuses.button-group{position:absolute;right:0;margin-top:-30px}#pullrequest .list .pr-list{margin-top:10px}#pullrequest .pr-list th,#pullrequest .pr-list td{width:400px}#pullrequest .pr-list th+th,#pullrequest .pr-list td.repo.deleted{width:270px}#pullrequest .pr-list td+td{width:242px}#pullrequest .pr-list th+th+th,#pullrequest .pr-list td+td+td{width:90px}#pullrequest .pr-list th+th+th.open-datetime,#pullrequest .pr-list td+td+td.open-datetime{width:200px}#pullrequest .pr-list td[colspan="3"],#pullrequest .pr-list td[colspan="4"]{width:910px;font-style:italic}#pullrequest.open h1:before{content:"Open\0020"}#pullrequest.accepted h1:before{content:"Accepted\0020"}#pullrequest.rejected h1:before{content:"Rejected\0020"}#pullrequest.open .button-group a[href="#open"],#pullrequest.accepted .button-group a[href="#accepted"],#pullrequest.rejected .button-group a[href="#rejected"],#pullrequest .button-group a:active{background:#ccc;background:-webkit-gradient(linear,0 0,0 100%,from(#aaa),to(#eee),color-stop(0.25,#ccc));background:-webkit-linear-gradient(#aaa,#ccc 25%,#eee);background:-moz-linear-gradient(top,#aaa,#ccc 25%,#eee);background:-o-linear-gradient(#aaa,#ccc 25%,#eee);color:#393939}#pullrequest .view>.metadata{font-size:12px}#pullrequest .view.can-fulfill>h1{margin-bottom:0}#pullrequest .view .conflicts-detected,#pullrequest .view .multiple-destination-heads,#pullrequest .view .accepted,#pullrequest .view .rejected,#pullrequest .view .superseded{margin:15px 0 12px;border-width:1px;border-style:solid;padding:0 24px}#pullrequest .view .conflicts-detected,#pullrequest .view .multiple-destination-heads{border-color:#fc0;border-color:rgba(255,204,0,0.6);background:rgba(255,204,0,0.08)}#pullrequest .view .conflicts-detected{display:none}#pullrequest.conflicts .view .conflicts-detected{display:inherit}#pullrequest.conflicts #pr-fulfill{color:#666;cursor:default}#pullrequest.conflicts #pr-fulfill:active{background:#eee;background:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ddd));background:-webkit-linear-gradient(#fff,#ddd);background:-moz-linear-gradient(top,#fff,#ddd);background:-o-linear-gradient(#fff,#ddd)}#pullrequest.conflicts #pr-fulfill:before{background-position:0 -16px}#pullrequest .view .accepted{border-color:#282;border-color:rgba(34,136,34,0.4);background:rgba(34,136,34,0.02)}#pullrequest .view .rejected{border-color:#c00;border-color:rgba(204,0,0,0.3);background:rgba(204,0,0,0.02)}#pullrequest .view .superseded{border-color:#2b547d;border-color:rgba(43,84,125,0.28);background:rgba(234,250,255,0.3)}#pullrequest .view h2.state{display:inline-block;padding:6px 0}#pullrequest .view .conflicts-detected h2.state,#pullrequest .view .multiple-destination-heads h2.state{padding-bottom:0;color:#333}#pullrequest .view .accepted h2.state{color:#282}#pullrequest .view .rejected h2.state{color:#c00}#pullrequest .view h2.state+p.metadata{display:inline;font-size:12px}#pullrequest .view .conflicts-detected>pre{margin-bottom:18px;border-color:#e7e7e7;background:#f7f7f7}#pullrequest .view .resolution{margin:-6px 0 12px}#pullrequest .view label{display:block;padding:16px 0 4px;font-weight:bold;color:#666}#pullrequest .view #summary{margin-top:-13px}#pullrequest #pr-description{clear:both}#pullrequest .view .bb-dropdown li[data-exists="false"]>h4:before{content:"+\0020"}#pullrequest .view #pull-from,#pullrequest .view #pull-into{min-height:51px;margin-bottom:8px;padding-bottom:8px}#pullrequest .view #pull-into:before{top:26px}#pullrequest .view #pull-from>h3,#pullrequest .view #pull-into>h3{float:left;margin-top:-1px;margin-bottom:2px}#pullrequest .view #pull-from.deleted>h3{float:none;margin-top:2px;margin-bottom:12px}#pullrequest .view #pull-from>.metadata{float:left;margin-left:4px;margin-top:5px}#pullrequest .view .bb-dropdown{clear:both}#pullrequest .view #pull-into>.metadata,#pullrequest .view #pull-from>label,#pullrequest .view #pull-into>label{display:none}#pullrequest .view .bb-dropdown{border:1px solid #ddd;background:#fcfcfc}#pullrequest .view .branch-dropdown{margin:2px;border:0;background:0}#pullrequest .view .branch-dropdown>li{margin:0;border:0}#pullrequest .list .pr-list{display:none}#pullrequest.open .pr-list.open,#pullrequest.accepted .pr-list.accepted,#pullrequest.rejected .pr-list.rejected{display:table}#pullrequest #pr-actions{float:right;margin:0 0 10px 20px}#pullrequest #pr-actions .bb-button{width:60px}#pullrequest #pr-description pre{border:1px solid #ddd;background:#eee;padding:0 2px;font-size:12px}#pullrequest #pr-description pre{padding:4px 8px}#pullrequest #pr-description pre>code{border:0;background:0;padding:0}#id_reason{height:100px}#id_commit_message{height:20px}#id_commit_message.expanded{height:100px}#pullrequest #pullrequest-actions .update-tooltip{display:none}#pullrequest .loading h3{background:url(../img/loading.gif) no-repeat 0 11px;padding-left:20px}#pullrequest #comments-count{color:#aaa}#search #search-form_{margin-bottom:20px;border-bottom:1px solid #e7e7e7;padding-bottom:10px;position:relative}#search #search-form_>div{float:left}#search #search-text{display:block;float:left;margin:0;width:650px;height:22px;padding:4px 5px 4px 30px;font-size:18px;outline:0;line-height:20px;border:1px solid #ddd;background:url(../images/search-docs-white-bg-30px.png) no-repeat 0 -1px;background-color:white;-webkit-appearance:none}#search #search-button{display:block;float:left;margin-left:0;margin-right:5px;height:32px;border:1px solid #ddd;padding:0 10px;font-size:15px}#search #search-form_ label{font-weight:normal}#search #search-form_ select{margin:0}#search #search-form_ .operand{min-width:215px}#search #search-form_ #results-count{background:#fff;bottom:-10px;clear:both;color:#999;font-size:12px;font-style:italic;left:44px;padding:0 6px;position:absolute}#search #search-form_ div.clearer{clear:both;float:none;font-size:1px;margin:0}#search #repositories>section{padding:10px 0 10px 50px}#search #repositories>section>.repository{margin:0;font-size:15px;line-height:1.2;color:#999;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:72%}#search #repositories>section>.repository>.owner{color:inherit}#search #repositories>section.repository>h1>a,#search #repositories>section.user>h1>a>span{color:#2b547d}#search #repositories>section.repository>h1>.owner{color:inherit}#search #repositories>section>.avatar{position:absolute;left:0;top:10px;display:block;width:32px;height:32px;border:1px solid #e7e7e7;padding:2px}#search #repositories>section>.avatar:after{position:absolute;left:27px;top:27px;display:block;width:16px;height:16px;content:""}#search #repositories .resulttype{color:#999;font-size:15px}#search #repositories>section.commit>.avatar:after{background:url(../img/icons/puzzle.png)}#search #repositories>section.user>.avatar:after{background:url(../img/icons/user.png)}#search #repositories>section.private.repository>.avatar:after{background:url(../img/icons/droplets-16.png)}#search #repositories>section.public.repository>.avatar:after{background:url(../img/icons/droplets-16.png)}#search #repositories>section.source>.avatar:after{background:url(../img/icons/source.png)}#search #repositories>section>.avatar>img{width:100%;height:100%}#search #repositories>section.commit>.message,#search #repositories>section.repository>.message,#search #repositories>section.user>.message{border:0;text-align:left}#search #repositories>section.commit>.message,#search #repositories>section.user>.message,#search #repositories>section.repository>.message,#search #repositories>section.source>pre{clear:both;margin:5px 0 4px;padding:1px}#search #repositories>section.commit>.message>div,#search #repositories>section.user>.message>div,#search #repositories>section.repository>.message>div,#search #repositories>section.source>pre{clear:both;background:#fff;padding:4px 6px;white-space:pre-wrap;line-height:16px;overflow:hidden;text-overflow:ellipsis}#search #repositories>section.commit>.message>div>h4{margin:0;font-weight:normal}#search #repositories>section .metadata{float:none;margin:0;font-size:11px;line-height:20px;color:#999}#search #repositories>section em{font-style:normal;font-weight:bold;color:#000}#search .error{color:#999}#search .searchInput{margin-bottom:10px}#search .searchInput .selectors-wrap{clear:both;float:left;margin-right:.5em}#search .actions-wrap{float:right}#search .actions-wrap input{margin-left:.5em;margin-top:-0.2em}#search #advanced_link{float:right;font-size:12px;padding:2px 4px}#search #sort-repos{background:#fff;bottom:-10px;display:none;padding-left:10px;position:absolute;right:0}#search .errorContainer{clear:left}#search #advanced-input{clear:both;margin-top:8px;width:510px}#search #mask.loading{position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(255,255,255,0.5)}#search .location{position:absolute;left:5px;top:5px;background:url(../img/icons/location-pin.png) no-repeat;padding-left:20px}#search dd.branches .info-button .info-button-action a{background-image:url(../img/icons/fugue/arrow_135.png)}#search dd.tags .info-button .info-button-action a{background-image:url(../img/icons/fugue/tags.png)}#search dd.tags,#search dd.user-followers,#search dd.branches{position:absolute;top:10px}#search dd.branches{right:86px}#search dd.user-followers{right:47px}#search dd.tags{right:205px}#search dd.user-followers .info-button-readonly .info-button-action a,#search dd.user-followers-not-following .info-button .info-button-action a{background-image:url(../img/icons/fugue/heart_empty.png)}#search dd.user-followers-not-following .info-button .info-button-action a:hover,#search dd.user-followers-following .info-button .info-button-action a{background-image:url(../img/icons/fugue/heart.png)}#search dd.user-followers-following .info-button .info-button-action a:hover{background-image:url(../img/icons/fugue/heart_break.png)}#search dd.user-followers .info-button-requesting .info-button-action a,#search dd.user-followers .info-button-requesting .info-button-action a:hover{background-image:url(../img/loading.gif)}#search .tags{float:none}#search .def-only dd ul{width:75px}#search .def-only dd .info-button-label{background:#fff}#search .def-only dd:hover .info-button-label{z-index:1;border-radius:0 5px 0 0;-moz-border-radius:0 5px 0 0;-webkit-border-radius:0 5px 0 0}#search .def-only dd li+li{display:none;border-top:1px solid #ddd;text-align:left}#search .def-only dd:hover li+li{display:block}#search .def-only dd li{display:block;padding:0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#search .def-only dd li+li{border-top:1px solid #ddd}#search .def-only dd li a{margin:0 2px;width:auto}#search .def-only>ul>li>a{display:block;text-decoration:none}#search dd.tags .info-button-label,dd.branches .info-button-label{width:auto}#search dd.privacy .private,#search dd.privacy .public{position:absolute;right:251px;top:14px;width:16px;height:16px;background:url(../img/icons/fugue/lock.png) no-repeat;text-indent:-9999px}#search dd.privacy .public{top:13px;background-image:url(../img/icons/fugue/lock_disable_unlock.png)}#search .button-group{float:left;margin-right:10px}#search .search-type{position:relative;top:2px}#search .second-row-input{clear:both;width:760px}#search .search-type-container{width:130px}#search .bb-dropdown>ul{margin:-4px}#signup #openid_form input[type="text"],#signup #openid_form input[type="url"]{width:286px}#signup .two-thirds-secondary{margin-top:31px}#signup .two-thirds-secondary>h3{margin-bottom:12px}#signup .two-thirds-secondary>ul{line-height:20px}#signup .two-thirds-secondary>ul>li{position:relative;padding-left:20px}#signup .two-thirds-secondary>ul>li:before{position:absolute;top:8px;margin:0 8px 0 -12px;vertical-align:middle;display:inline-block;width:4px;height:4px;background:#999;content:""}#source>#sourcelist{clear:both;margin-bottom:25px}#source>h3{margin-left:25px}#source .message_.note_{margin:10px 0;background-image:url(../img/icons/info.png);background-repeat:no-repeat;background-position:11px}#strip-repository .newform .submit>a{margin-left:5px;font-size:12px}#strip-repository .newtable .hash{width:100px}#strip-repository .newtable .text{width:490px}#wiki{border:1px solid #ddd;padding:15px 24px;font-size:14px;line-height:1.5em;color:#444}#wiki .wiki-edit-table th{vertical-align:middle}.wiki-edit-table #id_message,#wiki .wiki-edit-table #id_message,#wiki .wiki-edit-table #id_path{margin:inherit;width:690px;height:inherit;padding:3px 5px}#wiki-nav-links{margin:0 0 10px 25px;line-height:21px;overflow:auto}#wiki-nav-links li{float:left;margin:0 15px 0 0}#wiki-nav-links li.home,#wiki-nav-links li.page{font-weight:bold}#wiki-nav-links li.new{display:none}#wiki-nav-links a{background-image:url(../img/icons/fugue/document_small.png);background-repeat:no-repeat;background-position:0 50%;padding:0 0 0 17px}#wiki-nav-links li.home a{background-image:url(../img/icons/fugue/home_small.png)}#wiki-nav-links li.edit a{background-image:url(../img/icons/fugue/pencil_small.png)}#wiki-nav-links li.history a{background-image:url(../img/icons/fugue/clock_small.png)}#wiki-nav-links li.new a{background-image:url(../img/icons/fugue/plus_small.png)}p.clone-wiki{float:right;margin:0 25px 0 0;line-height:21px}#wiki-new-page-form{display:none;margin:0 0 10px 25px}#wiki h1{margin:.67em 0;font-size:2.5em}#wiki h2{margin:.83em 0;font-size:2em}#wiki h3{margin:1em 0;font-size:1.5em}#wiki h4,#wiki p,#wiki blockquote,#wiki ul,#wiki fieldset,#wiki form,#wiki ol,#wiki dl,#wiki dir,#wiki menu{margin:1.33em 0}#wiki ul li>ul{margin-left:13px}#wiki ol li>ol{margin-left:18px}#wiki p{padding:0}#wiki h5{margin:1.67em 0;font-size:.83em;line-height:1.17em}#wiki h6{margin:2.33em 0;font-size:.67em}#wiki h1,#wiki h2,#wiki h3,#wiki h4,#wiki h5,#wiki h6{font-weight:normal}#wiki table.wikitable{border-collapse:collapse}#wiki table.wikitable td{border-left:1px solid #b7b7b7;border-right:1px solid #b7b7b7;border-bottom:1px solid #b7b7b7;padding:6px 12px;font:11px "Lucida Grande",Verdana,Arial,sans-serif}#wiki table.wikitable th{border:1px solid #b7b7b7;background:#fff url(../img/wiki/bg-th.png) no-repeat left top;padding:6px 12px;text-align:left;font:bold 11px "Lucida Grande",Verdana,Arial,sans-serif;text-transform:uppercase;letter-spacing:1px;color:#2b547d}#wiki blockquote{margin-left:40px;margin-right:40px}#wiki .floatleft{float:left;width:50%}#wiki .floatright{float:right;width:50%}#wiki i,#wiki cite,#wiki em,#wiki var,#wiki address{font-style:italic}#wiki tt,#wiki kbd,#wiki samp{font-family:monospace}#wiki pre{margin:9px 0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;border:1px solid #ddd;background:#f7f7f7;padding:9px 12px;font-size:12px;line-height:16px;white-space:pre-wrap}#wiki big{font-size:1.17em}#wiki small,#wiki sub,#wiki sup{font-size:.83em}#wiki sub{vertical-align:sub}#wiki sup{vertical-align:super}#wiki s,#wiki strike,#wiki del{text-decoration:line-through}#wiki hr{border:1px inset}#wiki dir,#wiki menu,#wiki dd{margin-left:40px}#wiki ul,.issues-issue-description ul{list-style:square inside url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAGklEQVQI12OcPn36fwY0wEgLwf//McSwCwIAOd4T1l7L6vcAAAAASUVORK5CYII=)}#wiki ol{position:relative;counter-reset:ol;list-style:none}#wiki ol>li{counter-increment:ol}#wiki ol>li:before{position:absolute;left:-24px;display:block;width:20px;padding:0 4px 0 0;text-align:right;content:counter(ol) "."}#wiki ol ul,#wiki ul ol,#wiki ul ul,#wiki ol ol{margin:0 0 0 24px}#wiki u,#wiki ins{text-decoration:underline}#wiki center{text-align:center}#wiki br:before{content:"\a"}#wiki>.highlighttable{margin:0 -24px -15px;width:auto}.revision-date{text-align:right;font-size:12px}#wiki-history .paginator{margin-top:9px}#wiki-history h1{margin:0;font-weight:normal;font-size:18px;line-height:2;color:#666}#wiki-history .newtable{clear:both}#wiki-history .newtable .hash{width:100px}#wiki-history .newtable .text{width:490px}#wiki-history .newtable .text a{color:inherit}#zealots h1{margin-top:20px}#zealots #zealots-list{margin:10px 0 0;padding:0 0 0 42px;color:#999;list-style:none}#zealots #zealots-list>li{position:relative;margin:4px 0;border:1px solid #eee;padding:7px 0 7px 10px}#zealots .avatar{position:absolute;left:-43px;top:-1px}#zealots .name{font-size:18px;line-height:22px}#zealots .full.name>a~a{color:inherit}#zealots .location{position:absolute;right:20px;top:10px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAQCAYAAADJViUEAAAAZklEQVQoz62TSQ7AIAwDeTr/c75E3VMkGpkUmh7mgjQ2S2gk21fkIoBuZpcDoL/KUYrEkIeciY6U51aSIwhDtS9bXYhBc3u6ZSVuyUfNpTOXbrv8zlmAmrL/xlMFrMRU9oDjX7XLDX4F9kYonZ6DAAAAAElFTkSuQmCC) no-repeat;padding-left:20px}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-widget{font-family:Lucida Grande,Lucida Sans,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Lucida Grande,Lucida Sans,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#f2f5f7 url(../../images/jqueryui/ui-bg_highlight-hard_100_f2f5f7_1x100.png) 50% top repeat-x;color:#362b36}.ui-widget-content a{color:#362b36}.ui-widget-header{border:1px solid #aed0ea;background:#deedf7 url(../../images/jqueryui/ui-bg_highlight-soft_100_deedf7_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #aed0ea;background:#d7ebf9 url(../../images/jqueryui/ui-bg_glass_80_d7ebf9_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#2779aa}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#2779aa;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #74b2e2;background:#e4f1fb url(../../images/jqueryui/ui-bg_glass_100_e4f1fb_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#0070a3}.ui-state-hover a,.ui-state-hover a:hover{color:#0070a3;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #2694e8;background:#3baae3 url(../../images/jqueryui/ui-bg_glass_50_3baae3_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #f9dd34;background:#ffef8f url(../../images/jqueryui/ui-bg_highlight-soft_25_ffef8f_1x100.png) 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#cd0a0a url(../../images/jqueryui/ui-bg_flat_15_cd0a0a_40x100.png) 50% 50% repeat-x;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-icon{width:16px;height:16px;background-image:url(../../images/jqueryui/ui-icons_72a7cf_256x240.png)}.ui-widget-content .ui-icon{background-image:url(../../images/jqueryui/ui-icons_72a7cf_256x240.png)}.ui-widget-header .ui-icon{background-image:url(../../images/jqueryui/ui-icons_72a7cf_256x240.png)}.ui-state-default .ui-icon{background-image:url(../../images/jqueryui/ui-icons_3d80b3_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(../../images/jqueryui/ui-icons_2694e8_256x240.png)}.ui-state-active .ui-icon{background-image:url(../../images/jqueryui/ui-icons_ffffff_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(../../images/jqueryui/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(../../images/jqueryui/ui-icons_ffffff_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-off{background-position:-96px -144px}.ui-icon-radio-on{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-tl{-moz-border-radius-topleft:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px}.ui-corner-tr{-moz-border-radius-topright:6px;-webkit-border-top-right-radius:6px;border-top-right-radius:6px}.ui-corner-bl{-moz-border-radius-bottomleft:6px;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px}.ui-corner-br{-moz-border-radius-bottomright:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px}.ui-corner-top{-moz-border-radius-topleft:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-topright:6px;-webkit-border-top-right-radius:6px;border-top-right-radius:6px}.ui-corner-bottom{-moz-border-radius-bottomleft:6px;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-moz-border-radius-bottomright:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px}.ui-corner-right{-moz-border-radius-topright:6px;-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-moz-border-radius-bottomright:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px}.ui-corner-left{-moz-border-radius-topleft:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px}.ui-corner-all{-moz-border-radius:6px;-webkit-border-radius:6px;border-radius:6px}.ui-widget-overlay{background:#eee url(../../images/jqueryui/ui-bg_diagonals-thick_90_eeeeee_40x40.png) 50% 50% repeat;opacity:.80;filter:Alpha(Opacity=80)}.ui-widget-shadow{margin:-7px 0 0 -7px;padding:7px;background:#000 url(../../images/jqueryui/ui-bg_highlight-hard_70_000000_1x100.png) 50% top repeat-x;opacity:.30;filter:Alpha(Opacity=30);-moz-border-radius:8px;-webkit-border-radius:8px;border-radius:8px}.ui-autocomplete{position:absolute;cursor:default}* html .ui-autocomplete{width:1px}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:left}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:left;clear:left;width:100%}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-dialog{position:absolute;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.5em 1em .3em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .2em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:0;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-widget{font-family:inherit;font-size:inherit}.ui-widget .ui-widget{font-size:inherit}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:inherit;font-size:inherit}.ui-widget-header{border:0;background:#fff url(../../img/wiki/bg-th-blue.png) no-repeat -1px -1px;color:#133362;font-size:14px;font-weight:normal}.ui-widget-overlay{background:#eee}.ui-widget-header.ui-corner-all{-moz-border-radius:6px 6px 0 0;-webkit-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.ui-autocomplete.ui-widget-content{background:#fff 50% top repeat-x;color:#333}.ui-autocomplete.ui-widget-content a{color:#333}.ui-autocomplete.ui-widget-header{border:1px solid #f5f5f5;background:#f5f5f5 50% 50% repeat-x;color:#000}.ui-autocomplete.ui-widget-header a{color:#fff}.ui-autocomplete .ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#eee 50% 50% repeat-x;color:#1c94c4}.ui-autocomplete .ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4}.ui-autocomplete .ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #ddd;background:#eee 50% 50% repeat-x;color:#000}.ui-autocomplete .ui-state-hover a,.ui-state-hover a:hover{color:#f5f5f5}.ui-autocomplete .ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #eee;background:#fff 50% 50% repeat-x;color:#000}.ui-autocomplete .ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#000}.ui-autocomplete{max-width:400px;overflow-x:hidden;white-space:nowrap}.ui-menu{padding:1px 0 1px 0}.ui-dialog{padding:inherit}.ui-dialog .ui-dialog-buttonpane{border-width:0;margin:inherit}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-progressbar{height:2em;text-align:left}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}#fancybox-loading{position:fixed;top:50%;left:50%;width:40px;height:40px;margin-top:-20px;margin-left:-20px;cursor:pointer;overflow:hidden;z-index:1104;display:none}#fancybox-loading div{position:absolute;top:0;left:0;width:40px;height:480px;background-image:url('../../../img/lib/fancybox/fancybox.png')}#fancybox-overlay{position:absolute;top:0;left:0;width:100%;z-index:1100;display:none}#fancybox-tmp{padding:0;margin:0;border:0;overflow:auto;display:none}#fancybox-wrap{position:absolute;top:0;left:0;padding:20px;z-index:1101;outline:0;display:none}#fancybox-outer{position:relative;width:100%;height:100%;background:#fff}#fancybox-content{width:0;height:0;padding:0;outline:0;position:relative;overflow:hidden;z-index:1102;border:0 solid #fff}#fancybox-hide-sel-frame{position:absolute;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1101}#fancybox-close{position:absolute;top:-15px;right:-15px;width:30px;height:30px;background:transparent url('../../../img/lib/fancybox/fancybox.png') -40px 0;cursor:pointer;z-index:1103;display:none}#fancybox-error{color:#444;font:normal 12px/20px Arial;padding:14px;margin:0}#fancybox-img{width:100%;height:100%;padding:0;margin:0;border:0;outline:0;line-height:0;vertical-align:top}#fancybox-frame{width:100%;height:100%;border:0;display:block}#fancybox-left,#fancybox-right{position:absolute;bottom:0;height:100%;width:35%;cursor:pointer;outline:0;background:transparent url('../../../img/lib/fancybox/blank.gif');z-index:1102;display:none}#fancybox-left{left:0}#fancybox-right{right:0}#fancybox-left-ico,#fancybox-right-ico{position:absolute;top:50%;left:-9999px;width:30px;height:30px;margin-top:-15px;cursor:pointer;z-index:1102;display:block}#fancybox-left-ico{background-image:url('../../../img/lib/fancybox/fancybox.png');background-position:-40px -30px}#fancybox-right-ico{background-image:url('../../../img/lib/fancybox/fancybox.png');background-position:-40px -60px}#fancybox-left:hover,#fancybox-right:hover{visibility:visible}#fancybox-left:hover span{left:20px}#fancybox-right:hover span{left:auto;right:20px}.fancybox-bg{position:absolute;padding:0;margin:0;border:0;width:20px;height:20px;z-index:1001}#fancybox-bg-n{top:-20px;left:0;width:100%;background-image:url('../../../img/lib/fancybox/fancybox-x.png')}#fancybox-bg-ne{top:-20px;right:-20px;background-image:url('../../../img/lib/fancybox/fancybox.png');background-position:-40px -162px}#fancybox-bg-e{top:0;right:-20px;height:100%;background-image:url('../../../img/lib/fancybox/fancybox-y.png');background-position:-20px 0}#fancybox-bg-se{bottom:-20px;right:-20px;background-image:url('../../../img/lib/fancybox/fancybox.png');background-position:-40px -182px}#fancybox-bg-s{bottom:-20px;left:0;width:100%;background-image:url('../../../img/lib/fancybox/fancybox-x.png');background-position:0 -20px}#fancybox-bg-sw{bottom:-20px;left:-20px;background-image:url('../../../img/lib/fancybox/fancybox.png');background-position:-40px -142px}#fancybox-bg-w{top:0;left:-20px;height:100%;background-image:url('../../../img/lib/fancybox/fancybox-y.png')}#fancybox-bg-nw{top:-20px;left:-20px;background-image:url('../../../img/lib/fancybox/fancybox.png');background-position:-40px -122px}#fancybox-title{font-family:Helvetica;font-size:12px;z-index:1102}.fancybox-title-inside{padding-bottom:10px;text-align:center;color:#333;background:#fff;position:relative}.fancybox-title-outside{padding-top:10px;color:#fff}.fancybox-title-over{position:absolute;bottom:0;left:0;color:#FFF;text-align:left}#fancybox-title-over{padding:10px;background-image:url('../../../img/lib/fancybox/fancy_title_over.png');display:block}.fancybox-title-float{position:absolute;left:0;bottom:-20px;height:32px}#fancybox-title-float-wrap{border:0;border-collapse:collapse;width:auto}#fancybox-title-float-wrap td{border:0;white-space:nowrap}#fancybox-title-float-left{padding:0 0 0 15px;background:url('../../../img/lib/fancybox/fancybox.png') -40px -90px no-repeat}#fancybox-title-float-main{color:#FFF;line-height:29px;font-weight:bold;padding:0 0 3px 0;background:url('../../../img/lib/fancybox/fancybox-x.png') 0 -40px}#fancybox-title-float-right{padding:0 0 0 15px;background:url('../../../img/lib/fancybox/fancybox.png') -55px -90px no-repeat}.fancybox-ie6 #fancybox-close{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_close.png',sizingMethod='scale')}.fancybox-ie6 #fancybox-left-ico{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_nav_left.png',sizingMethod='scale')}.fancybox-ie6 #fancybox-right-ico{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_nav_right.png',sizingMethod='scale')}.fancybox-ie6 #fancybox-title-over{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_title_over.png',sizingMethod='scale');zoom:1}.fancybox-ie6 #fancybox-title-float-left{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_title_left.png',sizingMethod='scale')}.fancybox-ie6 #fancybox-title-float-main{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_title_main.png',sizingMethod='scale')}.fancybox-ie6 #fancybox-title-float-right{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_title_right.png',sizingMethod='scale')}.fancybox-ie6 #fancybox-bg-w,.fancybox-ie6 #fancybox-bg-e,.fancybox-ie6 #fancybox-left,.fancybox-ie6 #fancybox-right,#fancybox-hide-sel-frame{height:expression(this.parentNode.clientHeight+"px")}#fancybox-loading.fancybox-ie6{position:absolute;margin-top:0;top:expression((-20+(document.documentElement.clientHeight ? document.documentElement.clientHeight/2:document.body.clientHeight/2)+(ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop))+'px')}#fancybox-loading.fancybox-ie6 div{background:transparent;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_loading.png',sizingMethod='scale')}.fancybox-ie .fancybox-bg{background:transparent!important}.fancybox-ie #fancybox-bg-n{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_n.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-ne{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_ne.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-e{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_e.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-se{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_se.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-s{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_s.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-sw{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_sw.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-w{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_w.png',sizingMethod='scale')}.fancybox-ie #fancybox-bg-nw{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/lib/fancybox/fancy_shadow_nw.png',sizingMethod='scale')}@font-face{font-family:Inconsolata;src:url(../fonts/Inconsolata.otf)}.clear{overflow:hidden;width:100%}a.button{background:transparent url(../img/v2/bg_button_a.gif) no-repeat scroll top right;color:#444;display:block;float:right;font:normal 12px arial,sans-serif;height:24px;margin-right:6px;padding-right:18px;text-decoration:none}a.button span{background:transparent url(../img/v2/bg_button_span.gif) no-repeat;display:block;line-height:14px;letter-spacing:1px;padding:5px 0 5px 18px}a.button:active{background-position:bottom right;outline:0}a.button:active span{background-position:bottom left;padding:6px 0 4px 18px}#plans h2{margin-bottom:10px;padding-bottom:10px}#plans h3{margin:20px 0 10px;font-size:15px}#featuretour .hero-shot{margin:10px 0 40px;width:100%;height:216px;background:url(../img/featuretour/heroshot.jpg) no-repeat;color:#c5efff}#featuretour .hero-shot h1{position:absolute;left:30px;bottom:30px;font-weight:normal;font-size:4.5em;line-height:1.1;color:#f5fba5}#featuretour div.center{margin:20px 0;width:100%;text-align:center}#featuretour a.btn{width:250px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:1px solid #0f2557;background:#245192;background:-webkit-gradient(linear,0 0,0 100%,color-stop(0,#245192),color-stop(0.75,#1e3b73),color-stop(1,#12295d));background:-moz-linear-gradient(#245192,#1e3b73 75%,#12295d);padding:10px 75px 10px;font-weight:bold;font-size:24px;font-family:Arial;text-decoration:none;color:#fff;text-shadow:-1px -1px 2px #465f97}#featuretour a.btn:hover{border:1px solid #19377c;background:#3567b7;background:-moz-linear-gradient(#3567b7,#294f99 75%,#1e3e86);background:-webkit-gradient(linear,0 0,0 100%,color-stop(0,#3567b7),color-stop(0.75,#294f99),color-stop(1,#1e3e86));text-shadow:-1px -1px 2px #465f97}#featuretour .feature{float:left;width:47%;padding:0 20px 40px 0;vertical-align:top}#featuretour h4{margin:0 0 10px;padding:0}#featuretour img{float:left;margin-right:10px;width:200px;border:1px solid #999}#featuretour p{display:block;margin:0 0 10px}#featuretour .example{font-size:90%}#featuretour .compoundheadline h1{float:left}#featuretour .compoundheadline:after{display:block;clear:both;height:0;content:".";visibility:hidden}#featuretour .compoundheadline p{position:relative;left:5px;top:7px;color:#777}.markItUp *{margin:0;padding:0}.markItUp a:link,.markItUp a:visited{color:#000;text-decoration:none}.markItUp{margin:5px 0}.markItUpContainer{font:11px Verdana,Arial,Helvetica,sans-serif}.markItUpEditor{font:12px 'Courier New',Courier,monospace;padding:5px;width:690px;height:320px;clear:both;line-height:18px;overflow:auto}.markItUpPreviewFrame{overflow:auto;background-color:#FFF;width:99.9%;height:300px;margin:5px 0}.markItUpFooter{width:100%}.markItUpResizeHandle{overflow:hidden;width:22px;height:5px;margin-left:auto;margin-right:auto;background-image:url(images/handle.png);cursor:n-resize}.markItUp .markItUpHeader ul{padding:0}.markItUp .markItUpHeader ul li{margin-top:0;margin-bottom:0;list-style:none;float:left;position:relative}.markItUpHeader ul li:before{content:""}.markItUpHeader ul li:hover>ul{display:block}.markItUpHeader ul .markItUpDropMenu{background:transparent url(images/menu.png) no-repeat 115% 50%;margin-right:5px}.markItUpHeader ul .markItUpDropMenu li{margin-right:0}.markItUpHeader ul ul{display:none;position:absolute;top:18px;left:0;background:#FFF;border:1px solid #000}.markItUpHeader ul ul li{float:none;border-bottom:1px solid #000}.markItUpHeader ul ul .markItUpDropMenu{background:#FFF url(images/submenu.png) no-repeat 100% 50%}.markItUpHeader ul .markItUpSeparator{margin:0 10px;width:1px;height:16px;overflow:hidden;background-color:#CCC}.markItUpHeader ul ul .markItUpSeparator{width:auto;height:1px;margin:0}.markItUpHeader ul ul ul{position:absolute;top:-1px;left:150px}.markItUpHeader ul ul ul li{float:none}.markItUpHeader ul a{display:block;width:16px;height:16px;text-indent:-10000px;background-repeat:no-repeat;padding:3px;margin:0}.markItUpHeader ul ul a{display:block;padding-left:0;text-indent:0;width:120px;padding:5px 5px 5px 25px;background-position:2px 50%}.markItUpHeader ul ul a:hover{color:#FFF;background-color:#000}.markItUp .markItUpButton1 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACmSURBVCjPvdCxDYQwDAVQSxQ0NFRUl4ouHVV0TapIiIYCKRnEE2QCNrgJ/iIs4jW4KOR0qSiRK+s/2U7opPuiR4CPHh5bOGkJDhYmnqTnca8meNlwtSmWFL9HKKnAJmsBBlMOFA81WGU5HFs2PB06BwP3NVjElQkaYw567mrgxBbw291xWwMrpgCFa3fLzR/YmE6DTs9UYUCPLrah+RBop9dTX31fX9NT9CS3ZDF4AAAAAElFTkSuQmCC)}.markItUp .markItUpButton2 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADCSURBVCjPvdCxDYMwEAXQkyhoEBGKIipcRHTuXFk0riwhGgoke4JMwARMkA2Y4G+QCdggE9waxAKHkhJd+Z++z0crnQ9dAtzk4DD4lTpvYaAnJeVcQ7RHg+MBuzdQrCq51JP4PLioIhi4j0DjydLXISibG2dNBD13ix3NqEe1SN5pgeyb5hF0bGODRL2B4p0hlccOlk0EYTXe4tdKSU7/HQzrCATuXDShHAlooXYDZtJQkOGbwpcIb89bDJqvO/X5/ABgCuuOdgJr8AAAAABJRU5ErkJggg==)}.markItUp .markItUpButton3 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADESURBVCjPvdCxqcMwFIXhw/MDF3HgqRIErEqdOlcijSqDcZPCYE+QCTSBJsgGmeBu4AmyyFnjpnjCZcrwtx+nOFB8Dl8Ba1lllWVTzNsoSWIZQnh6cdOxsHKRf58kUhF2X9xueYCFtwqiDFT4XmHvZj/AjfNrzCnHPLwCFa63cmaXDzBzrAtBPBUK03d7y2aqYGSqwMuFNpi7ou1/iVxBYqzAyR9NPrG9NuGHuCqgSCXKIGFTuM2Kke7RluaJB6bvXf25N1fx7E1Sq2rLAAAAAElFTkSuQmCC)}.markItUp .markItUpButton4 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC3SURBVCjPvdCxDYMwEAVQSxQ0CImKKldE19FRITeu3LihiGQGYYKbIBtkgtuACdiACW4NcgEnpKJE11j6T98+m9Wcj7kERIqsM6ymHwJ7dvQJmhvSryFK5N1rLFtc4gT8Bx4JOO42gC+Y6wM8pJ/D6Ec3dnOrAJ9ga64O0EtIDS3fBS0sGi/FklMCQXwCjQIoa1vZYsqnrEnAi0sAGWQ/5Zx9r/CkT+NW18QBWMu39TIydN1Xn88bUK7xEQPM95QAAAAASUVORK5CYII=)}.markItUp .markItUpButton5 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADCSURBVCjPvdCxCcMwEAXQAxcuIoIFAUMgqtypcyXSqBIYNy4M0gSZQBNoAm2QCW6DTOBFbg1HTo6QyqW5QsU9vj4HK+wPHAJ88uhxDiuMwaFFk/qksUOF7cAJnmb8+rKmFXiN8sxgpomBwb6A7qUe7e2vw0Tj4qKNJvaLLkDRhRoS+QdGcpxQwml7pRaxpiowcGQZdHilVssoyu9VhsjAkmGgsCEZT1Rv/RHuH2BTqYa6xKlQmqPIda6ekGA47tT78wZ72Oy4vOPLEgAAAABJRU5ErkJggg==)}.markItUp .markItUpButton6 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADCSURBVCjPY/jPgB8yUEtBeUL5+ZL/Be+z61PXJ7yPnB8sgGFCcX3m/6z9IFbE/JD/XucxFOTWp/5PBivwr/f77/gfQ0F6ffz/aKACXwG3+27/LeZjKEioj/wffN+n3vW8y3+z/Vh8EVEf/N8LLGEy3+K/2nl5ATQF/vW+/x3BCrQF1P7r/hcvQFPgVg+0GWq0zH/N/wL1aAps6x3+64M9J12g8p//PZcCigKbBJP1uvvV9sv3S/YL7+ft51SgelzghgBKWvx6E5D1XwAAAABJRU5ErkJggg==)}.markItUp .markItUpButton7 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABxSURBVCjPY/jPgB8yUFtBdkPqh4T/kR+CD+A0Ie5B5P/ABJwmxBiE//f/gMeKkAlB/90W4FHg88Dzv20ATgVeBq7/bT7g8YXjBJf/RgvwKLB4YPFfKwCnAjMH0/8a/3EGlEmD7gG1A/IHJDfQOC4wIQALYP87Y6unEgAAAABJRU5ErkJggg==)}.markItUp .markItUpButton8 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVDjLY/j//z8DJZiBKgbkzH9cMHXX6wcgmiwDQJq3nv/4H0SD+OXl5dlA/L+kpOR/QUHB/+zs7P+pqan/ExIS/kdGRv4PDg7+T10XDHwgpsx8VNC56eWDkJ675Hmhbf3zB0uPvP1fuvQpOBDj4uKyIyIi/gcGBv738vL67+zs/N/Gxua/iYnJf11d3f9qamqogRjQcaugZPHjB66V14ZqINrmXyqIn3bvgXXeJfK8ANLcv+3lfxAN4hsZGWVra2v/V1FR+S8nJ/dfXFz8v5CQ0H8eHp7/7Ozs/5mZmVEDEWQzRS6gBAMAYBDQP57x26IAAAAASUVORK5CYII=)}.markItUp .markItUpButton9 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAD3SURBVDjLY/j//z8DJRhM5Mx/rLLo8Lv/ZBsA0kyRATBDYOzy8vJsIP5fUlLyv6Cg4H92dvb/1NTU/wkJCf8jIyP/BwcH/8fqgkUHSXcFA1UCce7+t/9n7Xn9P2LiPRWyXRDae0+ld8tL8rwQ1HVHpXPTc7jmuLi47IiIiP+BgYH/vby8/js7O/+3sbH5b2Ji8l9XV/e/mpoaaiC2rX/+v3HN0/81q54OUCCWL3v8v3Tp4//Fix+T7wKQZuu8S+THAkgzzAVGRkbZ2tra/1VUVP7Lycn9FxcX/y8kJPSfh4fnPzs7+39mZmbUQARpBGG7oisddA9EAPd/1bRtLxctAAAAAElFTkSuQmCC)}.markItUp .markItUpButton10 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALtSURBVBgZTcFLaFxVAIDh/5577jwzj0wSUmqMtKIiBltbbJ1FUCxVoQu3FrHGVRU3BVcKrkTcKOhCUOtOAyJ23WIQtFawpoooZWKJpnbsNJN5PzP3PO5xArPo93nOOfasXCgfAz48mE8UhzpiqCN0FLFrog7QA+qABVpAA/gC+FYyERlz/NC+qeIbT85xt4GKckMV5Voju6A09ELLzXqfi38PTgLnJBORMfPZmMeectsSeB7SA19CPBAsxgW+EAQ+PLaQZH8uXTj/S+UDwYTVOitxmAh6yqOjoR1CZwSdETR2Yadv2fPm6i2KB9IszQZzkgkVmvnLZcuP21VeO1rgs+tdAu1YOZxlKiHw8fA9iADPdvn5nxa/3epUBGOH39sqjETu2UJG4oUwDB2RcmRSHuevdtjpWgZhxEBH4KDaDflobbNrlVoRh97demHpgfTth+5J5ZpNw5kjWQxw6mCa7aYlk4bPr7X54XqfkfGIHNjAYpQ6cOH1x9fEw/cnP13M+Ik7bc3ZYxniMR9PQCElObmYptox7E97XK0MscbhHJgwxKrQMiZ+v9Y9u3knHBUCn08ut6m2DQJHe6C5WOqQl4KbVcXR2QSxwENbS38wNEapLmNi4/0Hv/r3zxvHN0p1YnGP1e/r4ODr9TbZlKBTU7xSnKG4lCUZQKMfYkJVvfT2c44xyVjKr6lpEUI3g3UOPIE1lu6O5aUTcyRjPjhISUGttYtVYYUJuXxudRZ4p/jIvZx+eoHvSopmz/Ly8jyJwBFIkD7EfMimYLM8xChVZUJapU4Ap34tbdHalfRDh7aOUHsoE2FsROQchVyOV5/Zx3ZjiFWqxoS0Wh95/qlHk2+9+AR3sw60dSgDOPj4UoVUAL3+EKt1gwlptd7arnf4cq1EfipJPpsgn46TS8fJpGLEY4K4FJxenicuodbsYbX+jwkZGfPNlfWNhSvrG/cBM8AMMA1MA7lELAgSiYBsOkk+m+KPv8o3gJ+Y+B9yFXCQeyJWrQAAAABJRU5ErkJggg==)}.markItUp .markItUpButton11 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALhSURBVDjLfZHtT1JhGMbd+kPaNElNnCHqARQ4TmnjRYRVSPk2hShMJ5IK2hQxdDqxZBnVkuYLpgg4TaeuzElutdXogyw/NNeHvrg258t0mCuuzmHLomVnu7frvp9rv+e+nhMHIO6kEsxzIZjlkLTO9Wcrc7xZ4b89MQ35gpfwZ8+fIZA7lT2j7yuM501karlj7L3/AoT0jXOcW7/6HF/2Ic+TtXWtTeQu6uSGuYMZkWIDr/tEAP85sZM7TRxGV/YSu9zxTHDcbHCG2GgN1CPwZQqm3nLItKyGfwKonGTOZBb4E8SOxFsAX8iFhfVR9L22gXk3CeXzcnjeOnHenor07nNIs6VQt04Tu1TO4+xUzojAzcF48B7uLFaiZaYYjTMV4DsJMLuTUTmqQn4R49PxBlTOIyonuM8yh/lu4utlXxHc7+2wzJXB6JOjdCQPao8CE+8eY2C5D2kdyeCpE6diInBG2W+IoQwQrgx4P9xH62wJ6r2FKB0ioRorxMMVG4zjKjx61QlmawpSzIz95MbEmpg3IJ5kHKonlOh9WYM6jwwlT/NwcViKB8sdqBlRoGrwAgXoRkoTA0nGM5GzdQlg3Iz//YDZA6yD675K2BcaUNV1CdaudrhcLjRbmqBxyNExaUBze3N0ZrPZtvV6fUNiVYL1GMB1ZHaxepjQ3q5Af38/Njc3sbGxgZWVFbR3taG6RY/Qx1B0trq6GvVIpdKemN/I0qWHao21CK6tQWMog0QqgVarhcPhgNlsjmqJRIyiK/J1GmI0GndiASxWj8Vq+UEDal06kPkklpaWEAwGEQgE4PP5wBfyI/FXT0e3sFqt4RgAk8m8odPptmmASF0QEeYJIRKJoFAookVrkiSPlErlIg2gvTGAZOqTyWTrdrt92+PxbPn9fjidThgMBphMpqimZ/QZ7aG9MQAGg3FKIBCki8XizxqNZr+6ujpM1Td2Met7mir1gNJ79Iw+oz209ye6Km+xbu69pwAAAABJRU5ErkJggg==)}.markItUp .markItUpButton12 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKYSURBVDjLnZPJT1NRFMb5G1wDHV5boNiqdHrvFYolCAtsGSSWKpMFKhYqlDI6oAEKaVJwCIgSphaKtLYWCgSNBgRjMNHoxsSFS3cmJmA0NMTw+R6JKKZl4eJL7sm953fOd3JPHIC4WMpcppG5SGnZc8ZjVVF6QLn975sDgfaZmvg71oRJZIRUYcuAnq/2KWroGfm3QwEn2YpLVPPvOD2oiqj9yq/mGznegl56mx6T7ZbY1M6YAM0CuZkxT0b2Wg6QW/SsApRXDsotR+d6E9Y/h9DuqoCuJq0lKoDxqU1/pITGR27mBU4h+GEcTz5OY+ClA5JbyahYzof/9TBO9B/FcWcqpA4xU3We3GJ87ntnfO5meinMvruNnqcmXA2XoDVcCc0wCYkzBaZpA7ILRJ/2O2B87jA+QT9UeDRe8svZYAG8b/txc6kc9mA+yqayYPQXwvdmBEOrA5B2p0BtFIYOWKCm5RukWwZyXIbA+0F0LpaiKaBHmVsLw4we99ccsM8a8GClF5JOMcQdou8prULrgRmQo7KI0VcE13MrGv06lE5kodhzGvdWu2GdKkTVWC4DcELcJkKyXbCb1EhAVM//M0DVUNqP2qAJd1baUDaZjTMTeXAttsPi0cM0mgvHvA0NkxYk2QRIrieOsDmEmXttH0DfVfSluSToWmpD8bgOroUOWNw6VI7koGfOBuq6EqLLTNU6ojrmP5D1HVsjmrkYezGIrlA9LjKgnrlGXJlpgbCOD0EtD0QNN8I3cZqjAlhJr4rXpB1iNLhrYffUQWoT7yUKzbxqJlHLq0jc5JYmgHMunogKYJVqF7mTrPyfgktMRTMX/CrOq1gLF3fYNrLiX+Bs8MoTwT2fQPwXgBXHGL+TaIjfinb3C7cscRMIcYL6AAAAAElFTkSuQmCC)}.markItUp .markItUpButton13 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJ/SURBVDjLY/j//z8DJZgsTV+9fAu+uHo8+GzvXECWAV+c3R//mTn9/ydLu4eka3ZyY/ts63T3k4Xt+4/GlqS74JONY+9Hc5tdH4wsmAmGgWv9xQKX2nMPnapOF4A1WzsEfjSzefLB0FwUHoi/szPX/05P/f0rOWk9ugHONWefzNl44X/B/L3/o7LXnn1h4fitN6i22Tx7W5tpxqYHxmnrChh+p6X+/rd10/+fsbF/f0REmiE0n7F3rDz5wb7s6Bu3gt3Vz80db69zTd1mlr11tUnGxt89Cw/8N0ha9YDhZ2LC+p8xMb9/hEdc+h4Ucu+br//JFXFNi5zKjz20KztiDzIMGFgzP+iZboQZbpSypsAgaeUjvfilqIEI9C9bV1LfvnnRaT96Ghr/zZs3739Xccn/S9aO3+cEh6nV1NTcBYm1tra+y8rKKrWzs+NG9q+EY8XR4+7RFdcmTpz4/8WLF//v3r37/+DBg//7S8v+V1dXH7p58+YPkNiRI0f+g9T4+Ph0MbjWXSgA+vc50L9vLEJq5hcXF384d/ny/4iIiG0+3t7/UlNT/0+aNOl/ZWXlfxDb29v7D0gOZAhILYNT9ZkHk9dc/A/070sjI6Oupqam7yAD/P39uezt7T/v3bv3/7lz5/4fOnTo/9q1a/+DxEByIANAahkcKo4X2JUefmBTtL9AV1c3Mz09/T3IgKCgoJ2Ojo6/3Nzc/gcGBoIxiA0SA8mBDACpRQlETSDw9fW93tvb+37VqlVv161b93/69On/CwsL/1dUVIDZIDGQHEgNSC2KAWpqasxAJxp4eXndT0lJ+ZKdnf0diH+Gh4fHAbEzkP0JJAaSA6kBqQUAGVqFjOjvxXwAAAAASUVORK5CYII=)}.markItUp .markItUpButton14 a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJ3SURBVDjLpZNtSNNRFIcNKunF1rZWBMJqKaSiX9RP1dClsjldA42slW0q5oxZiuHrlqllLayoaJa2jbm1Lc3QUZpKFmmaTMsaRp+kMgjBheSmTL2//kqMBJlFHx44XM7vOfdyuH4A/P6HFQ9zo7cpa/mM6RvCrVDzaVDy6C5JJKv6rwSnIhlFd0R0Up/GwF2KWyl01CTSkM/dQoQRzAurCjRCGnRUUE2FaoSL0HExiYVzsQwcj6RNrSqo4W5Gh6Yc4+1qDDTkIy+GhYK4nTgdz0H2PrrHUJzs71NQn86enPn+CVN9GnzruoYR63mMPbkC59gQzDl7pt7rc9f7FNyUhPY6Bx9gwt4E9zszhWWpdg6ZcS8j3O7zCTuEpnXB+3MNZkUUZu0NmHE8XsL91oSWwiiEc3MeseLrN6woYCWa/Zl8ozyQ3w3Hl2lYy0SwlCUvsVi/Gv2JwITnYPDun2Hy6jYuEzAF1jUBCVYpO6kXo+NuGMeBAgcgfwNkvgBOPgUqXgKvP7rBFvRhE1crp8Vq1noFYSlacVyqGk0D86gbART9BDk9BFnPCNJbCY5aCFL1Cyhtp0RWAp74MsKSrkq9guHyvfMTtmLc1togpZoyqYmyNoITzVTYRJCiXYBIQ3CwFqi83o3JDhX6C0M8XsGIMoQ4OyuRlq1DdZcLkmbgGDX1iIEKNxAcbgTEOqC4ZRaJ6Ub86K7CYFEo8Qo+GBQlQyXBczLZpbloaQ9k1NUz/kD2myBBKxRZpa5hVcQslalatoUxizxAVVrN3CW21bFj9F858Q9dnIRmDyeuybM71uxmH9BNBB1q6zybV7H9s1Ue4PM3/gu/AEbfqfWy2twsAAAAAElFTkSuQmCC)}.markItUp .preview a{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLvZPZLkNhFIV75zjvYm7VGFNCqoZUJ+roKUUpjRuqp61Wq0NKDMelGGqOxBSUIBKXWtWGZxAvobr8lWjChRgSF//dv9be+9trCwAI/vIE/26gXmviW5bqnb8yUK028qZjPfoPWEj4Ku5HBspgAz941IXZeze8N1bottSo8BTZviVWrEh546EO03EXpuJOdG63otJbjBKHkEp/Ml6yNYYzpuezWL4s5VMtT8acCMQcb5XL3eJE8VgBlR7BeMGW9Z4yT9y1CeyucuhdTGDxfftaBO7G4L+zg91UocxVmCiy51NpiP3n2treUPujL8xhOjYOzZYsQWANyRYlU4Y9Br6oHd5bDh0bCpSOixJiWx71YY09J5pM/WEbzFcDmHvwwBu2wnikg+lEj4mwBe5bC5h1OUqcwpdC60dxegRmR06TyjCF9G9z+qM2uCJmuMJmaNZaUrCSIi6X+jJIBBYtW5Cge7cd7sgoHDfDaAvKQGAlRZYc6ltJlMxX03UzlaRlBdQrzSCwksLRbOpHUSb7pcsnxCCwngvM2Rm/ugUCi84fycr4l2t8Bb6iqTxSCgNIAAAAAElFTkSuQmCC)}.patch-container{float:left;width:100%}.diff-container{margin:0 auto 30px;border:1px solid #ddd;width:100%}.patch-container .heading>h3{padding:10px 0 0;font-weight:normal;font-size:18px;line-height:2;color:#666}#content .patch-container .heading>h3{color:#666}.patch-container .heading>h3>a{color:inherit}.patch-container .heading.conflicts>h3:after{content:"\0020(conflicts)";color:#c00}#compare .patch-container .heading a[href="#files"],.patch-container .heading a[href="#files"]{position:absolute;right:0;top:0;width:15px;height:15px;background:url(../img/up-arrow-sprite.png);text-indent:-9999px}#compare .patch-container .heading a[href="#files"]:focus,#compare .patch-container .heading a[href="#files"]:hover,.patch-container .heading a[href="#files"]:focus,.patch-container .heading a[href="#files"]:hover{background-position:-15px}.diff-container .box-container,.diff-container .scrollable,.diff-container .from-numbers-box,.diff-container .to-numbers-box,.diff-container .from-revision-blame-box,.diff-container .to-revision-blame-box,.diff-container .from-auth-blame-box,.diff-container .to-auth-blame-box,.diff-container .segment-box{position:relative}.diff-container .box-container,.diff-container .box,.diff-container .numbers-box,.diff-container .segment-box{height:100%}.diff-container .box-container{border-left:none;float:left}.diff-container .box{width:100%}.diff-container .scrollable{overflow-x:auto;overflow-y:auto}.diff-container.unified .scrollable,.diff-container.view-annotation .scrollable{overflow-y:hidden}.diff-container .from-box{float:left;background:transparent url(../images/spinner-sbs-diff.gif) fixed no-repeat 21% 50%}.diff-container.unified .from-box{overflow:hidden}.diff-container .to-box{float:left;background:transparent url(../images/spinner-sbs-diff.gif) fixed no-repeat 77% 50%}.diff-container.unified .to-box{background-position:50% 50%}.diff-container .from-box.all-lines-visible,.diff-container .to-box.all-lines-visible{background-image:none}.diff-container .to-numbers-box{border-left:none;border-right:1px solid #ccc}.diff-container .to-box{border-left:none}.diff-container .from-numbers-box,.diff-container .to-numbers-box{float:left;text-align:right;overflow:hidden}.diff-container .segment-box{border-left:1px solid #ccc;border-right:1px solid #ccc}.diff-container>div{border-left:1px solid #ccc;border-right:0}.diff-container.unified .from-numbers-box,.diff-container .from-box,.diff-container .heading{border-left:none}.diff-container .from-revision-blame-box,.diff-container .to-revision-blame-box,.diff-container .from-auth-blame-box,.diff-container .to-auth-blame-box{float:left;overflow:hidden;color:#036}.diff-container .gutter-box{float:left;overflow:hidden}.diff-container .segment-box{float:left;width:30px;overflow:hidden}.diff-container .content-box{position:relative}.diff-container .page{background:#fff;margin:0;padding:0;position:absolute}.diff-container pre{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:0;background:inherit;white-space:pre}.diff-container pre{font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,monospace;font-size:11px;margin:0;height:16px;line-height:16px;padding:0 .2em;border-width:1px 0;border-style:solid;border-color:transparent}.diff-container a{color:#999;text-decoration:none}.diff-container a:hover,.diff-container a:active{color:#00e;text-decoration:underline}.diff-container .line-numbers{left:0;width:100%}.diff-container .addition,.diff-container .change.to,.diff-container .ediffChangedB{background-color:#cfc;border-color:#cfc}.diff-container ins{background-color:#cfc;text-decoration:none;display:inline-block}.diff-container .change.to ins{background-color:#9f9}.diff-container .anchored-line .ediffChangedB{background-color:#e5ffd6;border-color:#e5ffd6}.diff-container .deletion,.diff-container .change.from,.diff-container .ediffChangedA{background-color:pink;border-color:pink}.diff-container .addition.conflict,.diff-container .change.conflict{background-color:#ffffe0;border-color:#ffffe0}.diff-container del{background-color:pink;text-decoration:none;display:inline-block}.diff-container .change.from del{background-color:#ff909b}.diff-container .anchored-line .ediffChangedA{background-color:#ffdfd6;border-color:#ffdfd6}.diff-container .change{background-color:#f4f4f4;border-color:#f4f4f4}.diff-container .anchored-line{background-color:#ffffe0;border-color:#ffffe0}.diff-container .ediffChangedA,.diff-container .ediffChangedB{border-width:1px 0;border-style:solid}.diff-container .start .ediffChangedA,.diff-container .start .ediffChangedB{border-top:0}.diff-container .end .ediffChangedA,.diff-container .end .ediffChangedB{border-bottom:0}.diff-container .from-box .change.conflict.start,.diff-container .from-numbers-box .change.conflict.start,.diff-container .addition.conflict.start.end,.diff-container .change.from.start,.diff-container .start{border-top-color:#ccc}.diff-container .addition.conflict.end,.diff-container .change.to.end,.diff-container .end{border-bottom-color:#ccc}.diff-container.unified .addition.conflict.end,.diff-container .addition.conflict.start.end{border-bottom-color:#ffffe0}.diff-container.unified .from-numbers-box .change.conflict.start,.diff-container .conflict.from.start,.diff-container .conflict.to.end{border-color:#ffffe0}.diff-container .skipped,.diff-container .skipped-top,.diff-container .skipped-bottom{border-color:#fff;background:#ebf1fb url('../images/diff-skipped-background.png') repeat-x bottom left;padding:0}.diff-container .skipped-top{border-top-color:#ebf1fb}.diff-container .skipped-bottom{background-image:none}.diff-container .skipped span,.diff-container .skipped-bottom span{display:block;background:transparent url('../images/diff-skipped-background.png') repeat-x top left}.diff-container .skipped-top span{visibility:hidden}.diff-container .addition.conflict.start,.diff-container .addition.conflict.start.end,.diff-container .addition.conflict.end{color:#999;border-color:#ccc;background-color:#fffacd}.diff-container>.overlay{width:100%;height:100%;background:transparent;border:0;cursor:pointer}.diff-container.unified>.overlay,.diff-container.unified .overlay-x,.diff-container.unified .overlay-y,.diff-container.scrollable-on>.overlay{display:none}.diff-container.scrollable-off .overlay-x,.diff-container.scrollable-off .overlay-y{position:absolute;background:#fff;-ms-filter:"alpha(opacity=75)";filter:alpha(opacity=75);-moz-opacity:.75;opacity:.75}.diff-container.scrollable-off .overlay-x{left:0;right:15px;bottom:0}.diff-container.scrollable-off .overlay-y{top:0;right:0;bottom:0}.diff-container .margin-buttons{position:absolute;right:0}.diff-container .margin-buttons a{display:block;text-indent:-10000em;position:absolute;left:2px;width:9px;height:15px}.diff-container .margin-buttons .prev-hunk,.diff-container .margin-buttons .next-hunk{background:#fff url(../img/sbs-diff-sprite.png) no-repeat -3px -15px}.diff-container .margin-buttons .prev-hunk:hover,.diff-container .margin-buttons .prev-hunk:active{background-position:-18px -15px}.diff-container .margin-buttons .prev-hunk{left:12px}.diff-container .margin-buttons .next-hunk{background-position:-3px -30px}.diff-container .margin-buttons .next-hunk:hover,.diff-container .margin-buttons .next-hunk:active{background-position:-18px -30px}.diff-container .margin-buttons .more-context{width:15px;background:#fff url(../img/sbs-diff-sprite.png) no-repeat 0 0}.diff-container .margin-buttons .more-context:hover,.diff-container .margin-buttons .more-context:active{background-position:-15px 0}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.cm{color:#998;font-style:italic}.c1{color:#998;font-style:italic}.cs{color:#999;font-style:italic}.gd{color:#000;background-color:#fdd}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.go{color:#888}.gp{color:#555}.gu{color:#aaa}.gt{color:#a00}.k{color:#004080}.kd{color:#004080}.kt{color:#458}.m{color:#099}.s{color:#b84}.na{color:#008080}.nb{color:#999}.nc{color:#458}.no{color:#008080}.ni{color:#800080}.ne{color:#900}.nf{color:#900}.nn{color:#555}.nt{color:#000080}.nv{color:#008080}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#b84}.sc{color:#b84}.sd{color:#b84}.s2{color:#b84}.se{color:#b84}.sh{color:#b84}.si{color:#b84}.sx{color:#b84}.sr{color:#808000}.s1{color:#b84}.ss{color:#b84}.bp{color:#999}.vc{color:#008080}.vg{color:#008080}.vi{color:#008080}.il{color:#099}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/cases.css0000644000175000017500000000012300000000000021301 0ustar00kovidkovid@import url("a"); b{color:red} a{float:left;bogus:foo(a, url(a))} b{color:red} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/csscombine-1.css0000644000175000017500000000014100000000000022466 0ustar00kovidkovid@charset "iso-8859-1"; /* combined sheet 1 */ @namespace s1 "uri"; s1|sheet-1 { top: 1px }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/csscombine-2.css0000644000175000017500000000015700000000000022476 0ustar00kovidkovid@charset "ascii"; /* combined sheet 2 */ @import "1.css"; @namespace s2 "uri"; s2|sheet-2 { top: 2px; }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/csscombine-proxy.css0000644000175000017500000000033400000000000023513 0ustar00kovidkovid@charset "utf-8"; /* proxy sheet were imported sheets should be combined */ /* non-ascii chars: öäü */ @import "csscombine-1.css"; @import url(csscombine-2.css); @namespace other "other"; proxy { top: 3px } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/cthedot_default.css0000644000175000017500000000741300000000000023352 0ustar00kovidkovid/* default stylesheet for all variations */ .jsonly { display: none; } .stylenav { text-align: right; margin-top: -1em; } html, body { padding: 0; margin: 0; } body { font: normal 90%/1.1 Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; color: #fff; background-color: #344; } h1, h2, h3, h4, h5, h6 { font: normal 2.4em Verdana, "Lucida Grande", Arial, Helvetica, sans-serif; } h1, h2, h3, caption { color: #a00; margin: 0.3em -0.4em 0.5em -0.5em; } h2, h3, caption { margin: 0.3em -1.3em 0.3em 0; } h2 { font-size: 1.5em; border-right: 1.3em solid #677; border-bottom: 1px dotted #677; padding-left: 0.1em; padding-bottom: 0.1em; margin-top: 2.1em; margin-bottom: 0.4em; } h3 { font-size: 0.9em; font-weight: bold; text-transform: uppercase; margin-top: 1.2em; margin-bottom: 0.1em; } caption { font-size: 1.05em; text-align: left; margin-bottom: 0.2em; } h4 { font-size: 0.9em; font-weight: bold; margin: 1.2em 0 0; } h5, h6 { font-size: 1em; margin: 0; } h6 { font-size: 0.9em; } p, ol, ul, dl { line-height: 1.3; margin-top: 0; margin-bottom: 1em; } ul { list-style-type: square; } ul.code { line-height: 1.3; } li, dd { margin-bottom: 0.3em; } dt { font-weight: bold; } pre, code { color: #00a; line-height: 1.4; font-size: 1.1em; } table code, table pre { font-size: 1.3em; } .deprecated { color: #888; } table { font-size: 0.9em; border-collapse: collapse; } tr { vertical-align: top; line-height: 1.3; } td, th { text-align: left; padding: 0.4em 0.5em; border-bottom: 1px dotted #667; } td.center { text-align: center; } tr:hover, li:hover { background-color: #f8f8f8; } acronym, .explain { border-bottom: 1px dotted #344; } a { text-decoration: none; color: #fff; border-bottom: 1px solid #aaa; } #main a { color: #a00; } a:visited { color: #eee; } #main a:visited { color: #344; } a:hover { text-decoration: underline; color: #fff; } #main a:hover { background-color: #f5f8ff; } #main a:active { color: #fff; background-color: #abb; } label { display: block; padding: 0.5em 0 0.1em; } input, textarea { font: bold 1em Georgia, Verdana, "Lucida Grande", Helvetica, sans-serif; background-color: #eee; width: 100%; border: 1px inset; } #submit, textarea { margin-bottom: 1.5em; } #submit { font-weight: bold; color: #00a; background-color: #fff; border: 1px outset; margin-top: 2em; } input:focus, input:hover, textarea:focus, textarea:hover { font-weight: bold; background-color: #fff; border-style: solid; } #submit:hover, #submit:focus { background-color: #eee; border-style: solid; } #submit:active { border-style: inset; } #header { padding: 1.1em 5% 0; padding: 40px 5% 0; color: #334; background: #fff url(/img/header_r.jpg) no-repeat; border-bottom: 1px solid #344; height: 90px; he\ight: 50px; } #header dt, #header p { font-family: Arial, Helvetica, sans-serif; letter-spacing: 0.3em; } #header a { color: #334; } #main .nav { padding-bottom: 0.5em; border-bottom: 3px solid #eee; margin-bottom: 1em; margin-left: -8%; } .nav dt , .nav dd, .nav dd ul, .nav li { display: inline; } .nav dt { font-weight: bold; } .nav li { font-weight: bold; padding-right: 0.5em; } .nav a { font-weight: normal; } #footer { padding: 1em 5% 1em 10%; border-top: 3px double #fff; text-align: right; } #main { color: #000; background-color: #fff; padding: 1em 26% 1em 12%; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/default_html4.css0000644000175000017500000000535500000000000022753 0ustar00kovidkovid/* default HTML 4 CSSStyleSheet from http://www.w3.org/TR/2004/CR-CSS21-20040225/sample.html */ html, address, blockquote, body, dd, div, dl, dt, fieldset, form, frame, frameset, h1, h2, h3, h4, h5, h6, noframes, ol, p, ul, center, dir, hr, menu, pre { display: block } li { display: list-item } head { display: none } table { display: table } tr { display: table-row } thead { display: table-header-group } tbody { display: table-row-group } tfoot { display: table-footer-group } col { display: table-column } colgroup { display: table-column-group } td, th { display: table-cell } caption { display: table-caption } th { font-weight: bolder; text-align: center } caption { text-align: center } body { margin: 8px; line-height: 1.12 } h1 { font-size: 2em; margin: .67em 0 } h2 { font-size: 1.5em; margin: .75em 0 } h3 { font-size: 1.17em; margin: .83em 0 } h4, p, blockquote, ul, fieldset, form, ol, dl, dir, menu { margin: 1.12em 0 } h5 { font-size: .83em; margin: 1.5em 0 } h6 { font-size: .75em; margin: 1.67em 0 } h1, h2, h3, h4, h5, h6, b, strong { font-weight: bolder } blockquote { margin-left: 40px; margin-right: 40px } i, cite, em, var, address { font-style: italic } pre, tt, code, kbd, samp { font-family: monospace } pre { white-space: pre } button, textarea, input, object, select { display: inline-block } big { font-size: 1.17em } small, sub, sup { font-size: .83em } sub { vertical-align: sub } sup { vertical-align: super } table { border-spacing: 2px; } thead, tbody, tfoot { vertical-align: middle } td, th { vertical-align: inherit } s, strike, del { text-decoration: line-through } hr { border: 1px inset } ol, ul, dir, menu, dd { margin-left: 40px } ol { list-style-type: decimal } ol ul, ul ol, ul ul, ol ol { margin-top: 0; margin-bottom: 0 } u, ins { text-decoration: underline } br:before { content: "\A" } :before, :after { white-space: pre-line } center { text-align: center } abbr, acronym { font-variant: small-caps; letter-spacing: 0.1em } :link, :visited { text-decoration: underline } :focus { outline: thin dotted invert } /* Begin bidirectionality settings (do not change) */ BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override } BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override } *[DIR="ltr"] { direction: ltr; unicode-bidi: embed } *[DIR="rtl"] { direction: rtl; unicode-bidi: embed } @media print { h1 { page-break-before: always } h1, h2, h3, h4, h5, h6 { page-break-after: avoid } ul, ol, dl { page-break-before: avoid } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/hacks.css0000644000175000017500000000133500000000000021302 0ustar00kovidkovid.normal {background-color: gray;} .backslash {bac\kground-color: gray;} html>body .childselector {background-color: green;} html>/**/body .childselector-with-comment {background-color: orange} .colon-default2, x:default html>/**/body .colon-default, x:default *:not(hr) .not-hr {background-color: red;} * html .ie-only-1 {background-color: blue;} *+html .ie-only-2 {background-color: blue;} *+html .ie-only-3 {background-color: blue;} html:first-child .first-child-2 {background-color: red;} /* does not work as CSSUnknownRule read: @mediaall { .mediaall { background-color: red; }} */ .not-class:not([class='XXX']) {background-color: red;} @media all and (min-width: 0) { .mediaquery { background-color: red;} } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/html.css0000644000175000017500000000017400000000000021155 0ustar00kovidkovidbody { color: red } body { color: blue } body { color: pink } body { color: green } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/html20.css0000644000175000017500000000273700000000000021326 0ustar00kovidkovid BODY { margin: 1em; font-family: serif; line-height: 1.1; background: white; color: black } H1, H2, H3, H4, H5, H6, P, UL, OL, DIR, MENU, DIV, DT, DD, ADDRESS, BLOCKQUOTE, PRE, BR, HR { display: block } B, STRONG, I, EM, CITE, VAR, TT, CODE, KBD, SAMP, IMG, SPAN { display: inline } LI { display: list-item } H1, H2, H3, H4 { margin-top: 1em; margin-bottom: 1em } H5, H6 { margin-top: 1em } H1 { text-align: center } H1, H2, H4, H6 { font-weight: bold } H3, H5 { font-style: italic } H1 { font-size: xx-large } H2 { font-size: x-large } H3 { font-size: large } B, STRONG { font-weight: bolder } /* relative to the parent */ I, CITE, EM, VAR, ADDRESS, BLOCKQUOTE { font-style: italic } PRE, TT, CODE, KBD, SAMP { font-family: monospace } PRE { white-space: pre } ADDRESS { margin-left: 3em } BLOCKQUOTE { margin-left: 3em; margin-right: 3em } UL, DIR { list-style: disc } OL { list-style: decimal } MENU { margin: 0 } /* tight formatting */ LI { margin-left: 3em } DT { margin-bottom: 0 } DD { margin-top: 0; margin-left: 3em } HR { border-top: solid } /* 'border-bottom' could also have been used */ A:link { color: blue } /* unvisited link */ A:visited { color: red } /* visited links */ A:active { color: lime } /* active links */ /* setting the anchor border around IMG elements requires contextual selectors */ A:link IMG { border: 2px solid blue } A:visited IMG { border: 2px solid red } A:active IMG { border: 2px solid lime } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/html40.css0000644000175000017500000001026200000000000021320 0ustar00kovidkovid@charset "US-ASCII"; ADDRESS, BLOCKQUOTE, BODY, DD, DIV, DL, DT, FIELDSET, FORM, FRAME, FRAMESET, H1, H2, H3, H4, H5, H6, IFRAME, NOFRAMES, OBJECT, OL, P, UL, APPLET, CENTER, DIR, HR, MENU, PRE { display: block } LI { display: list-item } HEAD { display: none } TABLE { display: table } TR { display: table-row } THEAD { display: table-header-group } TBODY { display: table-row-group } TFOOT { display: table-footer-group } COL { display: table-column } COLGROUP { display: table-column-group } TD, TH { display: table-cell } CAPTION { display: table-caption } TH { font-weight: bolder; text-align: center } CAPTION { text-align: center } BODY { padding: 8px; line-height: 1.33 } H1 { font-size: 2em; margin: .67em 0 } H2 { font-size: 1.5em; margin: .83em 0 } H3 { font-size: 1.17em; margin: 1em 0 } H4, P, BLOCKQUOTE, UL, FIELDSET, FORM, OL, DL, DIR, MENU { margin: 1.33em 0 } H5 { font-size: .83em; line-height: 1.17em; margin: 1.67em 0 } H6 { font-size: .67em; margin: 2.33em 0 } H1, H2, H3, H4, H5, H6, B, STRONG { font-weight: bolder } BLOCKQUOTE { margin-left: 40px; margin-right: 40px } I, CITE, EM, VAR, ADDRESS { font-style: italic } PRE, TT, CODE, KBD, SAMP { font-family: monospace } PRE { white-space: pre } BIG { font-size: 1.17em } SMALL, SUB, SUP { font-size: .83em } SUB { vertical-align: sub } SUP { vertical-align: super } S, STRIKE, DEL { text-decoration: line-through } HR { border: 1px inset } OL, UL, DIR, MENU, DD { margin-left: 40px } OL { list-style-type: decimal } OL UL, UL OL, UL UL, OL OL { margin-top: 0; margin-bottom: 0 } U, INS { text-decoration: underline } CENTER { text-align: center } BR:before { content: "\A" } /* An example of style for HTML 4.0's ABBR/ACRONYM elements */ ABBR, ACRONYM { font-variant: small-caps; letter-spacing: 0.1em } A[href] { text-decoration: underline } :focus { outline: thin dotted invert } /* Begin bidirectionality settings (do not change) */ BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override } BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override } *[DIR="ltr"] { direction: ltr; unicode-bidi: embed } *[DIR="rtl"] { direction: rtl; unicode-bidi: embed } /* Elements that are block-level in HTML4 */ ADDRESS, BLOCKQUOTE, BODY, DD, DIV, DL, DT, FIELDSET, FORM, FRAME, FRAMESET, H1, H2, H3, H4, H5, H6, IFRAME, NOSCRIPT, NOFRAMES, OBJECT, OL, P, UL, APPLET, CENTER, DIR, HR, MENU, PRE, LI, TABLE, TR, THEAD, TBODY, TFOOT, COL, COLGROUP, TD, TH, CAPTION { unicode-bidi: embed } /* End bidi settings */ @media print { @page { margin: 10% } H1, H2, H3, H4, H5, H6 { page-break-after: avoid; page-break-inside: avoid } BLOCKQUOTE, PRE { page-break-inside: avoid } UL, OL, DL { page-break-before: avoid } } @media speech { H1, H2, H3, H4, H5, H6 { voice-family: paul, male; stress: 20; richness: 90 } H1 { pitch: x-low; pitch-range: 90 } H2 { pitch: x-low; pitch-range: 80 } H3 { pitch: low; pitch-range: 70 } H4 { pitch: medium; pitch-range: 60 } H5 { pitch: medium; pitch-range: 50 } H6 { pitch: medium; pitch-range: 40 } LI, DT, DD { pitch: medium; richness: 60 } DT { stress: 80 } PRE, CODE, TT { pitch: medium; pitch-range: 0; stress: 0; richness: 80 } EM { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } STRONG { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } DFN { pitch: high; pitch-range: 60; stress: 60 } S, STRIKE { richness: 0 } I { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } B { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } U { richness: 0 } A:link { voice-family: harry, male } A:visited { voice-family: betty, female } A:active { voice-family: betty, female; pitch-range: 80; pitch: x-high } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1340148 css-parser-1.0.7/css_parser_tests/sheets/import/0000755000175000017500000000000000000000000021007 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/css_parser_tests/sheets/import/import-impossible.css0000644000175000017500000000013000000000000025171 0ustar00kovidkovid@namespace "y"; @media tv {} .import4 { background: url(images2/example2.gif); } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/css_parser_tests/sheets/import/import2.css0000644000175000017500000000065300000000000023121 0ustar00kovidkovid@import "../import3.css"; @import "import-impossible.css" print; .import2 { /* sheets/import2.css */ background: url(http://example.com/images/example.gif); background: url(//example.com/images/example.gif); background: url(/images/example.gif); background: url(images2/example.gif); background: url(./images2/example.gif); background: url(../images/example.gif); background: url(./../images/example.gif); }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/import.css0000644000175000017500000000016100000000000021517 0ustar00kovidkovid@import "import/import2.css"; .import { /* ./import.css */ background-image: url(images/example.gif) } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/import3.css0000644000175000017500000000046300000000000021607 0ustar00kovidkovid/* import3 */ .import3 { /* from ./import/../import3.css */ background: url(images/example3.gif); background: url(./images/example3.gif); background: url(import/images2/example2.gif); background: url(./import/images2/example2.gif); background: url(import/images2/../../images/example3.gif); }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/ll.css0000644000175000017500000002227200000000000020623 0ustar00kovidkovidbody { font-family: "Verdana", "Arial", "XHelvetica", "Helvetica", sans-serif; font-size: 12px; background-color: #333; background-image: url(images/Backdrops/Raster.gif); background-attachment: fixed; background-position: top left; margin: 0px; padding: 0px; } body table.body { background-image: url(images/Backdrops/NautilusBlack.jpg); background-attachment: fixed; background-position: top left; background-repeat: no-repeat; } td.header1, td.header2 { background-color: #0063a8; background-image: url(images/Backdrops/NautilusBlue.jpg); background-attachment: fixed; background-position: top left; background-repeat: no-repeat; } td.blank { background-color: #000; } td.header2 { border-left: 1px solid #000; border-right: 1px solid #000; } td.header2 h1 { color: #fff; font-family: "Trebuchet MS", sans-serif; font-weight: normal; font-size: 50px; margin: 0px 0px 5px 30px; padding: 0px; line-height: 50px; } td.header2 h2 { color: #fff; font-family: "Trebuchet MS", sans-serif; font-weight: normal; font-size: 18px; margin: 0px 0px 10px 30px; padding: 0px; line-height: 18px; } td.header3 { width: 30px; } td.crumbs1, td.crumbs2 { font-family: "Verdana", "Arial", "XHelvetica", "Helvetica", sans-serif; font-size: 11px; background-color: #194b6e; background-image: url(images/Backdrops/NautilusDarkBlue.jpg); background-attachment: fixed; background-position: top left; background-repeat: no-repeat; } td.crumbs1 { border-top: 1px solid #000; } td.crumbs2 { padding: 4px 6px 4px 30px; color: #fff; border-top: 1px solid #000; border-left: 1px solid #000; border-right: 1px solid #000; } td.crumbs2 td.alternate { padding-left: 30px; font-size: 9px; } td.crumbs2 a:link { color: #fff; } td.crumbs2 a:visited { color: #ccc; } td.crumbs2 a:hover { color: #ffc; border-bottom: 1px solid #fff; } td.crumbs2 span.here { color: #fff; font-weight: bold; } td.content { border: 1px solid #000; background-image: url(images/Backdrops/NautilusWhite.jpg); background-attachment: fixed; background-position: top left; background-repeat: no-repeat; background-color: #fff; padding: 20px 30px; line-height: 18px; } table, tr, td, th { margin: 0px; border: 0px solid #000; padding: 0px; } a { text-decoration: none; } td.content a { border-bottom: 1px solid #cc8080; } a:link { color: #900; } a:visited { color: #633; } a:hover, a:active { color: #f00; } td.links { font-family: "Verdana", "Arial", "XHelvetica", "Helvetica", sans-serif; border-top: 1px solid #000; padding: 16px 0px 0px 0px; color: #fff; } td.links ul, td.links li { margin: 0px; padding: 0px; } td.links div, td.links a { display: block; margin: 4px 0px 0px 0px; padding: 2px 2px 2px 10px; white-space: nowrap; color: #fff; font-size: 13px; border-top: 1px solid #333; border-bottom: 1px solid #000; background-color: #858585; background-image: url(images/Backdrops/NautilusGrey.jpg); background-attachment: fixed; background-position: top left; background-repeat: no-repeat; } td.links>div, td.links>a { padding: 6px 2px 6px 10px; } td.links div { position: relative; left: 1px; background-color: #fff; background-image: none; color: #000; } td.links li li div, td.links li li a { background-color: transparent; background-image: none; color: #fff; font-size: 11px; border: 0px; margin: 1px 0px 0px 0px; padding: 0px 3px 2px 3px; } td.links li li div { position: relative; left: 1px; border-bottom: 2px solid #fff; padding: 0px 4px 0px 2px; } td.links ul ul { padding: 0px 0px 0px 30px; } td.links ul ul ul { padding: 0px 0px 0px 20px; } td.links div, td.links a.path { font-weight: bold; } td.links a span.visited { display: none; } td.links a:visited span.visited { display: block; float: right; font-size: 16px; font-weight: normal; padding: 1px 4px 1px 20px; color: #bbb; } td.links a:link { color: #fff; } td.links a:visited { color: #fff; } td.links a:hover { background-color: #0063a8; border-top: 1px solid #333; border-bottom: 1px solid #333; color: #000; background-color: #ccc; background-image: none; } td.links a:hover span.visited { color: #666; } td.links li li a:hover { padding: 0px 2px 0px 2px; } td.links a:active { color: #ff0; } span.tab { color: #ccc; } abbr[title], acronym[title] { border-bottom: 1px solid #ddd; } p, li, dd { font-size: 12px; line-height: 20px; margin: 0px 0px 10px 0px; } dt { font-size: 12px; line-height: 20px; } h1, h2, h3, h4, h5, h6 { color: #0063a8; font-family: "Trebuchet MS", sans-serif; font-weight: normal; } h1 abbr[title], h2 abbr[title], h3 abbr[title], h4 abbr[title], h5 abbr[title], h6 abbr[title], h1 acronym[title], h2 acronym[title], h3 acronym[title], h4 acronym[title], h5 acronym[title], h6 acronym[title] { border-bottom: 0px; } h1 { font-size: 20px; margin: 36px 0 3px 0; line-height: 30px; } h2 { font-size: 18px; margin: 24px 0 2px 0; line-height: 26px; } h3 { font-size: 18px; margin: 16px 0 1px 0; line-height: 22px; } h4, h5, h6 { font-size: 14px; margin: 14px 0 1px 0; line-height: 20px; } td.content div:first-child h1 { margin-top: 0px; } div.section.level1 { } div.section.level1 h1 { margin-right: -30px; border-bottom: 1px solid #666; padding-right: 30px; } ol, ul, dl { margin-top: 10px; margin-bottom: 10px; } li { margin-top: 0px; margin-bottom: 0px; } ul li { list-style-type: square; } ol li { list-style-type: decimal; } dd { margin-left: 20px; margin-top: 0px; margin-bottom: 10px; } em { font-weight: bold; font-style: normal; } address { font-style: normal; font-size: 13px; line-height: 16px; } div.display { margin-left: 1.5em; margin-top: 2px; margin-bottom: 2px; } table.downloads span.note { font-weight: normal; font-size: 9px; } table.downloads { margin-left: 30px; margin-right: 30px; margin-top: 4px; margin-bottom: 10px; font-size: 11px; line-height: 16px; border-bottom: 1px solid #ccc; } table.downloads th, table.downloads td { font-size: 11px; } table.downloads th { padding: 8px 0px 2px 0px; border-bottom: 1px solid #999; } table.downloads th.version { padding: 10px 10px 2px 3px; text-align: left; } table.downloads th.type { color: #666; font-weight: normal; padding: 10px 10px 2px 0px; text-align: left; } table.downloads th.size { color: #666; font-weight: normal; text-align: right; padding: 10px 3px 2px 0px; } table.downloads tr.download { background-color: #fafafa; } table.downloads td.file { padding: 2px 10px 2px 20px; } table.downloads td.type { padding: 2px 10px 2px 0px; } table.downloads td.size { text-align: right; padding: 2px 3px 2px 0px; } pre.prog, pre.tty { margin: 0px 30px 10px 30px; font-size: 12px; line-height: 18px; border: 1px solid #eee; background-color: #fafafa; padding: 1px 5px 2px 5px; overflow: auto; } div.example-title { font-family: "Verdana", "Arial", "XHelvetica", "Helvetica", sans-serif; font-size: 9px; margin-top: -8px; margin-bottom: 4px; margin-right: 30px; color: #666; text-align: right; padding-top: 0px; padding-right: 2px; } span.secnum { background-color: #ccc; color: #fff; font-size: 6px; padding: 1px; vertical-align: middle; } div.class>h1, div.class>h2, div.class>h3, div.class>h4, div.class>h5, div.class>h6, div.function>h1, div.function>h2, div.function>h3, div.function>h4, div.function>h5, div.function>h6, div.method>h1, div.method>h2, div.method>h3, div.method>h4, div.method>h5, div.method>h6, div.property>h6, div.property>h1, div.property>h2, div.property>h3, div.property>h4, div.property>h5 { font-family: monospace; margin-bottom: 0px; padding: 0px 0px 2px 30px; text-indent: -30px; margin-right: -30px; border-bottom: 1px solid #999; padding-right: 30px; } div.method>div.content, div.function>div.content, div.property>div.content, div.class>div.content { margin-left: 30px; } div.class>div.content { border-right: 30px solid #eee; margin-right: -30px; padding-right: 30px; } div.class div.class>div.content { margin-right: 0px; border-right: 0px; padding-right: 0px; } h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { letter-spacing: -1px; } div.class.nodoc, div.method.private, div.method.nodoc, div.function.nodoc, div.property.nodoc { display: none; } code { font-family: "Courier New", monospace; } code.option { } code.literal { } code.function { } code.method { } code.property { } code.class { } var.rep { } code.markup { } code.parameter { } code.module { } code.arg { } code.filename { } code.dirname { } code.hostname { } code.username { } code.prompt { } code.input { } pre code.input { color: #060; } span.application { } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/ll2.css0000644000175000017500000027731300000000000020715 0ustar00kovidkovid/* >> Zusaetzliche Steuerelemente (Zurueck, Drucken, Empfehlung versenden) */ /* Steuerelement Container oben*/ div.topAddContrContainer { display: none; margin-top: 5px; } /* Steuerelement Container unten*/ div.bottomAddContrContainer { padding: 10px 15px; } * html div.bottomAddContrContainer { width: 100%; } *+html div.bottomAddContrContainer { width: 100%; } /* Anpassungen der Steuerelemente Tabelle*/ table.aCShell { width: 100%; } table.aCShell td.rSpc { } /* Definiert das Icon fuer Zurueck */ img.backIcon { width: 17px; height: 14px; border: 0; vertical-align: text-top; background: url(../xist4c/web/krusekopf/01/img/backIcon.gif) bottom left no-repeat; } img.recomButton { width: 28px; height: 17px; border: 0; vertical-align: middle; background: url(../xist4c/web/krusekopf/01/img/recommendationButton.gif) bottom right no-repeat; } img.printButton { width: 24px; height: 17px; border: 0; vertical-align: middle; background: url(../xist4c/web/krusekopf/01/img/printIcon.gif) bottom right no-repeat; } img.bFreeButton { width: 24px; height: 18px; border: 0; vertical-align: middle; background: url(../xist4c/web/krusekopf/01/img/barrierFreeIcon.gif) bottom right no-repeat; display: none; } img.cBlindButton, img.cBlindResetButton { width: 26px; height: 18px; border: 0; vertical-align: middle; background: url(../xist4c/web/krusekopf/01/img/colorBlindIcon.gif) center right no-repeat; display: none; } img.cBlindResetButton { background: url(../xist4c/web/krusekopf/01/img/colorBlindResetIcon.gif) center right no-repeat; } /* << */ /* >> Autonews Uebersicht */ /* Autonews Tabelle */ table.aN { } /* Definition fuer das Datum auf der Uebersicht */ table.aN td.date { font-weight: bold; color: #666; padding-right: 8px; } /* Anpassungen der Beschreibungsspalte*/ table.aN td.item { padding-bottom: 10px; } /* Anpassungen des Titels auf der Uebersicht */ table.aN td.item h3 { margin: 0 0 8px 0; } /* Anpassungen des Untertitels auf der Uebersicht */ table.aN td.item h4 { margin: 0 0 3px 0; } /* Definitionen fuer das Uebersichtsbild */ table.aN td.item a img, table.aN td.item img { float: right; margin-left: 10px; margin-bottom: 5px; } /* Anpassungen fuer die Autonews Beschreibung auf der Uebersicht*/ table.aN td.item div.desc { margin: 0; } /* Einstellungen des "mehr..." Links */ table.aN td.item div.more { clear: both; margin-top: 5px; margin-bottom: 5px; } /* Definitionen fuer das "mehr..." Link Icon */ table.aN td.item div.more img { display: none; } /* << */ /* >> Autonews Panel */ /* Auto news Tabelle */ table.aNP { } /* Definition des Autonews Panel Datum */ table.aNP td span.date { font-size: 10px; padding-bottom: 3px; } /* Einstellungen des Autonews Titels */ table.aNP td h3.title { display: inline; margin: 0 0 1px 0; font-size: 12px; } /* Formatiert die Trennlinie zwischen Ueberschrift und Unterueberschrift*/ table.aNP td img.line { width: 100%; margin: 2px 0 2px 0; background: #000; } /* Einstellungen des Autonews Untertitels */ table.aNP td h4.subtitle { margin: 0 0 3px 0; font-size: 11px; } /* Einstellungen der Autonews Beschreibung */ table.aNP td div.desc { margin: 0; font-size: 11px; } /* Einstellungen fuer das Autonewsbild */ table.aNP td img.rFloat, table.aNP td a img.rFloat { float: right; margin-left: 5px; margin-bottom: 2px; } /* Definitionen fuer den "mehr..." Link */ table.aNP td div.more { clear: both; margin-top: 5px; margin-bottom: 5px; font-size: 11px; } /* Definitionen fuer das "mehr..." Link Icon */ table.aNP td div.more img { display: none; } /* Autonews sublink Tabelle */ table.aNPSublink { } /* Definiert den Link mit dem es zu der Autonews Uebersicht geht */ table.aNPSublink td a { font-size: 11px; } /* Anpassen des Autonews Panel "zur Uebersicht...." Icons */ table.aNPSublink td a img { display: none; } /* << */ /* >> Sprungmarke */ /* Sprungmarkencontainer */ div.bToTop { margin-top: 0; margin-bottom: 15px; } /* Sprungmarkencontainer Designelement*/ div.bToTop div.des1 { text-align: right; } /* Sprungmarkencontainer Prompt*/ div.bToTop div.des1 span { font-size: 11px; } /* Sprungmarkencontainer Prompt Link*/ div.bToTop div.des1 span a.text { } /* Sprungmarkencontainer Bild*/ div.bToTop div.des1 img { width: 18px; height: 13px; vertical-align: text-top; background: url(../xist4c/web/krusekopf/01/img/bToTopImg.gif) top right no-repeat; border: 0; } /* Sprungmarkencontainer Bildlink*/ div.bToTop div.des1 a.img { } /* << */ /* >> Standard body Definitionen */ /* Legt Hoehe und Breite fest */ html, body { margin: 0; padding: 0; } /* Anpassungen fuer die Standardeinstellungen des Bodys */ body { background: #fff; font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; font-size: 12px; color: #333; } /* << */ /* >> Angaben nicht veraendern!!! */ /* for framesets */ body.mainFrame { background: #fff; font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; font-size: 12px; color: #000; } /* for print page */ body.print { background: #fff; font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; font-size: 13px; color: #000; } /* << */ /* >> Freie Inhaltscont*/ /* Standard Definition fuer die freien Inhaltscontainer. Diese Angabe bitte unveraendert lassen*/ div.stdSty { height: 100px; width: 100px; position: absolute; left: 0; top: 0; z-index: 0; } /* Hier koennen die angelegten Container definiert werden */ div.banner { width: 468px; height: auto; position: absolute; left: 280px; top: 19px; z-index: 4; display: none; } /* << */ /* >> Dokumentenliste */ /* Document Liste Tabelle */ table.dList { margin: 0 15px; } table.dList a { font-weight: bold; } /* Einstellungen fuer die Dateibeschreibung */ table.dList td span.desc { font-size: 11px; } /* Groesse des Listentitels */ table.dList td div.size { font-size: 11px; color: #000; white-space: nowrap; } /* << */ /* >> Fragen und Antworten*/ /* Einstellungen fuer Gruppenkontainer */ div.faqGrShell { margin-bottom: 15px; } /* Definition der Gruppentitel in der Fragenuebersicht */ div.faqGrShell h3 { margin-bottom: 3px; } /* Anpassungen fuer den Listenelementekontainer */ div.faqGrShell ul { margin-top: 0; margin-bottom: 0; } /* Definitionen des Listenelements */ div.faqGrShell ul li { margin-bottom: 5px; } /* Einstellungen fuer den Fragekontainer in der Detailansicht*/ div.dQShell { margin: 10px 0 15px 0; border: 1px solid #000; background: #dde5ee; } /* Anpassungen des "Frage" Titels auf der Detailseite */ div.dQShell div.prefix { padding: 5px; background: #a0b8cf; font-weight: bold; font-size: 16px; } /* Einstellungen der Detail Ansicht der Frage */ div.dQShell div.question { padding: 5px 5px 5px 20px; } /* << */ /* >> Termin uebersicht */ div.appointmentOuterShell { } div.appointmentOuterShell div.grpShell { margin-top: 10px; margin-bottom: 15px; background: #fff; } div.appointmentOuterShell div.grpShell h3 { font-size: 14px; margin: 0; background: #E3DEC4; padding: 2px 5px 2px 5px; } div.appointmentOuterShell div.grpShell div.item { padding: 4px 5px 4px 5px; border-bottom: 1px solid #dde5ee; } div.appointmentOuterShell div.grpShell div.item div.date { float: left; width: 70px; } div.appointmentOuterShell div.grpShell div.item div.title { margin-left: 80px; margin-bottom: 5px; } div.appointmentOuterShell div.grpShell div.item div.desc { margin-left: 82px; margin-bottom: 5px; font-size: 11px; } div.appointmentOuterShell div.grpShell div.item div.img { float: right; margin-left: 5px; } div.appointmentOuterShell div.grpShell div.item div.more { margin-bottom: 8px; } div.appointmentOuterShell div.grpShell div.item div.more div { text-align: right; } div.appointmentOuterShell div.grpShell div.item div.more div a { background: url(../xist4c/web/krusekopf/01/img/littleTeaserArrow.gif) 0 5px no-repeat; padding-left: 10px; } /* << */ /* >>Erste Ebene Navigation*/ /* Erste Ebene Navigation aeusserer Navigationskontainer*/ div.fLOuterShell { width: 989px; } /* Anpassungen der erste Ebene Navigationspalte */ div.fLOuterShell div.des1 { background: #513E11 url(../xist4c/web/krusekopf/01/img/background.gif) top left repeat-y; } div.fLOuterShell div.des1 div.des2 { } /* Einstellungen fuer die erste Ebene Navigation */ div.fLOuterShell div.des1 table.nav { } /* Spacer fuer die Ausrichtung der erste Ebene Navigation*/ div.fLOuterShell div.des1 table.nav td.spcLeft { } div.fLOuterShell div.des1 table.nav td.spcLeft img { width: 4px; height: 1px; } div.fLOuterShell div.des1 table.nav td.spcRight { } div.fLOuterShell div.des1 table.nav td.spcRight img { width: 4px; height: 1px; } /* Spalte fuer ein erste Ebene Navigationselement */ div.fLOuterShell div.des1 table.nav td.navItem { } /* Grundeinstellung der erste Ebene Navigation fuer die Zustaende "normal", "im Pfad" und "hier"*/ div.fLOuterShell div.des1 table.nav td.navItem span.here, div.fLOuterShell div.des1 table.nav td.navItem a.normal, div.fLOuterShell div.des1 table.nav td.navItem a.inPath { font-size: 12px; color: #000; display: block; width: auto; height: 34px; } div.fLOuterShell div.des1 table.nav td.navItem span.here span, div.fLOuterShell div.des1 table.nav td.navItem a.normal span, div.fLOuterShell div.des1 table.nav td.navItem a.inPath span { display: none; } /* Abweichende einstellungen fuer den Zustand "im Pfad" */ div.fLOuterShell div.des1 table.nav td.navItem a.inPath { font-style: italic; } /* Folgende Angaben definieren die Reaktion beim ueberfahren mit der Maus */ div.fLOuterShell div.des1 table.nav td.navItem a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem a.inPath:hover { color: #336; } /* Angabe fuer den Zustand "hier" */ div.fLOuterShell div.des1 table.nav td.navItem span.here { color: #336; } /* Einstellung der Trennelemente */ div.fLOuterShell div.des1 table.nav td.sep img { height: 34px; background-color: #fff; } /* Kueche und Haushalt */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.inPath { width: 125px; background: url(../xist4c/web/krusekopf/01/img/nav_kueche.gif) top left no-repeat; } *+html div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.normal, *+html div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.normal:hover, *+html div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 span.here, *+html div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.inPath { width: 123px; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav1 a.inPath { background-image: url(../xist4c/web/krusekopf/01/img/nav_kueche2.gif); } /* Haus und Garten */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 a.inPath { width: 110px; background: url(../xist4c/web/krusekopf/01/img/nav_haus.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav2 a.inPath { background-image: url(../xist4c/web/krusekopf/01/img/nav_haus2.gif); } /* Wellness und Beauty */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 a.inPath { width: 135px; background: url(../xist4c/web/krusekopf/01/img/nav_wellness.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav3 a.inPath { background-image: url(../xist4c/web/krusekopf/01/img/nav_wellness2.gif); } /* Arbeit und Buero */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 a.inPath { width: 100px; background: url(../../upload/nav_arbeit_3430.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav4 a.inPath { background-image: url(../../upload/nav_arbeit2_3431.gif); } /* Delikatessen und Suesses */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 a.inPath { width: 154px; background: url(../xist4c/web/krusekopf/01/img/nav_delikatessen.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav5 a.inPath { background-image: url(../xist4c/web/krusekopf/01/img/nav_delikatessen2.gif); } /* Geschenke und Accessoires */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 a.inPath { width: 170px; background: url(.././upload/nav_geschenke_3432.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav6 a.inPath { background-image: url(../../upload/nav_geschenke2_3433.gif); } /* Blumen Online */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 a.inPath { width: 80px; background: url(../../upload/blumen_9221.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav7 a.inPath { background-image: url(../../upload/blumen2_9222.gif); } /* trendmagazin */ div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 a.normal, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 a.inPath { width: 100px; background: url(../xist4c/web/krusekopf/01/img/nav_trend.gif) top left no-repeat; } div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 a.normal:hover, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 span.here, div.fLOuterShell div.des1 table.nav td.navItem div.co_flNav8 a.inPath { background-image: url(../../upload/nav_trend2_2145.gif); } /* << */ /* >> Fusszeile*/ /* Spalte des Footers */ td.msFooter { padding-bottom: 20px; } /* Anpassungen fuer den Fusszeilenkontainer*/ div.decoEl { width: 989px; background: #E3DEC4 url(../xist4c/web/krusekopf/01/img/background.gif) top left repeat-y; } div.deco_1 { background: url(../xist4c/web/krusekopf/01/img/footerbg.gif) bottom left no-repeat; position: relative; top: 1px; } div.deco_2 { } /* Definitionen fuer die Fusszeilentabelle */ table.footer { width: 951px; margin: 0 auto; } /* Einstellungen der Fusszeilen Tabellenzelle */ table.footer td { font-size: 11px; color: #000; text-align: left; padding: 4px 0; } /* Anpassungen der Fusszeilen Inhaltselemente*/ table.footer td p, table.footer td ul, table.footer td ol, table.footer td form, table.footer td h1, table.footer td h2, table.footer td h3, table.footer td h4, table.footer td h5, table.footer td h6 { font-size: 11px; padding-top: 0; padding-bottom: 0; margin-top: 0; margin-bottom: 0; } table.footer td a { color: #5C4714; } table.footer td a:hover { color: #000; } /* << */ /* >>Full Size Media*/ /* Voll Breite Media Container */ div.fsMediaShell { margin-bottom: 10px; } /* Bildelement */ div.fsMediaShell div.img { } /* Freies XHTML Element */ div.fsMediaShell div.free { } /* << */ /* >> Standard Listenelement */ /* Anpassungen fuer den Listeneintragkontainer */ div.gOvItem { margin: 12px 15px; border-bottom: 1px solid #513E11; font-size: 11px; } /* Definition des Titels des Listeneintrags */ div.gOvItem h3 { margin: 0 0 5px 0; } /* Definition des Untertitels des Listeneintrags */ div.gOvItem h4 { margin: 0 0 3px 0; } div.gOvItem p { font-size: 11px; } /* Definition der Beschreibung des Listeneintrags */ div.gOvItem div { margin: 0; } /* Definitionen fuer das Listenbild */ div.gOvItem a img.rFloat, div.gOvItem img.rFloat { float: left; margin: 10px 10px 10px 0; } /* Einstellungen des Listen "mehr..." Links */ div.gOvItem div.more { clear: both; margin-top: 5px; margin-bottom: 5px; } /* Einstellungen des Listen "mehr..." Icons */ div.gOvItem div.more img { display: none; } /*** Layouts ****/ /* layout three columns with image */ table.genOvVar1 { width: 100%; } table.genOvVar1 td { background: #edc352; } table.genOvVar1 td.vSpc, table.genOvVar1 td.hSpc { background: transparent; } table.genOvVar1 td.vSpc img, table.genOvVar1 td.hSpc img { height: 3px; width: 3px; } table.genOvVar1 td.title { vertical-align: top; } table.genOvVar1 td.title h3 { font-size: 13px; margin-top: 0; margin-bottom: 8px; } table.genOvVar1 td.title h3 a { } table.genOvVar1 td.descShell { vertical-align: top; } table.genOvVar1 td.descShell2 { vertical-align: top; white-space: nowrap; } table.genOvVar1 td.title, table.genOvVar1 td.descShell, table.genOvVar1 td.descShell2 { padding: 5px; } table.genOvVar1 td.descShell2 { text-align: center; } /* << */ /* >> Anpassungen des Logos und Bilder im Kopfbereich */ /* Einstellungen der positionierung des Logo Klickbereich */ div.linkCont1 { width: auto; position: absolute; left: 270px; top: 0; z-index: 2; } div.linkCont2 { width: auto; position: absolute; left: 651px; top: 21px; z-index: 3; display: none; } /* Definition der groesse des Logo Klickbereichs*/ div.linkCont1 img { width: 430px; height: 80px; border: 0; } div.linkCont2 img { width: 153px; height: 55px; border: 0; } /* Tabelle fuer Kopfbilder */ table.headerImg { } /* Verhalten des 1 Kopfbereichsbild */ td.hImg { width: 100%; background: url(../../upload/topBg_4270.jpg) top left no-repeat; height: 87px; } /* Verhalten des 2 Kopfbereichsbild */ td.hImg1 { width: 100%; display: none; } /* Verhalten des 3 Kopfbereichsbild */ td.hImg2 { width: 151px; display: none; } /* << */ /* >> Sprachumschalter*/ /* Definitionen fuer den Sprachumschalterkontainer */ div.lSwCont { display: none; } /* Layout fuer Select Box */ div.lSwCont div.selectBox { width: 165px; position: absolute; left: 8px; top: 1px; z-index: 5; } /* Sprachumschalter Formularfeld einstellungen */ div.lSwCont select { font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; font-size: 10px; width: 100%; } /* Layout fuer Flaggen */ div.lSwCont div.flags { width: auto; position: absolute; left: 8px; top: 1px; z-index: 5; font-size: 10px; } div.lSwCont div.flags div.active, div.lSwCont div.flags div.passive { } div.lSwCont div.flags div.language, div.lSwCont div.flags div.flag, div.lSwCont div.flags div.spc { float: left; } div.lSwCont div.flags div.spc, div.lSwCont div.flags div.spc img { display: none; } div.lSwCont div.flags div.language { padding-top: 4px; padding-right: 5px; } div.lSwCont div.flags div.language a { } div.lSwCont div.flags div.flag { padding-right: 10px; } /* << */ /* >>Navigation links*/ /* Aeusserer Navigationskontainer */ div.navOuterShell { } /* Bild fue den Start der Navigation */ div.navOuterShell div.topImg { } div.navOuterShell div.topImg img { display: none; } /* Bild fuer das Ende der Navigation */ div.navOuterShell div.bottomImg { } div.navOuterShell div.bottomImg img { display: none; } /* Anpassung der minimalen Navigationskontainerhoehe */ div.navOuterShell img.minHeight { display: block; height: 1px; float: left; } /*Angaben zur Navigationshuelle. Je Ebene haben Sie die Moeglichkeit den Ebenenhintergrund zu veraendern.*/ div.navCHS_0, div.navCHS_1, div.navCHS_2, div.navCHS_3 { } div.navCHS_1 { border-bottom: 7px solid #E3DEC4; } /* Angaben zur Navigationsknotenhuelle */ div.navNS_0, div.navNS_1, div.navNS_2, div.navNS_3 { text-align: right; } /* Benutzen Sie folgende Klassen um die aeussere Huelle der Navigationslinks zu veraendern.*/ div.navEl_0_normal, div.navEl_0_inPath, div.navEl_0_here, div.navEl_1_normal, div.navEl_1_inPath, div.navEl_1_here, div.navEl_2_normal, div.navEl_2_inPath, div.navEl_2_here, div.navEl_3_normal, div.navEl_3_inPath, div.navEl_3_here { } div.navEl_1_normal, div.navEl_1_inPath, div.navEl_1_here, div.navEl_2_normal, div.navEl_2_inPath, div.navEl_2_here, div.navEl_3_normal, div.navEl_3_inPath, div.navEl_3_here { border-right: 1px solid #E3DEC4; } div.navEl_1_inPath, div.navEl_1_here, div.navEl_2_normal, div.navEl_2_inPath, div.navEl_2_here, div.navEl_3_normal, div.navEl_3_inPath, div.navEl_3_here, div.navEl_4_normal, div.navEl_4_inPath, div.navEl_4_here { border-right: 7px solid #9A2534; } div.navEl_1_normal, div.navEl_1_inPath, div.navEl_1_here { border-top: 1px solid #E3DEC4; } /* Um Bullets vor die Navigationslinks zu Platzieren benutzen Sie die folgenden Klassen*/ div.navEl_0_normal div.outer, div.navEl_0_inPath div.outer, div.navEl_0_here div.outer, div.navEl_1_normal div.outer, div.navEl_1_inPath div.outer, div.navEl_1_here div.outer, div.navEl_2_normal div.outer, div.navEl_2_inPath div.outer, div.navEl_2_here div.outer, div.navEl_3_normal div.outer, div.navEl_3_inPath div.outer, div.navEl_3_here div.outer { } div.navEl_0_normal div.outer, div.navEl_0_inPath div.outer, div.navEl_0_here div.outer { display: none; } div.navEl_2_normal div.outer, div.navEl_2_inPath div.outer, div.navEl_2_here div.outer { border-top: 1px solid #fff; } div.navEl_3_normal div.outer, div.navEl_3_inPath div.outer, div.navEl_3_here div.outer { border-top: 1px solid #E3DEC4; } div.navEl_0_normal span.inner, div.navEl_0_inPath span.inner, div.navEl_0_here span.inner, div.navEl_1_normal span.inner, div.navEl_1_inPath span.inner, div.navEl_1_here span.inner, div.navEl_2_normal span.inner, div.navEl_2_inPath span.inner, div.navEl_2_here span.inner, div.navEl_3_normal span.inner, div.navEl_3_inPath span.inner, div.navEl_3_here span.inner { } /* Ebene 0 */ div.navEl_0_here div.noLink, div.navEl_0_normal a, div.navEl_0_inPath a { display: block; font-weight: bold; font-size: 12px; color: #339; } div.navEl_0_normal a:hover, div.navEl_0_inPath a:hover, div.navEl_0_here div.noLink, div.navEl_0_inPath a { color: #000; } /* Ebene 1 */ div.navEl_1_here div.noLink, div.navEl_1_normal a, div.navEl_1_inPath a { display: block; font-weight: bold; font-size: 12px; color: #5C4714; padding: 10px 20px 10px 0; } div.navEl_1_normal a:hover, div.navEl_1_inPath a div.navEl_1_here div.noLink { color: #9A2534; } div.navEl_1_here div.noLink, div.navEl_1_inPath a { padding-right: 14px; } /* Ebene 2 */ div.navEl_2_here div.noLink, div.navEl_2_normal a, div.navEl_2_inPath a { display: block; font-size: 11px; color: #000; font-weight: bold; padding: 4px 0 4px 0; background: #E3DEC4; } div.navEl_2_normal a:hover, div.navEl_2_here div.noLink, div.navEl_2_inPath a { color: #fff; background: #9A2534; } div.navEl_2_here span.inner, div.navEl_2_normal span.inner, div.navEl_2_inPath span.inner { padding-right: 14px; } /* Ebene 3 */ div.navEl_3_here div.noLink, div.navEl_3_normal a, div.navEl_3_inPath a { display: block; font-size: 11px; color: #000; font-weight: bold; padding: 4px 14px 4px 0; background: #fff; } div.navEl_3_normal a:hover, div.navEl_3_here div.noLink, div.navEl_3_inPath a { color: #9A2534; } /* Ebene 4 */ div.navEl_4_here div.noLink, div.navEl_4_normal a, div.navEl_4_inPath a { display: block; font-size: 11px; color: #000; font-weight: normal; padding: 2px 14px 2px 0; background: #fff; } div.navEl_4_normal a:hover, div.navEl_4_here div.noLink, div.navEl_4_inPath a { color: #9A2534; font-weight: bold; } /* Navigationsbild Kontainer */ div.navigationImage { text-align: center; } /* << */ /* Navigationsbilder */ /* A B C D E F G H I J K L M N O P Q R S T U V W X Y Z */ /* Aktionen */ div.co_aktionen div.navEl_1_here div.noLink, div.co_aktionen div.navEl_1_normal a, div.co_aktionen div.navEl_1_inPath a { background: url(../../upload/aktionen_3992.gif) 10px center no-repeat; } div.co_aktionen div.navEl_1_normal a:hover, div.co_aktionen div.navEl_1_here div.noLink, div.co_aktionen div.navEl_1_inPath a { background-image: url(../../upload/aktionen2_3993.gif); } /* ASA */ div.co_asa div.navEl_1_here div.noLink, div.co_asa div.navEl_1_normal a, div.co_asa div.navEl_1_inPath a { background: url(../../upload/asa_2772.gif) 10px center no-repeat; } /* Blomus */ div.co_blomus div.navEl_1_here div.noLink, div.co_blomus div.navEl_1_normal a, div.co_blomus div.navEl_1_inPath a { background: url(../../upload/blomus_2773.gif) 10px center no-repeat; } /* Brainstream */ div.co_brainstream div.navEl_1_here div.noLink, div.co_brainstream div.navEl_1_normal a, div.co_brainstream div.navEl_1_inPath a { background: url(../../upload/brainstream_2745.gif) 10px center no-repeat; } div.co_brainstream div.navEl_1_normal a:hover, div.co_brainstream div.navEl_1_here div.noLink, div.co_brainstream div.navEl_1_inPath a { background-image: url(../../upload/brainstream2_2746.gif); } /* Bodypaint */ div.co_bodypaint div.navEl_1_here div.noLink, div.co_bodypaint div.navEl_1_normal a, div.co_bodypaint div.navEl_1_inPath a { background: url(../../upload/bodypaint_2807.gif) 10px center no-repeat; } div.co_bodypaint div.navEl_1_normal a:hover, div.co_bodypaint div.navEl_1_here div.noLink, div.co_bodypaint div.navEl_1_inPath a { background-image: url(../../upload/bodypaint2_2808.gif); } /* Colony */ div.co_colony div.navEl_1_here div.noLink, div.co_colony div.navEl_1_normal a, div.co_colony div.navEl_1_inPath a { background: url(../../upload/colony_2774.gif) 10px center no-repeat; } /* Conmoto */ div.co_conmoto div.navEl_1_here div.noLink, div.co_conmoto div.navEl_1_normal a, div.co_conmoto div.navEl_1_inPath a { background: url(../../upload/conmoto_2749.gif) 10px center no-repeat; } div.co_conmoto div.navEl_1_normal a:hover, div.co_conmoto div.navEl_1_here div.noLink, div.co_conmoto div.navEl_1_inPath a { background-image: url(../../upload/conmoto2_2750.gif); } /* Delux */ div.co_delux div.navEl_1_here div.noLink, div.co_delux div.navEl_1_normal a, div.co_delux div.navEl_1_inPath a { background: url(../../upload/deluxe2_6445.gif) 10px center no-repeat; } div.co_delux div.navEl_1_normal a:hover, div.co_delux div.navEl_1_here div.noLink, div.co_delux div.navEl_1_inPath a { background-image: url(../../upload/deluxe_6444.gif); } /* El Casco */ div.co_elcasco div.navEl_1_here div.noLink, div.co_elcasco div.navEl_1_normal a, div.co_elcasco div.navEl_1_inPath a { background: url(../../upload/elcasco_2775.gif) 10px center no-repeat; } /* Eschenbach */ div.co_eschenbach div.navEl_1_here div.noLink, div.co_eschenbach div.navEl_1_normal a, div.co_eschenbach div.navEl_1_inPath a { background: url(../../upload/eschenbach2_7983.gif) 10px center no-repeat;} div.co_eschenbach div.navEl_1_normal a:hover, div.co_eschenbach div.navEl_1_here div.noLink, div.co_eschenbach div.navEl_1_inPath a { background-image: url(../../upload/eschenbach_7982.gif); } /* Herrnhuter Sterne */ div.co_hernhuter div.navEl_1_here div.noLink, div.co_hernhuter div.navEl_1_normal a, div.co_hernhuter div.navEl_1_inPath a { background: url(../../upload/hernhuter_2794.gif) 10px center no-repeat; } div.co_hernhuter div.navEl_1_normal a:hover, div.co_hernhuter div.navEl_1_here div.noLink, div.co_hernhuter div.navEl_1_inPath a { background-image: url(../../upload/hernhuter2_2795.gif); } /* Hussel */ div.co_hussel div.navEl_1_here div.noLink, div.co_hussel div.navEl_1_normal a, div.co_hussel div.navEl_1_inPath a { background: url(../../upload/hussel_2753.gif) 10px center no-repeat; } div.co_hussel div.navEl_1_normal a:hover, div.co_hussel div.navEl_1_here div.noLink, div.co_hussel div.navEl_1_inPath a { background-image: url(../../upload/hussel2_2754.gif); } /* Kneisz Design */ div.co_kneisz div.navEl_1_here div.noLink, div.co_kneisz div.navEl_1_normal a, div.co_kneisz div.navEl_1_inPath a { background: url(../../upload/kneisz_2813.gif) 10px center no-repeat; } div.co_kneisz div.navEl_1_normal a:hover, div.co_kneisz div.navEl_1_here div.noLink, div.co_kneisz div.navEl_1_inPath a { background-image: url(../../upload/kneisz2_2814.gif); } /* Kahla */ div.co_kahla div.navEl_1_here div.noLink, div.co_kahla div.navEl_1_normal a, div.co_kahla div.navEl_1_inPath a { background: url(../../upload/kahla_2776.gif) 10px center no-repeat; } /* Keilbach */ div.co_keilbach div.navEl_1_here div.noLink, div.co_keilbach div.navEl_1_normal a, div.co_keilbach div.navEl_1_inPath a { background: url(../../upload/keilbach_2777.gif) 10px center no-repeat; } div.co_keilbach div.navEl_1_normal a:hover, div.co_keilbach div.navEl_1_here div.noLink, div.co_keilbach div.navEl_1_inPath a { background-image: url(../../upload/keilbach2_2759.gif); } /* Lardon */ div.co_lardon div.navEl_1_here div.noLink, div.co_lardon div.navEl_1_normal a, div.co_lardon div.navEl_1_inPath a { background: url(../../upload/lardon_sw_9042.gif) 10px center no-repeat; } div.co_lardon div.navEl_1_normal a:hover, div.co_lardon div.navEl_1_here div.noLink, div.co_lardon div.navEl_1_inPath a { background-image: url(../../upload/lardon_9041.gif); } /* LaNature */ div.co_lanature div.navEl_1_here div.noLink, div.co_lanature div.navEl_1_normal a, div.co_lanature div.navEl_1_inPath a { background: url(../../upload/lanature_2760.gif) 10px center no-repeat; } div.co_lanature div.navEl_1_normal a:hover, div.co_lanature div.navEl_1_here div.noLink, div.co_lanature div.navEl_1_inPath a { background-image: url(../../upload/lanature2_2761.gif); } /* Meybona */ div.co_meybona div.navEl_1_here div.noLink, div.co_meybona div.navEl_1_normal a, div.co_meybona div.navEl_1_inPath a { background: url(../../upload/meybona_2800.gif) 10px center no-repeat; } div.co_meybona div.navEl_1_normal a:hover, div.co_meybona div.navEl_1_here div.noLink, div.co_meybona div.navEl_1_inPath a { background-image: url(../../upload/meybona2_2801.gif); } /* MBM */ div.co_mbm div.navEl_1_here div.noLink, div.co_mbm div.navEl_1_normal a, div.co_mbm div.navEl_1_inPath a { background: url(../../upload/mbm_2778.gif) 10px center no-repeat; } /* menue */ div.co_menu div.navEl_1_here div.noLink, div.co_menu div.navEl_1_normal a, div.co_menu div.navEl_1_inPath a { background: url(../../upload/menu_2779.gif) 10px center no-repeat; } /* Oralfixation */ div.co_oralfixation div.navEl_1_here div.noLink, div.co_oralfixation div.navEl_1_normal a, div.co_oralfixation div.navEl_1_inPath a { background: url(../../upload/oralfixation_2780.gif) 10px center no-repeat; } /* Philippi */ div.co_philippi div.navEl_1_here div.noLink, div.co_philippi div.navEl_1_normal a, div.co_philippi div.navEl_1_inPath a { background: url(../../upload/philippi_2781.gif) 10px center no-repeat; } /* PatSaysNow */ div.co_patsaysnow div.navEl_1_here div.noLink, div.co_patsaysnow div.navEl_1_normal a, div.co_patsaysnow div.navEl_1_inPath a { background: url(../../upload/patsaysnow_2770.gif) 10px center no-repeat; } div.co_patsaysnow div.navEl_1_normal a:hover, div.co_patsaysnow div.navEl_1_here div.noLink, div.co_patsaysnow div.navEl_1_inPath a { background-image: url(../../upload/patsaysnow2_2771.gif); } /* Raeder Geschenke */ div.co_raeder div.navEl_1_here div.noLink, div.co_raeder div.navEl_1_normal a, div.co_raeder div.navEl_1_inPath a { background: url(../../upload/raeder_2782.gif) 10px center no-repeat; } div.co_raeder div.navEl_1_normal a:hover, div.co_raeder div.navEl_1_here div.noLink, div.co_raeder div.navEl_1_inPath a { background-image: url(../../upload/raeder2_2783.gif); } /* Sitting Bull */ div.co_sittingbull div.navEl_1_here div.noLink, div.co_sittingbull div.navEl_1_normal a, div.co_sittingbull div.navEl_1_inPath a { background: url(../../upload/sittingbull_sw_6802.gif) 10px center no-repeat; } div.co_sittingbull div.navEl_1_normal a:hover, div.co_sittingbull div.navEl_1_here div.noLink, div.co_sittingbull div.navEl_1_inPath a { background-image: url(../../upload/sittingbull_6801.gif); } /* Reed Diffuser */ div.co_reeddiffuser div.navEl_1_here div.noLink, div.co_reeddiffuser div.navEl_1_normal a, div.co_reeddiffuser div.navEl_1_inPath a { } div.co_reeddiffuser div.navEl_1_normal a:hover, div.co_reeddiffuser div.navEl_1_here div.noLink, div.co_reeddiffuser div.navEl_1_inPath a { } /* Roensch */ div.co_roensch div.navEl_1_here div.noLink, div.co_roensch div.navEl_1_normal a, div.co_roensch div.navEl_1_inPath a { background: url(../../upload/roensch_7981.gif) 10px center no-repeat; } div.co_roensch div.navEl_1_normal a:hover, div.co_roensch div.navEl_1_here div.noLink, div.co_roensch div.navEl_1_inPath a { background-image: url(../../upload/roensch2_2811.gif); } /* Sheepworld */ div.co_sheepworld div.navEl_1_here div.noLink, div.co_sheepworld div.navEl_1_normal a, div.co_sheepworld div.navEl_1_inPath a { background: url(../../upload/sheepworld_2789.gif) 10px center no-repeat; } /* Skia */ div.co_skia div.navEl_1_here div.noLink, div.co_skia div.navEl_1_normal a, div.co_skia div.navEl_1_inPath a { background: url(../../upload/skia_2790.gif) 10px center no-repeat; } div.co_skia div.navEl_1_normal a:hover, div.co_skia div.navEl_1_here div.noLink, div.co_skia div.navEl_1_inPath a { background-image: url(../../upload/skia2_2791.gif); } /* Viteo */ div.co_viteo div.navEl_1_here div.noLink, div.co_viteo div.navEl_1_normal a, div.co_viteo div.navEl_1_inPath a { background: url(../../upload/viteo_3941.gif) 10px center no-repeat; } div.co_viteo div.navEl_1_normal a:hover, div.co_viteo div.navEl_1_here div.noLink, div.co_viteo div.navEl_1_inPath a { background-image: url(../../upload/viteo2_3942.gif); } /* Zotter */ div.co_zotter div.navEl_1_here div.noLink, div.co_zotter div.navEl_1_normal a, div.co_zotter div.navEl_1_inPath a { background: url(../../upload/zotter_2805.gif) 10px center no-repeat; } div.co_zotter div.navEl_1_normal a:hover, div.co_zotter div.navEl_1_here div.noLink, div.co_zotter div.navEl_1_inPath a { background-image: url(../../upload/zotter2_2804.gif); } /* << */ /* >> Teaser Navigation */ /* Servicecenter */ /* Titel */ div.co_servicecenter div.nssTitle div.tDes1 h3 { font-size: 14px; margin: 0; color: #fff; display: block; background: url(../xist4c/web/krusekopf/01/img/teaserheadbg.gif) top left repeat-x; padding: 4px 7px 4px 7px; } /* Aeussere Navigationsheulle */ div.co_servicecenter div.nssDes1 { background: url(../xist4c/web/krusekopf/01/img/teaserbg.gif) top left repeat; } div.co_servicecenter div.nssDes2 { background: url(../xist4c/web/krusekopf/01/img/teaserbgtop.gif) top left repeat-x; } /*Angaben zur Navigationshuelle. Je Ebene haben Sie die Moeglichkeit den Ebenenhintergrund zu veraendern.*/ div.co_servicecenter div.navCHS_1, div.co_servicecenter div.navCHS_2, div.co_servicecenter div.navCHS_3 { border: 0; } /* Angaben zur Navigationsknotenhuelle */ div.co_servicecenter div.navNS_0, div.co_servicecenter div.navNS_1, div.co_servicecenter div.navNS_2, div.co_servicecenter div.navNS_3 { text-align: left; } /* Benutzen Sie folgende Klassen um die aeussere Huelle der Navigationslinks zu veraendern.*/ div.co_servicecenter div.navEl_1_normal, div.co_servicecenter div.navEl_1_inPath, div.co_servicecenter div.navEl_1_here, div.co_servicecenter div.navEl_2_normal, div.co_servicecenter div.navEl_2_inPath, div.co_servicecenter div.navEl_2_here, div.co_servicecenter div.navEl_3_normal, div.co_servicecenter div.navEl_3_inPath, div.co_servicecenter div.navEl_3_here { border: 0; } div.rElCont div.co_servicecenter div.navEl_1_normal, div.co_servicecenter div.navEl_1_inPath, div.co_servicecenter div.navEl_1_here { border-bottom: 1px solid #fff; } /* Ebene 1 */ div.co_servicecenter div.navEl_1_here div.noLink, div.co_servicecenter div.navEl_1_normal a, div.co_servicecenter div.navEl_1_inPath a { display: block; font-size: 11px; color: #000; padding: 4px 5px 4px 7px; } div.co_servicecenter div.navEl_1_normal a:hover, div.co_servicecenter div.navEl_1_inPath a:hover, div.co_servicecenter div.navEl_1_here div.noLink, div.co_servicecenter div.navEl_1_inPath a { color: #6E6D26; } /* Infotek */ div.co_infotek { margin-bottom: 20px; } /* << */ /* >> Linkliste */ /* Linkliste Tabelle */ table.llShell { margin: 0 15px 20px 15px; } /* Einstellungen fuer den Linklisten Titel fuer normal, besucht und bei ueberfahren mit der Maus */ table.llShell td a { font-size: 13px; color: #f90; font-weight: bold; } table.llShell td a:hover { color: #000; } /* Linklisten Bullet */ table.llShell td img.bullet { width: 6px; height: 6px; margin-top: 5px; margin-right: 6px; background: #574313; } /* Anpassungen der Linkbeschreibung */ table.llShell td.desc { font-size: 11px; } /* << */ /* >> Login und Logout Element */ /* >>>>>Login */ /* Definitionen fuer den Schnelllogin- Schnelllogoutkontainer */ div.quickLogKontainer { width: 165px; position: absolute; left: 823px; top: 82px; z-index: 4; display: none; } /* Einstellungen fuer das Login und Logout Formular */ form.quicklogin, form.login, form.logoutFormField { margin: 0; padding: 0; } /* Angaben zu den Input Formularfeldern im Quicklogin Bereich*/ form.quicklogin table td input.text { font-size: 10px; width: 100%; } /* Angaben zu dem Input Button im Quicklogin Bereich*/ form.quicklogin table td input.button { font-weight: auto; } /* Beschriftungen fuer den Standard Login */ form.login table td span { } /* Angaben zu den Input Formularfeldern im Standard Login Bereich*/ form.login table td input.text { font-size: 10px; width: 160px; } /* Angaben zu dem Input Button im Standard Login*/ form.login table td input.button { cursor: pointer; /cursor: hand; width: auto; } /* Einstellungen zur Standard Login Tabelle */ table.designShell { border: 1px solid #5C4714; background: #E3DEC4; } /* Anpassen der Login Fehlermeldung bei missgluecktem Loginversuch*/ form.login table.designShell td table td div { font-weight: bold; color: #9A2534; } /* >>>>Logout */ /* logout Tabelle */ table.logout { } /* Anpassungen fuer den Beschreibungstext im Logout Bereich */ table.logout td span { font-size: 9px; font-weight: normal; } /* Formatierung des Benutzernamens im Logout Bereich.*/ table.logout td { font-weight: bold; font-size: 10px; white-space: nowrap; line-height: 10px; } /* Formatierung des Buttons im Logout Bereich.*/ table.logout td a img { } /* >> */ /* >> News Uebersicht*/ /* News Uebersicht Tabelle */ table.news { } /* Anpassungen fuer das News Datum in der Uebersicht*/ table.news td.date { font-weight: bold; color: #666; font-size: 11px; padding-right: 8px; } /* News Info Spalte */ table.news td.item { padding-bottom: 12px; } /* Definition der News Uebersicht Ueberschrift */ table.news td.item h3 { margin: 0 0 3px 0; } /* Definition der News Uebersicht Unterueberschrift */ table.news td.item h4 { margin: 0 0 3px 0; } /* Einstellungen fuer die News Beschreibung */ table.news td.item div.desc { margin: 0; } /* Definition fuer das News Bild */ table.news td.item img.rFloat { float: right; margin-left: 10px; margin-bottom: 5px; } /* Anpassungen des News "mehr...." Links */ table.news td.item div.more { clear: both; margin-top: 5px; margin-bottom: 5px; text-align: left; font-size: 11px; } /* Einstellungen des News "mehr..." Icons */ table.news td.item div.more img { } /* << */ /* >> News Teaser */ div.newsTContShell { padding-bottom: 10px; } div.newsTContShell div.inner { } div.newsTContShell div.inner div.date { font-weight: bold; color: #000; font-size: 10px; } div.newsTContShell div.inner h3 { font-size: 11px; margin: 0; font-weight: normal; margin-bottom: 5px; padding-left: 10px; } div.newsTContShell div.inner h3 a { } /* << */ /* >> Blaetterelement */ /* Pager Kontainer */ div.pager { margin-bottom: 3px; } /* Einstellungen fuer die Informationen wieviel Seiten gefunden wurden bsp. "Seite (1 / 23)" */ div.pager table.pInfo td, div.pager table.pInfo td span { font-size: 11px; color: #333; display: none; } /* Definition der Schriftfarbe der Tabellenzelle fuer die gefundenen Seiten */ div.pager table.pPages td { color: #369; } /* Einstellung der momentan angewaehlten Seite */ div.pager table.pPages td span { font-weight: bold; color: #fff; display: block; background: #9A2534; height: 16px; width: 20px; padding: 0 5px; } /* Anpassen der Links */ div.pager table.pPages td a { font-weight: bold; vertical-align: middle; color: #000; display: block; height: 16px; width: 20px; padding: 0 5px; } div.pager table.pPages td a:hover { background: #9A2534; color: #fff; } /* Anpassungen fuer den linken Pager Pfeil aktiv und passiv */ div.pager table.pPages td a img.firstAct, div.pager table.pPages td img.firstPass { height: 16px; width: 20px; background: url(../xist4c/web/krusekopf/01/img/firstPageButtActive.gif) center center no-repeat; margin: 0 4px 0 0; border: 0; } div.pager table.pPages td a:hover img.firstAct, div.pager table.pPages td img.firstPass { background-image: url(../xist4c/web/krusekopf/01/img/firstPageButtPassive.gif); } /* Anpassungen fuer den halb linken Pager Pfeil aktiv und passiv */ div.pager table.pPages td a img.prevAct, div.pager table.pPages td img.prevPass { height: 16px; width: 20px; margin: 0 2px 0 0; background: url(../xist4c/web/krusekopf/01/img/prevPageButtActive.gif) center center no-repeat; border: 0; } div.pager table.pPages td a:hover img.prevAct, div.pager table.pPages td img.prevPass { background-image: url(../xist4c/web/krusekopf/01/img/prevPageButtPassive.gif); } /* Anpassungen fuer den halb rechten Pager Pfeil aktiv und passiv */ div.pager table.pPages td a img.nextAct, div.pager table.pPages td img.nextPass { height: 16px; width: 20px; margin: 0 0 0 2px; background: url(../xist4c/web/krusekopf/01/img/nextPageButtActive.gif) center center no-repeat; border: 0; } div.pager table.pPages td a:hover img.nextAct, div.pager table.pPages td img.nextPass { background-image: url(../xist4c/web/krusekopf/01/img/nextPageButtPassive.gif); } /* Anpassungen fuer den rechten Pager Pfeil aktiv und passiv */ div.pager table.pPages td a img.lastAct, div.pager table.pPages td img.lastPass { height: 16px; width: 20px; margin: 0 0 0 4px; background: url(../xist4c/web/krusekopf/01/img/lastPageButtActive.gif) center center no-repeat; border: 0; } div.pager table.pPages td a:hover img.lastAct, div.pager table.pPages td img.lastPass { background: url(../xist4c/web/krusekopf/01/img/lastPageButtPassive.gif) center center no-repeat; } /* Anpassungen fuer das Pager Trennelement */ div.pager table.pPages td img.sep { height: 16px; width: 1px; border: 0; background: #E3DEC4; } /* << */ /* >> Blaetterelement fuer Dokumentunterteilung */ div.topContentPager { } div.bottomContentPager { } div.contPagerShell { background: #efefef; margin-bottom: 10px; } div.contPagerShell div.outDes1 { padding: 2px 5px 2px 5px; border: 1px solid #b3b3b3; } div.contPagerShell div.outDes2 { text-align: right; } div.contPagerShell div.outDes2 table { margin-left: auto; } div.contPagerShell div.outDes2 table td { } div.contPagerShell div.outDes2 table td div.des1 { } div.contPagerShell div.outDes2 table td div.des2 { } /* Links des normalen pagers */ div.contPagerShell div.outDes2 table td div.here, div.contPagerShell div.outDes2 table td a { display: block; font-size: 12px; line-height: 12px; font-weight: bold; padding: 1px 4px 1px 4px; border: 1px solid #000; color: #fff; background: #a0b8cf; } div.contPagerShell div.outDes2 table td a:hover { background: #7ca4c7; } div.contPagerShell div.outDes2 table td div.here { background: #5589b7; } div.contPagerShell div.outDes2 table td div.spc { display: block; width: 1px; height: 10px; margin: 1px 3px 0 3px; background: #000; } /* Anordnung der simplen Bildpagerelemente */ div.smpContPagerShell { background: #efefef; margin-bottom: 10px; } * html div.smpContPagerShell { width: 100%; } div.smpContPagerShell div.outDes1 { border: 1px solid #b3b3b3; } div.smpContPagerShell div.outDes2 { margin: 2px 5px 2px 5px; text-align: right; } * html div.smpContPagerShell div.outDes2 { width: 100%; } div.smpContPagerShell div.outDes2 table.outer { margin-left: auto; } div.smpContPagerShell div.outDes2 table.outer td.left { text-align: left; } div.smpContPagerShell div.outDes2 table.outer td.right { text-align: right; } div.smpContPagerShell div.outDes2 table.outer td.left div.d1_left, div.smpContPagerShell div.outDes2 table.outer td.right div.d1_right { } div.smpContPagerShell div.outDes2 table.outer td.left div.d2_left, div.smpContPagerShell div.outDes2 table.outer td.right div.d2_right { } div.smpContPagerShell div.outDes2 table.outer td.sep { width: 1px; } table.pElOuter_left, table.pElOuter_right { } table.pElOuter_right { margin-left: auto; } table.pElOuter_left td.spcL, table.pElOuter_left td.spcR, table.pElOuter_right td.spcL, table.pElOuter_right td.spcR { } table.pElOuter_left td.spcR img, table.pElOuter_right td.spcL img { width: 3px; } table.pElOuter_left td.spcL img, table.pElOuter_left td.spcR img, table.pElOuter_right td.spcL img, table.pElOuter_right td.spcR img { } /* Links des simplen Bildpagerelements */ table.pElOuter_left td.pEl div.noLink img, table.pElOuter_right td.pEl div.noLink img, table.pElOuter_left td.pEl a img, table.pElOuter_right td.pEl a img { height: 20px; width: 24px; background-position: left top; background-repeat: no-repeat; border: none; } table.pElOuter_left td.pEl a img { background-image: url(../xist4c/web/krusekopf/01/img/smpPagArrowLeft.gif); } table.pElOuter_right td.pEl a img { background-image: url(../xist4c/web/krusekopf/01/img/smpPagArrowRight.gif); } table.pElOuter_left td.pEl div.noLink img { background-image: url(../xist4c/web/krusekopf/01/img/smpPagArrowLeftPass.gif); } table.pElOuter_right td.pEl div.noLink img { background-image: url(../xist4c/web/krusekopf/01/img/smpPagArrowRightPass.gif); } /* Anordnung der simplen Textpagerelemente */ div.smpTxtContPagerShell { background: #efefef; margin-bottom: 10px; } * html div.smpTxtContPagerShell { width: 100%; } div.smpTxtContPagerShell div.outDes1 { border: 1px solid #b3b3b3; padding: 2px 5px 2px 5px; } div.smpTxtContPagerShell div.outDes2 { text-align: right; } * html div.smpTxtContPagerShell div.outDes2 { width: 100%; } div.smpTxtContPagerShell div.outDes2 table.outer { margin-left: auto; } div.smpTxtContPagerShell div.outDes2 table.outer td.left { text-align: left; } div.smpTxtContPagerShell div.outDes2 table.outer td.right { text-align: right; } div.smpTxtContPagerShell div.outDes2 table.outer td.left div.d1_left, div.smpTxtContPagerShell div.outDes2 table.outer td.right div.d1_right { } div.smpTxtContPagerShell div.outDes2 table.outer td.left div.d2_left, div.smpTxtContPagerShell div.outDes2 table.outer td.right div.d2_right { } div.smpTxtContPagerShell div.outDes2 table.outer td.sep { width: 1px; background: #000; } table.pTxtElOuter_left, table.pTxtElOuter_right { } table.pTxtElOuter_right { margin-left: auto; } table.pTxtElOuter_left td.spcL, table.pTxtElOuter_left td.spcR, table.pTxtElOuter_right td.spcL, table.pTxtElOuter_right td.spcR { } table.pTxtElOuter_left td.spcR img, table.pTxtElOuter_right td.spcL img { width: 10px; } table.pTxtElOuter_left td.spcL img, table.pTxtElOuter_left td.spcR img, table.pTxtElOuter_right td.spcL img, table.pTxtElOuter_right td.spcR img { } /* Links des simplen Textpagerelements */ table.pTxtElOuter_left td.pEl div.noLink, table.pTxtElOuter_right td.pEl div.noLink, table.pTxtElOuter_left td.pEl a, table.pTxtElOuter_right td.pEl a { display: block; font-size: 12px; border: none; } table.pTxtElOuter_left td.pEl div.noLink, table.pTxtElOuter_left td.pEl a { padding-left: 20px; background: url(../xist4c/web/krusekopf/01/img/smpTxtPagArrowLeft.gif) 0 1px no-repeat; } table.pTxtElOuter_right td.pEl div.noLink, table.pTxtElOuter_right td.pEl a { padding-right: 20px; background: url(../xist4c/web/krusekopf/01/img/smpTxtPagArrowRight.gif) right 1px no-repeat; } table.pTxtElOuter_left td.pEl div.noLink { background-image: url(../xist4c/web/krusekopf/01/img/smpTxtPagArrowLeftPass.gif); color: #666; } table.pTxtElOuter_right td.pEl div.noLink { background-image: url(../xist4c/web/krusekopf/01/img/smpTxtPagArrowRightPass.gif); color: #666; } /* << */ /* >> Absaetze im Panel Modus */ /* Panel Titelkontainer */ div.panelTitle, table.footer td div.panelTitle { padding: 4px 15px 4px 15px; margin-bottom: 1px; } * html div.panelTitle, * html table.foot er td div.panelTitle { width: 100%; } /* Panel Titel */ div.panelTitle h3, table.footer td div.panelTitle h3 { font-size: 14px; margin: 0; color: #574313; } /* Enstellungen fuer den Panelkontainer */ div.panelOuter, table.footer td div.panelOuter { margin-bottom: 12px; } * html div.panelOuter, * html table.footer td div.panelOuter { width: 100%; } /* Einstellungen fuer den Inhaltskontainer */ div.panelOuter div.desOut1 div.des1 { } div.panelOuter div.desOut1 div.des2 { } div.panelOuter div.desOut1 div.des2 div.cont, table.footer td div.panelOuter div.desOut1 div.des2 div.cont { padding: 5px 15px; } * html div.panelOuter div.desOut1 div.des2 div.cont, * html table.footer td div.panelOuter div.desOut1 div.des2 div.cont { width: 100%; } div.panelOuter p, table.footer td div.panelOuter p { color: #574313; } div.panelOuter a, table.footer td div.panelOuter a { color: #000; } div.panelOuter a:hover, table.footer td div.panelOuter a:hover { color: #f90; } /* Design Panel Titelkontainer */ div.designTitle, div.co_orange div.designTitle, table.footer td div.designTitle { background: url(../xist4c/web/krusekopf/01/img/paneltitlebg.gif) top left repeat-x; padding: 4px 15px; margin-bottom: 1px; } div.co_orange div.designTitle { background-image: url(../xist4c/web/krusekopf/01/img/despaneltitlebg.gif); } * html div.designTitle, * html table.footer td div.designTitle { width: 100%; } /* Design Panel Titel */ div.designTitle h3, div.co_orange div.designTitle h3, table.footer td div.designTitle h3 { font-size: 14px; margin: 0; color: #574313; } /* Enstellungen fuer den Design Panelkontainer */ div.desPanelOuter, div.co_orange div.desPanelOuter, table.footer td div.desPanelOuter { margin-bottom: 12px; } * html div.desPanelOuter, * html table.footer td div.desPanelOuter { width: 100%; } /* Einstellungen fuer den Inhaltskontainer */ div.desPanelOuter div.desOut1 div.des1, div.co_orange div.desPanelOuter div.desOut1 div.des1 { background: url(../xist4c/web/krusekopf/01/img/panelbg.gif) top left repeat; } div.co_orange div.desPanelOuter div.desOut1 div.des1 { background-image: url(../xist4c/web/krusekopf/01/img/despanelbg.gif); } div.desPanelOuter div.desOut1 div.des2, div.co_orange div.desPanelOuter div.desOut1 div.des2 { background: url(../xist4c/web/krusekopf/01/img/paneltopbg.gif) top left repeat-x; } div.co_orange div.desPanelOuter div.desOut1 div.des2 { background-image: url(../xist4c/web/krusekopf/01/img/despaneltopbg.gif); } /* Einstellungen fuer den Design Inhaltskontainer */ div.desPanelOuter div.desOut1 div.des2 div.cont, div.co_orange div.desPanelOuter div.desOut1 div.des2 div.cont, table.footer td div.desPanelOuter div.desOut1 div.des2 div.cont { padding: 5px 15px; } * html div.desPanelOuter div.desOut1 div.des2 div.cont, * html table.footer td div.desPanelOuter div.desOut1 div.des2 div.cont { width: 100%; } /* Farben */ div.desPanelOuter, div.co_orange div.desPanelOuter { color: #574313; } div.desPanelOuter a, div.co_orange div.desPanelOuter a { color: #000; } div.desPanelOuter a:hover, div.co_orange div.desPanelOuter a:hover { color: #f90; } /* << */ /* >> Absaetze Normal */ /* Absatz Titel */ h3.paraTitle { display: block; font-style: normal; font-weight: bold; padding-top: 6px; margin-bottom: 5px; } /* Absatz Titel im Footerbereich*/ table.footer td h3.paraTitle { display: block; font-style: normal; font-weight: bold; font-size: 11px; padding-top: 3px; margin-bottom: 2px; } /* Einstellungen der Bild und Content ausrichtung */ div.paraImgOuter, div.paraImgOuterL, div.paraImgOuterR, div.paraFxImgOuter { padding-top: 2px; margin-bottom: 3px; } /* Fuer Bild zu Textabstand bei links und rechts umfliessend */ div.paraImgOuterL { padding-right: 6px; } div.paraImgOuterR { padding-left: 6px; } div.paraContOuter, div.paraContOuterL, div.paraContOuterR { } /* Bild zu Textabstand bei rechts und links freibleibend*/ div.paraContOuterL { padding-left: 6px; } div.paraContOuterR { padding-right: 6px; } /* Innerer Bild Container */ div.paraImgInner { margin-left: auto; margin-right: auto; } /* Bild zu Text im zentrierten Modus */ div.paraCeImgOuter { text-align: center; } /* Definition Absatz Untertitel */ h4.paraSubtitle { font-weight: bold; font-size: 13px; margin: 2px 0 5px 0; } /* Definition Absatz Untertitel im Footerbereich*/ table.footer td h4.paraSubtitle { font-weight: bold; font-size: 11px; margin: 2px 0 2px 0; } /* Einstellungen Absatz Inhalt */ table.contentTable td.contentColumn p, table.contentTable td.contentColumn p.paraEl { margin: 0; margin-bottom: 100px; } /* Einstellungen Absatz Inhalt im Footerbereich*/ table.footer td p, table.footer td p.paraEl { margin: 0; margin-bottom: 5px; } /* Definition Bilduntertext */ div.paraImgInner div.sT { margin-top: 2px; margin-bottom: 3px; font-size: 11px; text-align: left; } /* Definition Bilduntertext im Footerbereich */ table.footer td div.paraImgInner div.sT { margin-top: 1px; margin-bottom: 2px; font-size: 10px; text-align: left; } /* << */ /* >> Allgemeine Absatz und Panel Definitionen */ /* Nicht aendern!! */ img.clearAll { display: block; clear: both; visibility: hidden; } /* Standard definition des Absatzabstandes */ div.contentContainer table.contentTable td.contentColumn p { margin: 0; margin-bottom: 10px; } /* Standard definition des Absatzabstandes im Footerbereich*/ table.footer td div.contentContainer table.contentTable td.contentColumn p { margin: 0; margin-bottom: 7px; } /* Einstellungen fuer den Absatz "mehr..." Link */ div.paraSublinkShell { text-align: left; margin-bottom: 5px; font-size: 11px; } div.paraSublinkShell img { padding-left: 5px; } /* Absatz Sublink */ div.paraSublinkShell span.sL { } /* Einstellungen fuer den Absatz "mehr..." Link im Footerbereich */ table.footer td div.paraSublinkShell { } table.footer td div.paraSublinkShell span.sL { } /* Einstellungen der Absatz und Panel "mehr.." Link Icons */ div.paraSublinkShell span.sL img { width: 8px; height: 14px; vertical-align: text-top; border: 0; background: url(../xist4c/web/krusekopf/01/img/paragraphArrow.gif) left 1px no-repeat; } /* Einstellungen der Absatz und Panel "mehr.." Link Icons im Footerbereich */ table.footer td div.paraSublinkShell span.sL img { display: none; } /* << */ /* >> Schnellzugriff*/ /* Definitionen fuer den Schnellzugriffkontainer */ div.qAccessCont { width: 155px; position: absolute; left: 7px; top: 128px; z-index: 6; display: none; } /* Schnellzugriff Formularfeld einstellungen */ div.qAccessCont select { font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; font-size: 10px; width: 100%; } /* << */ /* >> Schnellsuche*/ /* Definitionen fuer den Schnellsuchekontainer */ div.qSearchCont { width: 155px; position: absolute; left: 7px; top: 107px; z-index: 3; display: none; } /* Schnellsuche Formulareinstellungen */ div.qSearchCont form { margin-top: 0; margin-bottom: 0; } div.qSearchCont table.qSearch { } /* Schnellsuche Formularfeld Einstellungen */ div.qSearchCont table.qSearch td input.text { font-size: 10px; width: 100%; } /* Schnellsuche "Go" button */ div.qSearchCont table.qSearch td input.button { } /* << */ /* >> Standard Suche */ /* Standard Suchfeld Einstellungen */ table.stdSearch td input.text { font-size: 13px; width: 300px; } /* Standard Suche Button Einstellungen */ table.stdSearch td input.button { cursor: pointer; /cursor: hand; border: 1px solid #E3DEC4; font-size: 12px; } /* Suche Formulareinstellungen */ table.stdSearch form { margin-top: 0; margin-bottom: 0; } /* Einstellungen fuer die farbige Hinterlegung im Suchergebnis */ span.searchResult { padding: 0 2px; background: #E3DEC4; } /* << */ /* >> Empfehlung versenden */ /* Empfehlung versenden Tabelle */ table.recomShell { } /* Anpassungen des Formulars */ table.recomShell form { padding: 0; margin: 0; } /* Einstellungen fuer die Fehlermeldungen bei unkorrektem Ausfuellen der Formularelemente */ table.recomShell td ul li { font-weight: bold; font-size: 11px; color: darkred; margin: 3px 0 3px 0; } /* Anpassungen der Formular Tabellenspalten */ table.recomShell td form table td { padding: 1px 8px 1px 8px; } /* Anpassungen der Formularelemente */ table.recomShell td form table td input.text, table.recomShell td form table td textarea { font-size: 11px; font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; width: 100%; margin-bottom: 5px; padding-left: 1px; padding-right: 1px; } table.recomShell td form table td textarea { overflow: auto; } /* Einstellungen fuer die Input Formularelemente */ table.recomShell td form table td input.button { font-size: 11px; margin-bottom: 8px; cursor: pointer; /cursor: hand; } /* Angaben zum Kontainer der Sendebestaetigung */ div.recomSuccess { font-weight: bold; font-size: 13px; color: #333; margin: 10px 0; } /* Einstellungen fuer den Kontainer der Nutzungsbedingungen */ table.recomShell td div.policy { font-size: 11px; color: #333; margin: 10px 0; } /* Anpassungen der Tabellenzelle des Paneltitels */ table.recomShell td form table td.title { font-weight: bold; color: #fff; padding: 1px 4px 1px 4px; background: #5C4714; } /* Definitionen fuer die Paneltabelle */ table.recomShell td form table { background: #E3DEC4; } /* Angaben zu den Tabellenzellen der Feldbezeichner der Formularfelder */ table.recomShell td form table td.prompt { font-size: 11px; } /* Angaben zu den Feldbezeichnern der Formularfelder */ table.recomShell td form table td.prompt span { font-size: 11px; color: #333; } /* << */ /* >> Trenner fuer Content Elemente */ /* Trennelement Container */ div.separatorShell { margin-top: 7px; margin-bottom: 15px; background-color: #9A2534; } /* Trennelement Abstandshalter */ div.separatorShell img { } /* << */ /* >> Seitenstruktur */ /* Seitenstruktur Eintragcontainer */ div.sitemapEntry { } /* Definition der Links fuer normal, besucht und beim ueberfahren mit der Maus*/ div.sitemapEntry table td a { text-decoration: none; } /* Anpassungen beim ueberfahren mit der Maus */ div.sitemapEntry table td a:hover { } /* Element definition fuer die momentan aktive Seite */ div.sitemapEntry table td em { font-style: normal; font-weight: bold; color: #000; } /* Einstellungen fuer das Einrueckungselement */ div.sitemapEntry table td img.bullet { height: 4px; width: 4px; margin: 5px 6px 10px 3px; border: 1px solid #000; background: #ccc; } /* Anpassungen fuer den Pfeil fuer die Seite von der aus man auf die Sitemap gegangen ist*/ div.sitemapEntry table td img.arrow { height: 11px; width: 7px; margin: 1px 0 0 5px; background: url(../xist4c/web/krusekopf/01/img/sitemapArrow.gif) left bottom no-repeat; } /* << */ /* >> Standardeinstellungen */ /* Anpassungen fuer die Seiten Stammtabelle */ table.mShell { width: 100%; height: auto; } /* Anpassungen fuer die Zellen der Stammtabelle */ table.mShell td.msCont { } /*Angaben zum Drucken (Diese Angaben bitte nicht veraendern) */ div.printHeader, div.printButtonShell { display: none; } img.printHeaderSpacer { display: none; } /* Schaltet den Skip Link aus (Wird bei Barriere freien Seiten verwendet) */ div.skipNav { display: none; } /* Globale Ebenen Shell */ div.globalLayerShell { width: 989px; position: absolute; left: auto; top: auto; z-index: 31; } /* Breite der Kopfelemente */ table.headerElements { width: 989px; } /* Anpassen der Topnavigation- und Loginzeile */ div.tNavKont, div.tNavKont div.des1 { width: 989px; } div.tNavKont { width: 989px; position: absolute; left: auto; top: 0; width auto; } /* Topnavigation Designcontainer 1 */ div.tNavKont div.des1 { } /* Topnavigation Designcontainer 2 */ div.tNavKont div.des1 div.des2 { } /* Anpassungen der Topnavigationzelle */ div.tNavKont div.des1 div.des2 table.tNavOuter td.navCol { width: 100%; } /* Einstellungen fuer die Schnelllogin- Schnelllogoutzelle */ div.tNavKont div.des1 div.des2 table.tNavOuter td.loginCol { } /* Einstellungen fuer die Schnelllogin- Schnelllogout Platzhalters */ div.tNavKont div.des1 div.des2 table.tNavOuter td.loginCol img { width: 10px; height: 1px; } /* Einstellungen fuer die Suche und Trail Tabelle*/ table.searchAndTrailBg { width: 989px; background: #dde5ee url(../xist4c/web/krusekopf/01/img/quickSearchBg.jpg) top left repeat-y; display: none; } /* Einstellungen fuer die Schnellsuche Tabellenzelle */ td.quickSearchBg { width: 170px; } /* Platzhalterzelle zwischen QuickSearch und Trail */ td.searchAndTrailSpacerColumn { height: 22px; width: 20px; } /* Platzhalter zwischen Quicksearch und Trail */ img.searchAndTrailSpacer { width: 20px; } /* Einstellungen fuer die Trail Tabellenzelle */ td.trailbg { width: 620px; } /* Platzhalter fuer Navigationszelle */ img.navigationColumnSpacer { width: 170px; } /* Definition der Breite des linken Platzhalters */ img.spacerLeft { width: 1px; } /* Definition des Platzhalters fuer den Inhalt */ img.contentSpacer { display: none; } /* Definition der Breite des rechten Platzhalters */ img.spacerRight { width: 1px; } /* Beseitigt ein Problem im Mozilla Browser */ img.block { display: block; } /* Einstellungen fuer die Basistabelle des Inhalts */ table.contentMainTable { width: 989px; height: auto; background: #fff url(../xist4c/web/krusekopf/01/img/background.gif) top left repeat-y; } /* Definitionen der Platzhalterzelle links*/ td.spacerColumnLeft { } /* Anpassungen fuer die Hauptspalte des Inhalts */ td.contentMainColumn { width: 100%; } /* Container fuer den Inhalt */ div.contentContainer { } /* Anpassungen fuer die Inhaltstabelle */ table.contentTable { width: 100%; } /* Definitionen des Inhaltsbereichs */ td.contentColumn { width: 100%; } td.contentColumn div.contSpcShellStd, td.contentColumn div.contSpcShellBL, td.contentColumn div.contSpcContentPager { padding: 0; } /* Definitionen der Platzhalterzelle rechts*/ td.spacerColumnRight { } /* Anpassungen fuer zweispaltigen Inhalt (linke Spalte, abstand zwischen den Spalten, rechte Spalte) */ table.twoColElShell { width: 100%; } table.twoColElShell td.leftSpc img { width: 190px; } table.twoColElShell td.middleSpc img { width: 12px; } table.twoColElShell td.rightSpc img { width: 190px; } table.twoColElShell td.l { } table.twoColElShell td.m { } table.twoColElShell td.r { } /* Grundeinstellungen fuer einige Element festlegen */ th, td, p { font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; font-size: 13px; } /* Anpassungen fuer die Standard Absaetze */ p { margin-top: 0; } /* Allgemeine Einstellung fuer Aufzaehlungspunkte */ ul { list-style-type: square; } /* Einstellungen fuer Ueberschriften */ h1 { font-size: 15px; margin: 0 0 12px 0; color: #9A2534; } h2 { font-size: 14px; margin: 0 0 10px 0; color: #5B4614; } h3 { font-size: 13px; margin: 0 0 8px 0; color: #5B4614; } h4 { font-size: 13px; margin: 0 0 13px 0; } /*Standard Link einstellungen*/ a { color: #9A2534; text-decoration: none; } a:link, a:visited, a:hover { } a:hover { color: #f90; } /* Formulare */ form { margin: 0; } input, textarea, select { font-family: Arial, Verdana, Helvetica, XHelvetica, sans-serif; background: #fff; border: 1px solid #E3DEC4; font-size: 10px; color: #000; width: 100%; -moz-opacity: 0.6; filter: alpha(opacity=60); } input:hover, input:focus, textarea:hover, textarea:focus, select:hover, select:focus { -moz-opacity: 1; filter: alpha(opacity=100); } select { width: auto; } input.button { border: 0; width: auto; } /* Bilder */ img { border: 0; } /* << */ /* >> Seitentitelelement */ /* Aeussere Elemethuelle */ div.titlesShell { margin: 12px 0; padding: 0 15px; } /* Links ausgerichtete Bildspalte */ div.titlesShell table td.imgL { padding-right: 10px; vertical-align: bottom; } /* Links ausgerichtetes Bild */ div.titlesShell table td.imgL div.img { } /* Rechts ausgerichtete Bildspalte */ div.titlesShell table td.imgR { padding-left: 10px; vertical-align: bottom; } /* Rechts ausgerichtetes Bild */ div.titlesShell table td.imgR div.img { } /* Linke Titelspalte */ div.titlesShell table td.titlesL { vertical-align: bottom; } /* Rechte Titelspalte */ div.titlesShell table td.titlesR { vertical-align: bottom; } /* << */ /* >> Umfragen Uebersicht*/ /* Umfrage Tabelle */ table.surveyOvShell { } /* Definition der Frage auf der Uebersichtsseite */ table.surveyOvShell td div span.question { font-size: 13px; font-weight: bold; } /* Einstellungen fuer den Hinweis ueber das Ende der Umfrage */ table.surveyOvShell td div span.date { font-size: 10px; } /* Umfrage Beschreibung */ table.surveyOvShell td div div.desc { } /* << */ /* >> Umfragen Detail */ /* Umfrage Formular */ form.surveyForm { } /* Anpassungen fuer die Tabellenzelle der Detailfrage */ form.surveyForm td.question { font-weight: bold; color: #339; } /* Einstellungen der Tabellenzelle fuer das Ende der Umfrage */ form.surveyForm td.date { font-size: 10px; } /* Versenden Button */ form.surveyForm td input.button { cursor: pointer; /cursor: hand; width: auto; } /* Ergebnistabelle */ table.surveyResult { } /* Frage auf der Ergebnisseite */ table.surveyResult td.question { font-weight: bold; color: #339; } /* Einstellungen fuer den Hinweis ueber das Ende der Umfrage */ table.surveyResult td.date { padding-top: 10px; font-size: 10px; } /* Anpassen des Teilnehmerzusatz */ table.surveyResult td.participants { } /* Anpassungen der Grafik fuer den Ergebnissbalken */ table.surveyResult td div.gfx { border-left: 1px solid #ffeea8; border-top: 1px solid #ffeea8; border-right: 1px solid #6a5503; border-bottom: 1px solid #6a5503; background: #f90 url("img/surveyGraphic.gif") repeat-x; } /* Anpassungen des Hintergrunds fuer den Ergebnissbalken */ table.surveyResult td.gfxBg { background: #efefef; border: 1px inset #ccc; width: auto; } /* << */ /* >> Randbereich links */ /* Definition des linken Randbereichs */ td.leftBorderCol { padding-left: 4px; } /* Anpassungen der Randbereich Breite und den Abstand von oben*/ td.leftBorderCol div.leBoElShell img.topSpc { display: none; } td.leftBorderCol div.leBoElShell img.bottomSpc { height: 1px; width: 236px; } /* Randbereich Inhaltscontainer */ td.leftBorderCol div.lElCont { } /* << */ /* >> Randbereich rechts */ /* Definition des rechten Randbereichs */ td.rightBorderCol { padding-right: 4px; } /* Anpassungen der Randbereich Breite und den Abstand von oben*/ td.rightBorderCol div.riBoElShell img.topSpc { display: none; } td.rightBorderCol div.riBoElShell img.bottomSpc { height: 1px; width: 181px; } /* Randbereich Inhaltscontainer */ td.rightBorderCol div.rElCont { } /* << */ /* >> Teaser rechts */ /* Einstellung des Teaser Inhaltbereichs */ td.rightBorderCol div.rElCont div.title, td.rightBorderCol div.rElCont div.titleBL { } /* Definition des Teasertitel */ td.rightBorderCol div.rElCont div.title h3, td.rightBorderCol div.rElCont div.titleBL h3 { font-size: 14px; margin: 0; color: #fff; display: block; background: url(../xist4c/web/krusekopf/01/img/teaserheadbg.gif) top left repeat-x; padding: 4px 7px 4px 7px; } td.rightBorderCol div.rElCont div.titleBL h3 { background: transparent; padding: 0; border-bottom: 1px solid #dde5ee; color: #000; } /* Definition des Teaseruntertitel */ td.rightBorderCol div.rElCont div.tDesOut1 h4, td.rightBorderCol div.rElCont div.tDesOut1BL h4 { font-size: 11px; margin: 0 0 3px 0; color: #5C4714; } /* Einstellungen fuer die aeussere Teaserhuelle */ td.rightBorderCol div.rElCont div.tDesOut1, td.rightBorderCol div.rElCont div.tDesOut1BL { } td.rightBorderCol div.rElCont div.tDesOut2, td.rightBorderCol div.rElCont div.tDesOut2BL { } td.rightBorderCol div.rElCont div.tOuter, td.rightBorderCol div.rElCont div.tOuterBL { margin-bottom: 1px; } * html td.rightBorderCol div.rElCont div.tOuter, * html td.rightBorderCol div.rElCont div.tOuterBL { width: 100%; } /* Zusaetzliche Design Kontainer fuer den Teaser Inhaltsbereich*/ td.rightBorderCol div.rElCont div.tOuter div.des1, td.rightBorderCol div.rElCont div.tOuterBL div.des1BL { background: url(../xist4c/web/krusekopf/01/img/teaserbg.gif) top left repeat; } td.rightBorderCol div.rElCont div.tOuterBL div.des1BL { background: transparent; } td.rightBorderCol div.rElCont div.tOuter div.des2, td.rightBorderCol div.rElCont div.tOuterBL div.des2BL { padding: 7px; background: url(../xist4c/web/krusekopf/01/img/teaserbgtop.gif) top left repeat-x; } td.rightBorderCol div.rElCont div.tOuterBL div.des2BL { padding: 0; background: transparent; } td.rightBorderCol div.rElCont div.tOuter div.des1 div.des2 div.des3, td.rightBorderCol div.rElCont div.tOuterBL div.des1BL div.des2BL div.des3BL { } /* Einstellungen fuer die Teaserhuelle */ td.rightBorderCol div.rElCont div.tOuter div.des1 div.des2 div.des3 div.content, td.rightBorderCol div.rElCont div.tOuterBL div.des1BL div.des2BL div.des3BL div.contentBL { } * html td.rightBorderCol div.rElCont div.tOuter div.des1 div.des2 div.des3 div.content, * html td.rightBorderCol div.rElCont div.tOuterBL div.des1BL div.des2BL div.des3BL div.contentBL { width: 100%; } /* Anpassungen des Text Inhaltsbereich */ /* Einstellungen der Bild und Content ausrichtung */ td.rightBorderCol div.rElCont div.teaserImgOuter, td.rightBorderCol div.rElCont div.teaserImgOuterL, td.rightBorderCol div.rElCont div.teaserImgOuterR, td.rightBorderCol div.rElCont div.teaserFxImgOuter { padding-top: 2px; margin-bottom: 3px; } /* Fuer Bild zu Textabstand bei links und rechts umfliessend */ td.rightBorderCol div.rElCont div.teaserImgOuterL { padding-right: 6px; } td.rightBorderCol div.rElCont div.teaserImgOuterR { padding-left: 6px; } td.rightBorderCol div.rElCont div.teaserContOuter { } /* Bild zu Textabstand bei rechts und links freibleibend*/ td.rightBorderCol div.rElCont div.teaserContOuterL { padding-left: 6px; } td.rightBorderCol div.rElCont div.teaserContOuterR { padding-right: 6px; } /* Innerer Bild Container */ td.rightBorderCol div.rElCont div.teaserImgInner { margin-left: auto; margin-right: auto; } /* Bild zu Text im zentrierten Modus */ div.paraCeImgOuter { text-align: center; } td.rightBorderCol div.rElCont p { font-size: 11px; margin: 0 0 10px 0; } /* Anpassungen des XHTML Inhaltsbereich */ td.rightBorderCol div.rElCont, td.rightBorderCol div.rElCont p, td.rightBorderCol div.rElCont ul, td.rightBorderCol div.rElCont ol { font-size: 11px; color: #5C4714; } td.rightBorderCol div.rElCont div.tOuterBL, td.rightBorderCol div.rElCont div.tOuterBL p, td.rightBorderCol div.rElCont div.tOuterBL ul, td.rightBorderCol div.rElCont div.tOuterBL ol { color: #5C4714; } td.rightBorderCol div.rElCont div.tOuter a, td.rightBorderCol div.rElCont div.tOuterBL a { color: #000; } td.rightBorderCol div.rElCont div.tOuter a:hover, td.rightBorderCol div.rElCont div.tOuterBL a:hover { color: #5C4714; } /* Anpassungen des XHTML Inhaltsbereich */ td.rightBorderCol div.rElCont p, td.rightBorderCol div.rElCont ul, td.rightBorderCol div.rElCont ol { margin-top: 0; margin-bottom: 10px; } /* Einstellungen der Abstaende des Mehrlinks */ td.rightBorderCol div.rElCont div.teaserSublinkShell { padding: 1px 3px 1px 3px; text-align: left; } /* Anpassen der Schriftgroesse des "mehr..." Links */ td.rightBorderCol div.rElCont div.teaserSublinkShell span.sL a { font-size: 11px; color: #000; } td.rightBorderCol div.rElCont div.teaserSublinkShell span.sL a:hover { color: #f90; } /* Definition fuer die "mehr..." Link Grafik */ td.rightBorderCol div.rElCont div.teaserSublinkShell span.sL img { width: 10px; height: 12px; vertical-align: text-top; border: 0; background: url(../xist4c/web/krusekopf/01/img/littleTeaserArrow.gif) left 4px no-repeat; } /* Anpassungen der Platzhalterzelle der rechten Teaser Spalte*/ td.tRightSpcCol { display: none; } /* Anpassungen des Platzhalters der rechten Teaser Spalte*/ td.tRightSpcCol img { display: none; } /* << */ /* >> Teaser links */ /* Einstellung des Teaser Inhaltbereichs */ td.leftBorderCol div.lElCont div.title, td.leftBorderCol div.lElCont div.titleBL { } /* Definition des Teasertitel */ td.leftBorderCol div.lElCont div.title h3, td.leftBorderCol div.lElCont div.titleBL h3 { font-size: 12px; margin: 12px 0 0 0; color: #000; display: block; padding: 1px 15px 1px 15px; border-bottom: 1px solid #574313; } td.leftBorderCol div.lElCont div.titleBL h3 { background: transparent; } /* Definition des Teaseruntertitel */ td.leftBorderCol div.lElCont div.tDesOut1 h4, td.leftBorderCol div.lElCont div.tDesOut1BL h4 { font-size: 11px; margin: 0 0 3px 0; } /* Einstellungen fuer die aeussere Teaserhuelle */ td.leftBorderCol div.lElCont div.tDesOut1, td.leftBorderCol div.lElCont div.tDesOut1BL { } td.leftBorderCol div.lElCont div.tDesOut2, td.leftBorderCol div.lElCont div.tDesOut2BL { } td.leftBorderCol div.lElCont div.tOuter, td.leftBorderCol div.lElCont div.tOuterBL { margin-bottom: 1px; } /* Zusaetzliche Design Kontainer fuer den Teaser Inhaltsbereich*/ td.leftBorderCol div.lElCont div.tOuter div.des1, td.leftBorderCol div.lElCont div.tOuterBL div.des1BL { background: #F4F3EA; } td.leftBorderCol div.lElCont div.tOuterBL div.des1BL { background: transparent; } td.leftBorderCol div.lElCont div.tOuter div.des2, td.leftBorderCol div.lElCont div.tOuterBL div.des2BL { padding: 7px 15px; } td.leftBorderCol div.lElCont div.tOuterBL div.des2BL { padding: 0; } td.leftBorderCol div.lElCont div.tOuter div.des1 div.des3, td.leftBorderCol div.lElCont div.tOuterBL div.des1BL div.des3BL { } /* Einstellungen fuer die Teaserhuelle */ td.leftBorderCol div.lElCont div.tOuter div.des1 div.des2 div.des3 div.content, td.leftBorderCol div.lElCont div.tOuterBL div.des1BL div.des2BL div.des3BL div.contentBL { margin-bottom: 5px; } * html td.leftBorderCol div.lElCont div.tOuter div.des1 div.des2 div.des3 div.content, * html td.leftBorderCol div.lElCont div.tOuterBL div.des1BL div.des2BL div.des3BL div.contentBL { width: 100%; } /* Anpassungen des Text Inhaltsbereich */ td.leftBorderCol div.lElCont p { font-size: 11px; margin: 0 0 10px 0; } /* Anpassungen des Text Inhaltsbereich */ /* Einstellungen der Bild und Content ausrichtung */ td.leftBorderCol div.lElCont div.teaserImgOuter, td.leftBorderCol div.lElCont div.teaserImgOuterL, td.leftBorderCol div.lElCont div.teaserImgOuterR, td.leftBorderCol div.lElCont div.teaserFxImgOuter { padding-top: 2px; margin-bottom: 3px; } /* Fuer Bild zu Textabstand bei links und rechts umfliessend */ td.leftBorderCol div.lElCont div.teaserImgOuterL { padding-right: 6px; } td.leftBorderCol div.lElCont div.teaserImgOuterR { padding-left: 6px; } td.leftBorderCol div.lElCont div.teaserContOuter { } /* Bild zu Textabstand bei rechts und links freibleibend*/ td.leftBorderCol div.lElCont div.teaserContOuterL { padding-left: 6px; } td.leftBorderCol div.lElCont div.teaserContOuterR { padding-right: 6px; } /* Innerer Bild Container */ td.leftBorderCol div.lElCont div.teaserImgInner { margin-left: auto; margin-right: auto; } /* Bild zu Text im zentrierten Modus */ td.leftBorderCol div.lElCont div.teaserCeImgOuter { text-align: center; } /* Anpassungen des XHTML Inhaltsbereich */ td.leftBorderCol div.lElCont, td.leftBorderCol div.lElCont p, td.leftBorderCol div.lElCont ul, td.leftBorderCol div.lElCont ol { font-size: 11px; color: #574313; } td.leftBorderCol div.lElCont div.tOuterBL, td.leftBorderCol div.lElCont div.tOuterBL p, td.leftBorderCol div.lElCont div.tOuterBL ul, td.leftBorderCol div.lElCont div.tOuterBL ol { color: #574313; } td.leftBorderCol div.lElCont div.tOuter a, td.leftBorderCol div.lElCont div.tOuterBL a { color: #000; } td.leftBorderCol div.lElCont div.tOuter a:hover, td.leftBorderCol div.lElCont div.tOuterBL a:hover { color: #574313; } /* Anpassungen des XHTML Inhaltsbereich */ td.leftBorderCol div.lElCont p, td.leftBorderCol div.lElCont ul, td.leftBorderCol div.lElCont ol { margin-top: 0; margin-bottom: 10px; } /* Einstellungen der Abstaende des Mehrlinks */ td.leftBorderCol div.lElCont div.teaserSublinkShell { padding: 1px 3px 0 3px; text-align: left; } /* Anpassen der Schriftgroesse des "mehr..." Links */ td.leftBorderCol div.lElCont div.teaserSublinkShell span.sL a { font-size: 11px; color: #000; } td.leftBorderCol div.lElCont div.teaserSublinkShell span.sL a:hover { color: #574313; } /* Definition fuer die "mehr..." Link Grafik */ td.leftBorderCol div.lElCont div.teaserSublinkShell span.sL img { width: 10px; height: 12px; vertical-align: text-top; border: 0; background: url(../xist4c/web/krusekopf/01/img/littleTeaserArrow.gif) left 4px no-repeat; } /* << */ /* >> Special Teaser rechts */ /* Einstellungen fuer die aeussere Teaserhuelle */ td.rightBorderCol div.rElCont div.el_wunschliste div.tOuter div.des2 { background-image: url(../../upload/wunschlistebg_2116.jpg); } td.rightBorderCol div.rElCont div.el_gutscheine div.tOuter div.des2 { background-image: url(../../upload/gutschein_3461.jpg); } td.rightBorderCol div.rElCont div.el_verpackung div.tOuter div.des2 { background-image: url(../../upload/verpackungbg_2117.jpg); } /* Einstellungen fuer die Teaserhuelle */ td.rightBorderCol div.rElCont div.el_wunschliste div.tOuter div.des1 div.des2 div.des3 div.content, td.rightBorderCol div.rElCont div.el_gutscheine div.tOuter div.des1 div.des2 div.des3 div.content, td.rightBorderCol div.rElCont div.el_verpackung div.tOuter div.des1 div.des2 div.des3 div.content { padding-top: 70px; } /* Einstellungen der Abstaende des Mehrlinks */ td.rightBorderCol div.rElCont div.el_nolink div.teaserSublinkShell { display: none; } td.rightBorderCol div.rElCont div.el_nolink div.tOuterBL { margin-bottom: 0; } /* << */ /* >> Special Teaser links */ /* Einstellungen der Abstaende des Mehrlinks */ td.leftBorderCol div.lElCont div.el_nolink div.teaserSublinkShell { display: none; } /* Einstellungen der Abstaende des Mehrlinks */ td.leftBorderCol div.lElCont div.el_braun div.tOuterBL div.des1BL { background: #F4F3EA; } /* << */ /* >>Top Navigation*/ /* Einstellungen fuer die Topnavigationtabelle */ table.tNav { margin-top: 5px; } /* Spacereinstellungen fuer die Topnavigation */ table.tNav td.spcLeft { width: 100%; } table.tNav td.spcRight { } /* Formatiert den Bezeichner fuer die Navigation */ table.tNav td.prompt { display: none; color: #574313; font-weight: bold; font-size: 12px; white-space: nowrap; } /* Spalte fuer ein Topnavigations element */ table.tNav td.navItem { } /* Grundeinstellung der Top Navigation fuer die Zustaende "normal", "im Pfad" und "hier"*/ table.tNav td.navItem span.here, table.tNav td.navItem a.normal, table.tNav td.navItem a.inPath { font-size: 11px; font-weight: normal; color: #574313; } table.tNav td.navItem span.here span, table.tNav td.navItem a.normal span, table.tNav td.navItem a.inPath span { } /* Abweichende einstellungen fuer den Zustand "im Pfad" */ table.tNav td.navItem a.inPath { } /* Folgende Angaben definieren die Reaktion beim ueberfahren mit der Maus */ table.tNav td.navItem a.normal:hover, table.tNav td.navItem a.inPath:hover { color: #f90; } /* Angabe fuer den Zustand "hier" */ table.tNav td.navItem span.here { color: #f90; } /* Einstellung der Trennelemente */ table.tNav td.sep img { height: 11px; margin: 0 7px; background-color: #574313; } /* << */ /* >> Navigationspfad */ /* Einstellungen fuer die Navigationspfad Tabelle */ table.trailShell { } /* Angaben zu den Tabellenzellen des Navigationspfad */ td.trailItemCol { } td.trailSepCol { } /* Gemeinsame Einstellungen fuer Trail items */ table.trailShell td a.hNormal, table.trailShell td a.normal { font-size: 10px; text-decoration: none; } table.trailShell td span.hHere, table.trailShell td span.here { font-size: 10px; font-weight: bold; } /* Anpassungen des Trail Homelinks fuer normalen Link, besuchter Link und Reaktion beim Ueberfahren mit der Maus */ table.trailShell td a.hNormal { } table.trailShell td a.hNormal span { } /* Einstellung fuer die momentan aktive Seite */ table.trailShell td span.hHere { } /* Anpassungen der Trail links fuer normalen Link, besuchter Link und Reaktion beim Ueberfahren mit der Maus */ table.trailShell td a.normal { } /* Einstellung fuer die momentan aktive Seite */ table.trailShell td span.here { } /* Anpassungen fuer das Navigationspfad Trennelement*/ table.trailShell td.sep img { height: 9px; width: 15px; margin: 2px 3px 0 3px; background: url(../xist4c/web/krusekopf/01/img/trailSeperator.gif) top left no-repeat; } /* << */ /* >>Panel with Product Table*/ /* Enstellungen fuer den Panelkontainer */ div.panelProductOuter { margin: 0 0 20px 0; } * html div.panelProductOuter { width: 100%; } /* Panel Titelkontainer */ div.panelProductOuter div.panelTitle { border-bottom: 0; } * html div.panelProductOuter div.panelTitle { width: 100%; } /* Panel Titel */ div.panelProductOuter div.panelTitle h3 { margin: 0; } /* Einstellungen fuer den Inhaltskontainer */ div.panelProductOuter div.desOut1 div.des1 { border: 1px solid #AFB0B2; } div.panelProductOuter div.desOut1 div.des2 { } div.panelProductOuter div.desOut1 div.des2 div.cont { padding: 5px 5px 5px 5px; } /* Einstellungen fuer XHTML-Feld */ div.panelProductOuter div.paragraphProductDataRow { padding: 5px 15px 5px 9px; background-color: #ddd; } * html div.panelProductOuter div.paragraphProductDataRow { width: 100%; } div.panelProductOuter form { margin: 0; padding: 0; } /* Einstellungen fuer Preis Tabelle */ div.panelProductOuter table.priceTable { height: 19px; font-size: 8px; background: #72739A; } div.panelProductOuter table.priceTable td.amountCol, div.panelProductOuter table.priceTable td.amountCol input, div.panelProductOuter table.priceTable td.pricePrompt, div.panelProductOuter table.priceTable td.buttonProductLinkShell, div.panelProductOuter table.priceTable td.buttonProductLinkShell input { white-space: nowrap; font-size: 12px; } /* Einstellungen fuer Menge */ div.panelProductOuter table.priceTable td.amountCol { padding: 1px 5px 0 10px; color: #fff; } div.panelProductOuter table.priceTable td.amountCol input { width: 20px; font-size: 8px; margin: 0 0 2px 5px; padding: 1px 2px 1px 2px; border: 1px solid #ccc; } /* Einstellungen fuer Preis */ div.panelProductOuter table.priceTable td.pricePrompt { color: #fff; padding-right: 5px; } div.panelProductOuter table.priceTable td.price { width: 100%; margin: 0; } div.panelProductOuter table.priceTable td.price div { padding: 6px 0 5px 0; text-align: left; background: #fff; } div.panelProductOuter table.priceTable td.price div span { padding-left: 5px; margin-right: 10px; font-size: 10px; line-height: 10px; font-weight: bold; color: #000; } /* Einstellungen fuer Button */ div.panelProductOuter table.priceTable td.buttonProductLinkShell { padding-left: 1px; padding-right: 1px; } div.panelProductOuter table.priceTable td.buttonProductLinkShell div { padding: 0 0 1px 0; } * html div.panelProductOuter table.priceTable td.buttonProductLinkShell div { width: 100%; } div.panelProductOuter table.priceTable td.buttonProductLinkShell input { width: 115px; height: 21px; margin: 0; padding: 0 3px 1px 25px; color: #000; font-size: 11px; line-height: 9px; border: 1px solid #fff; cursor: pointer; cursor: hand; background: #fff url(../xist4c/web/krusekopf/01/img/productRecomButton.gif) left center no-repeat; } div.floatTerm { clear: both; } /* << */ /* >> photogallery items */ /* top bar */ table.povMain div.tbDes4 { border: 1px solid #666; background: #ccc; font-size: 10px; text-align: center; padding: 2px 5px; } /* image */ table.povMain div.iDes3 { border-left: 1px solid #666; border-right: 1px solid #666; background: #efefef; padding-left: 3px; padding-right: 3px; } table.povMain div.iDes4 { text-align: center; vertical-align: middle; padding-top: 5px; } /* image shell */ table.povMain div.imgShell { vertical-align: middle; text-align: center; margin-left: auto; margin-right: auto; } /* text box */ table.povMain div.textBox { overflow: auto; margin-left: auto; margin-right: auto; } table.povMain div.textBox div.tbInner { padding: 0 0 2px 4px; } /* Title */ table.povMain div.title h4 { margin: 0; margin-bottom: 3px; font-size: 13px; } /* description */ table.povMain div.desc p { font-size: 11px; text-align: left; } /* bottom bar */ table.povMain div.bbDes4 { border: 1px solid #666; background: #ccc; font-size: 10px; text-align: right; padding: 2px 5px; } /* << */ /* >> photogallery elements table mode*/ table.povMain { width: 100%; margin-bottom: 12px; } table.povMain caption { text-align: left; font-size: 13px; font-weight: bold; margin-bottom: 10px; } table.povMain table.tableMode { } table.povMain table.tableMode td.iouter { padding: 10px; } /* << */ /* >> photogallery elements float mode*/ table.povMain ul { margin: 0; padding: 0; } table.povMain ul li { display: block; float: left; } table.povMain ul li { padding: 10px; } /* << */ /* >> Photogallery popup body elements*/ #leftPrevBar, #rightPrevBar { position: absolute; top: 0; left: 0; z-index: 2; height: 100%; width: 10px; background: #ccc; } #leftPrevBar[id="leftPrevBar"], #rightPrevBar[id="rightPrevBar"] { position: fixed; width: auto; } #rightPrevBar { left: auto; right: 0; z-index: 3; } #leftDes1, #rightDes1 { height: 100%; border-right: 1px solid #669; } #rightDes1 { border-right: none; border-left: 1px solid #669; } #cImgOuter1 { padding-left: 200px; padding-right: 200px; height: 100%; background: #999; } #cImgOuter1[id="cImgOuter1"] { padding: 0; } #cIOuter2 { padding-left: 30px; padding-right: 30px; } #cIOuter2, #cIOuter3, #cIOuter4 { height: 100%; } /* << */ /* >>Photogallery popup content elements */ div.thumb { padding: 15px; } div.thumb div.thbInner { text-align: center; vertical-align: middle; } div.thumb div.thbInner img { } div.thumb div.thbDes1, div.thumb div.thbDes2, div.thumb div.thbDes3, div.thumb div.thbDes4 { } div.thumb div.thbDes3 { padding: 5px; background: #ccc; } div.thumb div.thbDes4 { } #cImgOuter1 div.image img { width: 100%; } #cImgOuter1 div.image div.imgD3 { text-align: center; padding: 20px; } #cImgOuter1 div.title h1 { padding-left: 20px; padding-right: 20px; font-size: 16px; margin-bottom: 4px; } #cImgOuter1 div.desc p { padding-left: 20px; padding-right: 20px; } /* << */ /* >> Lightbox */ #lightbox { position: absolute; top: 40px; left: 0; width: 100%; z-index: 999; text-align: center; line-height: 0; } #lightbox a img { border: none; } #outerImageContainer { position: relative; background-color: #fff; width: 250px; height: 250px; margin: 0 auto; } #imageContainer { padding: 10px; } #loading { position: absolute; top: 40%; left: 0%; height: 25%; width: 100%; text-align: center; line-height: 0; } #hoverNav { position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 997; } #imageContainer>#hoverNav { left: 0; } #hoverNav a { outline: none; } #prevLink, #nextLink { width: 49%; height: 100%; background: transparent url(../xist4c/web/krusekopf/01/thirdParty/lightbox2_02/img/blank.gif) no-repeat; /* Trick IE into showing hover */ display: block; } #prevLink { left: 0; float: left; } #nextLink { right: 0; float: right; } #prevLink:hover, #prevLink:visited:hover { background: url(../xist4c/web/krusekopf/01/thirdParty/lightbox2_02/img/prevlabel.gif) left 15% no-repeat; } #nextLink:hover, #nextLink:visited:hover { background: url(../xist4c/web/krusekopf/01/thirdParty/lightbox2_02/img/nextlabel.gif) right 15% no-repeat; } #imageDataContainer { font: 10px Verdana, Helvetica, sans-serif; background-color: #fff; margin: 0 auto; line-height: 1.4em; } #imageData { padding:0 10px; } #imageData #imageDetails { width: 70%; float: left; text-align: left; } #imageData #caption { font-weight: bold; } #imageData #numberDisplay { display: block; clear: left; padding-bottom: 1.0em; } #imageData #bottomNavClose { width: 105px; float: right; padding-bottom: 0.7em; } #overlay { position: absolute; top: 0; left: 0; z-index: 998; width: 100%; height: 500px; background-color: #000; filter:alpha(opacity=60); -moz-opacity: 0.6; opacity: 0.6; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } * html>body .clearfix { display: inline-block; width: 100%; } * html .clearfix { /* Hides from IE-mac \*/ height: 1%; /* End hide from IE-mac */ } /* << */ /* >> Mailform */ .mailForm { width: 100%; margin-left: 15px; } .mailForm label { display: block; clear: both; margin: 5px 0 2px 0; } .mailForm input, .mailForm textarea { width: 100%; border: 1px solid #ccc; background: #F4F3EA; font-size: 12px; } .mailForm input.button { width: auto; background: transparent; } .mailForm input.submit { width: auto; margin-top: 20px; font-weight: bold; } /* << */ /* Sonstiges */ /* Designtabelle */ table.destable td, table.destable p, table.destable ul, table.destable li, table.destable a { font-size: 11px; } table.destable a { color: #5C4714; } table.destable a:hover { color: #992534; } /* Logos */ div.co_logos div.cont { text-align: center; } div.co_logos img { margin: 3px 12px; } /* << */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/multiple-values.css0000644000175000017500000000003500000000000023335 0ustar00kovidkovidfoo { a: 1, 2 3, bar 4 } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/page_test.css0000644000175000017500000000005100000000000022156 0ustar00kovidkovid@page pageStyle { size: 21.0cm 29.7cm; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/sample_5.css0000644000175000017500000004311600000000000021721 0ustar00kovidkovid*{border:0} *,#columns,#rss ul,#rss li,#video-frame,.video-right .mootabs_title,.post,.box,p.wp-caption-text,.widget ul li,table#wp-calendar,.wp-caption img,.wp-caption p.wp-caption-text{margin:0} *,select,#nav1 ul,#nav1,#nav1,#nav2 ul,#nav2,#nav2,#search .btn,#header,.video-right .mootabs_title,.box,.post-alt h2,ol.commentlist,table#wp-calendar,.wp-caption img{padding:0} body{font:12px arial,helvetica,sans-serif /18px;background:#333;background:#333 url(http://www.ng4a.com/wp-content/uploads/2010/09/9090981.png) repeat-y scroll center top} a,#search input,#header h1 a,.video-right h2,.video-right .mootabs_title li,.post h3 a,.post-alt h2,.post-alt h3,.post-alt h2 a,.post-alt h3 a,h3.posted a,.comments a,.navigation a,#archivebox h2,#archivebox a,.archivefeed a,.arclist h2,.singletags a,.entry blockquote,p.wp-caption-text,h2.commh2,ul.wooTabs li,.widget ul li a,table#wp-calendar a{color:#000} a,#nav1 li a:hover,#nav2 li a:hover,.video-right .mootabs_title li,ul.wooTabs li a:hover,ul.wooTabs li a.selected{text-decoration:none} a:hover,.singletags a{text-decoration:underline} textarea,select,input{font:12px tahoma,arial,helvetica,sans-serif;border:#ddd 1px solid} textarea,select,input,#rss li,#video-frame,.box .post img,.postedbox,#tabs,#advert_125x125 img,.widget h3,.th{padding:5px} li,#nav1 ul,#nav1,#nav2 ul,#nav2,.video-right .mootabs_title,ol.commentlist{list-style:none} #page,.col1,.col2,.subcol,#nav,#nav-right,#nav2,#video-frame,.post,.box .post p,h3.posted a,.navigation,#archivebox,.arclist ul li,.entry ol li,ol.commentlist li,ol.commentlist .comment-meta,ol.commentlist li,.more_entries,#tabs,.inside,ul.wooTabs,ul.wooTabs li a:hover,ul.wooTabs li a.selected,.inside li,.inside li img.thumbnail,.inside li img.avatar,.fix,.hl-full,.ar,.th,table#wp-calendar a{font:11px tahoma,arial,helvetica,sans-serif} #page,#columns,#nav,#header,.video-left,.video-right,.box .post,.box .post img,.author_photo,.entry img,.inside,.inside li img.thumbnail,.inside li img.avatar,.th{background:#fff} #page{width:939pxpx;margin:4px 27px} #columns{padding:15px 15px 25px} #columns,.video-left h2,.post h3 a,.post-alt h2 a,.post-alt h3 a{font-family:Arial,Helvetica,sans-serif} .col1,.col2,#search,#topbanner,#topbanner img,#rss img,.video-right,.author_photo,.alignright,#sub img,.flickr_badge_image img,.fr,.th{float:left} .col1,.navigation,comment,#slider-holder,#slider-holder .slide,.slider-shelf,.shelf-content{width:595px} .col1,#nav1 a,#nav2 a{padding-right:15px} .col2{width:320px} .subcol{width:150px} .subcol h2,#rss h2{font-size:14px} .subcol h2{padding:0 0 10px} .subcol h2,#nav1 li a:hover,#slider-holder{color:#fff} #nav,.postedbox{height:32px} #nav{padding:2px 0 !important;margin:7px 15px 0 !important;border-bottom:1px solid #ddd;width:930px !important} #nav,#nav2,.postedbox{border-top:1px solid #ddd} #nav-left,#nav-right,#nav1 ul,#nav1,#nav1,#nav1 li,#nav2 ul,#nav2,#nav2,#nav2 li,#search input,#header h1,#rss ul,#video-frame,.video-left,.author_info,.navigation,.alignleft,comment,ol.commentlist li .avatar,.more_entries .wp-pagenavi,.more_entries .wp-pagenavi span.pages,.more_entries .wp-pagenavi .on,.more_entries .wp-pagenavi .current,.more_entries .wp-pagenavi a:visited,.more_entries .wp-pagenavi a:link,.more_entries .wp-pagenavi a,.more_entries .wp-pagenavi .extend,#tabs,ul.wooTabs li,ul.wooTabs li a,.inside li img.thumbnail,.inside li img.avatar,.hl-full,.blog,.fl,.gravatar{float:right} #nav-left,#nav-right,#header h1,.video-left,.video-right,.alignleft,.alignright,ul.wooTabs li{display:inline} #nav-left{width:600px} #nav-right{width:330px} #nav-right,.video-left p,.video-right h2{margin:0 !important} #nav-right,#search,.video-left p,.hl-full{padding:0 !important} #nav1,#nav1 li,#nav2,#search input,span.clicker,.wp-caption p.wp-caption-text{font-size:11px} #nav1 a,#nav2 a,#header h1 a,.video-right .mootabs_title li,img.centered,#tabs,ul.wooTabs li a,#slider-holder .slide-1,#slider-holder .slide a,#slider-holder .slide img,#slider-holder .slider-right,#slider-holder .slider-left,span.clicker,div.aligncenter,.aligncenter{display:block} #nav1 a,#nav2 a,.continue{padding-left:15px} #nav1 li,#nav2 li{line-height:30px} #nav1 li,a.more,.navigation a,ol.commentlist cite{font-weight:bold} #nav1 li,table#wp-calendar caption,table#wp-calendar td#prev,table#wp-calendar td#next{text-transform:uppercase} #nav1 li a:hover,#nav2 li a:hover,.more_entries .wp-pagenavi .extend{background:none} #nav1 li ul,#nav2 li ul,#slider-holder .slide,#slider-holder .slide img,#slider-holder img.full-mask,#slider-holder .slide-content,#slider-holder .slider-right,#slider-holder .slider-left,.slider-shelf,span.clicker,.shelf-content{position:absolute} #nav1 li ul,#nav1 li ul li,#nav2 li ul,#nav2 li ul li{width:15em} #nav1 li ul,#nav1 li.sfhover ul ul,#nav1 li:hover ul ul,#nav1 li.sfhover ul ul ul,#nav1 li.sfhover ul ul,#nav1 li:hover ul ul ul,#nav1 li:hover ul ul,#nav2 li ul,#nav2 li.sfhover ul ul,#nav2 li:hover ul ul,#nav2 li.sfhover ul ul ul,#nav2 li.sfhover ul ul,#nav2 li:hover ul ul ul,#nav2 li:hover ul ul{right:-999em} #nav1 li ul{padding-top:1px} #nav1 li.sfhover ul,#nav1 li:hover ul,#nav1 li li.sfhover ul,#nav1 li.sfhover ul,#nav1 li li:hover ul,#nav1 li:hover ul,#nav1 li li li.sfhover ul,#nav1 li li.sfhover ul,#nav1 li.sfhover ul,#nav1 li li li:hover ul,#nav1 li li:hover ul,#nav1 li:hover ul,#nav1 li.sfhover ul,#nav1 li:hover ul,#nav2 li.sfhover ul,#nav2 li:hover ul,#nav2 li li.sfhover ul,#nav2 li.sfhover ul,#nav2 li li:hover ul,#nav2 li:hover ul,#nav2 li li li.sfhover ul,#nav2 li li.sfhover ul,#nav2 li.sfhover ul,#nav2 li li li:hover ul,#nav2 li li:hover ul,#nav2 li:hover ul{right:auto} #nav1 li ul li,#nav2 li ul li,#search input,ol.commentlist li .even,ul.wooTabs li{background:#fff} #nav1 li ul li,#nav2 li ul li{border-bottom:1px solid #dedbd1;border-left:1px solid #dedbd1;border-right:1px solid #dedbd1;line-height:28px} #nav1 li.hover,#nav1 li:hover,#nav2 li.hover,#nav2 li:hover{position:static} #nav1 li ul ul,#nav2 li ul ul{margin:-29px 0 0 15em} #nav1 li ul ul{border-top:1px solid #dedbd1} #nav1 li.sfhover ul,#nav1 li:hover ul,#nav2 li.sfhover ul,#nav2 li:hover ul{z-index:1000} #nav2{margin:0 0 15px 15px !important;margin:0 0 7px 7px} #nav2,.full{width:930px} #nav2 a{border-left:1px solid #ddd} #nav2 li ul{z-index:99999} #search{height:27px;width:310px;margin:3px 0 0 !important} * html #search{margin-left:8px} #search input,#video-frame,.video-right .mootabs_title li,.box .post,.box .post img,.entry blockquote,.entry img,.more_entries .wp-pagenavi span.pages,.more_entries .wp-pagenavi .on,.more_entries .wp-pagenavi .current,.more_entries .wp-pagenavi a:visited,.more_entries .wp-pagenavi a:link,.more_entries .wp-pagenavi a,#tabs,ul.wooTabs li,.inside li img.thumbnail,.inside li img.avatar,#mpu_banner,#advert_125x125 img,.th,.wp-caption{border:1px solid #ddd} #search input{width:223px;margin:1px 0 0;padding:3px 4px 4px} #search .btn{height:22px;margin:1px 0 0 5px !important;width:69px} #search .btn,span.clicker{width:auto} #search .btn,.more_entries .wp-pagenavi .extend{border:none} #header{height:100px;margin:0 15px} #header h1{line-height:100px;font-size:24px} #header h1 a{height:93px;width:389px;font-size:28px} #topbanner{width:468px;padding:20px 0 0} #rss a,.video-right p,.postedbox,.more_entries .wp-pagenavi span.pages,.more_entries .wp-pagenavi .on,.more_entries .wp-pagenavi .current,.more_entries .wp-pagenavi a:visited,.more_entries .wp-pagenavi a:link,.more_entries .wp-pagenavi a,#footer{color:#666} #rss h2,#rss ul,.video-right,.video-right .mootabs_title li,.author_info,#archivebox,.entry blockquote,#tagcloud{padding:10px} #rss h2,.author_info h3,ol.commentlist p,#respond p{margin-bottom:10px} #rss li,.widget ul{border:none} #rss img{padding-right:10px} #rss img,.singletags{margin-top:5px} #video-frame,.more_entries .wp-pagenavi span.pages,.more_entries .wp-pagenavi .on,.more_entries .wp-pagenavi .current,.more_entries .wp-pagenavi a:visited,.more_entries .wp-pagenavi a:link,.more_entries .wp-pagenavi a{background:#eee} #video-frame{width:583px;height:auto} .video-left{width:285px !important} .video-left h2,#slider-holder img.full-mask{display:none} .video-right{width:273px;min-height:214px} .video-right h2{font:11px tahoma,arial,helvetica,sans-serif bold !important /normal} .video-right h2,.post h2,h2.commh2{padding:0 0 5px} .video-right p{font:11px tahoma,arial,helvetica,sans-serif /12px;padding:0 0 20px;margin:10px 0 0} .video-right h3{font:18px arial,helvetica,sans-serif normal !important bold /20px} .video-right h3,.archivefeed{padding-bottom:0} .video-right .mootabs_title{border-top:1px solid #FFF} .video-right .mootabs_title li{font:11px tahoma,arial,helvetica,sans-serif normal /10px;margin:0 0 5px} .video-right .mootabs_title li,ol.commentlist li.alt{background:#f7f7f7} .video-right .mootabs_title li,ul.wooTabs li,#slider-holder .slider-right,#slider-holder .slider-left,span.clicker,.shelf-content img{cursor:pointer} .video-right .mootabs_title li.active,.video-right .mootabs_title li:hover{background:#ddd url(images/ico-watch.gif) left center no-repeat !important} .post h2{font:18px arial,helvetica,sans-serif} .post h3{font:14px arial,helvetica,sans-serif} .post p,.post-alt p,.arclist h2{padding:0 0 15px} .post p{line-height:15px} .box,.postedbox,.more_entries .wp-pagenavi,#footer,.fix,.hl-full{clear:both} .box{height:1%} .box .post{width:288px} .box .post h2 a,#archivebox h2,#archivebox h3,#archivebox h4{color:#333} .box .post h2{font-size:16px} .box .post h2,.inside li img.thumbnail,.inside li img.avatar{padding:3px} .box .post h2,ol.commentlist p{line-height:20px} .box .post p{padding:5px 10px} .box .post img,.wp-caption{margin:10px} .box .post img{float:none} .post-alt,h2.commh2{margin:0 0 15px 0} .post-alt h2{font-size:22px;line-height:26px} .post-alt h3{font:15px arial,helvetica,sans-serif /18px} .post-alt h3,.post-alt h4{padding-bottom:5px} .post-alt h4{font:14px arial,helvetica,sans-serif /16px} .post-alt p.post_date,.comments a{font:11px tahoma,arial,helvetica,sans-serif normal} .post-alt p.post_date,table#wp-calendar td{color:#999} .post-alt p.post_date{padding:5px 0 10px} .postedbox{font:11px tahoma,arial,helvetica,sans-serif normal normal /15px} .postedbox,.author_info,#tabs,#mpu_banner{background:#eee} .postedbox,.archivefeed{margin-top:10px} .postedbox,.entry p,.ar{text-align:right} .box .post h3.posted{border-bottom:none} .author_info{width:573px;border:1px solid #ccc} .author_info,#archivebox,.arclist ul,.wp-caption img,.entry ol,.entry ul,comment,#mpu_banner,.widget,.between{margin-bottom:15px} .author_photo{margin:0 10px 0 0} .author_photo img{border:1px solid #666} .continue,.archivefeed a{font:11px tahoma,arial,helvetica,sans-serif bold} .continue{background:url(images/ico-arrow.gif) no-repeat left} .comments{padding-bottom:0 !important} .comments a{background:url("images/ico-comm.gif") no-repeat scroll left center transparent;padding:0 0 0 18px} .comments a{margin-right:5px} a.more{background:url(images/ico-arrow2.gif) no-repeat right;font-size:1.2em;padding:0 0 0 20px} a.more,.arclist ul li{color:#c0c0c0} .post-alt blog{background:url(http://gs-shoping.net/blog/wp-content/uploads/2010/04/item_list_back.gif) repeat-x right top} .navigation{margin-bottom:-30px;padding:15px 0} .navigation a{font-size:1em} .alignleft,.entry ul li{margin-right:15px} .alignright{margin-left:15px} img.wp-smiley{padding:0!important;border:none!important} img.centered,div.aligncenter,.aligncenter{margin-left:auto;margin-right:auto} #archivebox,ul.wooTabs li a{color:#000 !important} #archivebox h3 em,#archivebox h2 em,#archivebox h4 em,table#wp-calendar caption{font-weight:normal} .archivefeed a{background:url(images/ico-arcfeed.gif) center right no-repeat;height:20px;padding:3px 22px 1px 0} .arclist{width:260px;margin:0 0 25px;padding:5px 0 0} .arclist,.inside li,.flickr_badge_image img,.widget ul li,.hl3{border-bottom:1px solid #eee} .arclist ul li{padding:8px 10px 8px 0;border-top:1px solid #eee} .singletags a{font:11px tahoma,arial,helvetica,sans-serif normal !important} .entry p{font:11px tahoma,arial,helvetica,sans-serif /18px} .entry blockquote{font:12px tahoma,arial,helvetica,sans-serif italic;width:85%;margin:5px auto 15px} .entry blockquote,#footer{background:#f7f7f7} .entry blockquote p,.entry ol,.entry ul,ol.commentlist,#respond p{font:11px tahoma,arial,helvetica,sans-serif /20px} .entry blockquote p,.widget ul{padding:5px 0} .entry blockquote p{margin:5px 0} .entry img{padding:4px} p.wp-caption-text{font:11px tahoma,arial,helvetica,sans-serif italic;padding:0 0 15px 0} p.wp-caption-text,.ac,table#wp-calendar th,table#wp-calendar td,.wp-caption{text-align:center} .entry ol li ol,.entry ul li ul{margin-bottom:0;padding-top:3px} .entry ul li{list-style:circle inside !important} .entry ul li,.entry ol li{padding:3px 0} .entry ul li ul li{list-style:disc inside !important} .entry ol li{list-style:decimal inside !important} .entry ol li ol li{list-style:decimal-leading-zero inside !important} h2.commh2{font:1.5em tahoma,arial,helvetica,sans-serif bold !important} ol.commentlist{margin:10px 0 20px} ol.commentlist p{font-size:12px;margin-top:17px} ol.commentlist cite{font-style:normal} ol.commentlist li{padding:20px 10px 10px 10px;margin:10px 0 10px;padding:15px 10px 10px} ol.commentlist li.alt{padding:10px 10px 0 10px !important} ol.commentlist li.alt,ol.commentlist li,.widget select{border:1px solid #eee} ol.commentlist li .avatar,.gravatar{border:1px solid #000} ol.commentlist li .avatar{margin-left:10px} .commentlist li ul li,.gravatar{margin-right:10px} .commentlist li ul li{margin-left:-5px} ol.commentlist li .reply{padding:0 0 5px 0} .more_entries .wp-pagenavi a{text-decoration:none !important} .more_entries .wp-pagenavi a:hover,.more_entries .wp-pagenavi .on,.more_entries .wp-pagenavi .current{background:#9f9f9f !important} .more_entries .wp-pagenavi span.pages,.more_entries .wp-pagenavi .on,.more_entries .wp-pagenavi .current,.more_entries .wp-pagenavi a:visited,.more_entries .wp-pagenavi a:link,.more_entries .wp-pagenavi a{font:11px tahoma,arial,helvetica,sans-serif /13px;padding:4px !important;margin-right:4px} #tabs{width:308px !important;height:auto !important} .inside{width:293px !important;padding:5px !important;border:1px solid #ddd !important;margin:5px 0 0 !important} ul.wooTabs,.hl-full{width:100%} ul.wooTabs{padding:0 0 7px 0} ul.wooTabs li{font:11px tahoma,arial,helvetica,sans-serif bold /34px;margin:0 2px 0 0 !important} ul.wooTabs li a{font:11px tahoma,arial,helvetica,sans-serif /31px;padding:0 6px} ul.wooTabs li a:hover,ul.wooTabs li a.selected{color:#fff !important} #sub img{margin:0 8px 8px 0} .inside li{padding:8px} .inside li img.thumbnail,.inside li img.avatar{margin:0 0 0 8px} #mpu_banner{width:300px;height:250px;padding:9px} #advert_125x125{padding-left:14px} #advert_125x125 img{margin:0 14px 14px 0} #flickr .wrap,.textwidget{padding-top:15px} .widget ul{margin:0 0 10px 0} .widget ul li{padding:5px 5px 5px 0} .widget select{width:148px;margin:15px 0 0 0} #footer{font:11px tahoma,arial,helvetica,sans-serif /35px;height:35px;margin:0 15px 15px;padding:0 15px} #footer img{vertical-align:middle} .fix,.hl-full{height:1px} .fix{margin:0 0 -1px} .fix,#slider-holder,#slider-holder .slide{overflow:hidden} .hl{border-bottom:2px solid #eee} .hl-full{margin:0 0 15px} .hl2{border-bottom:3px solid #c0c0c0} .blog{width:100% !important} .between{height:2px} .th{margin:0 10px 5px 0} table#wp-calendar{border-collapse:collapse} table#wp-calendar caption,table#wp-calendar th{font-size:0.91667em;line-height:1.72727em;border-bottom:1px dotted #ddd} table#wp-calendar caption{letter-spacing:2px;background:#e5e5e5} table#wp-calendar th,table#wp-calendar td{width:26px} table#wp-calendar th{background:#f2f2f2} table#wp-calendar td{line-height:1.66667em} table#wp-calendar td#prev,table#wp-calendar td#next{width:78px;font-size:0.83333em;letter-spacing:1px} .jdGallery a{cursor:pointer !important} #slider-holder{position:relative;margin-bottom:20px} #slider-holder,#slider-holder .slide{height:270px} #slider-holder,#slider-holder .slide,#slider-holder .slide-content,.slider-shelf,span.clicker{background:#000} #slider-holder .slide,#slider-holder .slide-1,#slider-holder .slide img,#slider-holder img.full-mask,#slider-holder .slider-left,#slider-holder .slider-right,.slider-shelf,span.clicker,.shelf-content{top:0} #slider-holder .slide{left:595px;z-index:1} #slider-holder .slide-1,#slider-holder .slide img,#slider-holder img.full-mask,#slider-holder .slide-content,#slider-holder .slider-right{right:0} #slider-holder img.full-mask{z-index:0} #slider-holder .slide-content{top:270px;height:700px;width:575px;padding:10px 10px 0 10px} #slider-holder .slide-content,#slider-holder .slider-right,#slider-holder .slider-left,.slider-shelf,span.clicker{z-index:999} #slider-holder .slider-right,#slider-holder .slider-left{height:150px;width:120px;color:white} #slider-holder .slider-left{left:0;background:url('images/fleche1.png') no-repeat center bottom} * html #slider-holder .slider-left{background:url('images/fleche1.gif') no-repeat center left} #slider-holder .slider-right{background:url('images/fleche2.png') no-repeat center bottom} * html #slider-holder .slider-right{background:url('images/fleche2.gif') no-repeat center right} .slider-shelf{overflow:visible} span.clicker{right:120px;padding:2px 10px} .shelf-content{z-index:9999} .shelf-content img{border:1px solid #fff;margin:10px 10px 0 0} .shelf-title{padding:0 10px} .wp-caption{background:#f3f3f3;padding-top:4px;-moz-border-radius:3px;-khtml-border-radius:3px;-webkit-border-radius:3px;border-radius:3px} .wp-caption img{border:0 none} .wp-caption p.wp-caption-text{line-height:17px;padding:0 4px 5px} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/sample_7.css0000644000175000017500000001675600000000000021735 0ustar00kovidkovid/* CSS Document */ body { background:url(image/bg.gif) 0 0 repeat; /*background-color:#D1CFD0;*/ padding:0px; margin:0px; } /*table.outer-table{ background:url(image/bg.gif) 0 0 repeat; width:1024px; border:0; }*/ td a{margin:0; padding:0;} table.inner-table{ background:url(image/background-inner.jpg) 0 0 repeat-y; width:1004px; margin:0 10px; border:0; } p,img, input,span { padding:0px; margin:0px; } ul{ list-style-type:none; padding:0px; margin:0px; } .signup { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#FF8619; text-decoration:none; font-weight:bold; padding:0px 5px 0px 0px; text-decoration:underline; } .signup a{ font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#FF8619; text-decoration:none; font-weight:bold; padding:0px 5px 0px 0px; } .signup a:hover{ font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#000066; text-decoration:none; font-weight:bold; padding:0px 5px 0px 0px; } .rull { background-image:url(image/rull.jpg); background-repeat:no-repeat; } p.textlink { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:21px; color:#605E5F; font-weight:normal; padding:36px 0px 0px 2px; } p.textlink a{ font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:21px; color:#605E5F; text-decoration:none; } p.textlink a:hover { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; color:#FF8619; } .text_welcome { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:20px; color:#FF8619; text-decoration:none; font-weight:normal; } .text_welcome1 { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:20px; color:#3B65A5; text-decoration:none; font-weight:normal; } .soliborder{ border-top: solid #BFC8CF 1px; } .padding { padding-top:21px; padding-left:17px; padding-bottom:12px; } .text { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#000000; font-weight:normal; } .texte { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#0E4190; text-decoration:underline; font-weight:normal; } .texte a { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#0E4190; text-decoration:underline; font-weight:normal; } .texte:hover { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#FF8619; text-decoration:none; font-weight:normal; } .text3 { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#605E5F; text-decoration:none; font-weight:normal; } .text3{ list-style:inside disc; padding:0px 0px 5px 5px; margin-left:10px; } .text4 { list-style:circle inside; margin:5px 0px 0px 15px; } .text_orange { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#FF8619; text-decoration:none; font-weight:bold; } .bacground_map { background-image:url(image/map.jpg); background-repeat:no-repeat; } .padding2 { padding:8px 35px 118px 17px; } .text_2 { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#605E5F; text-decoration:none; font-weight:normal; } .text_orange2 { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#FF8619; text-decoration:none; font-weight:bold; } .bacground_map2 { background-image:url(image/map.jpg); background-repeat:no-repeat; } .padding3 { padding-top:23px 0px 8px 14px; } .text_blue { padding:23px 0px 8px 14px; font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:normal; color:#0E4190; text-decoration:none; font-weight:bold; } .text_blue a { padding:23px 0px 8px 14px; font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:normal; color:#0E4190; text-decoration:none; font-weight:bold; margin:0; padding:0; } .text_blue a:hover { padding:23px 0px 8px 14px; font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:normal; color:#0E4190; text-decoration:none; font-weight:bold; margin:0; padding:0; } /*.text_blue a:hover { padding:23px 0px 8px 14px; font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:normal; color:#0E4190; text-decoration:none; font-weight:bold; }*/ .menuh { height:30px; padding-top:9px; } .text_blue2 a{ font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:normal; color:#0E4190; text-decoration:none; font-weight:bold; } .text_blue2 a:hover{ font-family:Arial, Helvetica, sans-serif; font-size:12px; line-height:normal; color:#FF6600; text-decoration:underline; font-weight:bold; } .rightpannel_bg{ background-image:url(image/right_bg.gif); background-position:bottom; vertical-align:bottom; background-repeat:repeat-x; border:solid #E2964B 1px; background-color:#FFF; padding:5px; } .rightpannel_bg_last{ border-left:solid #BFC8CF 1px; border-right:solid #BFC8CF 1px; border-bottom:solid #BFC8CF 1px; background-color:#ffffff; padding:5px; } /*------------------------Button----------------------*/ .sup { float:right; margin:5px 0 0 0 } /*------------------------Button End-------------------*/ ul.link{ list-style-type:none; padding:12px 0px 10px 0px; } ul.link li{ background:url(image/icon.gif) no-repeat 0 5px; font:normal 12px/19px Arial, Helvetica, sans-serif "Trebuchet MS"; color:#605E5F; margin:0px 2px 0px 12px; } ul.link li span{ font:normal 12px/19px Arial, Helvetica, sans-serif "Trebuchet MS"; color:#605E5F; margin:0px 0px 0px 8px; } ul.link li span.align{ font:normal 12px/19px Arial, Helvetica, sans-serif "Trebuchet MS"; color:#605E5F; margin:0px 0px 0px 8px; } /*-----------------------------footer----------------------*/ .boackground_footr { background-image:url(image/footer-rull.gif); background-repeat:no-repeat; } .textsky { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:19px; color:#1B57B3; font-weight:normal; text-decoration:none; } .textsky span{padding:0 5px;} .textsky:hover { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:19px; color:#FF8619; font-weight:normal; text-decoration:none; } .text_ash { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:11px; line-height:19px; color:#605E5F; font-weight:normal; } .style1 {color: #FF9900} .text_3 { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#605E5F; text-decoration:none; font-weight:normal; padding: 0 0 12px 0; } .text-as { font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#000000; font-weight:normal; padding: 8px 15px 5px 8px; } .text-as1 { background:url(image/bullet.gif) left 10px no-repeat; font-family:Arial, Helvetica, sans-serif "Trebuchet MS"; font-size:12px; line-height:16px; color:#000000; font-weight:normal; padding: 8px 15px 5px 15px; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/simple.css0000644000175000017500000000046100000000000021501 0ustar00kovidkovid @import url("fineprint.css") print; @import url(bogus.css); @import url( bogus.css ); @media screen { @three-dee { @background-lighting { azimuth: 30deg; elevation: 190deg; } H1 { color: red } } h1 { foo: bar } h2 { thing: 1; whatsit: 2px } h3 { foo: 2 ! important } } h9 { } b1 & b2 { } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/single-color.css0000644000175000017500000000016100000000000022602 0ustar00kovidkovid@media aural { BLOCKQUOTE:after { content: url("beautiful-music.wav") } } a { content: url("other.wav") } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/slashcode.css0000644000175000017500000004432500000000000022164 0ustar00kovidkovidbody, div, form, #links ul li, #links ul, #topnav, img { font-size: 100%; } body { background: #fff; /* font: 85%/150% verdana, sans-serif; */ font: 84%/150% sans-serif; } * a { color: #006699; } a:visited { color: #6a6a6a; } #topnav { position: relative; height: 55px; margin: 5px 0 0 0; background: #f57300 url(/images/topnav-orange-bg.png) repeat-x left; clear: both; } #topnav div.search { position: absolute; top: 10px; right: 1em; text-align: right; white-space: nowrap; } div.search input { font-size: 93%; width: 11.25em; } div.search input.button { width: auto; margin-left: 5px; } div.search fieldset { border: none; margin: 0; padding: 0; } #topnav div.search fieldset legend { display: none; } /* layout */ #contents { min-width: 37em; } #slashboxes #contents ul { padding-left: 1.25em; margin-bottom: 20px; } body #index #articles { margin: 0 15.6em 1em .8em; position: relative; } /* Header */ #logo h1 a { width: 642px; height: 55px; outline: none; text-decoration: none; background: url("/images/logo-bp-orange4.png") no-repeat left top; } #slogan h2 { color: #555; font-family: sans-serif; font-weight: bold; font-size: 90%; margin: -1em 0 1em 0; } #slashboxes #contents ul { padding-left: 1.25em; margin-bottom: 20px; } /* links */ #links { background: #fff; } #links div.block { background: #fff url("/images/cbl.gif") bottom left no-repeat; } #links div.title { background: #f60 url("/images/ctl_grey.png") top left no-repeat; } #links h4 { font-weight: bold; font-size: 84%; font-family: sans-serif; } #links div.content { padding: .3em .3em .6em .3em; font-size: 85%; line-height: 140%; position: relative; border: 1px #ddd solid; } /* Slashboxes (right sidebar) */ div#slashboxes { float: right; width: 14.5em; margin-left: 1.25em; } div#slashboxes div.block { margin-bottom: 1.25em; font-size: 88%; } div#slashboxes div.block div.title h4 { background: #777 url(//images.slashdot.org/block-title-right.png) no-repeat right top; padding: .1em .1em .1em .8em; color: #fff; font-size: 100%; } div#slashboxes div.block div.title h4 a { color: #fff; } div#slashboxes div.block div.title h4 a:visited { color: #fff; } div#slashboxes div.block div.content { padding: .4em; background: #fff url(//images.slashdot.org/slashbox-bottom-left.png) no-repeat left bottom; } div#slashboxes div.block div.content p { margin: 0;} div#slashboxes div.block div.content ul { margin: 0; padding: 0; list-style-image: none; } div#slashboxes div.block div.content ul li { padding: .1em .1em .3em .5em; /* list-style-image: url(/images//bullet.gif); */ list-style: none; border-top: 1px solid #ddd; } div#slashboxes div.block div.right { padding: .5em .8em .6em .8em; } /* Links (left sidebar) */ div#links { float: left; width: 7.1em; padding-bottom: 10px; margin-right: 0; } div#links div.block div.content ul li a { display: block; padding: .3em .2em .3em .2em; font-size: 100%; border-top: 1px solid #ddd; } div#links div.block div.content ul li a:hover {background: #ef8218 url(//images.slashdot.org/link-arrow.gif) no-repeat right center; color: #fff;} div#links div.block div.content, div#slashboxes div.block div.content { background: #eee; } div#links div.block div.title, div#slashboxes div.block div.title, div#links div.block { /* background: #ef8218 url(/images/article-title-orange-bg.png) repeat-x left top; */ background: #777; } div#links div.block div#sitebox-title, div#links div.block div#navigation-title { background: url(//images.slashdot.org/block-title-right.png) no-repeat right top; } div#links div.block div.content ul { border-bottom: 1px solid #fff; } div#links div.block div.content ul li a { border-top: 1px solid #fff;} /* General */ .generaltitle { background: url("/images/article-title-orange-bg.png") #ef8218 xrepeat-x; } .generaltitle div.title { background: #ef8218; } .generaltitle h3 { color: #fff; font-family: sans-serif; font-size: 120%; /* 16px */ font-weight: bold; } .generaltitle h3 a, .generaltitle h3 a:visited { color: #fff; font-family: sans-serif; font-size: 100%; font-weight: bold; text-decoration: none; } .generalbody { background: #fff; border: 1px solid #eee; border-top: none; } /* articles */ .article div.title { background: #ef8218 url(/images/article-title-orange-bg.png) repeat-x left top; } #journal div.title { background: #ef8218 url(/images/article-title-orange-bg.png) repeat-x left top; } .article div.title h3 { background: url(/images/article-title-left-orange.png) no-repeat left top; color: #fff; font-family: sans-serif; font-size: 120%; } /* titulo secciones */ .article div.title h3 a, .article div.title h3 a:visited { color: #006699; text-decoration: underline; } #journal .article h3 { background: url(//article-title-left-orange.png) no-repeat left top; color: #fff; font-family: sans-serif; font-size: 100%; } .article .details { font-size: 84%; padding: .2em .7em .5em .7em; font-weight: normal; font-family: sans-serif; border: 1px solid #eee; line-height: 130%; /* background: #ddd url("/images/bg_details.gif") repeat-x left top; */ background: #ddd url(//images.slashdot.org/article-details-bg.png) repeat-x left top; } .article .body { font-size: 100%; /* border: 1px solid #eee; */ border-top: none; background: #fff url("/images/bg_white.gif") repeat-x; } div.article div.intro em { display: block; padding: 0 0 0 .85em; margin: .25em 0 .6em 0; font-style: normal; border-left: 3px solid #ddd; } div.article div.intro i a { font-weight: normal; } div.storylinks { margin: 0 0 1.5em 0;} div.storylinks * { line-height: 110%; } div.storylinks div { margin: 0; padding: 0; background: url(//images.barrapunto.com/storylinks-bg.png) repeat-x left bottom; } div.storylinks div ul { margin: 0; padding: .75em 12em .75em .6em; background: url(//images.barrapunto.com/storylinks-right.png) no-repeat right bottom; } div.storylinks ul li { display: inline; padding: 0; list-style: none; background: none; border: none; } div.storylinks ul li.comments { width: 11em; right: 0; position: absolute; margin: -.1em 0; padding: .1em 1em .2em 1em; text-align: right; text-shadow: #000 0 0 0; background: none; } div.storylinks ul li:before {content:"|"; color: #ccc;} div.storylinks ul li.more:before, div.storylinks ul li.comments:before {content:" ";} /* Footer */ #footer { font-family: sans-serif; margin-top: 1.25em; background: #e6e6e6 url(//images.slashdot.org/sections-bg.png) repeat-x left bottom; clear: both; } #footer em { font-size: 85%; float: right; width: 69%; } #footer .search { float: left; width: 15.25em; padding: 1.35em 0 0 1.25em; white-space: nowrap; } #footer legend, #footer fieldset, #footer label { margin: 0; padding: 0; } .checkbox {padding: 0 0 0 0; float: left; } #footer .search input { margin: 0; } #footer .copyright { padding: .85em 1.25em; font-size: x-small; text-align: center; color: #000; } #footer .admin li { border-left: 1px solid #eee; margin: 0; padding: 0; display: inline; list-style: none; } /* Poll */ .barColor { background: #f60; } /* Bottom Nav */ .btmnav { color: #eee; font-size: 85%; } .btmnav ul li a { color: #006699; } .btmnav ul li a:visited { color: #6a6a6a; } /* Journal */ .journaldate { position: relative; } .generalbody #journalgeneric .article { height: auto; margin: 0 0 1em 0; } #journalgeneric div.intro em {display: inline; font-style: italic; border: none; margin: 0; padding: 0;} #journalgeneric div.intro em a {font-style: italic;} #journalgeneric div.storylinks ul li { margin: 0; padding: 0; border: none; } #journalgeneric div.storylinks ul li a { padding: 0 .4em 0 1em; border-left: 1px solid #000; } #journalgeneric div.storylinks ul li.edit a, #journalgeneric div.storylinks ul li.discussion a, #journalgeneric div.storylinks ul li.journalid a { border: none; } #journalslashdot .journaldate {font-weight: bold;} #journalslashdot .title { margin: 0 0 0 .6em; padding: 0; float: left;} #journalslashdot h3 { font-size: 65%; margin: 0; padding: 0; font-family: geneva,verdana,sans-serif;} #journalslashdot .details { float: left; font-style: italic; font-size: 65%; font-family: geneva,verdana,sans-serif; background: transparent; } #journalslashdot .intro { padding: 1em 0 2em 3.7em;} #journalslashdot div.storylinks { margin: 0; padding: 0; background: transparent;font-size: 65%;} #journalslashdot div.storylinks ul { margin: 0; padding: 0; background: transparent;} #journalslashdot div.storylinks ul li { margin: 0; padding: 0; border: none; font-family: geneva,verdana,sans-serif; } #journalslashdot div.storylinks ul li a { padding: 0 .4em 0 1em; border-left: 1px solid #000; } #journalslashdot div.storylinks ul li.edit a, #journalslashdot div.storylinks ul li.discussion a, #journalslashdot div.storylinks ul li.journalid a { border: none; } #journalslashdot .journalpage {font-size: 65.5%; font-family: geneva,verdana,sans-serif; text-align: right;} #journalgrey .journaldate { font-weight: bold; background: #eee; } #journalgrey .details { float: left; font-weight: bold; background: #eee; padding: 0 0.6em 0 0; } #journalgrey .title { background: #eee; padding: 0 0 0 0.6em; } #journalgrey .title h3 { background: #eee; } #journalgrey { border: 3px solid #999; padding: 3px 0 1em;} #journalgrey div.storylinks, #journalblue div.storylinks { background: transparent; } #journalgrey div.storylinks ul, #journalblue div.storylinks ul { background: transparent; } #journalgrey .body, #journalblue .body {min-height: 60px; padding: 0 .5em; } #journalblue div.storylinks ul li, #journalgrey div.storylinks ul li { border: none; margin: 0; padding: 0; } #journalblue .journaldate { background: #369; font-weight: bold; padding: 3px; } #journalblue { border: 3px solid #000; } #journalblue .details { float: left; margin: 0 0.6em 0 0.3em; font-weight: bold; } #journalblue .title h3 { margin: 0; padding: 0; font-family: serif; } .generalbody ul.menu { padding: .5em 0; overflow: auto; } #usermenu ul.menu { padding: .5em 0; overflow: auto; } /* Forms */ #journal input.button, #admin input.button { width: auto; } /* Submit */ #submit .message { margin: 0 0 1em 0; padding: 0; } /* Related */ div.briefarticle {position: relative;} div.briefarticle a span {text-indent: -5000px; position: relative; float: left;} div.briefarticle a span.expanded {width: 15px; background: url(//images.barrapunto.com/login-arrow-expanded.gif) no-repeat 0 50%; } div.briefarticle a span, div.briefarticle a span.condensed {width: 15px; background: url(//images.barrapunto.com/login-arrow-collapsed.gif) no-repeat 0 50%; } div div.briefcomment { padding-left: 4em; padding-top: 0px } /* User section menu */ div#user-section { margin-bottom: 1.25em; white-space: nowrap; font-size: 90%; background: #ef8218 url(/images/userbar-title-orange-bg.png) repeat-x left bottom; } div#user-section * { line-height: 100%; } div#user-section div.content { overflow: hidden; padding: 0; } div#user-section ul { float: left; } div#user-section ul { padding: .3em 0 .1em .6em; margin: 0; } div#user-section ul li { display: inline; list-style: none; } div#user-section ul li.begin {font-weight: bold; padding-left: 0; } div#user-section ul li.name a { font-weight: bold; } div#user-section ul li.name { padding-left: 0; } div#user-section ul li a { font-size: 100%; text-decoration: none; padding: 0 .3em; color: #fff; } div#user-section ul li a:visited { color: #fff; } div#user-section ul li:before {content:"|"; color: #ddd;} div#user-section ul li:first-child:before {content:" ";} div#user-section ul li a#loginlink { padding-left: 1.2em; background: url(//images.slashdot.org/login-arrow-collapsed.gif) no-repeat 0 50%; } div#user-section ul#user-utils { float: right; padding: .3em .6em .3em 0; } div#user-section ul li a#loginlink.expanded { background-image: url(//images.slashdot.org/login-arrow-expanded.gif); } div#user-section div#loginform { clear: both; overflow: hidden; height: 0; width: 100%; padding: 0; margin: 0; } /* Curse the phantom */ @media all and (min-width:0px) { div#user-section div#loginform { height: auto; } head~body div#user-section div#loginform { height: 0; } } div#user-section div#loginform p {display: none;} div#user-section div#loginform form { padding: .75em 0 .25em 0; } div#user-section div#loginform form input { width: 12em; margin-right: 1em;} div#user-section div#loginform form input.button { width: auto; margin-right: 0; } div#user-section div#loginform form label.checkbox { margin-right: 1em; } div#user-section div#loginform form label {display: inline;} div#user-section div#loginform form label.checkbox input { width: auto; margin-right: .25em; } /* System messages */ .indexhead {padding: 0; border-bottom: 1px solid #888; background: #ddd url("//images.barrapunto.com/article-details-bg.png") bottom repeat-x; margin: 0 15.6em 1em .8em; position: relative; } .indexhead div.content { padding: 0; margin: 0; background: url(/images/block-title-left-messages.png) no-repeat left top; } .indexhead div.content p {font-size: 85%; margin: 0; padding: .3em 1.5em; color: #000;} .indexhead div.content a {color: #036;} .indexhead div.content a:visited {color: #6a6a6a;} /* Ads */ .ad1 { width: 750px; background-color: #eee; } .fad1 { width: 468px; margin: 0 240px 0 15px; } .fad2 { float: right; width: 240px; margin: -65px 15px 0 5px; } div#ad2 { text-align: center; margin: 1em 0 1em 0; } div#ad3 { text-align: center; } /* Tags */ div.tags { margin: 1em 0 0 0; padding: 0; font-size: 93%; line-height: 100%; background: #666 url(//images.slashdot.org/block-title-bg.png) repeat-x; } div.tags div.tagtitleclosed, div.tags div.tagtitleopen { margin: 0; padding: .5em 0 .5em 20px; position: relative; background: #fff; } div.tags div.tagtitleopen {color: #fff; background: url(//images.barrapunto.com/block-title-right.png) no-repeat top right; } div.tags div.tagtitleopen a {color: #fff;} div.tags div.tagtitleclosed a { text-decoration: none; } div.tags div.tagtitleclosed .tagmenu a { text-decoration: underline; } div.tags div.tagtitleopen span.tagsheader, div.tags div.tagtitleclosed span.tagsheader {text-indent: -5000px; position:absolute; left: 10px; } div.tags div.tagtitleopen span.tagsheader {width: 15px; background: url(//images.barrapunto.com/block-arrow-expanded.gif) no-repeat 0 50%; } div.tags div.tagtitleclosed span.tagsheader {width: 15px; background: url(//images.barrapunto.com/login-arrow-collapsed.gif) no-repeat 0 50%; } div.tags div.tagtitleopen i a, div.tags div.tagtitleclosed i a {background: transparent;} div.tags div.tagbody { background: #ddd url(//images.barrapunto.com/article-details-bg.png) repeat-x; padding: .85em 0 0 .85em; color: #666; border: #ccc 1px solid; border-top: none; } div.tags div.tagbody input { width: 50%; margin: .5em 0; } div.tags div.tagbody input.button { width: 5em } div.tags div.tagshide { display: none } /* Misc */ pre, code { font-size: 93%; } .tb_col { background: #eee;} .secpref_master td { border-bottom: solid 2px #069;} .secpref_nexus_row { color: #aaa; background: #ccc; } #sectionprefs_message { background: #066; font-size: 120%; color: #fff; font-weight: bold; text-align: right; } #sectionprefs_hdr {text-align: right; background: #069; font-size: 120%; color: #fff; } #sectionprefs_hdr a {float: left; color: #fff;} .popup div.title span, .popup div.title span a { display: none} #vendorStory-26-popup {width: 18.5em; border: 2px solid #c5d1a6} #vendorStory-26-contents {background: #DBE8B8} #vendorStory-26-popup div.title h3 { background: #6d745f url(//images.slashdot.org/vendors-slc.gif) no-repeat top left; } #vendorStory-26-popup div.title h3 a {color: #fff} #vendorStory-26-popup div.title span a, #vendorStory-26-popup div.title span { color: #DBE8B8; } #vendorStory-26-contents a { color: #6d745f; } #vendorStory-26-popup div.details {background: #DBE8B8} #vendorStory-26-title {background: #6D745F; padding: .3em} #sponsorlinks span {color: #069; text-decoration: underline} #contents {margin-top: 2em;} #vendorStory-26-popup #art1, #vendorStory-26-popup #art2, #sponsorlinks #slink1, #sponsorlinks #slink1 .comments {margin: 0} .popup iframe { position:absolute; top: -1px; left:-3px; z-index:-1; width:18.5em; height: 100%; border: none; opacity: 0; } #sectionprefs_hdr span a {float: none; color: #fff; font-size: 10px; font-weight: bold; text-decoration: none;} #sectionprefs_hdr span { margin: -.3em 0; padding: 0 4px; height: 11px; width: 11px;} .ps_23, .ps_22, .ps_26 { display: none; } .curstory { border-top: solid 2px #069; } .popup { border: solid 2px #069; } .popup .data { font-size: 100% } .popup-title {text-align: left; background: #069; font-size: 100%; color: #fff; padding-right: 4em; } .popup-title .buttons { position: absolute; right: 0.2em; } .popup-title .buttons span a {float: none; color: #fff; font-size: 10px; font-weight: bold; text-decoration: none;} .popup-title .buttons span { margin: -.3em 0; padding: 0 4px; height: 11px; width: 11px;} .popup-message { background: #069; color: #fff; } .popup-title a, .popup-message a { color: #fff } .popup-contents { background: #ccc; font-size: 80%; padding: 5px; } .popup-message { background: #069; font-size: 100%; color: #fff; font-weight: bold; text-align: right; } #sectionprefs-contents { background: #fff } #subscribe div.generaltitle div.title {margin-bottom: 0;} #st-display table { background: #069; color: #fff; } blockquote, .quote { margin-bottom: .75em; padding-left: .75em; color: #555; border-left: 3px solid #ddd; position: relative; display: block; } blockquote * { font-style: normal; } .comment > .oneline blockquote { border: 0; padding: 0; margin: 0; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/t-HACKS.css0000644000175000017500000000124500000000000021303 0ustar00kovidkovid/* http://centricle.com/ref/css/filters/?highlight_columns=true */ @import 'styles1.css'; @import "styles2.css"; @import url(styles3.css); @import url('styles4.css'); @import url("styles5.css"); @import "null?\"\{"; @import "styles6.css"; a { color: red; voice-family:"\"}\""; voice-family:inherit; color: green; } b { color: red; c\olor:green; } c { color: red; /*/*/color:green;/* */ } /* NS4 only, should not work: */ d { color: green; /*/*//*/color:red;/* */ } e1{content:"\"/*"} e2{color:green} /* THIS SHOULD WORK??? */ /* \*/ div{color:green} /* */ div#test { } head:first-child+body div { } @media all{/* rules */} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/test-unicode.css0000644000175000017500000002206400000000000022616 0ustar00kovidkovidÿþ@charset "UTF-16"; /* * This is a test file only - it doesn't represent a usable style sheet, * it is meant only to test the lexer and parser. */ @import url(foo.css); @import "bar.css"; @import url("fineprint.css") print; @import url("bluish.css") projection, tv; @import url(http://www.steadystate.com/primary.css) screen, tv; @import "sounds_good.css" aural; E[class~="hipster"][thing~="bob"]#myid { att1: hi; foo: bar; } E:lang(c)#unique E.hipster#myid { att1: hi; foo: bar; } @media tv, radio { @page { foo: bar; } H1 { fillcolor: blue } } /* @import url(bad_import.css); */ H1, H2 {color:green;background-color:blue} H1, H2 {color: green;background-color: blue} H3, H4 & H5 { color: red; a: red; b: red; c: red; d: red; e: red; f: red; g: red; h: red; i: red; j: red; k: red; l: red; m: red; n: red; o: red; } H6 { color: black } H7 & H8 { color: red; @page { foo: bar } } P[example="public class foo\ {\ private int x;\ \ foo(int x) {\ this.x = x;\ }\ \ }"] { color: red } H1 { color: red; font-style: 12pt } /* Invalid value: 12pt */ P { color: blue; font-vendor: any; /* Invalid prop.: font-vendor */ font-variant: small-caps } EM EM { font-style: normal } H1 { color: red; rotation: 70minutes } IMG { float: left } /* correct CSS2 */ IMG { float: left here } /* "here" is not a value of 'float' */ IMG { background: "red" } /* keywords cannot be quoted in CSS2 */ IMG { border-width: 3 } /* a unit must be specified for length values */ @three-dee { @background-lighting { azimuth: 30deg; elevation: 190deg; } H1 { color: red } } H1 { color: blue !important } P {counter-increment: par-num ! important } H1 {counter-reset: par-num} P:before {content: counter(par-num, upper-roman) ". "} EM { color: #f00 } /* #rgb */ EM { color: #ff0000 } /* #rrggbb */ EM { color: rgb(255,0,0) } /* integer range 0 - 255 */ EM { color: rgb(100%, 0%, 0%) } /* float range 0.0% - 100.0% */ FOO { content: "Argos\t\n" } R1 { a: U+aaaaa-ffffff } R2 { a: U+?????? } R3 { a: U+f????? } R4 { a: U+fffff? } R5 { a: U+ffffff } * {} /* a=0 b=0 c=0 -> specificity = 0 */ LI {} /* a=0 b=0 c=1 -> specificity = 1 */ UL LI {} /* a=0 b=0 c=2 -> specificity = 2 */ UL OL+LI {} /* a=0 b=0 c=3 -> specificity = 3 */ H1 + *[REL=up]{} /* a=0 b=1 c=1 -> specificity = 11 */ UL OL LI.red {} /* a=0 b=1 c=3 -> specificity = 13 */ LI.red.level {} /* a=0 b=2 c=1 -> specificity = 21 */ #x34y {} /* a=1 b=0 c=0 -> specificity = 100 */ DIV > P:first-child { text-indent: 0 } HTML:lang(fr) { quotes: '« ' ' »' } HTML:lang(de) { quotes: '»' '«' '\2039' '\203A' } :lang(fr) > Q { quotes: '« ' ' »' } :lang(de) > Q { quotes: '»' '«' '\2039' '\203A' } H1 { bogus: alpha & beta } H2 { foo: inherit; ;; } /* * Counters and stuff */ P {counter-increment: par-num} H1 {counter-reset: par-num} P:before {content: counter(par-num, upper-roman) ". "} H1:before { content: "Chapter " counter(chapter) ". "; counter-increment: chapter; /* Add 1 to chapter */ counter-reset: section; /* Set section to 0 */ } H2:before { content: counter(chapter) "." counter(section) " "; counter-increment: section; } OL { counter-reset: item } LI { display: block } LI:before { content: counters(item, "."); counter-increment: item } H1:before { content: counter(chno, upper-latin) ". " } H2:before { content: counter(section, upper-roman) " - " } BLOCKQUOTE:after { content: " [" counter(bq, hebrew) "]" } DIV.note:before { content: counter(notecntr, disc) " " } P:before { content: counter(p, none) } /* * Content stuff (w/attr function) */ @media aural { BLOCKQUOTE:after { content: url("beautiful-music.wav") } } IMG:before { content: attr(alt) } H1:before { display: block; text-align: center; content: "chapter\A hoofdstuk\A chapitre" } /* * Clip (w/rect function) */ P { clip: rect(5px, 10px, 10px, 5px); } P { clip: rect(5px, -5px, 10px, 5px); } P { font-size: 12pt; line-height: 12pt } P:first-letter { font-size: 200%; font-style: italic; font-weight: bold; float: left; bogus: foo(p1, p2, "hello", url(primary.css)) } SPAN { text-transform: uppercase } P { color: red; font-size: 12pt } P:first-letter { color: green; font-size: 200% } P:first-line { color: blue } P { text-indent:-.5in; text-indent:-3.5in }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/test.css0000644000175000017500000001042100000000000021164 0ustar00kovidkovid /* * This is a test file only - it doesn't represent a usable style sheet, * it is meant only to test the lexer and parser. */ @import url(foo.css); @import "bar.css"; @import url("fineprint.css") print; @import url("bluish.css") projection, tv; @import url(http://www.steadystate.com/primary.css) screen, tv; @import "sounds_good.css" aural; E[class~="hipster"][thing~="bob"]#myid { att1: hi; foo: bar; } E:lang(c)#unique E.hipster#myid { att1: hi; foo: bar; } @media tv, radio { @page { foo: bar; } H1 { fillcolor: blue } } /* @import url(bad_import.css); */ H1, H2 {color:green;background-color:blue} H1, H2 {color: green;background-color: blue} H3, H4 & H5 { color: red; a: red; b: red; c: red; d: red; e: red; f: red; g: red; h: red; i: red; j: red; k: red; l: red; m: red; n: red; o: red; } H6 { color: black } H7 & H8 { color: red; @page { foo: bar } } P[example="public class foo\ {\ private int x;\ \ foo(int x) {\ this.x = x;\ }\ \ }"] { color: red } H1 { color: red; font-style: 12pt } /* Invalid value: 12pt */ P { color: blue; font-vendor: any; /* Invalid prop.: font-vendor */ font-variant: small-caps } EM EM { font-style: normal } H1 { color: red; rotation: 70minutes } IMG { float: left } /* correct CSS2 */ IMG { float: left here } /* "here" is not a value of 'float' */ IMG { background: "red" } /* keywords cannot be quoted in CSS2 */ IMG { border-width: 3 } /* a unit must be specified for length values */ @three-dee { @background-lighting { azimuth: 30deg; elevation: 190deg; } H1 { color: red } } H1 { color: blue !important } P {counter-increment: par-num ! important } H1 {counter-reset: par-num} P:before {content: counter(par-num, upper-roman) ". "} EM { color: #f00 } /* #rgb */ EM { color: #ff0000 } /* #rrggbb */ EM { color: rgb(255,0,0) } /* integer range 0 - 255 */ EM { color: rgb(100%, 0%, 0%) } /* float range 0.0% - 100.0% */ FOO { content: "Argos\t\n" } R1 { a: U+aaaaa-ffffff } R2 { a: U+?????? } R3 { a: U+f????? } R4 { a: U+fffff? } R5 { a: U+ffffff } * {} /* a=0 b=0 c=0 -> specificity = 0 */ LI {} /* a=0 b=0 c=1 -> specificity = 1 */ UL LI {} /* a=0 b=0 c=2 -> specificity = 2 */ UL OL+LI {} /* a=0 b=0 c=3 -> specificity = 3 */ H1 + *[REL=up]{} /* a=0 b=1 c=1 -> specificity = 11 */ UL OL LI.red {} /* a=0 b=1 c=3 -> specificity = 13 */ LI.red.level {} /* a=0 b=2 c=1 -> specificity = 21 */ #x34y {} /* a=1 b=0 c=0 -> specificity = 100 */ DIV > P:first-child { text-indent: 0 } HTML:lang(fr) { quotes: '« ' ' »' } HTML:lang(de) { quotes: '»' '«' '\2039' '\203A' } :lang(fr) > Q { quotes: '« ' ' »' } :lang(de) > Q { quotes: '»' '«' '\2039' '\203A' } H1 { bogus: alpha & beta } H2 { foo: inherit; ;; } /* * Counters and stuff */ P {counter-increment: par-num} H1 {counter-reset: par-num} P:before {content: counter(par-num, upper-roman) ". "} H1:before { content: "Chapter " counter(chapter) ". "; counter-increment: chapter; /* Add 1 to chapter */ counter-reset: section; /* Set section to 0 */ } H2:before { content: counter(chapter) "." counter(section) " "; counter-increment: section; } OL { counter-reset: item } LI { display: block } LI:before { content: counters(item, "."); counter-increment: item } H1:before { content: counter(chno, upper-latin) ". " } H2:before { content: counter(section, upper-roman) " - " } BLOCKQUOTE:after { content: " [" counter(bq, hebrew) "]" } DIV.note:before { content: counter(notecntr, disc) " " } P:before { content: counter(p, none) } /* * Content stuff (w/attr function) */ @media aural { BLOCKQUOTE:after { content: url("beautiful-music.wav") } } IMG:before { content: attr(alt) } H1:before { display: block; text-align: center; content: "chapter\A hoofdstuk\A chapitre" } /* * Clip (w/rect function) */ P { clip: rect(5px, 10px, 10px, 5px); } P { clip: rect(5px, -5px, 10px, 5px); } P { font-size: 12pt; line-height: 12pt } P:first-letter { font-size: 200%; font-style: italic; font-weight: bold; float: left; bogus: foo(p1, p2, "hello", url(primary.css)) } SPAN { text-transform: uppercase } P { color: red; font-size: 12pt } P:first-letter { color: green; font-size: 200% } P:first-line { color: blue } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/tigris.css0000644000175000017500000002073100000000000021513 0ustar00kovidkovid/* contains rules unsuitable for Netscape 4.x; simpler rules are in ns4_only.css. see */ /* colors, backgrounds, borders, link indication */ body { background: #fff; color: #000; } .app h3, .app h4, .tabs td, .tabs th, .functnbar { background-image: url(../images/nw_min.gif); background-repeat: no-repeat; } #toptabs td, #toptabs th { background-image: url(../images/nw_min_036.gif); } #navcolumn .body div, body.docs #toc li li { background-image: url(../images/strich.gif); background-repeat: no-repeat; background-position: .5em .5em; } #search .body div, .body .heading { background-image: none; } .app h3, .app h4 { color: #fff; } .app h3, #banner td { background-color: #036; color: #fff; } body #banner td a { color: #fff !important; } .app h4 { background-color: #888; } .a td { background: #ddd; } .b td { background: #efefef; } table, th, td { border: none } .mtb { border-top: solid 1px #ddd; } div.colbar { background: #bbb; } #banner { border-top: 1px solid #369; } .toolgroup { background: #eee; } .toolgroup .label { border-bottom: 1px solid #666; border-right: 1px solid #666; background: #ccc; } .toolgroup .body { border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } #mytools .label, #projecttools .label, #admintools .label { background: #fff; border-top: 1px solid #666; border-right: none; border-bottom: none; border-left: 1px solid #666; } #mytools .body, #projecttools .body, #admintools .body { background: #fff; border-top: none; border-right: none; border-bottom: none; border-left: 1px solid #666; } #mytools, #projecttools, #admintools { border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } #helptext { background: #ffc; } #helptext .label { border-bottom: 1px solid #996; border-right: 1px solid #996; background: #cc9; } #helptext .body { border-bottom: 1px solid #cc9; border-right: 1px solid #cc9; } #breadcrumbs { border-top: 1px solid #fff; background-color: #ccc } #main { border-top: 1px solid #999; } #rightcol div.www, #rightcol div.help { border: 1px solid #ddd; } body.docs div.docs { background: #fff; border-left: 1px solid #ddd; border-top: 1px solid #ddd; } body.docs { background: #eee url(../images/help_logo.gif) top right no-repeat !important; } .docs h3, .docs h4 { border-top: solid 1px #000; } #alerterrormessage { background: url(../images/icon_alert.gif) top left no-repeat !important; } .functnbar { background-color: #aaa; } .functnbar2, .functnbar3 { background: #aaa url(../images/sw_min.gif) no-repeat bottom left; } .functnbar3 { background-color: #ddd; } .functnbar, .functnbar2, .functnbar3 { color: #000; } .functnbar a, .functnbar2 a, .functnbar3 a { color: #000; text-decoration: underline; } #topmodule { background: #ddd; border-top: 1px solid #fff; border-bottom: 1px solid #aaa; } #topmodule #issueid { border-right: 1px solid #aaa; } a:link, #navcolumn a:visited, .app a:visited, .tasknav a:visited { color: blue; } a:link.selfref, a:visited.selfref { color: #555 !important; text-decoration: none; } a:active, a:hover, #leftcol a:active, #leftcol a:hover { color: #f30 !important; } #login a:link, #login a:visited { color: white; text-decoration: underline; } #banner a:active, #banner a:hover { color: #f90 !important; } #leftcol a, #breadcrumbs a { text-decoration: none; } #apphead h2 em { color: #777; } .app th { background-color: #bbb; } .tabs th { border-right: 1px solid #333; background-color: #ddd; color: #fff; } .tabs td { background-color: #999; border-bottom: 1px solid #fff; border-right: 1px solid #fff; } .tabs { border-bottom: 6px #ddd solid; } .tabs th, .tabs th a:link, .tabs th a:visited { color: #555; } .tabs td, .tabs td a:link, .tabs td a:visited { color: #fff; } .tabs a { text-decoration: none; } #toptabs td { border-bottom: 1px solid #666; border-right: 1px solid #333; border-left: 1px solid #036; } #toptabs th { border-left: 1px solid #036; } .axial th { background-color: #ddd; color: black } .alert { color: #c00; } .confirm { color: green; } .info { color: blue; } .selection { background: #ffc; } #login { color: #fff; } h4 a:link, h4 a:visited { text-decoration: underline; color: #fff; } /* font and text properties, exclusive of link indication, alignment, text-indent */ body, th, td, input, select, textarea, h2 small { font-family: Verdana, Helvetica, Arial, sans-serif; } code, pre { font-family: 'Andale Mono', Courier, monospace; } html body, body th, body td, textarea, h2 small, .app h3, .app h4, #rightcol h3, #bodycol pre, #bodycol code { font-size: x-small; voice-family: "\"}\""; voice-family: inherit; font-size: small } html>body, html>body th, html>body td, html>body input, html>body select, html>body textarea, html>body h2 small, html>body .app h3, html>body .app h4, html>body #rightcol h3, html>body #bodycol pre, html>body #bodycol code { font-size: small } small, div#footer td, div#login, div.tabs th, div.tabs td, input, select, .paginate, .functnbar, .functnbar2, .functnbar3, #breadcrumbs td, .courtesylinks, #rightcol div.help, .colbar, .tasknav, body.docs div#toc, #leftcol { font-size: xx-small; voice-family: "\"}\""; voice-family: inherit; font-size: x-small } html>body small, html>body div#footer td, html>body div#login, html>body div.tabs th, html>body div.tabs td, html>body input, html>body select, html>body .paginate, html>body .functnbar, html>body .functnbar2, html>body .functnbar3, html>body #breadcrumbs td, html>body .courtesylinks, html>body #rightcol div.help, html>body .colbar, html>body .tasknav, html>body.docs #toc, html>body #leftcol { font-size: x-small } #bodycol h2 { font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; font-size: 1.5em; font-weight: normal; } .tabs td, .tabs th, dt, .tasknav .selfref, #login .username, .selection { font-weight: bold } h4 { font-size: 1em; } #apphead h2 em { font-style: normal; } /* box properties (exclusive of borders), positioning, alignments, list types, text-indent */ #bodycol h2 { margin-top: .3em; margin-bottom: .5em; } p, ul, ol, dl { margin-top: .67em; margin-bottom: .67em; } h3, h4 { margin-bottom: 0; } form { margin-top: 0; margin-bottom: 0; } #bodycol { padding-left: 12px; padding-right: 12px; width: 100%; voice-family: "\"}\""; voice-family: inherit; width: auto; } html>body #bodycol { width: auto; } .docs { line-height: 1.4; } .app h3, .app h4 { padding: 5px; margin-right: 2px; margin-left: 2px; } .h3 p, .h4 p, .h3 dt, .h4 dt { margin-right: 7px; margin-left: 7px; } .tasknav { margin-bottom: 1.33em } div.colbar { padding: 3px; margin: 2px 2px 0; } .tabs { margin-top: .67em; margin-right: 2px; margin-left: 2px; } .tabs td, .tabs th { padding: 3px 9px; } #toptabs { margin: 0; padding-top: .67em; padding-left: 8px; } #breadcrumbs td { padding: 2px 8px; } #rightcol div.www, #rightcol div.help { padding: 0 .5em } body.docs #toc { position: absolute; top: 15px; left: 0px; width: 120px; padding: 0 20px 0 0 } body.docs #toc ul, #toc ol { margin-left: 0; padding-left: 0; } body.docs #toc li { margin-top: 7px; padding-left: 10px; list-style-type: none; } body.docs div.docs { margin: 61px 0 0 150px; padding: 1em 2em 1em 1em !important; } .docs p+p { text-indent: 5%; margin-top: -.67em } .docs h3, .docs h4 { margin-bottom: .1em; padding-top: .3em; } #alerterrormessage { padding-left: 100px; } .functnbar, .functnbar2, .functnbar3 { padding: 5px; margin: .67em 2px; } #topmodule td { vertical-align: middle; padding: 2px 8px } body { padding: 1em; } body.composite, body.docs { margin: 0; padding: 0; } th, td { text-align: left; vertical-align: top } .right { text-align: right !important; } .center { text-align: center !important; } .axial th { text-align: right; } .app .axial td th { text-align: left; } body td .stb { margin-top: 1em; text-indent: 0; } body td .mtb { margin-top: 2em; text-indent: 0; } .courtesylinks { margin-top: 1em; padding-top: 1em } dd { margin-bottom: .67em; } .toolgroup { margin-bottom: 6px } .toolgroup .body { padding: 4px 4px 4px 0; } .toolgroup .label { padding: 4px; } .toolgroup .body div { padding-bottom: .3em; padding-left: 1em; } #banner td { vertical-align: bottom; } #mytools .body, #projecttools .body, #admintools .body { padding-top: 0; } #mytools, #projecttools, #admintools { margin: -4px 0 6px -4px; padding: 6px; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/tigris2.css0000644000175000017500000002157200000000000021601 0ustar00kovidkovid/* contains rules unsuitable for Netscape 4.x; simpler rules are in ns4_only.css. see */ /* colors, backgrounds, borders, link indication */ body { background: #fff; color: #000; } .app h3, .app h4, .tabs td, .tabs th, .functnbar { background-image: url(../images/nw_min.gif); background-repeat: no-repeat; } #toptabs td, #toptabs th { background-image: url(../images/nw_min_036.gif); } #navcolumn .body div, body.docs #toc li li { background-image: url(../images/strich.gif); background-repeat: no-repeat; background-position: 0.5em 0.5em; } #search .body div, .body .heading { background-image: none; } .app h3, .app h4 { color: #fff; } .app h3, #banner td { background-color: #036; color: #fff; } body #banner td a { color: #fff !important; } .app h4 { background-color: #888; } .a td { background: #ddd; } .b td { background: #efefef; } table, th, td { border: none; } .mtb { border-top: solid 1px #ddd; } div.colbar { background: #bbb; } #banner { border-top: 1px solid #369; } .toolgroup { background: #eee; } .toolgroup .label { border-bottom: 1px solid #666; border-right: 1px solid #666; background: #ccc; } .toolgroup .body { border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } #mytools .label, #projecttools .label, #admintools .label { background: #fff; border-top: 1px solid #666; border-right: none; border-bottom: none; border-left: 1px solid #666; } #mytools .body, #projecttools .body, #admintools .body { background: #fff; border-top: none; border-right: none; border-bottom: none; border-left: 1px solid #666; } #mytools, #projecttools, #admintools { border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } #helptext { background: #ffc; } #helptext .label { border-bottom: 1px solid #996; border-right: 1px solid #996; background: #cc9; } #helptext .body { border-bottom: 1px solid #cc9; border-right: 1px solid #cc9; } #breadcrumbs { border-top: 1px solid #fff; background-color: #ccc; } #main { border-top: 1px solid #999; } #rightcol div.www, #rightcol div.help { border: 1px solid #ddd; } body.docs div.docs { background: #fff; border-left: 1px solid #ddd; border-top: 1px solid #ddd; } body.docs { background: #eee url(../images/help_logo.gif) top right no-repeat !important; } .docs h3, .docs h4 { border-top: solid 1px #000; } #alerterrormessage { background: url(../images/icon_alert.gif) top left no-repeat !important; } .functnbar { background-color: #aaa; } .functnbar2, .functnbar3 { background: #aaa url(../images/sw_min.gif) no-repeat bottom left; } .functnbar3 { background-color: #ddd; } .functnbar, .functnbar2, .functnbar3 { color: #000; } .functnbar a, .functnbar2 a, .functnbar3 a { color: #000; text-decoration: underline; } #topmodule { background: #ddd; border-top: 1px solid #fff; border-bottom: 1px solid #aaa; } #topmodule #issueid { border-right: 1px solid #aaa; } a:link, #navcolumn a:visited, .app a:visited, .tasknav a:visited { color: blue; } a:link.selfref, a:visited.selfref { color: #555 !important; text-decoration: none; } a:active, a:hover, #leftcol a:active, #leftcol a:hover { color: #f30 !important; } #login a:link, #login a:visited { color: white; text-decoration: underline; } #banner a:active, #banner a:hover { color: #f90 !important; } #leftcol a, #breadcrumbs a { text-decoration: none; } #apphead h2 em { color: #777; } .app th { background-color: #bbb; } .tabs th { border-right: 1px solid #333; background-color: #ddd; color: #fff; } .tabs td { background-color: #999; border-bottom: 1px solid #fff; border-right: 1px solid #fff; } .tabs { border-bottom: 6px #ddd solid; } .tabs th, .tabs th a:link, .tabs th a:visited { color: #555; } .tabs td, .tabs td a:link, .tabs td a:visited { color: #fff; } .tabs a { text-decoration: none; } #toptabs td { border-bottom: 1px solid #666; border-right: 1px solid #333; border-left: 1px solid #036; } #toptabs th { border-left: 1px solid #036; } .axial th { background-color: #ddd; color: black; } .alert { color: #c00; } .confirm { color: green; } .info { color: blue; } .selection { background: #ffc; } #login { color: #fff; } h4 a:link, h4 a:visited { text-decoration: underline; color: #fff; } /* font and text properties, exclusive of link indication, alignment, text-indent */ body, th, td, input, select, textarea, h2 small { font-family: Verdana, Helvetica, Arial, sans-serif; } code, pre { font-family: 'Andale Mono', Courier, monospace; } html body, body th, body td, textarea, h2 small, .app h3, .app h4, #rightcol h3, #bodycol pre, #bodycol code { font-size: x-small; font-size: small; voice-family: "\"}\""; voice-family: inherit; } html>body, html>body th, html>body td, html>body input, html>body select, html>body textarea, html>body h2 small, html>body .app h3, html>body .app h4, html>body #rightcol h3, html>body #bodycol pre, html>body #bodycol code { font-size: small; } small, div#footer td, div#login, div.tabs th, div.tabs td, input, select, .paginate, .functnbar, .functnbar2, .functnbar3, #breadcrumbs td, .courtesylinks, #rightcol div.help, .colbar, .tasknav, body.docs div#toc, #leftcol { font-size: xx-small; font-size: x-small; voice-family: "\"}\""; voice-family: inherit; } html>body small, html>body div#footer td, html>body div#login, html>body div.tabs th, html>body div.tabs td, html>body input, html>body select, html>body .paginate, html>body .functnbar, html>body .functnbar2, html>body .functnbar3, html>body #breadcrumbs td, html>body .courtesylinks, html>body #rightcol div.help, html>body .colbar, html>body .tasknav, html>body.docs #toc, html>body #leftcol { font-size: x-small; } #bodycol h2 { font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; font-size: 1.5em; font-weight: normal; } .tabs td, .tabs th, dt, .tasknav .selfref, #login .username, .selection { font-weight: bold; } h4 { font-size: 1em; } #apphead h2 em { font-style: normal; } /* box properties (exclusive of borders), positioning, alignments, list types, text-indent */ #bodycol h2 { margin-top: 0.3em; margin-bottom: 0.5em; } p, ul, ol, dl { margin-top: 0.67em; margin-bottom: 0.67em; } h3, h4 { margin-bottom: 0; } form { margin-top: 0; margin-bottom: 0; } #bodycol { padding-left: 12px; padding-right: 12px; width: 100%; width: auto; voice-family: "\"}\""; voice-family: inherit; } html>body #bodycol { width: auto; } .docs { line-height: 1.4; } .app h3, .app h4 { padding: 5px; margin-right: 2px; margin-left: 2px; } .h3 p, .h4 p, .h3 dt, .h4 dt { margin-right: 7px; margin-left: 7px; } .tasknav { margin-bottom: 1.33em; } div.colbar { padding: 3px; margin: 2px 2px 0; } .tabs { margin-top: 0.67em; margin-right: 2px; margin-left: 2px; } .tabs td, .tabs th { padding: 3px 9px; } #toptabs { margin: 0; padding-top: 0.67em; padding-left: 8px; } #breadcrumbs td { padding: 2px 8px; } #rightcol div.www, #rightcol div.help { padding: 0 0.5em; } body.docs #toc { position: absolute; top: 15px; left: 0px; width: 120px; padding: 0 20px 0 0; } body.docs #toc ul, #toc ol { margin-left: 0; padding-left: 0; } body.docs #toc li { margin-top: 7px; padding-left: 10px; list-style-type: none; } body.docs div.docs { margin: 61px 0 0 150px; padding: 1em 2em 1em 1em !important; } .docs p+p { text-indent: 5%; margin-top: -0.67em; } .docs h3, .docs h4 { margin-bottom: 0.1em; padding-top: 0.3em; } #alerterrormessage { padding-left: 100px; } .functnbar, .functnbar2, .functnbar3 { padding: 5px; margin: 0.67em 2px; } #topmodule td { vertical-align: middle; padding: 2px 8px; } body { padding: 1em; } body.composite, body.docs { margin: 0; padding: 0; } th, td { text-align: left; vertical-align: top; } .right { text-align: right !important; } .center { text-align: center !important; } .axial th { text-align: right; } .app .axial td th { text-align: left; } body td .stb { margin-top: 1em; text-indent: 0; } body td .mtb { margin-top: 2em; text-indent: 0; } .courtesylinks { margin-top: 1em; padding-top: 1em; } dd { margin-bottom: 0.67em; } .toolgroup { margin-bottom: 6px; } .toolgroup .body { padding: 4px 4px 4px 0; } .toolgroup .label { padding: 4px; } .toolgroup .body div { padding-bottom: 0.3em; padding-left: 1em; } #banner td { vertical-align: bottom; } #mytools .body, #projecttools .body, #admintools .body { padding-top: 0; } #mytools, #projecttools, #admintools { margin: -4px 0 6px -4px; padding: 6px; border-right: 1px solid #aaa; border-bottom: 1px solid #aaa; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/u_simple.css0000644000175000017500000000045000000000000022023 0ustar00kovidkovidÿþ@media screen { h1 { foo: bar } h2 { thing: 1; whatsit: 2px } h3 { foo: 2 ! important } } foo :lang(c) #bob { a: 1; b: 2; } foo { } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/v_simple.css0000644000175000017500000000003500000000000022023 0ustar00kovidkovidh1 { foo: alpha; bar:beta } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1340148 css-parser-1.0.7/css_parser_tests/sheets/var/0000755000175000017500000000000000000000000020265 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/css_parser_tests/sheets/var/start.css0000644000175000017500000000020500000000000022131 0ustar00kovidkovid@import "vars.css"; @import "vars2.css"; @import "use.css"; @variables { TEST: 1px; T2: 'T2' } a { left: var(T2); } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/css_parser_tests/sheets/var/use.css0000644000175000017500000000044200000000000021573 0ustar00kovidkovida { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } a { content: var(TEST) } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/css_parser_tests/sheets/var/vars.css0000644000175000017500000000005100000000000021746 0ustar00kovidkovid@variables { TEST: 1px; T2: 'T2' } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/css_parser_tests/sheets/var/vars2.css0000644000175000017500000000004100000000000022027 0ustar00kovidkovid@variables { TEST: 'VARS2' } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/vars.css0000644000175000017500000000033600000000000021164 0ustar00kovidkovid@import "varsimport.css"; @variables { c2: "c2 own file (OVERWRITTEN)"; c3: "c1 own file" } a1 { content: var(c1); } a2 { content: var(c2); } a3 { content: var(c3); } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/varsimport.css0000644000175000017500000000037600000000000022423 0ustar00kovidkovid@variables { c1: "c1 imported"; c2: "c2 imported (SHOULD BE OVERWRITTEN)"; } .import1 { content: var(c1); } .import2 { content: var(c2); } .import3 { /* not defined here! */ content: var(c3); } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/xhtml2.css0000644000175000017500000001145600000000000021434 0ustar00kovidkovid@namespace url("http://www.w3.org/2002/06/xhtml2/"); /* A sample style sheet for XHTML 2.0 This style sheet describes a very incomplete, sample rendering of XHTML 2.0 elements. Editor: Masayasu Ishikawa Revision: $Id$ */ /* new elements */ section, h, nl, label, l, blockcode, separator, di { display: block; } section, h, nl, label, l, blockcode, di { unicode-bidi: embed } nl { margin: 1.33em 0 } summary, standby, handler { display: none } blockcode { font-family: monospace; white-space: pre } separator { border-bottom: thin black solid; border: 1px; inset; width 100%} h { display: block; font-weight: bolder; font-family: sans-serif } h1, h2, h3, h4, h5, h6 { font-family: sans-serif; font-weight: bolder } body h, h1 { font-size: 2em; margin: .67em 0; } section h, h2 { font-size: 1.5em; margin: .83em 0; } section section h, h3 { font-size: 1.17em; margin: 1em 0; } section section section h, h4, p, blockquote, ul, ol, dl { margin: 1.33em 0; } section section section section h, h5 { font-size: .83em; line-height: 1.17em; margin: 1.67em 0; } section section section section section h, h6 { font-size: .67em; margin: 2.33em 0; } *[edit="deleted"] { display: none } /* no special presentation by default *[edit="inserted"] { } *[edit="changed"] { } *[edit="moved"] { } */ /* experimental navigation list style */ nl { height: 1.5em; overflow: hidden; margin: 0; line-height: normal !important; white-space: nowrap; text-align: start; cursor: default; border-width: 2px !important; border-style: inset !important; vertical-align: baseline; padding: 0; } nl:hover { height: auto; overflow: visible; } nl > li, nl > label { display: block; min-height: 1em; line-height: normal !important; } nl > li, nl > label { padding: 0 5px 0 3px; } nl > li { margin-left: 1em; } nl > label { font-weight: bold; } nl > nl > label { display: block; line-height: normal !important; font-style: italic; font-weight: bold; } nl > nl > li { padding-left: 2em; font-style: normal; font-weight: normal; } /* inherited elements */ html, body, div, p, h1, h2, h3, h4, h5, h6, address, blockquote, pre, ol, ul, dl, dt, dd { display: block } li { display: list-item } head, style, link, meta { display: none } table { display: table; border-spacing: 0; border-top: thin black solid; border-left: thin black solid } tr { display: table-row } thead { display: table-header-group } tbody { display: table-row-group } tfoot { display: table-footer-group } col { display: table-column } colgroup { display: table-column-group } td, th { display: table-cell; border-right: thin black solid; border-bottom: thin black solid; padding 2px } caption { display: table-caption } table:hover summary { display: block } th { font-weight: bolder; text-align: center } caption { text-align: center } body { padding: 8px; line-height: 1.2 } strong { font-weight: bolder } blockquote { margin-left: 4em; margin-right: 4em } cite, em, q, var, address { font-style: italic } pre code, kbd, samp { font-family: monospace } pre { white-space: pre } sub, sup { font-size: smaller } sub { vertical-align: sub } sup { vertical-align: super } ol, ul, dd { margin-left: 4em } ol { list-style-type: decimal } ol ul, ul ol, ul ul, ol ol { margin-top: 0; margin-bottom: 0 } abbr[title] { border-bottom: dotted 1px } :link { text-decoration: underline; color: blue; } :focus { outline: thin dotted invert } /* Hover effects should be default */ :link:hover,:link:visited { color: #b7f } /* begin bidirectionality settings (do not change) */ *[dir="ltr"] { direction: ltr; unicode-bidi: embed } *[dir="rtl"] { direction: rtl; unicode-bidi: embed } *[dir="lro"] { direction: ltr; unicode-bidi: bidi-override } *[dir="rlo"] { direction: rtl; unicode-bidi: bidi-override } /* block-level elements */ body, div, p, hr, h1, h2, h3, h4, h5, h6, address, blockquote, pre, ol, ul, li, di, dt, dd, table, thead, tbody, tfoot, tr, td, th, col, colgroup, caption, object, summary, standby, blockcode { unicode-bidi: embed } /* end bidi settings */ /* end xhtml2.css */././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/xhtml22.css0000644000175000017500000001133100000000000021506 0ustar00kovidkovid@namespace "http://www.w3.org/2002/06/xhtml2/"; /* A sample style sheet for XHTML 2.0 This style sheet describes a very incomplete, sample rendering of XHTML 2.0 elements. Editor: Masayasu Ishikawa Revision: $Id$ */ /* new elements */ section, h, nl, label, l, blockcode, separator, di { display: block } section, h, nl, label, l, blockcode, di { unicode-bidi: embed } nl { margin: 1.33em 0 } summary, standby, handler { display: none } blockcode { font-family: monospace; white-space: pre } separator { border-bottom: thin black solid; border: 1px } h { display: block; font-weight: bolder; font-family: sans-serif } h1, h2, h3, h4, h5, h6 { font-family: sans-serif; font-weight: bolder } body h, h1 { font-size: 2em; margin: 0.67em 0 } section h, h2 { font-size: 1.5em; margin: 0.83em 0 } section section h, h3 { font-size: 1.17em; margin: 1em 0 } section section section h, h4, p, blockquote, ul, ol, dl { margin: 1.33em 0 } section section section section h, h5 { font-size: 0.83em; line-height: 1.17em; margin: 1.67em 0 } section section section section section h, h6 { font-size: 0.67em; margin: 2.33em 0 } *[edit="deleted"] { display: none } /* no special presentation by default *[edit="inserted"] { } *[edit="changed"] { } *[edit="moved"] { } */ /* experimental navigation list style */ nl { height: 1.5em; overflow: hidden; margin: 0; line-height: normal !important; white-space: nowrap; text-align: start; cursor: default; border-width: 2px !important; border-style: inset !important; vertical-align: baseline; padding: 0 } nl:hover { height: auto; overflow: visible } nl>li, nl>label { display: block; min-height: 1em; line-height: normal !important } nl>li, nl>label { padding: 0 5px 0 3px } nl>li { margin-left: 1em } nl>label { font-weight: bold } nl>nl>label { display: block; line-height: normal !important; font-style: italic; font-weight: bold } nl>nl>li { padding-left: 2em; font-style: normal; font-weight: normal } /* inherited elements */ html, body, div, p, h1, h2, h3, h4, h5, h6, address, blockquote, pre, ol, ul, dl , dt, dd { display: block } li { display: list-item } head, style, link, meta { display: none } table { display: table; border-spacing: 0; border-top: thin black solid; border-left: thin black solid } tr { display: table-row } thead { display: table-header-group } tbody { display: table-row-group } tfoot { display: table-footer-group } col { display: table-column } colgroup { display: table-column-group } td, th { display: table-cell; border-right: thin black solid; border-bottom: thin black solid } caption { display: table-caption } table:hover summary { display: block } th { font-weight: bolder; text-align: center } caption { text-align: center } body { padding: 8px; line-height: 1.2 } strong { font-weight: bolder } blockquote { margin-left: 4em; margin-right: 4em } cite, em, q, var, address { font-style: italic } pre code, kbd, samp { font-family: monospace } pre { white-space: pre } sub, sup { font-size: smaller } sub { vertical-align: sub } sup { vertical-align: super } ol, ul, dd { margin-left: 4em } ol { list-style-type: decimal } ol ul, ul ol, ul ul, ol ol { margin-top: 0; margin-bottom: 0 } abbr[title] { border-bottom: dotted 1px } :link { text-decoration: underline; color: blue } :focus { outline: thin dotted invert } /* Hover effects should be default */ :link:hover, :link:visited { color: #b7f } /* begin bidirectionality settings (do not change) */ *[dir="ltr"] { direction: ltr; unicode-bidi: embed } *[dir="rtl"] { direction: rtl; unicode-bidi: embed } *[dir="lro"] { direction: ltr; unicode-bidi: bidi-override } *[dir="rlo"] { direction: rtl; unicode-bidi: bidi-override } /* block-level elements */ body, div, p, hr, h1, h2, h3, h4, h5, h6, address, blockquote, pre, ol, ul, li, di, dt, dd, table, thead, tbody, tfoot, tr, td, th, col, colgroup, caption, obje ct, summary, standby, blockcode { unicode-bidi: embed }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/css_parser_tests/sheets/yuck.css0000644000175000017500000000016500000000000021164 0ustar00kovidkovidE[class~="hipster"][thing~="bob"]#myid { att1: hi; foo: bar } E:lang(c)#unique E.hipster#myid { att1: hi; foo: bar } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_codec.py0000644000175000017500000003640700000000000020702 0ustar00kovidkovidfrom __future__ import absolute_import from __future__ import unicode_literals from css_parser import codec """Testcases for css_parser.codec""" import codecs import unittest import sys PY2x = sys.version_info < (3, 0) if PY2x: import StringIO iostream = StringIO.StringIO else: import io iostream = io.BytesIO try: codecs.lookup("utf-32") except LookupError: haveutf32 = False else: haveutf32 = True class Queue(object): """ queue: write bytes at one end, read bytes from the other end """ def __init__(self): self._buffer = "".encode() def write(self, chars): # TODO ??? if not PY2x: if isinstance(chars, str): chars = chars.encode() elif isinstance(chars, int): chars = bytes([chars]) self._buffer += chars def read(self, size=-1): if size < 0: s = self._buffer self._buffer = "".encode() return s else: s = self._buffer[:size] self._buffer = self._buffer[size:] return s class CodecTestCase(unittest.TestCase): def test_detectencoding_str(self): "codec.detectencoding_str()" self.assertEqual(codec.detectencoding_str(''.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('\xef'.encode('latin1')), (None, False)) self.assertEqual(codec.detectencoding_str('\xef\x33'.encode("utf-8")), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('\xc3\xaf3'.encode("utf-8")), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('\xef\xbb'.encode("latin1")), (None, False)) self.assertEqual(codec.detectencoding_str('\xef\xbb\x33'.encode("utf-8")), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('\xef\xbb\xbf'.encode("utf-8-sig")), ("utf-8-sig", True)) self.assertEqual(codec.detectencoding_str('\xff'.encode("latin1")), (None, False)) self.assertEqual(codec.detectencoding_str('\xff\x33'.encode("utf-8")), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('\xff\xfe'.encode("latin1")), (None, False)) self.assertEqual(codec.detectencoding_str('\xff\xfe\x33'.encode("utf-16")), ("utf-16", True)) self.assertEqual(codec.detectencoding_str('\xff\xfe\x00'.encode("latin1")), (None, False)) self.assertEqual(codec.detectencoding_str('\xff\xfe\x00\x33'.encode("utf-16")), ("utf-16", True)) if haveutf32: self.assertEqual(codec.detectencoding_str('\xff\xfe\x00\x00'.encode("utf-32")), ("utf-32", True)) self.assertEqual(codec.detectencoding_str('\x00'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('\x00\x33'.encode()), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('\x00\x00'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('\x00\x00\x33'.encode()), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('\x00\x00\xfe'.encode('latin1')), (None, False)) self.assertEqual(codec.detectencoding_str('\x00\x00\x00\x33'.encode()), ("utf-8", False)) if haveutf32: self.assertEqual(codec.detectencoding_str('\x00\x00\x00@'.encode()), ("utf-32-be", False)) self.assertEqual(codec.detectencoding_str('\x00\x00\xfe\xff'.encode('utf-32')), ("utf-32", True)) self.assertEqual(codec.detectencoding_str('@'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@\x33'.encode()), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('@\x00'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@\x00\x33'.encode()), ("utf-8", False)) self.assertEqual(codec.detectencoding_str('@\x00\x00'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@\x00\x00\x33'.encode()), ("utf-8", False)) if haveutf32: self.assertEqual(codec.detectencoding_str('@\x00\x00\x00'.encode()), ("utf-32-le", False)) self.assertEqual(codec.detectencoding_str('@c'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@ch'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@cha'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@char'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@chars'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@charse'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@charset'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@charset '.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@charset "'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@charset "x'.encode()), (None, False)) self.assertEqual(codec.detectencoding_str('@charset ""'.encode()), ("", True)) self.assertEqual(codec.detectencoding_str('@charset "x"'.encode()), ("x", True)) self.assertEqual(codec.detectencoding_str("@".encode(), False), (None, False)) self.assertEqual(codec.detectencoding_str("@".encode(), True), ("utf-8", False)) self.assertEqual(codec.detectencoding_str("@c".encode(), False), (None, False)) self.assertEqual(codec.detectencoding_str("@c".encode(), True), ("utf-8", False)) def test_detectencoding_unicode(self): "codec.detectencoding_unicode()" # Unicode version (only parses the header) self.assertEqual(codec.detectencoding_unicode('@charset "x'), (None, False)) self.assertEqual(codec.detectencoding_unicode('a {}'), ("utf-8", False)) self.assertEqual(codec.detectencoding_unicode('@charset "x', True), (None, False)) self.assertEqual(codec.detectencoding_unicode('@charset "x"'), ("x", True)) def test_fixencoding(self): "codec._fixencoding()" s = '@charset "' self.assertTrue(codec._fixencoding(s, "utf-8") is None) s = '@charset "x' self.assertTrue(codec._fixencoding(s, "utf-8") is None) s = '@charset "x' self.assertEqual(codec._fixencoding(s, "utf-8", True), s) s = '@charset x' self.assertEqual(codec._fixencoding(s, "utf-8"), s) s = '@charset "x"' self.assertEqual(codec._fixencoding(s, "utf-8"), s.replace('"x"', '"utf-8"')) def test_decoder(self): "codecs.decoder" def checkauto(encoding, input='@charset "x";g\xfcrk\u20ac{}'): outputencoding = encoding if outputencoding == "utf-8-sig": outputencoding = "utf-8" # Check stateless decoder with encoding autodetection d = codecs.getdecoder("css") self.assertEqual(d(input.encode(encoding))[0], input.replace('"x"', '"%s"' % outputencoding)) # Check stateless decoder with specified encoding self.assertEqual(d(input.encode(encoding), encoding=encoding)[ 0], input.replace('"x"', '"%s"' % outputencoding)) if hasattr(codec, "getincrementaldecoder"): # Check incremental decoder with encoding autodetection id = codecs.getincrementaldecoder("css")() self.assertEqual("".join(id.iterdecode(input.encode(encoding))), input.replace('"x"', '"%s"' % outputencoding)) # Check incremental decoder with specified encoding id = codecs.getincrementaldecoder("css")(encoding=encoding) self.assertEqual("".join(id.iterdecode(input.encode(encoding))), input.replace('"x"', '"%s"' % outputencoding)) # Check stream reader with encoding autodetection q = Queue() sr = codecs.getreader("css")(q) result = [] # TODO: py3 only??? for c in input.encode(encoding): q.write(c) result.append(sr.read()) self.assertEqual("".join(result), input.replace('"x"', '"%s"' % outputencoding)) # Check stream reader with specified encoding q = Queue() sr = codecs.getreader("css")(q, encoding=encoding) result = [] for c in input.encode(encoding): q.write(c) result.append(sr.read()) self.assertEqual("".join(result), input.replace('"x"', '"%s"' % outputencoding)) # Autodetectable encodings checkauto("utf-8-sig") checkauto("utf-16") checkauto("utf-16-le") checkauto("utf-16-be") if haveutf32: checkauto("utf-32") checkauto("utf-32-le") checkauto("utf-32-be") def checkdecl(encoding, input='@charset "%s";g\xfcrk{}'): # Check stateless decoder with encoding autodetection d = codecs.getdecoder("css") input = input % encoding outputencoding = encoding if outputencoding == "utf-8-sig": outputencoding = "utf-8" self.assertEqual(d(input.encode(encoding))[0], input) # Check stateless decoder with specified encoding self.assertEqual(d(input.encode(encoding), encoding=encoding)[0], input) if hasattr(codec, "getincrementaldecoder"): # Check incremental decoder with encoding autodetection id = codecs.getincrementaldecoder("css")() self.assertEqual("".join(id.iterdecode(input.encode(encoding))), input) # Check incremental decoder with specified encoding id = codecs.getincrementaldecoder("css")(encoding) self.assertEqual("".join(id.iterdecode(input.encode(encoding))), input) # Check stream reader with encoding autodetection q = Queue() sr = codecs.getreader("css")(q) result = [] for c in input.encode(encoding): q.write(c) result.append(sr.read()) self.assertEqual("".join(result), input) # Check stream reader with specified encoding q = Queue() sr = codecs.getreader("css")(q, encoding=encoding) result = [] for c in input.encode(encoding): q.write(c) result.append(sr.read()) self.assertEqual("".join(result), input) # Use correct declaration checkdecl("utf-8") checkdecl("iso-8859-1", '@charset "%s";g\xfcrk') checkdecl("iso-8859-15") checkdecl("cp1252") # No recursion self.assertRaises(ValueError, '@charset "css";div{}'.encode().decode, "css") def test_encoder(self): "codec.encoder" def check(encoding, input='@charset "x";g\xfcrk\u20ac{}'): outputencoding = encoding if outputencoding == "utf-8-sig": outputencoding = "utf-8" # Check stateless encoder with encoding autodetection e = codecs.getencoder("css") inputdecl = input.replace('"x"', '"%s"' % encoding) outputdecl = input.replace('"x"', '"%s"' % outputencoding) self.assertEqual(e(inputdecl)[0].decode(encoding), outputdecl) # Check stateless encoder with specified encoding self.assertEqual(e(input, encoding=encoding)[0].decode(encoding), outputdecl) if hasattr(codec, "getincrementalencoder"): # Check incremental encoder with encoding autodetection ie = codecs.getincrementalencoder("css")() self.assertEqual("".join(ie.iterencode(inputdecl)).decode(encoding), outputdecl) # Check incremental encoder with specified encoding ie = codecs.getincrementalencoder("css")(encoding=encoding) self.assertEqual("".join(ie.iterencode(input)).decode(encoding), outputdecl) # Check stream writer with encoding autodetection q = Queue() sw = codecs.getwriter("css")(q) for c in inputdecl: # .encode(outputencoding): # TODO: .encode()??? sw.write(c) self.assertEqual(q.read().decode(encoding), input.replace('"x"', '"%s"' % outputencoding)) # Check stream writer with specified encoding q = Queue() sw = codecs.getwriter("css")(q, encoding=encoding) for c in input: sw.write(c) self.assertEqual(q.read().decode(encoding), input.replace('"x"', '"%s"' % outputencoding)) # Autodetectable encodings check("utf-8-sig") check("utf-16") check("utf-16-le") check("utf-16-be") if haveutf32: check("utf-32") check("utf-32-le") check("utf-32-be") check("utf-8") check("iso-8859-1", '@charset "x";g\xfcrk{}') check("iso-8859-15") check("cp1252") # No recursion self.assertRaises(ValueError, '@charset "css";div{}'.encode, "css") def test_decode_force(self): "codec.decode (force)" info = codecs.lookup("css") def decodeall(input, **kwargs): # Py 2.5: info.decode('@charset "utf-8"; x') return info[1](input, **kwargs)[0] def incdecode(input, **kwargs): decoder = info.incrementaldecoder(**kwargs) return decoder.decode(input) def streamdecode(input, **kwargs): stream = iostream(input) # py3 .decode('utf-8') but still error?! reader = info.streamreader(stream, **kwargs) return reader.read() for d in (decodeall, incdecode, streamdecode): # input = '@charset "utf-8"; \xc3\xbf' # output = u'@charset "utf-8"; \xff' # self.assertEqual(d(input), output) # # input = '@charset "utf-8"; \xc3\xbf' # output = u'@charset "iso-8859-1"; \xc3\xbf' # self.assertEqual(d(input, encoding="iso-8859-1", force=True), output) # # input = '\xc3\xbf' # output = u'\xc3\xbf' # self.assertEqual(d(input, encoding="iso-8859-1", force=True), output) # # input = '@charset "utf-8"; \xc3\xbf' # output = u'@charset "utf-8"; \xff' # self.assertEqual(d(input, encoding="iso-8859-1", force=False), output) input = '@charset "utf-8"; \xff'.encode('utf-8') output = '@charset "utf-8"; \xff' self.assertEqual(d(input), output) #input = b'@charset "utf-8"; \xc3\xbf' input = '@charset "utf-8"; \xff'.encode('utf-8') output = '@charset "iso-8859-1"; \xc3\xbf' self.assertEqual(d(input, encoding="iso-8859-1", force=True), output) #input = b'\xc3\xbf' input = '\xff'.encode('utf-8') output = '\xc3\xbf' self.assertEqual(d(input, encoding="iso-8859-1", force=True), output) #input = b'@charset "utf-8"; \xc3\xbf' input = '@charset "utf-8"; \xff'.encode('utf-8') output = '@charset "utf-8"; \xff' self.assertEqual(d(input, encoding="iso-8859-1", force=False), output) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1563593644.0 css-parser-1.0.7/css_parser_tests/test_csscharsetrule.py0000644000175000017500000001115100000000000022644 0ustar00kovidkovid"""Testcases for css_parser.css.CSSCharsetRule""" from __future__ import absolute_import from __future__ import unicode_literals import re import xml.dom from . import test_cssrule import css_parser.css class CSSCharsetRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSCharsetRuleTestCase, self).setUp() self.r = css_parser.css.CSSCharsetRule() self.rRO = css_parser.css.CSSCharsetRule(readonly=True) self.r_type = css_parser.css.CSSCharsetRule.CHARSET_RULE self.r_typeString = 'CHARSET_RULE' def test_init(self): "CSSCharsetRule.__init__()" super(CSSCharsetRuleTestCase, self).test_init() self.assertEqual(None, self.r.encoding) self.assertEqual('', self.r.cssText) self.assertRaises(xml.dom.InvalidModificationErr, self.r._setCssText, 'xxx') def test_InvalidModificationErr(self): "CSSCharsetRule InvalidModificationErr" self._test_InvalidModificationErr('@charset') def test_init_encoding(self): "CSSCharsetRule.__init__(encoding)" for enc in (None, 'UTF-8', 'utf-8', 'iso-8859-1', 'ascii'): r = css_parser.css.CSSCharsetRule(enc) if enc is None: self.assertEqual(None, r.encoding) self.assertEqual('', r.cssText) else: self.assertEqual(enc.lower(), r.encoding) self.assertEqual( '@charset "%s";' % enc.lower(), r.cssText) for enc in (' ascii ', ' ascii', 'ascii '): self.assertRaisesEx(xml.dom.SyntaxErr, css_parser.css.CSSCharsetRule, enc, exc_pattern=re.compile("Syntax Error")) for enc in ('unknown', ): self.assertRaisesEx(xml.dom.SyntaxErr, css_parser.css.CSSCharsetRule, enc, exc_pattern=re.compile(r"Unknown \(Python\) encoding")) def test_encoding(self): "CSSCharsetRule.encoding" for enc in ('UTF-8', 'utf-8', 'iso-8859-1', 'ascii'): self.r.encoding = enc self.assertEqual(enc.lower(), self.r.encoding) self.assertEqual( '@charset "%s";' % enc.lower(), self.r.cssText) for enc in (None, ' ascii ', ' ascii', 'ascii '): self.assertRaisesEx(xml.dom.SyntaxErr, self.r.__setattr__, 'encoding', enc, exc_pattern=re.compile("Syntax Error")) for enc in ('unknown', ): self.assertRaisesEx(xml.dom.SyntaxErr, self.r.__setattr__, 'encoding', enc, exc_pattern=re.compile("Unknown \(Python\) encoding")) def test_cssText(self): """CSSCharsetRule.cssText setting cssText is ok to use @CHARSET or other but a file using parse MUST use ``@charset "ENCODING";`` """ tests = { '@charset "utf-8";': None, "@charset 'utf-8';": '@charset "utf-8";', } self.do_equal_r(tests) self.do_equal_p(tests) # also parse tests = { # token is "@charset " with space! '@charset;"': xml.dom.InvalidModificationErr, '@CHARSET "UTF-8";': xml.dom.InvalidModificationErr, '@charset "";': xml.dom.SyntaxErr, '''@charset /*1*/"utf-8"/*2*/;''': xml.dom.SyntaxErr, '''@charset /*1*/"utf-8";''': xml.dom.SyntaxErr, '''@charset "utf-8"/*2*/;''': xml.dom.SyntaxErr, '@charset { utf-8 }': xml.dom.SyntaxErr, '@charset "utf-8"': xml.dom.SyntaxErr, '@charset a;': xml.dom.SyntaxErr, '@charset /**/;': xml.dom.SyntaxErr, # trailing content '@charset "utf-8";s': xml.dom.SyntaxErr, '@charset "utf-8";/**/': xml.dom.SyntaxErr, '@charset "utf-8"; ': xml.dom.SyntaxErr, # comments do not work in this rule! '@charset "utf-8"/*1*//*2*/;': xml.dom.SyntaxErr } self.do_raise_r(tests) def test_repr(self): "CSSCharsetRule.__repr__()" self.r.encoding = 'utf-8' self.assertTrue('utf-8' in repr(self.r)) def test_reprANDstr(self): "CSSCharsetRule.__repr__(), .__str__()" encoding = 'utf-8' s = css_parser.css.CSSCharsetRule(encoding=encoding) self.assertTrue(encoding in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(encoding == s2.encoding) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_csscomment.py0000644000175000017500000000436200000000000021773 0ustar00kovidkovid# -*- coding: utf-8 -*- """Testcases for css_parser.css.CSSComment""" from __future__ import absolute_import from __future__ import unicode_literals import xml from . import test_cssrule import css_parser.css class CSSCommentTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSCommentTestCase, self).setUp() self.r = css_parser.css.CSSComment() self.rRO = css_parser.css.CSSComment(readonly=True) self.r_type = css_parser.css.CSSComment.COMMENT self.r_typeString = 'COMMENT' def test_init(self): "CSSComment.type and init" super(CSSCommentTestCase, self).test_init() def test_csstext(self): "CSSComment.cssText" tests = { '/*öäü߀ÖÄÜ*/': '/*\xf6\xe4\xfc\xdf\u20ac\xd6\xc4\xdc*/', '/*x*/': None, '/* x */': None, '/*\t12\n*/': None, '/* /* */': None, '/* \\*/': None, '/*"*/': None, '''/*" */''': None, '/** / ** //*/': None } self.do_equal_r(tests) # set cssText tests.update({ '/*x': '/*x*/', '\n /*': '/**/', }) self.do_equal_p(tests) # parse tests = { '/* */ ': xml.dom.InvalidModificationErr, '/* *//**/': xml.dom.InvalidModificationErr, '/* */1': xml.dom.InvalidModificationErr, '/* */ */': xml.dom.InvalidModificationErr, ' */ /* ': xml.dom.InvalidModificationErr, '*/': xml.dom.InvalidModificationErr, '@x /* x */': xml.dom.InvalidModificationErr } self.do_raise_r(tests) # set cssText # no raising of error possible? # self.do_raise_p(tests) # parse def test_InvalidModificationErr(self): "CSSComment.cssText InvalidModificationErr" self._test_InvalidModificationErr('/* comment */') def test_reprANDstr(self): "CSSComment.__repr__(), .__str__()" text = '/* test */' s = css_parser.css.CSSComment(cssText=text) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(text == s2.cssText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssfontfacerule.py0000644000175000017500000002136400000000000023007 0ustar00kovidkovid"""Testcases for css_parser.css.CSSFontFaceRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class CSSFontFaceRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSFontFaceRuleTestCase, self).setUp() self.r = css_parser.css.CSSFontFaceRule() self.rRO = css_parser.css.CSSFontFaceRule(readonly=True) self.r_type = css_parser.css.CSSFontFaceRule.FONT_FACE_RULE self.r_typeString = 'FONT_FACE_RULE' def test_init(self): "CSSFontFaceRule.__init__()" super(CSSFontFaceRuleTestCase, self).test_init() r = css_parser.css.CSSFontFaceRule() self.assertTrue(isinstance(r.style, css_parser.css.CSSStyleDeclaration)) self.assertEqual(r, r.style.parentRule) # until any properties self.assertEqual('', r.cssText) # only possible to set @... similar name self.assertRaises(xml.dom.InvalidModificationErr, self.r._setAtkeyword, 'x') def checkrefs(ff): self.assertEqual(ff, ff.style.parentRule) for p in ff.style: self.assertEqual(ff.style, p.parent) checkrefs(css_parser.css.CSSFontFaceRule( style=css_parser.css.CSSStyleDeclaration('font-family: x'))) r = css_parser.css.CSSFontFaceRule() r.cssText = '@font-face { font-family: x }' checkrefs(r) r = css_parser.css.CSSFontFaceRule() r.style.setProperty('font-family', 'y') checkrefs(r) r = css_parser.css.CSSFontFaceRule() r.style['font-family'] = 'z' checkrefs(r) r = css_parser.css.CSSFontFaceRule() r.style.fontFamily = 'a' checkrefs(r) def test_cssText(self): "CSSFontFaceRule.cssText" tests = { '''@font-face { font-family: x; src: url(../fonts/LateefRegAAT.ttf) format("truetype-aat"), url(../fonts/LateefRegOT.ttf) format("opentype"); font-style: italic; font-weight: 500; font-stretch: condensed; unicode-range: u+1-ff, u+111 }''': None, '@font-face{font-family: x;}': '@font-face {\n font-family: x\n }', '@font-face { font-family: x; }': '@font-face {\n font-family: x\n }', '@f\\ont\\-face{font-family : x;}': '@font-face {\n font-family: x\n }', # comments '@font-face/*1*//*2*/{font-family: x;}': '@font-face /*1*/ /*2*/ {\n font-family: x\n }', # WS '@font-face\n\t\f {\n\t\f font-family:x;\n\t\f }': '@font-face {\n font-family: x\n }', } self.do_equal_r(tests) self.do_equal_p(tests) tests = { '@font-face;': xml.dom.SyntaxErr, '@font-face }': xml.dom.SyntaxErr, } self.do_raise_p(tests) # parse tests.update({ '@font-face {': xml.dom.SyntaxErr, # no } # trailing '@font-face {}1': xml.dom.SyntaxErr, '@font-face {}/**/': xml.dom.SyntaxErr, '@font-face {} ': xml.dom.SyntaxErr, }) self.do_raise_r(tests) # set cssText def test_style(self): "CSSFontFaceRule.style (and references)" r = css_parser.css.CSSFontFaceRule() s1 = r.style self.assertEqual(r, s1.parentRule) self.assertEqual('', s1.cssText) # set rule.cssText r.cssText = '@font-face { font-family: x1 }' self.assertNotEqual(r.style, s1) self.assertEqual(r, r.style.parentRule) self.assertEqual(r.cssText, '@font-face {\n font-family: x1\n }') self.assertEqual(r.style.cssText, 'font-family: x1') self.assertEqual(s1.cssText, '') s2 = r.style # set invalid rule.cssText try: r.cssText = '@font-face { $ }' except xml.dom.SyntaxErr as e: pass self.assertEqual(r.style, s2) self.assertEqual(r, s2.parentRule) self.assertEqual(r.cssText, '@font-face {\n font-family: x1\n }') self.assertEqual(s2.cssText, 'font-family: x1') self.assertEqual(r.style.cssText, 'font-family: x1') # set rule.style.cssText r.style.cssText = 'font-family: x2' self.assertEqual(r.style, s2) self.assertEqual(r, s2.parentRule) self.assertEqual(r.cssText, '@font-face {\n font-family: x2\n }') self.assertEqual(s2.cssText, 'font-family: x2') self.assertEqual(r.style.cssText, 'font-family: x2') # set new style object s2 sn = css_parser.css.CSSStyleDeclaration('font-family: y1') r.style = sn self.assertEqual(r.style, sn) self.assertEqual(r, sn.parentRule) self.assertEqual(r.cssText, '@font-face {\n font-family: y1\n }') self.assertEqual(sn.cssText, 'font-family: y1') self.assertEqual(r.style.cssText, 'font-family: y1') self.assertEqual(s2.cssText, 'font-family: x2') # old # set s2.cssText sn.cssText = 'font-family: y2' self.assertEqual(r.style, sn) self.assertEqual(r.cssText, '@font-face {\n font-family: y2\n }') self.assertEqual(r.style.cssText, 'font-family: y2') self.assertEqual(s2.cssText, 'font-family: x2') # old # set invalid s2.cssText try: sn.cssText = '$' except xml.dom.SyntaxErr as e: pass self.assertEqual(r.style, sn) self.assertEqual(r.style.cssText, 'font-family: y2') self.assertEqual(r.cssText, '@font-face {\n font-family: y2\n }') # set r.style with text r.style = 'font-family: z' self.assertNotEqual(r.style, sn) self.assertEqual(r.cssText, '@font-face {\n font-family: z\n }') self.assertEqual(r.style.cssText, 'font-family: z') self.assertEqual(sn.cssText, 'font-family: y2') def test_properties(self): "CSSFontFaceRule.style properties" r = css_parser.css.CSSFontFaceRule() r.style.cssText = ''' src: url(x) ''' exp = '''@font-face { src: url(x) }''' self.assertEqual(exp, r.cssText) tests = { 'font-family': [ # ('serif', True), # ('x', True), # ('"x"', True), ('x, y', False), ('"x", y', False), ('x, "y"', False), # ('"x", "y"', False) ] } for n, t in tests.items(): for (v, valid) in t: r = css_parser.css.CSSFontFaceRule() r.style[n] = v self.assertEqual(r.style.getProperty(n).parent, r.style) self.assertEqual(r.style.getProperty(n).valid, valid) def test_incomplete(self): "CSSFontFaceRule (incomplete)" tests = { '@font-face{': '', # no } and no content '@font-face { ': '', # no } and no content '@font-face { font-family: x': '@font-face {\n font-family: x\n }', # no } } self.do_equal_p(tests) # parse def test_InvalidModificationErr(self): "CSSFontFaceRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@font-face') tests = { '@font-fac {}': xml.dom.InvalidModificationErr, } self.do_raise_r(tests) def test_valid(self): "CSSFontFaceRule.valid" r = css_parser.css.CSSFontFaceRule() self.assertEqual(False, r.valid) N = 'font-family: x; src: local(x);' tests = { True: (N, N + 'font-style: italic; font-weight: bold', ), False: ('', 'font-family: x, y; src: local(x);', N + 'font-style: inherit', N + 'invalid: 1') } for valid, testlist in tests.items(): for test in testlist: r.style.cssText = test self.assertEqual(valid, r.valid) def test_reprANDstr(self): "CSSFontFaceRule.__repr__(), .__str__()" style = 'src: url(x)' s = css_parser.css.CSSFontFaceRule(style=style) self.assertTrue(style in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(style == s2.style.cssText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssimportrule.py0000644000175000017500000004443200000000000022535 0ustar00kovidkovid"""Testcases for css_parser.css.CSSImportRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser from . import basetest class CSSImportRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSImportRuleTestCase, self).setUp() self.r = css_parser.css.CSSImportRule() self.rRO = css_parser.css.CSSImportRule(readonly=True) self.r_type = css_parser.css.CSSImportRule.IMPORT_RULE self.r_typeString = 'IMPORT_RULE' def test_init(self): "CSSImportRule.__init__()" super(CSSImportRuleTestCase, self).test_init() # no init param self.assertEqual(None, self.r.href) self.assertEqual(None, self.r.hreftype) self.assertEqual(False, self.r.hrefFound) self.assertEqual('all', self.r.media.mediaText) self.assertEqual( css_parser.stylesheets.MediaList, type(self.r.media)) self.assertEqual(None, self.r.name) self.assertEqual(css_parser.css.CSSStyleSheet, type(self.r.styleSheet)) self.assertEqual(0, self.r.styleSheet.cssRules.length) self.assertEqual('', self.r.cssText) # all r = css_parser.css.CSSImportRule(href='href', mediaText='tv', name='name') self.assertEqual('@import url(href) tv "name";', r.cssText) self.assertEqual("href", r.href) self.assertEqual(None, r.hreftype) self.assertEqual('tv', r.media.mediaText) self.assertEqual( css_parser.stylesheets.MediaList, type(r.media)) self.assertEqual('name', r.name) self.assertEqual(None, r.parentRule) # see CSSRule self.assertEqual(None, r.parentStyleSheet) # see CSSRule self.assertEqual(css_parser.css.CSSStyleSheet, type(self.r.styleSheet)) self.assertEqual(0, self.r.styleSheet.cssRules.length) # href r = css_parser.css.CSSImportRule('x') self.assertEqual('@import url(x);', r.cssText) self.assertEqual('x', r.href) self.assertEqual(None, r.hreftype) # href + mediaText r = css_parser.css.CSSImportRule('x', 'print') self.assertEqual('@import url(x) print;', r.cssText) self.assertEqual('x', r.href) self.assertEqual('print', r.media.mediaText) # href + name r = css_parser.css.CSSImportRule('x', name='n') self.assertEqual('@import url(x) "n";', r.cssText) self.assertEqual('x', r.href) self.assertEqual('n', r.name) # href + mediaText + name r = css_parser.css.CSSImportRule('x', 'print', 'n') self.assertEqual('@import url(x) print "n";', r.cssText) self.assertEqual('x', r.href) self.assertEqual('print', r.media.mediaText) self.assertEqual('n', r.name) # media +name only self.r = css_parser.css.CSSImportRule(mediaText='print', name="n") self.assertEqual(css_parser.stylesheets.MediaList, type(self.r.media)) self.assertEqual('', self.r.cssText) self.assertEqual('print', self.r.media.mediaText) self.assertEqual('n', self.r.name) # only possible to set @... similar name self.assertRaises(xml.dom.InvalidModificationErr, self.r._setAtkeyword, 'x') def test_cssText(self): "CSSImportRule.cssText" tests = { # href string '''@import "str";''': None, '''@import"str";''': '''@import "str";''', '''@\\import "str";''': '''@import "str";''', '''@IMPORT "str";''': '''@import "str";''', '''@import 'str';''': '''@import "str";''', '''@import 'str' ;''': '''@import "str";''', '''@import "str";''': None, '''@import "str" ;''': '''@import "str";''', r'''@import "\"" ;''': r'''@import "\"";''', '''@import '\\'';''': r'''@import "'";''', '''@import '"';''': r'''@import "\"";''', # href url '''@import url(x.css);''': None, # nospace '''@import url(")");''': '''@import url(")");''', '''@import url("\\"");''': '''@import url("\\"");''', '''@import url('\\'');''': '''@import url("'");''', # href + media # all is removed '''@import "str" all;''': '''@import "str";''', '''@import "str" tv, print;''': None, '''@import"str"tv,print;''': '''@import "str" tv, print;''', '''@import "str" tv, print, all;''': '''@import "str";''', '''@import "str" handheld, all;''': '''@import "str";''', '''@import "str" all, handheld;''': '''@import "str";''', '''@import "str" not tv;''': None, '''@import "str" only tv;''': None, '''@import "str" only tv and (color: 2);''': None, # href + name '''@import "str" "name";''': None, '''@import "str" 'name';''': '''@import "str" "name";''', '''@import url(x) "name";''': None, '''@import "str" "\\"";''': None, '''@import "str" '\\'';''': '''@import "str" "'";''', # href + media + name '''@import"str"tv"name";''': '''@import "str" tv "name";''', '''@import\t\r\f\n"str"\t\t\r\f\ntv\t\t\r\f\n"name"\t;''': '''@import "str" tv "name";''', # comments '''@import /*1*/ "str" /*2*/;''': None, '@import/*1*//*2*/"str"/*3*//*4*/all/*5*//*6*/"name"/*7*//*8*/ ;': '@import /*1*/ /*2*/ "str" /*3*/ /*4*/ all /*5*/ /*6*/ "name" /*7*/ /*8*/;', '@import/*1*//*2*/url(u)/*3*//*4*/all/*5*//*6*/"name"/*7*//*8*/ ;': '@import /*1*/ /*2*/ url(u) /*3*/ /*4*/ all /*5*/ /*6*/ "name" /*7*/ /*8*/;', '@import/*1*//*2*/url("u")/*3*//*4*/all/*5*//*6*/"name"/*7*//*8*/ ;': '@import /*1*/ /*2*/ url(u) /*3*/ /*4*/ all /*5*/ /*6*/ "name" /*7*/ /*8*/;', # WS '@import\n\t\f "str"\n\t\f tv\n\t\f "name"\n\t\f ;': '@import "str" tv "name";', '@import\n\t\f url(\n\t\f u\n\t\f )\n\t\f tv\n\t\f "name"\n\t\f ;': '@import url(u) tv "name";', '@import\n\t\f url("u")\n\t\f tv\n\t\f "name"\n\t\f ;': '@import url(u) tv "name";', '@import\n\t\f url(\n\t\f "u"\n\t\f )\n\t\f tv\n\t\f "name"\n\t\f ;': '@import url(u) tv "name";', } self.do_equal_r(tests) # set cssText tests.update({ '@import "x.css" tv': '@import "x.css" tv;', '@import "x.css"': '@import "x.css";', # no ; "@import 'x.css'": '@import "x.css";', # no ; '@import url(x.css)': '@import url(x.css);', # no ; '@import "x;': '@import "x;";', # no "! }) self.do_equal_p(tests) # parse tests = { '''@import;''': xml.dom.SyntaxErr, '''@import all;''': xml.dom.SyntaxErr, '''@import all"name";''': xml.dom.SyntaxErr, '''@import;''': xml.dom.SyntaxErr, '''@import x";''': xml.dom.SyntaxErr, '''@import "str" ,all;''': xml.dom.SyntaxErr, '''@import "str" all,;''': xml.dom.SyntaxErr, '''@import "str" all tv;''': xml.dom.SyntaxErr, '''@import "str" "name" all;''': xml.dom.SyntaxErr, } self.do_raise_p(tests) # parse tests.update({ '@import "x.css"': xml.dom.SyntaxErr, "@import 'x.css'": xml.dom.SyntaxErr, '@import url(x.css)': xml.dom.SyntaxErr, '@import "x.css" tv': xml.dom.SyntaxErr, '@import "x;': xml.dom.SyntaxErr, '''@import url("x);''': xml.dom.SyntaxErr, # trailing '''@import "x";"a"''': xml.dom.SyntaxErr, # trailing S or COMMENT '''@import "x";/**/''': xml.dom.SyntaxErr, '''@import "x"; ''': xml.dom.SyntaxErr, }) self.do_raise_r(tests) # set cssText def test_href(self): "CSSImportRule.href" # set self.r.href = 'x' self.assertEqual('x', self.r.href) self.assertEqual('@import url(x);', self.r.cssText) # http self.r.href = 'http://www.example.com/x?css=z&v=1' self.assertEqual('http://www.example.com/x?css=z&v=1', self.r.href) self.assertEqual('@import url(http://www.example.com/x?css=z&v=1);', self.r.cssText) # also if hreftype changed self.r.hreftype = 'string' self.assertEqual('http://www.example.com/x?css=z&v=1', self.r.href) self.assertEqual('@import "http://www.example.com/x?css=z&v=1";', self.r.cssText) # string escaping? self.r.href = '"' self.assertEqual('@import "\\"";', self.r.cssText) self.r.hreftype = 'url' self.assertEqual('@import url("\\"");', self.r.cssText) # url escaping? self.r.href = ')' self.assertEqual('@import url(")");', self.r.cssText) self.r.hreftype = 'NOT VALID' # using default self.assertEqual('@import url(")");', self.r.cssText) def test_hrefFound(self): "CSSImportRule.hrefFound" def fetcher(url): if url == 'http://example.com/yes': return None, '/**/' else: return None, None parser = css_parser.CSSParser(fetcher=fetcher) sheet = parser.parseString('@import "http://example.com/yes" "name"') r = sheet.cssRules[0] self.assertEqual('/**/'.encode(), r.styleSheet.cssText) self.assertEqual(True, r.hrefFound) self.assertEqual('name', r.name) r.cssText = '@import url(http://example.com/none) "name2";' self.assertEqual(''.encode(), r.styleSheet.cssText) self.assertEqual(False, r.hrefFound) self.assertEqual('name2', r.name) sheet.cssText = '@import url(http://example.com/none);' self.assertNotEqual(r, sheet.cssRules[0]) def test_hreftype(self): "CSSImportRule.hreftype" self.r = css_parser.css.CSSImportRule() self.r.cssText = '@import /*1*/url(org) /*2*/;' self.assertEqual('uri', self.r.hreftype) self.assertEqual('@import /*1*/ url(org) /*2*/;', self.r.cssText) self.r.cssText = '@import /*1*/"org" /*2*/;' self.assertEqual('string', self.r.hreftype) self.assertEqual('@import /*1*/ "org" /*2*/;', self.r.cssText) self.r.href = 'new' self.assertEqual('@import /*1*/ "new" /*2*/;', self.r.cssText) self.r.hreftype = 'uri' self.assertEqual('@import /*1*/ url(new) /*2*/;', self.r.cssText) def test_media(self): "CSSImportRule.media" self.r.href = 'x' # @import url(x) # media is readonly self.assertRaises(AttributeError, self.r.__setattr__, 'media', None) # but not static self.r.media.mediaText = 'print' self.assertEqual('@import url(x) print;', self.r.cssText) self.r.media.appendMedium('tv') self.assertEqual('@import url(x) print, tv;', self.r.cssText) # for generated rule r = css_parser.css.CSSImportRule(href='x') self.assertRaisesMsg(xml.dom.InvalidModificationErr, basetest.msg3x( '''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'tv') as already specified "all" (set ``mediaText`` instead).'''), r.media.appendMedium, 'tv') self.assertEqual('@import url(x);', r.cssText) self.assertRaisesMsg(xml.dom.InvalidModificationErr, basetest.msg3x( '''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'tv') as already specified "all" (set ``mediaText`` instead).'''), r.media.appendMedium, 'tv') self.assertEqual('@import url(x);', r.cssText) r.media.mediaText = 'tv' self.assertEqual('@import url(x) tv;', r.cssText) r.media.appendMedium('print') # all + tv = all! self.assertEqual('@import url(x) tv, print;', r.cssText) # for parsed rule without initial media s = css_parser.parseString('@import url(x);') r = s.cssRules[0] self.assertRaisesMsg(xml.dom.InvalidModificationErr, basetest.msg3x( '''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'tv') as already specified "all" (set ``mediaText`` instead).'''), r.media.appendMedium, 'tv') self.assertEqual('@import url(x);', r.cssText) self.assertRaisesMsg(xml.dom.InvalidModificationErr, basetest.msg3x( '''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'tv') as already specified "all" (set ``mediaText`` instead).'''), r.media.appendMedium, 'tv') self.assertEqual('@import url(x);', r.cssText) r.media.mediaText = 'tv' self.assertEqual('@import url(x) tv;', r.cssText) r.media.appendMedium('print') # all + tv = all! self.assertEqual('@import url(x) tv, print;', r.cssText) def test_name(self): "CSSImportRule.name" r = css_parser.css.CSSImportRule('x', name='a000000') self.assertEqual('a000000', r.name) self.assertEqual('@import url(x) "a000000";', r.cssText) r.name = "n" self.assertEqual('n', r.name) self.assertEqual('@import url(x) "n";', r.cssText) r.name = '"' self.assertEqual('"', r.name) self.assertEqual('@import url(x) "\\"";', r.cssText) r.hreftype = 'string' self.assertEqual('@import "x" "\\"";', r.cssText) r.name = "123" self.assertEqual('@import "x" "123";', r.cssText) r.name = None self.assertEqual(None, r.name) self.assertEqual('@import "x";', r.cssText) r.name = "" self.assertEqual(None, r.name) self.assertEqual('@import "x";', r.cssText) self.assertRaises(xml.dom.SyntaxErr, r._setName, 0) self.assertRaises(xml.dom.SyntaxErr, r._setName, 123) def test_styleSheet(self): "CSSImportRule.styleSheet" def fetcher(url): if url == "/root/level1/anything.css": return None, '@import "level2/css.css" "title2";' else: return None, 'a { color: red }' parser = css_parser.CSSParser(fetcher=fetcher) sheet = parser.parseString('''@charset "ascii"; @import "level1/anything.css" tv "title";''', href='/root/') self.assertEqual(sheet.href, '/root/') ir = sheet.cssRules[1] self.assertEqual(ir.href, 'level1/anything.css') self.assertEqual(ir.styleSheet.href, '/root/level1/anything.css') # inherits ascii as no self charset is set self.assertEqual(ir.styleSheet.encoding, 'ascii') self.assertEqual(ir.styleSheet.ownerRule, ir) self.assertEqual(ir.styleSheet.media.mediaText, 'tv') self.assertEqual(ir.styleSheet.parentStyleSheet, None) # sheet self.assertEqual(ir.styleSheet.title, 'title') self.assertEqual(ir.styleSheet.cssText, '@charset "ascii";\n@import "level2/css.css" "title2";'.encode()) ir2 = ir.styleSheet.cssRules[1] self.assertEqual(ir2.href, 'level2/css.css') self.assertEqual(ir2.styleSheet.href, '/root/level1/level2/css.css') # inherits ascii as no self charset is set self.assertEqual(ir2.styleSheet.encoding, 'ascii') self.assertEqual(ir2.styleSheet.ownerRule, ir2) self.assertEqual(ir2.styleSheet.media.mediaText, 'all') self.assertEqual(ir2.styleSheet.parentStyleSheet, None) # ir.styleSheet self.assertEqual(ir2.styleSheet.title, 'title2') self.assertEqual(ir2.styleSheet.cssText, '@charset "ascii";\na {\n color: red\n }'.encode()) sheet = css_parser.parseString('@import "CANNOT-FIND.css";') ir = sheet.cssRules[0] self.assertEqual(ir.href, "CANNOT-FIND.css") self.assertEqual(type(ir.styleSheet), css_parser.css.CSSStyleSheet) def fetcher(url): if url.endswith('level1.css'): return None, '@charset "ascii"; @import "level2.css";'.encode() else: return None, 'a { color: red }'.encode() parser = css_parser.CSSParser(fetcher=fetcher) sheet = parser.parseString('@charset "iso-8859-1";@import "level1.css";') self.assertEqual(sheet.encoding, 'iso-8859-1') sheet = sheet.cssRules[1].styleSheet self.assertEqual(sheet.encoding, 'ascii') sheet = sheet.cssRules[1].styleSheet self.assertEqual(sheet.encoding, 'ascii') def test_incomplete(self): "CSSImportRule (incomplete)" tests = { '@import "x.css': '@import "x.css";', "@import 'x": '@import "x";', # TODO: "@import url(x": '@import url(x);', "@import url('x": '@import url(x);', '@import url("x;': '@import url("x;");', '@import url( "x;': '@import url("x;");', '@import url("x ': '@import url("x ");', '@import url(x ': '@import url(x);', '''@import "a @import "b"; @import "c";''': '@import "c";' } self.do_equal_p(tests, raising=False) # parse def test_InvalidModificationErr(self): "CSSImportRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@import') def test_reprANDstr(self): "CSSImportRule.__repr__(), .__str__()" href = 'x.css' mediaText = 'tv, print' name = 'name' s = css_parser.css.CSSImportRule(href=href, mediaText=mediaText, name=name) # str(): mediaText nor name are present here self.assertTrue(href in str(s)) # repr() s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(href == s2.href) self.assertTrue(mediaText == s2.media.mediaText) self.assertTrue(name == s2.name) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssmediarule.py0000644000175000017500000004332300000000000022300 0ustar00kovidkovid"""Testcases for css_parser.css.CSSMediaRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class CSSMediaRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSMediaRuleTestCase, self).setUp() self.r = css_parser.css.CSSMediaRule() self.rRO = css_parser.css.CSSMediaRule(readonly=True) self.r_type = css_parser.css.CSSMediaRule.MEDIA_RULE self.r_typeString = 'MEDIA_RULE' # for tests self.stylerule = css_parser.css.CSSStyleRule() self.stylerule.cssText = 'a {}' def test_init(self): "CSSMediaRule.__init__()" super(CSSMediaRuleTestCase, self).test_init() r = css_parser.css.CSSMediaRule() self.assertEqual(css_parser.css.CSSRuleList, type(r.cssRules)) self.assertEqual([], r.cssRules) self.assertEqual('', r.cssText) self.assertEqual(css_parser.stylesheets.MediaList, type(r.media)) self.assertEqual('all', r.media.mediaText) self.assertEqual(None, r.name) r = css_parser.css.CSSMediaRule(mediaText='print', name='name') self.assertEqual(css_parser.css.CSSRuleList, type(r.cssRules)) self.assertEqual([], r.cssRules) self.assertEqual('', r.cssText) self.assertEqual(css_parser.stylesheets.MediaList, type(r.media)) self.assertEqual('print', r.media.mediaText) self.assertEqual('name', r.name) # only possible to set @... similar name self.assertRaises(xml.dom.InvalidModificationErr, self.r._setAtkeyword, 'x') def test_iter(self): "CSSMediaRule.__iter__()" m = css_parser.css.CSSMediaRule() m.cssText = '''@media all { /*1*/a { left: 0} b{ top:0} }''' types = [css_parser.css.CSSRule.COMMENT, css_parser.css.CSSRule.STYLE_RULE, css_parser.css.CSSRule.STYLE_RULE] for i, rule in enumerate(m): self.assertEqual(rule, m.cssRules[i]) self.assertEqual(rule.type, types[i]) self.assertEqual(rule.parentRule, m) def test_refs(self): """CSSStylesheet references""" s = css_parser.parseString('@media all {a {color: red}}') r = s.cssRules[0] rules = r.cssRules self.assertEqual(r.cssRules[0].parentStyleSheet, s) self.assertEqual(rules[0].parentStyleSheet, s) # set cssText r.cssText = '@media all {a {color: blue}}' # not anymore: self.assertEqual(rules, r.cssRules) # set cssRules r.cssRules = css_parser.parseString(''' /**/ @x; b {}').cssRules''').cssRules # new object self.assertNotEqual(rules, r.cssRules) for i, sr in enumerate(r.cssRules): self.assertEqual(sr.parentStyleSheet, s) self.assertEqual(sr.parentRule, r) def test_cssRules(self): "CSSMediaRule.cssRules" r = css_parser.css.CSSMediaRule() self.assertEqual([], r.cssRules) sr = css_parser.css.CSSStyleRule() r.cssRules.append(sr) self.assertEqual([sr], r.cssRules) ir = css_parser.css.CSSImportRule() self.assertRaises(xml.dom.HierarchyRequestErr, r.cssRules.append, ir) s = css_parser.parseString('@media all { /*1*/a {x:1} }') m = s.cssRules[0] self.assertEqual(2, m.cssRules.length) del m.cssRules[0] self.assertEqual(1, m.cssRules.length) m.cssRules.append('/*2*/') self.assertEqual(2, m.cssRules.length) m.cssRules.extend(css_parser.parseString('/*3*/x {y:2}').cssRules) self.assertEqual(4, m.cssRules.length) self.assertEqual('@media all {\n a {\n x: 1\n }\n /*2*/\n /*3*/\n x {\n y: 2\n }\n }', m.cssText) for rule in m.cssRules: self.assertEqual(rule.parentStyleSheet, s) self.assertEqual(rule.parentRule, m) def test_cssText(self): "CSSMediaRule.cssText" style = '''{ a { color: red } }''' mls = { ' (min-device-pixel-ratio: 1.3), (min-resolution: 1.3dppx) ': None, ' tv ': None, ' only tv ': None, ' not tv ': None, ' only tv and (color) ': None, ' only tv and(color)': ' only tv and (color) ', ' only tv and (color: red) ': None, ' only tv and (color: red) and (width: 100px) ': None, ' only tv and (color: red) and (width: 100px), tv ': None, ' only tv and (color: red) and (width: 100px), tv and (width: 20px) ': None, ' only tv and(color :red)and( width :100px ) ,tv and(width: 20px) ': ' only tv and (color: red) and (width: 100px), tv and (width: 20px) ', ' (color: red) and (width: 100px), (width: 20px) ': None, ' /*1*/ only /*2*/ tv /*3*/ and /*4*/ (/*5*/ width) /*5*/ /*6*/, (color) and (height) ': None, '(color)and(width),(height)': ' (color) and (width), (height) ' } tests = {} for b, a in mls.items(): if a is None: a = b tests['@media%s%s' % (b, style)] = '@media%s%s' % (a, style) self.do_equal_p(tests) self.do_equal_r(tests) tests = { '@media only tv{}': '', '@media not tv{}': '', '@media only tv and (color){}': '', '@media only tv and (color: red){}': '', '@media only tv and (color: red) and (width: 100px){}': '', '@media only tv and (color: red) and (width: 100px), tv{}': '', '@media only tv and (color: red) and (width: 100px), tv and (width: 20px){}': '', '@media (color: red) and (width: 100px), (width: 20px){}': '', '@media (width){}': '', '@media (width:10px){}': '', '@media (width), (color){}': '', '@media (width) , (color),(height){}': '', '@media (width) , (color) and (height){}': '', '@media (width) and (color){}': '', '@media all and (width){}': '', '@media all and (width:10px){}': '', '@media all and (width), (color){}': '', '@media all and (width) , (color),(height){}': '', '@media all and (width) , (color) and (height){}': '', '@media all and (width) and (color){}': '', '@media only tv and (width){}': '', '@media only tv and (width:10px){}': '', '@media only tv and (width), (color){}': '', '@media only tv and (width) , (color),(height){}': '', '@media only tv and (width) , (color) and (height){}': '', '@media only tv and (width) and (color){}': '', '@media only tv and (width) "name" {}': '', '@media only tv and (width:10px) "name" {}': '', '@media only tv and (width), (color){}': '', '@media only tv and (width) , (color),(height){}': '', '@media only tv and (width) , (color) and (height){}': '', '@media only tv and (width) and (color){}': '', '@media all "name"{}': '', '@media all {}': '', '@media/*x*/all{}': '', '@media all { a{ x: 1} }': '@media all {\n a {\n x: 1\n }\n }', '@media all "name" { a{ x: 1} }': '@media all "name" {\n a {\n x: 1\n }\n }', '@MEDIA all { a{x:1} }': '@media all {\n a {\n x: 1\n }\n }', '@\\media all { a{x:1} }': '@media all {\n a {\n x: 1\n }\n }', '@media all {@x some;a{color: red;}b{color: green;}}': '''@media all { @x some; a { color: red } b { color: green } }''', '@media all { @x{}}': '@media all {\n @x {\n }\n }', '@media all "n" /**/ { @x{}}': '@media all "n" /**/ {\n @x {\n }\n }', # comments '@media/*1*//*2*/all/*3*//*4*/{/*5*/a{x:1}}': '@media /*1*/ /*2*/ all /*3*/ /*4*/ {\n /*5*/\n a {\n x: 1\n }\n }', '@media /*1*/ /*2*/ all /*3*/ /*4*/ { /*5*/ a{ x: 1} }': '@media /*1*/ /*2*/ all /*3*/ /*4*/ {\n /*5*/\n a {\n x: 1\n }\n }', # WS '@media\n\t\f all\n\t\f {\n\t\f a{ x: 1}\n\t\f }': '@media all {\n a {\n x: 1\n }\n }', # @page rule inside @media '@media all { @page { margin: 0; } }': '@media all {\n @page {\n margin: 0\n }\n }', # nested media rules '@media all { @media all { p { color: red; } } }': '@media all {\n @media all {\n p {\n ' 'color: red\n }\n }\n }', } self.do_equal_p(tests) self.do_equal_r(tests) tests = { '@media {}': xml.dom.SyntaxErr, '@media;': xml.dom.SyntaxErr, '@media/*only comment*/{}': xml.dom.SyntaxErr, '@media all;': xml.dom.SyntaxErr, '@media all "n";': xml.dom.SyntaxErr, '@media all; @x{}': xml.dom.SyntaxErr, '@media { a{ x: 1} }': xml.dom.SyntaxErr, '@media "name" { a{ x: 1} }': xml.dom.SyntaxErr, '@media "name" all { a{ x: 1} }': xml.dom.SyntaxErr, '@media all { @charset "x"; a{}}': xml.dom.HierarchyRequestErr, '@media all { @import "x"; a{}}': xml.dom.HierarchyRequestErr, '@media all { , }': xml.dom.SyntaxErr, '@media all {}EXTRA': xml.dom.SyntaxErr, '@media ({}': xml.dom.SyntaxErr, '@media (color{}': xml.dom.SyntaxErr, '@media (color:{}': xml.dom.SyntaxErr, '@media (color:red{}': xml.dom.SyntaxErr, '@media (:red){}': xml.dom.SyntaxErr, '@media (:){}': xml.dom.SyntaxErr, '@media color:red){}': xml.dom.SyntaxErr, } self.do_raise_p(tests) self.do_raise_r(tests) tests = { # extra stuff '@media all { x{} } a{}': xml.dom.SyntaxErr, } self.do_raise_r(tests) m = css_parser.css.CSSMediaRule() m.cssText = '''@media all {@x; /*1*/a{color: red;}}''' for r in m.cssRules: self.assertEqual(m, r.parentRule) self.assertEqual(m.parentStyleSheet, r.parentStyleSheet) css_parser.ser.prefs.useDefaults() def test_media(self): "CSSMediaRule.media" # see CSSImportRule.media # setting not allowed self.assertRaises(AttributeError, self.r.__setattr__, 'media', None) self.assertRaises(AttributeError, self.r.__setattr__, 'media', 0) # set mediaText instead self.r.media.mediaText = 'print' self.r.insertRule(self.stylerule) self.assertEqual('', self.r.cssText) css_parser.ser.prefs.keepEmptyRules = True self.assertEqual('@media print {\n a {}\n }', self.r.cssText) css_parser.ser.prefs.useDefaults() def test_name(self): "CSSMediaRule.name" r = css_parser.css.CSSMediaRule() r.cssText = '@media all "\\n\\"ame" {a{left: 0}}' self.assertEqual('\\n"ame', r.name) r.name = "n" self.assertEqual('n', r.name) self.assertEqual('@media all "n" {\n a {\n left: 0\n }\n }', r.cssText) r.name = '"' self.assertEqual('"', r.name) self.assertEqual('@media all "\\"" {\n a {\n left: 0\n }\n }', r.cssText) r.name = '' self.assertEqual(None, r.name) self.assertEqual('@media all {\n a {\n left: 0\n }\n }', r.cssText) r.name = None self.assertEqual(None, r.name) self.assertEqual('@media all {\n a {\n left: 0\n }\n }', r.cssText) self.assertRaises(xml.dom.SyntaxErr, r._setName, 0) self.assertRaises(xml.dom.SyntaxErr, r._setName, 123) def test_deleteRuleIndex(self): "CSSMediaRule.deleteRule(index)" # see CSSStyleSheet.deleteRule m = css_parser.css.CSSMediaRule() m.cssText = '''@media all { @a; /* x */ @b; @c; @d; }''' self.assertEqual(5, m.cssRules.length) self.assertRaises(xml.dom.IndexSizeErr, m.deleteRule, 5) # end -1 # check parentRule r = m.cssRules[-1] self.assertEqual(m, r.parentRule) m.deleteRule(-1) self.assertEqual(None, r.parentRule) self.assertEqual(4, m.cssRules.length) self.assertEqual( '@media all {\n @a;\n /* x */\n @b;\n @c;\n }', m.cssText) # beginning m.deleteRule(0) self.assertEqual(3, m.cssRules.length) self.assertEqual('@media all {\n /* x */\n @b;\n @c;\n }', m.cssText) # middle m.deleteRule(1) self.assertEqual(2, m.cssRules.length) self.assertEqual('@media all {\n /* x */\n @c;\n }', m.cssText) # end m.deleteRule(1) self.assertEqual(1, m.cssRules.length) self.assertEqual('@media all {\n /* x */\n }', m.cssText) def test_deleteRule(self): "CSSMediaRule.deleteRule(rule)" m = css_parser.css.CSSMediaRule() m.cssText = '''@media all { a { color: red; } b { color: blue; } c { color: green; } }''' s1, s2, s3 = m.cssRules r = css_parser.css.CSSStyleRule() self.assertRaises(xml.dom.IndexSizeErr, m.deleteRule, r) self.assertEqual(3, m.cssRules.length) m.deleteRule(s2) self.assertEqual(2, m.cssRules.length) self.assertEqual( m.cssText, '@media all {\n a {\n color: red\n }\n c {\n color: green\n }\n }') self.assertRaises(xml.dom.IndexSizeErr, m.deleteRule, s2) def test_add(self): "CSSMediaRule.add()" # see CSSStyleSheet.add r = css_parser.css.CSSMediaRule() stylerule1 = css_parser.css.CSSStyleRule() stylerule2 = css_parser.css.CSSStyleRule() r.add(stylerule1) r.add(stylerule2) self.assertEqual(r.cssRules[0], stylerule1) self.assertEqual(r.cssRules[1], stylerule2) def test_insertRule(self): "CSSMediaRule.insertRule" # see CSSStyleSheet.insertRule r = css_parser.css.CSSMediaRule() charsetrule = css_parser.css.CSSCharsetRule('ascii') importrule = css_parser.css.CSSImportRule('x') namespacerule = css_parser.css.CSSNamespaceRule() pagerule = css_parser.css.CSSPageRule() unknownrule = css_parser.css.CSSUnknownRule('@x;') stylerule = css_parser.css.CSSStyleRule('a') stylerule.cssText = 'a { x: 1}' comment1 = css_parser.css.CSSComment('/*1*/') comment2 = css_parser.css.CSSComment('/*2*/') # hierarchy self.assertRaises(xml.dom.HierarchyRequestErr, r.insertRule, charsetrule, 0) self.assertRaises(xml.dom.HierarchyRequestErr, r.insertRule, importrule, 0) self.assertRaises(xml.dom.HierarchyRequestErr, r.insertRule, namespacerule, 0) # start insert r.insertRule(stylerule, 0) self.assertEqual(r, stylerule.parentRule) self.assertEqual(r.parentStyleSheet, stylerule.parentStyleSheet) # before r.insertRule(comment1, 0) self.assertEqual(r, comment1.parentRule) self.assertEqual(r.parentStyleSheet, stylerule.parentStyleSheet) # end explicit r.insertRule(unknownrule, 2) self.assertEqual(r, unknownrule.parentRule) self.assertEqual(r.parentStyleSheet, stylerule.parentStyleSheet) # end implicit r.insertRule(comment2) self.assertEqual(r, comment2.parentRule) self.assertEqual(r.parentStyleSheet, stylerule.parentStyleSheet) self.assertEqual( '@media all {\n /*1*/\n a {\n x: 1\n }\n @x;\n /*2*/\n }', r.cssText) # index self.assertRaises(xml.dom.IndexSizeErr, r.insertRule, stylerule, -1) self.assertRaises(xml.dom.IndexSizeErr, r.insertRule, stylerule, r.cssRules.length + 1) def test_InvalidModificationErr(self): "CSSMediaRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@media') def test_incomplete(self): "CSSMediaRule (incomplete)" tests = { '@media all { @unknown;': # no } '@media all {\n @unknown;\n }', '@media all { a {x:"1"}': # no } '@media all {\n a {\n x: "1"\n }\n }', '@media all { a {x:"1"': # no }} '@media all {\n a {\n x: "1"\n }\n }', '@media all { a {x:"1': # no "}} '@media all {\n a {\n x: "1"\n }\n }', } self.do_equal_p(tests) # parse def test_reprANDstr(self): "CSSMediaRule.__repr__(), .__str__()" mediaText = 'tv, print' s = css_parser.css.CSSMediaRule(mediaText=mediaText) self.assertTrue(mediaText in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(mediaText == s2.media.mediaText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssnamespacerule.py0000644000175000017500000002167700000000000023165 0ustar00kovidkovid"""Testcases for css_parser.css.CSSImportRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class CSSNamespaceRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSNamespaceRuleTestCase, self).setUp() self.r = css_parser.css.CSSNamespaceRule(namespaceURI='x') # self.rRO = css_parser.css.CSSNamespaceRule(namespaceURI='x', # readonly=True) self.r_type = css_parser.css.CSSRule.NAMESPACE_RULE self.r_typeString = 'NAMESPACE_RULE' def test_init(self): "CSSNamespaceRule.__init__()" # cannot use here as self.r and self rRO and not useful #super(CSSNamespaceRuleTestCase, self).test_init() tests = [ (None, None), ('', ''), (None, ''), ('', None), ('', 'no-uri'), ] for uri, p in tests: r = css_parser.css.CSSNamespaceRule(namespaceURI=uri, prefix=p) self.assertEqual(None, r.namespaceURI) self.assertEqual('', r.prefix) self.assertEqual('', r.cssText) self.assertEqual(None, r.parentStyleSheet) self.assertEqual(None, r.parentRule) r = css_parser.css.CSSNamespaceRule(namespaceURI='example') self.assertEqual('example', r.namespaceURI) self.assertEqual('', r.prefix) self.assertEqual('@namespace "example";', r.cssText) self.sheet.add(r) self.assertEqual(self.sheet, r.parentStyleSheet) r = css_parser.css.CSSNamespaceRule(namespaceURI='example', prefix='p') self.assertEqual('example', r.namespaceURI) self.assertEqual('p', r.prefix) self.assertEqual('@namespace p "example";', r.cssText) css = '@namespace p "u";' r = css_parser.css.CSSNamespaceRule(cssText=css) self.assertEqual(r.cssText, css) # only possible to set @... similar name self.assertRaises(xml.dom.InvalidModificationErr, self.r._setAtkeyword, 'x') def test_cssText(self): "CSSNamespaceRule.cssText" # cssText may only be set initalially r = css_parser.css.CSSNamespaceRule() css = '@namespace p "u";' r.cssText = css self.assertEqual(r.cssText, css) self.assertRaises(xml.dom.NoModificationAllowedErr, r._setCssText, '@namespace p "OTHER";') tests = { '@namespace "";': None, '@namespace "u";': None, '@namespace p "u";': None, '@namespace empty "";': None, '@namespace p "p";': None, "@namespace p 'u';": '@namespace p "u";', '@\\namespace p "u";': '@namespace p "u";', '@NAMESPACE p "u";': '@namespace p "u";', '@namespace p "u" ;': '@namespace p "u";', '@namespace p"u";': '@namespace p "u";', '@namespace p "u";': '@namespace p "u";', '@namespace/*1*/"u"/*2*/;': '@namespace /*1*/ "u" /*2*/;', '@namespace/*1*/p/*2*/"u"/*3*/;': '@namespace /*1*/ p /*2*/ "u" /*3*/;', '@namespace p url(u);': '@namespace p "u";', '@namespace p url(\'u\');': '@namespace p "u";', '@namespace p url(\"u\");': '@namespace p "u";', '@namespace p url( \"u\" );': '@namespace p "u";', # comments '@namespace/*1*//*2*/p/*3*//*4*/url(u)/*5*//*6*/;': '@namespace /*1*/ /*2*/ p /*3*/ /*4*/ "u" /*5*/ /*6*/;', '@namespace/*1*//*2*/p/*3*//*4*/"u"/*5*//*6*/;': '@namespace /*1*/ /*2*/ p /*3*/ /*4*/ "u" /*5*/ /*6*/;', '@namespace/*1*//*2*/p/*3*//*4*/url("u")/*5*//*6*/;': '@namespace /*1*/ /*2*/ p /*3*/ /*4*/ "u" /*5*/ /*6*/;', '@namespace/*1*//*2*/url(u)/*5*//*6*/;': '@namespace /*1*/ /*2*/ "u" /*5*/ /*6*/;', # WS '@namespace\n\r\t\f p\n\r\t\f url(\n\r\t\f u\n\r\t\f )\n\r\t\f ;': '@namespace p "u";', '@namespace\n\r\t\f p\n\r\t\f url(\n\r\t\f "u"\n\r\t\f )\n\r\t\f ;': '@namespace p "u";', '@namespace\n\r\t\f p\n\r\t\f "str"\n\r\t\f ;': '@namespace p "str";', '@namespace\n\r\t\f "str"\n\r\t\f ;': '@namespace "str";' } self.do_equal_p(tests) # self.do_equal_r(tests) # cannot use here as always new r is needed for test, expected in tests.items(): r = css_parser.css.CSSNamespaceRule(cssText=test) if expected is None: expected = test self.assertEqual(expected, r.cssText) tests = { '@namespace;': xml.dom.SyntaxErr, # nothing '@namespace p;': xml.dom.SyntaxErr, # no namespaceURI '@namespace "u" p;': xml.dom.SyntaxErr, # order '@namespace "u";EXTRA': xml.dom.SyntaxErr, '@namespace p "u";EXTRA': xml.dom.SyntaxErr, } self.do_raise_p(tests) # parse tests.update({ '@namespace p url(x)': xml.dom.SyntaxErr, # missing ; '@namespace p "u"': xml.dom.SyntaxErr, # missing ; # trailing '@namespace "u"; ': xml.dom.SyntaxErr, '@namespace "u";/**/': xml.dom.SyntaxErr, '@namespace p "u"; ': xml.dom.SyntaxErr, '@namespace p "u";/**/': xml.dom.SyntaxErr, }) def _do(test): r = css_parser.css.CSSNamespaceRule(cssText=test) for test, expected in tests.items(): self.assertRaises(expected, _do, test) def test_namespaceURI(self): "CSSNamespaceRule.namespaceURI" # set only initially r = css_parser.css.CSSNamespaceRule(namespaceURI='x') self.assertEqual('x', r.namespaceURI) self.assertEqual('@namespace "x";', r.cssText) r = css_parser.css.CSSNamespaceRule(namespaceURI='"') self.assertEqual('@namespace "\\"";', r.cssText) self.assertRaises(xml.dom.NoModificationAllowedErr, r._setNamespaceURI, 'x') self.assertRaises(xml.dom.NoModificationAllowedErr, r._setCssText, '@namespace "u";') r._replaceNamespaceURI('http://example.com/new') self.assertEqual('http://example.com/new', r.namespaceURI) def test_prefix(self): "CSSNamespaceRule.prefix" r = css_parser.css.CSSNamespaceRule(namespaceURI='u') r.prefix = 'p' self.assertEqual('p', r.prefix) self.assertEqual('@namespace p "u";', r.cssText) r = css_parser.css.CSSNamespaceRule(cssText='@namespace x "u";') r.prefix = 'p' self.assertEqual('p', r.prefix) self.assertEqual('@namespace p "u";', r.cssText) valid = (None, '') for prefix in valid: r.prefix = prefix self.assertEqual(r.prefix, '') self.assertEqual('@namespace "u";', r.cssText) valid = ('a', '_x', 'a1', 'a-1') for prefix in valid: r.prefix = prefix self.assertEqual(r.prefix, prefix) self.assertEqual('@namespace %s "u";' % prefix, r.cssText) invalid = ('1', ' x', ' ', ',') for prefix in invalid: self.assertRaises(xml.dom.SyntaxErr, r._setPrefix, prefix) def test_InvalidModificationErr(self): "CSSNamespaceRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@namespace') def test_incomplete(self): "CSSNamespaceRule (incomplete)" tests = { '@namespace "uri': '@namespace "uri";', "@namespace url(x": '@namespace "x";', "@namespace url('x": '@namespace "x";', '@namespace url("x;': '@namespace "x;";', '@namespace url( "x;': '@namespace "x;";', '@namespace url("x ': '@namespace "x ";', '@namespace url(x ': '@namespace "x";', } self.do_equal_p(tests) # parse tests = { '@namespace "uri': xml.dom.SyntaxErr, "@namespace url(x": xml.dom.SyntaxErr, "@namespace url('x": xml.dom.SyntaxErr, '@namespace url("x;': xml.dom.SyntaxErr, '@namespace url( "x;': xml.dom.SyntaxErr, '@namespace url("x ': xml.dom.SyntaxErr, '@namespace url(x ': xml.dom.SyntaxErr } self.do_raise_r(tests) # set cssText def test_reprANDstr(self): "CSSNamespaceRule.__repr__(), .__str__()" namespaceURI = 'http://example.com' prefix = 'prefix' s = css_parser.css.CSSNamespaceRule(namespaceURI=namespaceURI, prefix=prefix) self.assertTrue(namespaceURI in str(s)) self.assertTrue(prefix in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(namespaceURI == s2.namespaceURI) self.assertTrue(prefix == s2.prefix) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_csspagerule.py0000644000175000017500000003413100000000000022132 0ustar00kovidkovid"""Testcases for css_parser.css.CSSPageRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class CSSPageRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSPageRuleTestCase, self).setUp() css_parser.ser.prefs.useDefaults() self.r = css_parser.css.CSSPageRule() self.rRO = css_parser.css.CSSPageRule(readonly=True) self.r_type = css_parser.css.CSSPageRule.PAGE_RULE self.r_typeString = 'PAGE_RULE' def tearDown(self): css_parser.ser.prefs.useDefaults() def test_init(self): "CSSPageRule.__init__()" super(CSSPageRuleTestCase, self).test_init() r = css_parser.css.CSSPageRule() self.assertEqual('', r.selectorText) self.assertEqual(css_parser.css.CSSStyleDeclaration, type(r.style)) self.assertEqual(r, r.style.parentRule) # until any properties self.assertEqual('', r.cssText) # only possible to set @... similar name self.assertRaises(xml.dom.InvalidModificationErr, self.r._setAtkeyword, 'x') def checkrefs(ff): self.assertEqual(ff, ff.style.parentRule) for p in ff.style: self.assertEqual(ff.style, p.parent) checkrefs(css_parser.css.CSSPageRule( style=css_parser.css.CSSStyleDeclaration('font-family: x'))) r = css_parser.css.CSSPageRule() r.cssText = '@page { font-family: x }' checkrefs(r) r = css_parser.css.CSSPageRule() r.style.setProperty('font-family', 'y') checkrefs(r) r = css_parser.css.CSSPageRule() r.style['font-family'] = 'z' checkrefs(r) r = css_parser.css.CSSPageRule() r.style.fontFamily = 'a' checkrefs(r) def test_InvalidModificationErr(self): "CSSPageRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@page') tests = { '@pag {}': xml.dom.InvalidModificationErr, } self.do_raise_r(tests) def test_incomplete(self): "CSSPageRule (incomplete)" tests = { '@page :left { ': '', # no } and no content '@page :left { color: red': '@page :left {\n color: red\n }', # no } } self.do_equal_p(tests) # parse def test_cssText(self): "CSSPageRule.cssText" EXP = '@page %s {\n margin: 0\n }' tests = { '@page {}': '', '@page:left{}': '', '@page :right {}': '', '@page {margin:0;}': '@page {\n margin: 0\n }', '@page name { margin: 0 }': EXP % 'name', '@page name:left { margin: 0 }': EXP % 'name:left', '@page name:right { margin: 0 }': EXP % 'name:right', '@page name:first { margin: 0 }': EXP % 'name:first', '@page :left { margin: 0 }': EXP % ':left', '@page:left { margin: 0 }': EXP % ':left', '@page :right { margin: 0 }': EXP % ':right', '@page :first { margin: 0 }': EXP % ':first', '@page :UNKNOWNIDENT { margin: 0 }': EXP % ':UNKNOWNIDENT', '@PAGE:left{margin:0;}': '@page :left {\n margin: 0\n }', '@\\page:left{margin:0;}': '@page :left {\n margin: 0\n }', # comments '@page/*1*//*2*/:left/*3*//*4*/{margin:0;}': '@page /*1*/ /*2*/ :left /*3*/ /*4*/ {\n margin: 0\n }', # WS '@page:left{margin:0;}': '@page :left {\n margin: 0\n }', '@page\n\r\f\t :left\n\r\f\t {margin:0;}': '@page :left {\n margin: 0\n }', # MarginRule '@page { @top-right { content: "2" } }': '@page {\n @top-right {\n content: "2"\n }\n }', '@page {padding: 1cm; margin: 1cm; @top-left {content: "1"}@top-right {content: "2";left: 1}}': '@page {\n padding: 1cm;\n margin: 1cm;\n @top-left {\n content: "1"\n }\n @top-right {\n content: "2";\n left: 1\n }\n }', '@page {@top-right { content: "1a"; content: "1b"; x: 1 }@top-right { content: "2"; y: 2 }}': '''@page {\n @top-right { content: "1a"; content: "1b"; x: 1; content: "2"; y: 2 }\n }''', } self.do_equal_r(tests) self.do_equal_p(tests) tests = { # auto is not allowed '@page AUto {}': xml.dom.SyntaxErr, '@page AUto:left {}': xml.dom.SyntaxErr, '@page : {}': xml.dom.SyntaxErr, '@page :/*1*/left {}': xml.dom.SyntaxErr, '@page : left {}': xml.dom.SyntaxErr, '@page :left :right {}': xml.dom.SyntaxErr, '@page :left a {}': xml.dom.SyntaxErr, # no S between IDENT and PSEUDO '@page a :left {}': xml.dom.SyntaxErr, '@page :left;': xml.dom.SyntaxErr, '@page :left }': xml.dom.SyntaxErr, } self.do_raise_p(tests) # parse tests.update({ # false selector '@page :right :left {}': xml.dom.SyntaxErr, # no } '@page :right X {}': xml.dom.SyntaxErr, # no } '@page X Y {}': xml.dom.SyntaxErr, # no } '@page :left {': xml.dom.SyntaxErr, # no } # trailing '@page :left {}1': xml.dom.SyntaxErr, # no } '@page :left {}/**/': xml.dom.SyntaxErr, # no } '@page :left {} ': xml.dom.SyntaxErr, # no } }) self.do_raise_r(tests) # set cssText def test_cssText2(self): "CSSPageRule.cssText 2" r = css_parser.css.CSSPageRule() s = 'a:left' r.selectorText = s self.assertEqual(r.selectorText, s) st = 'size: a4' r.style = st self.assertEqual(r.style.cssText, st) # invalid selector self.assertRaises(xml.dom.SyntaxErr, r._setStyle, '$') self.assertEqual(r.selectorText, s) self.assertEqual(r.style.cssText, st) self.assertRaises(xml.dom.SyntaxErr, r._setCssText, '@page $ { color: red }') self.assertEqual(r.selectorText, s) self.assertEqual(r.style.cssText, st) # invalid style self.assertRaises(xml.dom.SyntaxErr, r._setSelectorText, '$') self.assertEqual(r.selectorText, s) self.assertEqual(r.style.cssText, st) self.assertRaises(xml.dom.SyntaxErr, r._setCssText, '@page b:right { x }') self.assertEqual(r.selectorText, s) self.assertEqual(r.style.cssText, st) def test_selectorText(self): "CSSPageRule.selectorText" r = css_parser.css.CSSPageRule() r.selectorText = 'a:left' self.assertEqual(r.selectorText, 'a:left') tests = { '': '', 'name': None, ':left': None, ':right': None, ':first': None, ':UNKNOWNIDENT': None, 'name:left': None, ' :left': ':left', ':left': ':left', '/*1*/:left/*a*/': '/*1*/ :left /*a*/', '/*1*/ :left /*a*/ /*b*/': None, ':left/*a*/': ':left /*a*/', '/*1*/:left': '/*1*/ :left', } self.do_equal_r(tests, att='selectorText') tests = { ':': xml.dom.SyntaxErr, ':/*1*/left': xml.dom.SyntaxErr, ': left': xml.dom.SyntaxErr, ':left :right': xml.dom.SyntaxErr, ':left a': xml.dom.SyntaxErr, 'name :left': xml.dom.SyntaxErr, } self.do_raise_r(tests, att='_setSelectorText') def test_specificity(self): "CSSPageRule.specificity" r = css_parser.css.CSSPageRule() tests = { '': (0, 0, 0), 'name': (1, 0, 0), ':first': (0, 1, 0), ':left': (0, 0, 1), ':right': (0, 0, 1), ':UNKNOWNIDENT': (0, 0, 1), 'name:first': (1, 1, 0), 'name:left': (1, 0, 1), 'name:right': (1, 0, 1), 'name:X': (1, 0, 1) } for sel, exp in tests.items(): r.selectorText = sel self.assertEqual(r.specificity, exp) r = css_parser.css.CSSPageRule() r.cssText = '@page %s {}' % sel self.assertEqual(r.specificity, exp) def test_cssRules(self): "CSSPageRule.cssRules" s = css_parser.parseString('@page {}') p = s.cssRules[0] self.assertEqual(len(p.cssRules), 0) # add and insert m1 = css_parser.css.MarginRule('@top-left', 'color: red') i = p.add(m1) self.assertEqual(i, 0) self.assertEqual(len(p.cssRules), 1) m3 = css_parser.css.MarginRule() m3.cssText = '@top-right { color: blue }' i = p.insertRule(m3) self.assertEqual(i, 1) self.assertEqual(len(p.cssRules), 2) m2 = css_parser.css.MarginRule() m2.margin = '@top-center' m2.style = 'color: green' i = p.insertRule(m2, 1) self.assertEqual(i, 1) self.assertEqual(len(p.cssRules), 3) self.assertEqual(p.cssText, '''@page { @top-left { color: red } @top-center { color: green } @top-right { color: blue } }''') # keys and dict index self.assertEqual('@top-left' in p, True) self.assertEqual('@bottom-left' in p, False) self.assertEqual(p.keys(), ['@top-left', '@top-center', '@top-right']) self.assertEqual(p['@bottom-left'], None) self.assertEqual(p['@top-left'].cssText, 'color: red') p['@top-left'] = 'color: #f00' self.assertEqual(p['@top-left'].cssText, 'color: #f00') # delete p.deleteRule(m2) self.assertEqual(len(p.cssRules), 2) self.assertEqual(p.cssText, '''@page { @top-left { color: #f00 } @top-right { color: blue } }''') p.deleteRule(0) self.assertEqual(len(p.cssRules), 1) self.assertEqual(m3, p.cssRules[0]) self.assertEqual(p.cssText, '''@page { @top-right { color: blue } }''') del p['@top-right'] self.assertEqual(len(p.cssRules), 0) def test_style(self): "CSSPageRule.style (and references)" r = css_parser.css.CSSPageRule() s1 = r.style self.assertEqual(r, s1.parentRule) self.assertEqual('', s1.cssText) # set rule.cssText r.cssText = '@page { font-family: x1 }' self.assertNotEqual(r.style, s1) self.assertEqual(r, r.style.parentRule) self.assertEqual(r.cssText, '@page {\n font-family: x1\n }') self.assertEqual(r.style.cssText, 'font-family: x1') self.assertEqual(s1.cssText, '') s2 = r.style # set invalid rule.cssText try: r.cssText = '@page { $ }' except xml.dom.SyntaxErr as e: pass self.assertEqual(r.style, s2) self.assertEqual(r, r.style.parentRule) self.assertEqual(r.cssText, '@page {\n font-family: x1\n }') self.assertEqual(r.style.cssText, 'font-family: x1') self.assertEqual(s2.cssText, 'font-family: x1') s3 = r.style # set rule.style.cssText r.style.cssText = 'font-family: x2' self.assertEqual(r.style, s3) self.assertEqual(r, r.style.parentRule) self.assertEqual(r.cssText, '@page {\n font-family: x2\n }') self.assertEqual(r.style.cssText, 'font-family: x2') # set new style object s2 s2 = css_parser.css.CSSStyleDeclaration('font-family: y1') r.style = s2 self.assertEqual(r.style, s2) self.assertEqual(r, s2.parentRule) self.assertEqual(r.cssText, '@page {\n font-family: y1\n }') self.assertEqual(s2.cssText, 'font-family: y1') self.assertEqual(r.style.cssText, 'font-family: y1') self.assertEqual(s3.cssText, 'font-family: x2') # old # set s2.cssText s2.cssText = 'font-family: y2' self.assertEqual(r.style, s2) self.assertEqual(r.cssText, '@page {\n font-family: y2\n }') self.assertEqual(r.style.cssText, 'font-family: y2') self.assertEqual(s3.cssText, 'font-family: x2') # old # set invalid s2.cssText try: s2.cssText = '$' except xml.dom.SyntaxErr as e: pass self.assertEqual(r.style, s2) self.assertEqual(r.cssText, '@page {\n font-family: y2\n }') self.assertEqual(r.style.cssText, 'font-family: y2') self.assertEqual(s3.cssText, 'font-family: x2') # old # set r.style with text r.style = 'font-family: z' self.assertNotEqual(r.style, s2) self.assertEqual(r.cssText, '@page {\n font-family: z\n }') self.assertEqual(r.style.cssText, 'font-family: z') def test_properties(self): "CSSPageRule.style properties" r = css_parser.css.CSSPageRule() r.style.cssText = ''' margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; margin: 0; page-break-before: auto; page-break-after: auto; page-break-inside: auto; orphans: 3; widows: 3; ''' exp = '''@page { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; margin: 0; page-break-before: auto; page-break-after: auto; page-break-inside: auto; orphans: 3; widows: 3 }''' self.assertEqual(exp, r.cssText) def test_reprANDstr(self): "CSSPageRule.__repr__(), .__str__()" sel = ':left' s = css_parser.css.CSSPageRule(selectorText=sel) self.assertTrue(sel in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(sel == s2.selectorText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssproperties.py0000644000175000017500000000520500000000000022522 0ustar00kovidkovid"""Testcases for css_parser.css.cssproperties.""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser.css import css_parser.profiles class CSSPropertiesTestCase(basetest.BaseTestCase): # def test_cssvalues(self): # "cssproperties cssvalues" # # does actually return match object, so a very simplified test... # match = css_parser.css.cssproperties.cssvalues # # self.assertEqual(True, bool(match['color']('red'))) # self.assertEqual(False, bool(match['top']('red'))) # # self.assertEqual(True, bool(match['left']('0'))) # self.assertEqual(True, bool(match['left']('1px'))) # self.assertEqual(True, bool(match['left']('.1px'))) # self.assertEqual(True, bool(match['left']('-1px'))) # self.assertEqual(True, bool(match['left']('-.1px'))) # self.assertEqual(True, bool(match['left']('-0.1px'))) def test_toDOMname(self): "cssproperties _toDOMname(CSSname)" _toDOMname = css_parser.css.cssproperties._toDOMname self.assertEqual('color', _toDOMname('color')) self.assertEqual('fontStyle', _toDOMname('font-style')) self.assertEqual('MozOpacity', _toDOMname('-moz-opacity')) self.assertEqual('UNKNOWN', _toDOMname('UNKNOWN')) self.assertEqual('AnUNKNOWN', _toDOMname('-anUNKNOWN')) def test_toCSSname(self): "cssproperties _toCSSname(DOMname)" _toCSSname = css_parser.css.cssproperties._toCSSname self.assertEqual('color', _toCSSname('color')) self.assertEqual('font-style', _toCSSname('fontStyle')) self.assertEqual('-moz-opacity', _toCSSname('MozOpacity')) self.assertEqual('UNKNOWN', _toCSSname('UNKNOWN')) self.assertEqual('-anUNKNOWN', _toCSSname('AnUNKNOWN')) def test_CSS2Properties(self): "CSS2Properties" CSS2Properties = css_parser.css.cssproperties.CSS2Properties self.assertEqual(type(property()), type(CSS2Properties.color)) self.assertEqual(sum([len(x) for x in css_parser.profiles.properties.values()]), len(CSS2Properties._properties)) c2 = CSS2Properties() # CSS2Properties has simplified implementation return always None self.assertEqual(None, c2.color) self.assertEqual(None, c2.__setattr__('color', 1)) self.assertEqual(None, c2.__delattr__('color')) # only defined properties self.assertRaises(AttributeError, c2.__getattribute__, 'UNKNOWN') if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssrule.py0000644000175000017500000002052500000000000021277 0ustar00kovidkovid"""Testcases for css_parser.css.CSSRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser.css class CSSRuleTestCase(basetest.BaseTestCase): """ base class for all CSSRule subclass tests overwrite setUp with the appriopriate values, will be used in test_init and test_readonly overwrite all tests as you please, use:: super(CLASSNAME, self).test_TESTNAME(params) to use the base class tests too """ def setUp(self): """ OVERWRITE! self.r is the rule self.rRO the readonly rule relf.r_type the type as defined in CSSRule """ super(CSSRuleTestCase, self).setUp() self.sheet = css_parser.css.CSSStyleSheet() self.r = css_parser.css.CSSRule() self.rRO = css_parser.css.CSSRule() self.rRO._readonly = True # must be set here! self.r_type = css_parser.css.CSSRule.UNKNOWN_RULE self.r_typeString = 'UNKNOWN_RULE' def tearDown(self): css_parser.ser.prefs.useDefaults() def test_init(self): "CSSRule.type and init" self.assertEqual(self.r_type, self.r.type) self.assertEqual(self.r_typeString, self.r.typeString) self.assertEqual('', self.r.cssText) self.assertEqual(None, self.r.parentRule) self.assertEqual(None, self.r.parentStyleSheet) def test_parentRule_parentStyleSheet_type(self): "CSSRule.parentRule .parentStyleSheet .type" rules = [ ('@charset "ascii";', css_parser.css.CSSRule.CHARSET_RULE), ('@import "x";', css_parser.css.CSSRule.IMPORT_RULE), ('@namespace "x";', css_parser.css.CSSRule.NAMESPACE_RULE), ('@font-face { src: url(x) }', css_parser.css.CSSRule.FONT_FACE_RULE), ('''@media all { @x; a { color: red } /* c */ }''', css_parser.css.CSSRule.MEDIA_RULE), ('@page :left { color: red }', css_parser.css.CSSRule.PAGE_RULE), ('@unknown;', css_parser.css.CSSRule.UNKNOWN_RULE), ('b { left: 0 }', css_parser.css.CSSRule.STYLE_RULE), ('/*1*/', css_parser.css.CSSRule.COMMENT) # must be last for add test ] mrt = [css_parser.css.CSSRule.UNKNOWN_RULE, css_parser.css.CSSRule.STYLE_RULE, css_parser.css.CSSRule.COMMENT] def test(s): for i, rule in enumerate(s): self.assertEqual(rule.parentRule, None) self.assertEqual(rule.parentStyleSheet, s) #self.assertEqual(rule.type, rules[i][1]) if rule.MEDIA_RULE == rule.type: for j, r in enumerate(rule): self.assertEqual(r.parentRule, rule) self.assertEqual(r.parentStyleSheet, s) self.assertEqual(r.type, mrt[j]) if i == 0: # check encoding self.assertEqual('ascii', s.encoding) elif i == 2: # check namespaces self.assertEqual('x', s.namespaces['']) cssText = ''.join(r[0] for r in rules) # parsing s = css_parser.parseString(cssText) test(s) # sheet.cssText s = css_parser.css.CSSStyleSheet() s.cssText = cssText test(s) # sheet.add CSS s = css_parser.css.CSSStyleSheet() for css, type_ in rules: s.add(css) test(s) # sheet.insertRule CSS s = css_parser.css.CSSStyleSheet() for css, type_ in rules: s.insertRule(css) test(s) types = [css_parser.css.CSSCharsetRule, css_parser.css.CSSImportRule, css_parser.css.CSSNamespaceRule, css_parser.css.CSSFontFaceRule, css_parser.css.CSSMediaRule, css_parser.css.CSSPageRule, css_parser.css.CSSUnknownRule, css_parser.css.CSSStyleRule, css_parser.css.CSSComment] # sheet.add CSSRule s = css_parser.css.CSSStyleSheet() for i, (css, type_) in enumerate(rules): rule = types[i]() rule.cssText = css s.add(rule) test(s) # sheet.insertRule CSSRule s = css_parser.css.CSSStyleSheet() for i, (css, type_) in enumerate(rules): rule = types[i]() rule.cssText = css s.insertRule(rule) test(s) def test_CSSMediaRule_cssRules_parentRule_parentStyleSheet_type(self): "CSSMediaRule.cssRules.parentRule .parentStyleSheet .type" rules = [ ('b { left: 0 }', css_parser.css.CSSRule.STYLE_RULE), ('/*1*/', css_parser.css.CSSRule.COMMENT), ('@x;', css_parser.css.CSSRule.UNKNOWN_RULE) ] def test(s): mr = s.cssRules[0] for i, rule in enumerate(mr): self.assertEqual(rule.parentRule, mr) self.assertEqual(rule.parentStyleSheet, s) self.assertEqual(rule.parentStyleSheet, mr.parentStyleSheet) self.assertEqual(rule.type, rules[i][1]) cssText = '@media all { %s }' % ''.join(r[0] for r in rules) # parsing s = css_parser.parseString(cssText) test(s) # sheet.cssText s = css_parser.css.CSSStyleSheet() s.cssText = cssText test(s) def getMediaSheet(): s = css_parser.css.CSSStyleSheet() s.cssText = '@media all {}' return s, s.cssRules[0] # sheet.add CSS s, mr = getMediaSheet() for css, type_ in rules: mr.add(css) test(s) # sheet.insertRule CSS s, mr = getMediaSheet() for css, type_ in rules: mr.insertRule(css) test(s) types = [css_parser.css.CSSStyleRule, css_parser.css.CSSComment, css_parser.css.CSSUnknownRule] # sheet.add CSSRule s, mr = getMediaSheet() for i, (css, type_) in enumerate(rules): rule = types[i]() rule.cssText = css mr.add(rule) test(s) # sheet.insertRule CSSRule s, mr = getMediaSheet() for i, (css, type_) in enumerate(rules): rule = types[i]() rule.cssText = css mr.insertRule(rule) test(s) def test_readonly(self): "CSSRule readonly" self.rRO = css_parser.css.CSSRule() self.rRO._readonly = True self.assertEqual(True, self.rRO._readonly) self.assertEqual('', self.rRO.cssText) self.assertRaises(xml.dom.NoModificationAllowedErr, self.rRO._setCssText, 'x') self.assertEqual('', self.rRO.cssText) def _test_InvalidModificationErr(self, startwithspace): """ CSSRule.cssText InvalidModificationErr called by subclasses startwithspace for test starting with this not the test but " test" is tested e.g. " @page {}" exception is the style rule test """ tests = ('', '/* comment */', '@charset "utf-8";', '@font-face {}', '@import url(x);', '@media all {}', '@namespace "x";' '@page {}', '@unknown;', '@variables;', # TODO: #u'@top-left {}' 'a style rule {}' ) for test in tests: if startwithspace in ('a style rule', ) and test in ( '/* comment */', 'a style rule {}'): continue if test.startswith(startwithspace): test = ' %s' % test self.assertRaises(xml.dom.InvalidModificationErr, self.r._setCssText, test) # check that type is readonly self.assertRaises(AttributeError, self.r.__setattr__, 'parentRule', None) self.assertRaises(AttributeError, self.r.__setattr__, 'parentStyleSheet', None) self.assertRaises(AttributeError, self.r.__setattr__, 'type', 1) self.assertRaises(AttributeError, self.r.__setattr__, 'typeString', "") if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssrulelist.py0000644000175000017500000000227300000000000022173 0ustar00kovidkovid"""Testcases for css_parser.css.CSSRuleList""" from __future__ import absolute_import from __future__ import unicode_literals from . import basetest import css_parser class CSSRuleListTestCase(basetest.BaseTestCase): def test_init(self): "CSSRuleList.__init__()" r = css_parser.css.CSSRuleList() self.assertEqual(0, r.length) self.assertEqual(None, r.item(2)) # subclasses list but all setting options like append, extend etc # need to be added to an instance of this class by a using class! self.assertRaises(NotImplementedError, r.append, 1) def test_rulesOfType(self): "CSSRuleList.rulesOfType()" s = css_parser.parseString(''' /*c*/ @namespace "a"; a { color: red} b { left: 0 }''') c = list(s.cssRules.rulesOfType(css_parser.css.CSSRule.COMMENT)) self.assertEqual(1, len(c)) self.assertEqual('/*c*/', c[0].cssText) r = list(s.cssRules.rulesOfType(css_parser.css.CSSRule.STYLE_RULE)) self.assertEqual(2, len(r)) self.assertEqual('b {\n left: 0\n }', r[1].cssText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1563593644.0 css-parser-1.0.7/css_parser_tests/test_cssstyledeclaration.py0000644000175000017500000005633500000000000023706 0ustar00kovidkovid"""Testcases for css_parser.css.cssstyledelaration.CSSStyleDeclaration.""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser class CSSStyleDeclarationTestCase(basetest.BaseTestCase): def setUp(self): self.r = css_parser.css.CSSStyleDeclaration() def test_init(self): "CSSStyleDeclaration.__init__()" s = css_parser.css.CSSStyleDeclaration() self.assertEqual('', s.cssText) self.assertEqual(0, s.length) self.assertEqual(None, s.parentRule) s = css_parser.css.CSSStyleDeclaration(cssText='left: 0') self.assertEqual('left: 0', s.cssText) self.assertEqual('0', s.getPropertyValue('left')) sheet = css_parser.css.CSSStyleRule() s = css_parser.css.CSSStyleDeclaration(parentRule=sheet) self.assertEqual(sheet, s.parentRule) # should not be used but ordered parameter test s = css_parser.css.CSSStyleDeclaration('top: 0', sheet) self.assertEqual('top: 0', s.cssText) self.assertEqual(sheet, s.parentRule) def test_items(self): "CSSStyleDeclaration[CSSName]" s = css_parser.css.CSSStyleDeclaration() name, value, priority = 'color', 'name', '' s[name] = value self.assertEqual(value, s[name]) self.assertEqual(value, s.__getattribute__(name)) self.assertEqual(value, s.getProperty(name).value) self.assertEqual(priority, s.getProperty(name).priority) name, value, priority = 'UnKnown-ProPERTY', 'unknown value', 'important' s[name] = (value, priority) self.assertEqual(value, s[name]) self.assertEqual(value, s[name.lower()]) # will be normalized self.assertRaises(AttributeError, s.__getattribute__, name) self.assertEqual(value, s.getProperty(name).value) self.assertEqual(priority, s.getProperty(name).priority) name, value, priority = 'item', '1', '' s[name] = value self.assertEqual(value, s[name]) self.assertEqual(value, s.getProperty(name).value) self.assertEqual(priority, s.getProperty(name).priority) del s[name] self.assertEqual('', s[name]) self.assertEqual('', s['never set']) def test__contains__(self): "CSSStyleDeclaration.__contains__(nameOrProperty)" s = css_parser.css.CSSStyleDeclaration(cssText=r'x: 1;\y: 2') for test in ('x', r'x', 'y', r'y'): self.assertTrue(test in s) self.assertTrue(test.upper() in s) self.assertTrue(css_parser.css.Property(test, '1') in s) self.assertTrue('z' not in s) self.assertTrue(css_parser.css.Property('z', '1') not in s) def test__iter__item(self): "CSSStyleDeclaration.__iter__ and .item" s = css_parser.css.CSSStyleDeclaration() s.cssText = r''' color: red; c\olor: blue; CO\lor: green; left: 1px !important; left: 0; border: 0; ''' # __iter__ ps = [] for p in s: ps.append((p.literalname, p.value, p.priority)) self.assertEqual(len(ps), 3) self.assertEqual(ps[0], (r'co\lor', 'green', '')) self.assertEqual(ps[1], (r'left', '1px', 'important')) self.assertEqual(ps[2], (r'border', '0', '')) # item self.assertEqual(s.length, 3) self.assertEqual(s.item(0), 'color') self.assertEqual(s.item(1), 'left') self.assertEqual(s.item(2), 'border') self.assertEqual(s.item(10), '') def test_keys(self): "CSSStyleDeclaration.keys()" s = css_parser.parseStyle('x:1; x:2; y:1') self.assertEqual(['x', 'y'], s.keys()) self.assertEqual(s['x'], '2') self.assertEqual(s['y'], '1') def test_parse(self): "CSSStyleDeclaration parse" # error but parse tests = { # property names are caseinsensitive 'TOP:0': 'top: 0', 'top:0': 'top: 0', # simple escape 'c\\olor: red; color:green': 'color: green', 'color:g\\reen': 'color: g\\reen', # http://www.w3.org/TR/2009/CR-CSS2-20090423/syndata.html#illegalvalues 'color:green': 'color: green', 'color:green; color': 'color: green', 'color:red; color; color:green': 'color: green', 'color:green; color:': 'color: green', 'color:red; color:; color:green': 'color: green', 'color:green; color{;color:maroon}': 'color: green', 'color:red; color{;color:maroon}; color:green': 'color: green', # tantek hack r'''color: red; voice-family: "\"}\""; voice-family:inherit; color: green;''': 'voice-family: inherit;\ncolor: green', r'''col\or: blue; font-family: 'Courier New Times color: red; color: green;''': 'color: green', # special IE hacks are not preserved anymore (>=0.9.5b3) '/color: red; color: green': 'color: green', '/ color: red; color: green': 'color: green', '1px: red; color: green': 'color: green', '0: red; color: green': 'color: green', '1px:: red; color: green': 'color: green', r'$top: 0': '', r'$: 0': '', # really invalid! # unknown rule but valid '@x;\ncolor: red': None, '@x {\n }\ncolor: red': None, '/**/\ncolor: red': None, '/**/\ncolor: red;\n/**/': None, # issue #28 ';color: red': 'color: red', ';;color: red;;': 'color: red' } css_parser.ser.prefs.keepAllProperties = False for test, exp in tests.items(): sh = css_parser.parseString('a { %s }' % test) if exp is None: exp = '%s' % test elif exp != '': exp = '%s' % exp self.assertEqual(exp, sh.cssRules[0].style.cssText) css_parser.ser.prefs.useDefaults() def test_serialize(self): "CSSStyleDeclaration serialize" s = css_parser.css.CSSStyleDeclaration() tests = { 'a:1 !important; a:2;b:1': ('a: 1 !important;\nb: 1', 'a: 1 !important;\na: 2;\nb: 1') } for test, exp in tests.items(): s.cssText = test css_parser.ser.prefs.keepAllProperties = False self.assertEqual(exp[0], s.cssText) css_parser.ser.prefs.keepAllProperties = True self.assertEqual(exp[1], s.cssText) css_parser.ser.prefs.useDefaults() def test_children(self): "CSSStyleDeclaration.children()" style = '/*1*/color: red; color: green; @x;' types = [ css_parser.css.CSSComment, css_parser.css.Property, css_parser.css.Property, css_parser.css.CSSUnknownRule ] def t(s): for i, x in enumerate(s.children()): self.assertEqual(types[i], type(x)) self.assertEqual(x.parent, s) t(css_parser.parseStyle(style)) t(css_parser.parseString('a {'+style+'}').cssRules[0].style) t(css_parser.parseString('@media all {a {'+style+'}}').cssRules[0].cssRules[0].style) s = css_parser.parseStyle(style) s['x'] = '0' self.assertEqual(s, s.getProperty('x').parent) s.setProperty('y', '1') self.assertEqual(s, s.getProperty('y').parent) def test_cssText(self): "CSSStyleDeclaration.cssText" # empty s = css_parser.css.CSSStyleDeclaration() tests = { '': '', ' ': '', ' \t \n ': '', '/*x*/': '/*x*/' } for test, exp in tests.items(): s.cssText = 'left: 0;' # dummy to reset s s.cssText = test self.assertEqual(exp, s.cssText) # normal s = css_parser.css.CSSStyleDeclaration() tests = { ';': '', 'left: 0': 'left: 0', 'left:0': 'left: 0', ' left : 0 ': 'left: 0', 'left: 0;': 'left: 0', 'left: 0 !important ': 'left: 0 !important', 'left:0!important': 'left: 0 !important', 'left: 0; top: 1': 'left: 0;\ntop: 1', # comments # TODO: spaces? '/*1*//*2*/left/*3*//*4*/:/*5*//*6*/0/*7*//*8*/!/*9*//*a*/important/*b*//*c*/;': '/*1*/\n/*2*/\nleft/*3*//*4*/: /*5*/ /*6*/ 0 /*7*/ /*8*/ !/*9*//*a*/important/*b*//*c*/', '/*1*/left: 0;/*2*/ top: 1/*3*/': '/*1*/\nleft: 0;\n/*2*/\ntop: 1 /*3*/', 'left:0; top:1;': 'left: 0;\ntop: 1', '/*1*/left: 0;/*2*/ top: 1;/*3*/': '/*1*/\nleft: 0;\n/*2*/\ntop: 1;\n/*3*/', # WS 'left:0!important;margin:1px 2px 3px 4px!important;': 'left: 0 !important;\nmargin: 1px 2px 3px 4px !important', '\n\r\f\t left\n\r\f\t :\n\r\f\t 0\n\r\f\t !\n\r\f\t important\n\r\f\t ;\n\r\f\t margin\n\r\f\t :\n\r\f\t 1px\n\r\f\t 2px\n\r\f\t 3px\n\r\f\t 4px;': 'left: 0 !important;\nmargin: 1px 2px 3px 4px', } for test, exp in tests.items(): s.cssText = test self.assertEqual(exp, s.cssText) # exception tests = { 'color: #xyz': xml.dom.SyntaxErr, 'top': xml.dom.SyntaxErr, 'top:': xml.dom.SyntaxErr, 'top : ': xml.dom.SyntaxErr, 'top:!important': xml.dom.SyntaxErr, 'top:!important;': xml.dom.SyntaxErr, 'top:;': xml.dom.SyntaxErr, 'top 0': xml.dom.SyntaxErr, 'top 0;': xml.dom.SyntaxErr, ':': xml.dom.SyntaxErr, ':0': xml.dom.SyntaxErr, ':0;': xml.dom.SyntaxErr, ':0!important': xml.dom.SyntaxErr, ':;': xml.dom.SyntaxErr, ': ;': xml.dom.SyntaxErr, ':!important;': xml.dom.SyntaxErr, ': !important;': xml.dom.SyntaxErr, '0': xml.dom.SyntaxErr, '0!important': xml.dom.SyntaxErr, '0!important;': xml.dom.SyntaxErr, '0;': xml.dom.SyntaxErr, '!important': xml.dom.SyntaxErr, '!important;': xml.dom.SyntaxErr, } self.do_raise_r(tests) def test_getCssText(self): "CSSStyleDeclaration.getCssText(separator)" s = css_parser.css.CSSStyleDeclaration(cssText='a:1;b:2') self.assertEqual('a: 1;\nb: 2', s.getCssText()) self.assertEqual('a: 1;b: 2', s.getCssText(separator='')) self.assertEqual('a: 1;/*x*/b: 2', s.getCssText(separator='/*x*/')) def test_parentRule(self): "CSSStyleDeclaration.parentRule" s = css_parser.css.CSSStyleDeclaration() sheet = css_parser.css.CSSStyleRule() s.parentRule = sheet self.assertEqual(sheet, s.parentRule) sheet = css_parser.parseString('a{x:1}') s = sheet.cssRules[0] d = s.style self.assertEqual(s, d.parentRule) s = css_parser.parseString(''' @font-face { font-weight: bold; } a { font-weight: bolder; } @page { font-weight: bolder; } ''') for r in s: self.assertEqual(r.style.parentRule, r) def test_getProperty(self): "CSSStyleDeclaration.getProperty" s = css_parser.css.CSSStyleDeclaration() P = css_parser.css.Property s.cssText = r''' color: red; c\olor: blue; CO\lor: green; left: 1px !important; left: 0; border: 0; ''' self.assertEqual(s.getProperty('color').cssText, r'co\lor: green') self.assertEqual(s.getProperty(r'COLO\r').cssText, r'co\lor: green') self.assertEqual(s.getProperty('left').cssText, r'left: 1px !important') self.assertEqual(s.getProperty('border').cssText, r'border: 0') def test_getProperties(self): "CSSStyleDeclaration.getProperties()" s = css_parser.css.CSSStyleDeclaration(cssText='/*1*/y:0;x:a !important;y:1; \\x:b;') tests = { # name, all (None, False): [('y', '1', ''), ('x', 'a', 'important')], (None, True): [('y', '0', ''), ('x', 'a', 'important'), ('y', '1', ''), ('\\x', 'b', '') ], ('x', False): [('x', 'a', 'important')], ('\\x', False): [('x', 'a', 'important')], ('x', True): [('x', 'a', 'important'), ('\\x', 'b', '')], ('\\x', True): [('x', 'a', 'important'), ('\\x', 'b', '')], } for test in tests: name, all = test expected = tests[test] actual = s.getProperties(name, all) self.assertEqual(len(expected), len(actual)) for i, ex in enumerate(expected): a = actual[i] self.assertEqual(ex, (a.literalname, a.value, a.priority)) # order is be effective properties set s = css_parser.css.CSSStyleDeclaration(cssText='a:0;b:1;a:1') self.assertEqual('ba', ''.join([p.name for p in s])) def test_getPropertyCSSValue(self): "CSSStyleDeclaration.getPropertyCSSValue()" s = css_parser.css.CSSStyleDeclaration(cssText='color: red;c\\olor: green') self.assertEqual('green', s.getPropertyCSSValue('color').cssText) self.assertEqual('green', s.getPropertyCSSValue('c\\olor').cssText) self.assertEqual('red', s.getPropertyCSSValue('color', False).cssText) self.assertEqual('green', s.getPropertyCSSValue('c\\olor', False).cssText) # # shorthand CSSValue should be None # SHORTHAND = [ # u'background', # u'border', # u'border-left', u'border-right', # u'border-top', u'border-bottom', # u'border-color', u'border-style', u'border-width', # u'cue', # u'font', # u'list-style', # u'margin', # u'outline', # u'padding', # u'pause'] # for short in SHORTHAND: # s.setProperty(short, u'inherit') # self.assertEqual(None, s.getPropertyCSSValue(short)) def test_getPropertyValue(self): "CSSStyleDeclaration.getPropertyValue()" s = css_parser.css.CSSStyleDeclaration() self.assertEqual('', s.getPropertyValue('unset')) s.setProperty('left', '0') self.assertEqual('0', s.getPropertyValue('left')) s.setProperty('border', '1px solid green') self.assertEqual('1px solid green', s.getPropertyValue('border')) s = css_parser.css.CSSStyleDeclaration(cssText='color: red;c\\olor: green') self.assertEqual('green', s.getPropertyValue('color')) self.assertEqual('green', s.getPropertyValue('c\\olor')) self.assertEqual('red', s.getPropertyValue('color', False)) self.assertEqual('green', s.getPropertyValue('c\\olor', False)) tests = { r'color: red; color: green': 'green', r'c\olor: red; c\olor: green': 'green', r'color: red; c\olor: green': 'green', r'color: red !important; color: green !important': 'green', r'color: green !important; color: red': 'green', } for test in tests: s = css_parser.css.CSSStyleDeclaration(cssText=test) self.assertEqual(tests[test], s.getPropertyValue('color')) def test_getPropertyPriority(self): "CSSStyleDeclaration.getPropertyPriority()" s = css_parser.css.CSSStyleDeclaration() self.assertEqual('', s.getPropertyPriority('unset')) s.setProperty('left', '0', '!important') self.assertEqual('important', s.getPropertyPriority('left')) s = css_parser.css.CSSStyleDeclaration(cssText='x: 1 !important;\\x: 2;x: 3 !important;\\x: 4') self.assertEqual('important', s.getPropertyPriority('x')) self.assertEqual('important', s.getPropertyPriority('\\x')) self.assertEqual('important', s.getPropertyPriority('x', True)) self.assertEqual('', s.getPropertyPriority('\\x', False)) def test_removeProperty(self): "CSSStyleDeclaration.removeProperty()" css_parser.ser.prefs.useDefaults() s = css_parser.css.CSSStyleDeclaration() css = r'\x:0 !important; x:1; \x:2; x:3' # normalize=True DEFAULT s.cssText = css self.assertEqual('0', s.removeProperty('x')) self.assertEqual('', s.cssText) # normalize=False s.cssText = css self.assertEqual('3', s.removeProperty('x', normalize=False)) self.assertEqual(r'\x: 0 !important;\x: 2', s.getCssText(separator='')) self.assertEqual('0', s.removeProperty(r'\x', normalize=False)) self.assertEqual('', s.cssText) s.cssText = css self.assertEqual('0', s.removeProperty(r'\x', normalize=False)) self.assertEqual(r'x: 1;x: 3', s.getCssText(separator='')) self.assertEqual('3', s.removeProperty('x', normalize=False)) self.assertEqual('', s.cssText) def test_setProperty(self): "CSSStyleDeclaration.setProperty()" s = css_parser.css.CSSStyleDeclaration() s.setProperty('top', '0', '!important') self.assertEqual('0', s.getPropertyValue('top')) self.assertEqual('important', s.getPropertyPriority('top')) s.setProperty('top', '1px') self.assertEqual('1px', s.getPropertyValue('top')) self.assertEqual('', s.getPropertyPriority('top')) s.setProperty('top', '2px') self.assertEqual('2px', s.getPropertyValue('top')) s.setProperty('\\top', '3px') self.assertEqual('3px', s.getPropertyValue('top')) s.setProperty('\\top', '4px', normalize=False) self.assertEqual('4px', s.getPropertyValue('top')) self.assertEqual('4px', s.getPropertyValue('\\top', False)) self.assertEqual('3px', s.getPropertyValue('top', False)) # case insensitive s.setProperty('TOP', '0', '!IMPORTANT') self.assertEqual('0', s.getPropertyValue('top')) self.assertEqual('important', s.getPropertyPriority('top')) tests = { ('left', '0', ''): 'left: 0', ('left', '0', 'important'): 'left: 0 !important', ('LEFT', '0', 'important'): 'left: 0 !important', ('left', '0', 'important'): 'left: 0 !important', } for test, exp in tests.items(): s = css_parser.css.CSSStyleDeclaration() n, v, p = test s.setProperty(n, v, p) self.assertEqual(exp, s.cssText) self.assertEqual(v, s.getPropertyValue(n)) self.assertEqual(p, s.getPropertyPriority(n)) # empty s = css_parser.css.CSSStyleDeclaration() self.assertEqual('', s.top) s.top = '0' self.assertEqual('0', s.top) s.top = '' self.assertEqual('', s.top) s.top = None self.assertEqual('', s.top) def test_setProperty(self): "CSSStyleDeclaration.setProperty(replace=)" s = css_parser.css.CSSStyleDeclaration() s.setProperty('top', '1px') self.assertEqual(len(s.getProperties('top', all=True)), 1) s.setProperty('top', '2px') self.assertEqual(len(s.getProperties('top', all=True)), 1) self.assertEqual(s.getPropertyValue('top'), '2px') s.setProperty('top', '3px', replace=False) self.assertEqual(len(s.getProperties('top', all=True)), 2) self.assertEqual(s.getPropertyValue('top'), '3px') def test_length(self): "CSSStyleDeclaration.length" s = css_parser.css.CSSStyleDeclaration() # cssText s.cssText = 'left: 0' self.assertEqual(1, s.length) self.assertEqual(1, len(s.seq)) s.cssText = '/*1*/left/*x*/:/*x*/0/*x*/;/*2*/ top: 1;/*3*/' self.assertEqual(2, s.length) self.assertEqual(5, len(s.seq)) # set s = css_parser.css.CSSStyleDeclaration() s.setProperty('top', '0', '!important') self.assertEqual(1, s.length) s.setProperty('top', '1px') self.assertEqual(1, s.length) s.setProperty('left', '1px') def test_nameParameter(self): "CSSStyleDeclaration.XXX(name)" s = css_parser.css.CSSStyleDeclaration() s.setProperty('top', '1px', '!important') self.assertEqual('1px', s.getPropertyValue('top')) self.assertEqual('1px', s.getPropertyValue('TOP')) self.assertEqual('1px', s.getPropertyValue(r'T\op')) self.assertEqual('important', s.getPropertyPriority('top')) self.assertEqual('important', s.getPropertyPriority('TOP')) self.assertEqual('important', s.getPropertyPriority(r'T\op')) s.setProperty('top', '2px', '!important') self.assertEqual('2px', s.removeProperty('top')) s.setProperty('top', '2px', '!important') self.assertEqual('2px', s.removeProperty('TOP')) s.setProperty('top', '2px', '!important') self.assertEqual('2px', s.removeProperty(r'T\op')) def test_css2properties(self): "CSSStyleDeclaration.$css2property get set del" s = css_parser.css.CSSStyleDeclaration( cssText='left: 1px;color: red; font-style: italic') s.color = 'green' s.fontStyle = 'normal' self.assertEqual('green', s.color) self.assertEqual('normal', s.fontStyle) self.assertEqual('green', s.getPropertyValue('color')) self.assertEqual('normal', s.getPropertyValue('font-style')) self.assertEqual( '''left: 1px;\ncolor: green;\nfont-style: normal''', s.cssText) del s.color self.assertEqual( '''left: 1px;\nfont-style: normal''', s.cssText) del s.fontStyle self.assertEqual('left: 1px', s.cssText) self.assertRaises(AttributeError, s.__setattr__, 'UNKNOWN', 'red') # unknown properties must be set with setProperty! s.setProperty('UNKNOWN', 'red') # but are still not usable as property! self.assertRaises(AttributeError, s.__getattribute__, 'UNKNOWN') self.assertRaises(AttributeError, s.__delattr__, 'UNKNOWN') # but are kept self.assertEqual('red', s.getPropertyValue('UNKNOWN')) self.assertEqual( '''left: 1px;\nunknown: red''', s.cssText) def test_reprANDstr(self): "CSSStyleDeclaration.__repr__(), .__str__()" s = css_parser.css.CSSStyleDeclaration(cssText='a:1;b:2') self.assertTrue("2" in str(s)) # length s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) def test_valid(self): "CSSStyleDeclaration.valid" cases = [ ('color: red;', True), ('color: asd;', False), ('foo: red;', False), ('color: red; foo: red;', False), ] for case, expected in cases: s = css_parser.css.CSSStyleDeclaration(cssText=case) msg = '%r should be %s' % (case, 'valid' if expected else 'invalid') self.assertEqual(s.valid, expected, msg) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssstylerule.py0000644000175000017500000002227300000000000022362 0ustar00kovidkovid"""Testcases for css_parser.css.CSSStyleRuleTestCase""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class CSSStyleRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSStyleRuleTestCase, self).setUp() self.r = css_parser.css.CSSStyleRule() self.rRO = css_parser.css.CSSStyleRule(readonly=True) self.r_type = css_parser.css.CSSStyleRule.STYLE_RULE self.r_typeString = 'STYLE_RULE' def test_init(self): "CSSStyleRule.type and init" super(CSSStyleRuleTestCase, self).test_init() self.assertEqual('', self.r.cssText) self.assertEqual(css_parser.css.selectorlist.SelectorList, type(self.r.selectorList)) self.assertEqual('', self.r.selectorText) self.assertEqual(css_parser.css.CSSStyleDeclaration, type(self.r.style)) self.assertEqual(self.r, self.r.style.parentRule) def test_refs(self): "CSSStyleRule references" s = css_parser.css.CSSStyleRule() sel, style = s.selectorList, s.style self.assertEqual(s, sel.parentRule) self.assertEqual(s, style.parentRule) s.cssText = 'a { x:1 }' self.assertNotEqual(sel, s.selectorList) self.assertEqual('a', s.selectorList.selectorText) self.assertNotEqual(style, s.style) self.assertEqual('1', s.style.getPropertyValue('x')) sel, style = s.selectorList, s.style invalids = ('$b { x:2 }', # invalid selector 'c { $x3 }', # invalid style '/b { 2 }' # both invalid ) for invalid in invalids: try: s.cssText = invalid except xml.dom.DOMException as e: pass self.assertEqual(sel, s.selectorList) self.assertEqual('a', s.selectorList.selectorText) self.assertEqual(style, s.style) self.assertEqual('1', s.style.getPropertyValue('x')) # CHANGING s = css_parser.parseString('a {s1: 1}') r = s.cssRules[0] sel1 = r.selectorList st1 = r.style # selectorList r.selectorText = 'b' self.assertNotEqual(sel1, r.selectorList) self.assertEqual('b', r.selectorList.selectorText) self.assertEqual('b', r.selectorText) sel1b = r.selectorList sel1b.selectorText = 'c' self.assertEqual(sel1b, r.selectorList) self.assertEqual('c', r.selectorList.selectorText) self.assertEqual('c', r.selectorText) sel2 = css_parser.css.SelectorList('sel2') s.selectorList = sel2 self.assertEqual(sel2, s.selectorList) self.assertEqual('sel2', s.selectorList.selectorText) sel2.selectorText = 'sel2b' self.assertEqual('sel2b', sel2.selectorText) self.assertEqual('sel2b', s.selectorList.selectorText) s.selectorList.selectorText = 'sel2c' self.assertEqual('sel2c', sel2.selectorText) self.assertEqual('sel2c', s.selectorList.selectorText) # style r.style = 's1: 2' self.assertNotEqual(st1, r.style) self.assertEqual('s1: 2', r.style.cssText) st2 = css_parser.parseStyle('s2: 1') r.style = st2 self.assertEqual(st2, r.style) self.assertEqual('s2: 1', r.style.cssText) # cssText sl, st = r.selectorList, r.style # fails try: r.cssText = '$ {content: "new"}' except xml.dom.SyntaxErr as e: pass self.assertEqual(sl, r.selectorList) self.assertEqual(st, r.style) r.cssText = 'a {content: "new"}' self.assertNotEqual(sl, r.selectorList) self.assertNotEqual(st, r.style) def test_cssText(self): "CSSStyleRule.cssText" tests = { '* {}': '', 'a {}': '', } self.do_equal_p(tests) # parse # self.do_equal_r(tests) # set cssText # TODO: WHY? css_parser.ser.prefs.keepEmptyRules = True tests = { # u'''a{;display:block;float:left}''': 'a {\n display:block;\n float:left\n }', # issue 28 '''a\n{color: #000}''': 'a {\n color: #000\n }', # issue 4 '''a\n{color: #000000}''': 'a {\n color: #000\n }', # issue 4 '''a\n{color: #abc}''': 'a {\n color: #abc\n }', # issue 4 '''a\n{color: #abcdef}''': 'a {\n color: #abcdef\n }', # issue 4 '''a\n{color: #00a}''': 'a {\n color: #00a\n }', # issue 4 '''a\n{color: #1a1a1a}''': 'a {\n color: #1a1a1a\n }', # issue 4 '''#id\n{ color: red }''': '#id {\n color: red\n }', # issue 3 '''* {}''': None, 'a {}': None, 'b { a: 1; }': 'b {\n a: 1\n }', # mix of comments and properties 'c1 {/*1*/a:1;}': 'c1 {\n /*1*/\n a: 1\n }', 'c2 {a:1;/*2*/}': 'c2 {\n a: 1;\n /*2*/\n }', 'd1 {/*0*/}': 'd1 {\n /*0*/\n }', 'd2 {/*0*//*1*/}': 'd2 {\n /*0*/\n /*1*/\n }', # comments # TODO: spaces? '''a/*1*//*2*/,/*3*//*4*/b/*5*//*6*/{color: #000}''': 'a/*1*//*2*/, /*3*//*4*/b/*5*//*6*/ {\n color: #000\n }', '''a,b{color: #000}''': 'a, b {\n color: #000\n }', # issue 4 '''a\n\r\t\f ,\n\r\t\f b\n\r\t\f {color: #000}''': 'a, b {\n color: #000\n }', # issue 4 } self.do_equal_p(tests) # parse self.do_equal_r(tests) # set cssText tests = { '''a;''': xml.dom.SyntaxErr, '''a {{}''': xml.dom.SyntaxErr, '''a }''': xml.dom.SyntaxErr, } self.do_raise_p(tests) # parse tests.update({ '''/*x*/''': xml.dom.SyntaxErr, '''a {''': xml.dom.SyntaxErr, # trailing '''a {}x''': xml.dom.SyntaxErr, '''a {/**/''': xml.dom.SyntaxErr, '''a {} ''': xml.dom.SyntaxErr, }) self.do_raise_r(tests) # set cssText css_parser.ser.prefs.useDefaults() def test_selectorList(self): "CSSStyleRule.selectorList" r = css_parser.css.CSSStyleRule() r.selectorList.appendSelector('a') self.assertEqual(1, r.selectorList.length) self.assertEqual('a', r.selectorText) r.selectorList.appendSelector(' b ') # only simple selector! self.assertRaises(xml.dom.InvalidModificationErr, r.selectorList.appendSelector, ' h1, x ') self.assertEqual(2, r.selectorList.length) self.assertEqual('a, b', r.selectorText) def test_selectorText(self): "CSSStyleRule.selectorText" r = css_parser.css.CSSStyleRule() r.selectorText = 'a' self.assertEqual(1, r.selectorList.length) self.assertEqual('a', r.selectorText) r.selectorText = ' b, h1 ' self.assertEqual(2, r.selectorList.length) self.assertEqual('b, h1', r.selectorText) def test_style(self): "CSSStyleRule.style" d = css_parser.css.CSSStyleDeclaration() self.r.style = d self.assertEqual(d.cssText, self.r.style.cssText) # check if parentRule of d is set self.assertEqual(self.r, d.parentRule) def test_incomplete(self): "CSSStyleRule (incomplete)" css_parser.ser.prefs.keepEmptyRules = True tests = { 'a {': 'a {}', # no } 'a { font-family: "arial sans': # no "} 'a {\n font-family: "arial sans"\n }', 'a { font-family: "arial sans";': # no } 'a {\n font-family: "arial sans"\n }', '''p { color: green; font-family: 'Courier New Times color: red; color: green; }''': '''p {\n color: green;\n color: green\n }''', # no ; '''p { color: green; font-family: 'Courier New Times' color: red; color: green; ''': '''p {\n color: green;\n color: green\n }''' } self.do_equal_p(tests, raising=False) # parse css_parser.ser.prefs.useDefaults() # TODO: def test_InvalidModificationErr(self): # "CSSStyleRule.cssText InvalidModificationErr" # self._test_InvalidModificationErr(u'@a a {}') def test_reprANDstr(self): "CSSStyleRule.__repr__(), .__str__()" sel = 'a > b + c' s = css_parser.css.CSSStyleRule(selectorText=sel) self.assertTrue(sel in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(sel == s2.selectorText) def test_valid(self): "CSSStyleRule.valid" rule = css_parser.css.CSSStyleRule(selectorText='*', style='color: red') self.assertTrue(rule.valid) rule.style = 'color: foobar' self.assertFalse(rule.valid) rule.style = 'foobar: red' self.assertFalse(rule.valid) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssstylesheet.py0000644000175000017500000010342700000000000022524 0ustar00kovidkovid# -*- coding: utf-8 -*- """Tests for css.CSSStyleSheet""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser.css class CSSStyleSheetTestCase(basetest.BaseTestCase): def setUp(self): super(CSSStyleSheetTestCase, self).setUp() self.r = css_parser.css.CSSStyleSheet() # used by basetest self.s = self.r # used here self.rule = css_parser.css.CSSStyleRule() def tearDown(self): css_parser.ser.prefs.useDefaults() def test_init(self): "CSSStyleSheet.__init__()" self.assertEqual('text/css', self.s.type) self.assertEqual(False, self.s._readonly) self.assertEqual([], self.s.cssRules) self.assertEqual(False, self.s.disabled) self.assertEqual(None, self.s.href) self.assertEqual(None, self.s.media) self.assertEqual(None, self.s.ownerNode) self.assertEqual(None, self.s.parentStyleSheet) self.assertEqual('', self.s.title) # check that type is readonly self.assertRaises(AttributeError, self.r.__setattr__, 'href', 'x') self.assertRaises(AttributeError, self.r.__setattr__, 'parentStyleSheet', 'x') self.assertRaises(AttributeError, self.r.__setattr__, 'ownerNode', 'x') self.assertRaises(AttributeError, self.r.__setattr__, 'type', 'x') def test_iter(self): "CSSStyleSheet.__iter__()" s = css_parser.css.CSSStyleSheet() s.cssText = '''@import "x";@import "y";@namespace "u";''' types = [css_parser.css.CSSRule.IMPORT_RULE, css_parser.css.CSSRule.IMPORT_RULE, css_parser.css.CSSRule.NAMESPACE_RULE] for i, rule in enumerate(s): self.assertEqual(rule, s.cssRules[i]) self.assertEqual(rule.type, types[i]) def test_refs(self): """CSSStylesheet references""" s = css_parser.parseString('a {}') rules = s.cssRules self.assertEqual(s.cssRules[0].parentStyleSheet, s) self.assertEqual(rules[0].parentStyleSheet, s) # set cssText s.cssText = 'b{}' # from 0.9.7b1 self.assertNotEqual(rules, s.cssRules) # set cssRules s.cssRules = css_parser.parseString(''' @charset "ascii"; /**/ @import "x"; @namespace "http://example.com/ns0"; @media all { a { color: green; } } @font-face { font-family: x; } @page { font-family: Arial; } @x; b {}').cssRules''').cssRules # new object self.assertNotEqual(rules, s.cssRules) for i, r in enumerate(s.cssRules): self.assertEqual(r.parentStyleSheet, s) # namespaces s = css_parser.parseString('@namespace "http://example.com/ns1"; a {}') namespaces = s.namespaces self.assertEqual(s.namespaces.items(), [('', 'http://example.com/ns1')]) s.cssText = '@namespace x "http://example.com/ns2"; x|a {}' # not anymore! self.assertNotEqual(namespaces, s.namespaces) self.assertEqual(s.namespaces.items(), [('x', 'http://example.com/ns2')]) # variables s = css_parser.parseString('@variables { a:1}') vars1 = s.variables self.assertEqual(vars1['a'], '1') s = css_parser.parseString('@variables { a:2}') vars2 = s.variables self.assertNotEqual(vars1, vars2) self.assertEqual(vars1['a'], '1') self.assertEqual(vars2['a'], '2') def test_cssRules(self): "CSSStyleSheet.cssRules" s = css_parser.parseString('/*1*/a {x:1}') self.assertEqual(2, s.cssRules.length) del s.cssRules[0] self.assertEqual(1, s.cssRules.length) s.cssRules.append('/*2*/') self.assertEqual(2, s.cssRules.length) s.cssRules.extend(css_parser.parseString('/*3*/x {y:2}').cssRules) self.assertEqual(4, s.cssRules.length) self.assertEqual('a {\n x: 1\n }\n/*2*/\n/*3*/\nx {\n y: 2\n }'.encode(), s.cssText) for r in s.cssRules: self.assertEqual(r.parentStyleSheet, s) def test_cssText(self): "CSSStyleSheet.cssText" tests = { '': ''.encode(), # @charset '@charset "ascii";\n@import "x";': '@charset "ascii";\n@import "x";'.encode(), '@charset "ascii";\n@media all {}': '@charset "ascii";'.encode(), '@charset "ascii";\n@x;': '@charset "ascii";\n@x;'.encode(), '@charset "ascii";\na {\n x: 1\n }': '@charset "ascii";\na {\n x: 1\n }'.encode(), # @import '@x;\n@import "x";': '@x;\n@import "x";'.encode(), '@import "x";\n@import "y";': '@import "x";\n@import "y";'.encode(), '@import "x";\n@media all {}': '@import "x";'.encode(), '@import "x";\n@x;': '@import "x";\n@x;'.encode(), '@import "x";\na {\n x: 1\n }': '@import "x";\na {\n x: 1\n }'.encode(), # @namespace '@x;\n@namespace a "x";': '@x;\n@namespace a "x";'.encode(), '@namespace a "x";\n@namespace b "y";': '@namespace a "x";\n@namespace b "y";'.encode(), '@import "x";\n@namespace a "x";\n@media all {}': '@import "x";\n@namespace a "x";'.encode(), '@namespace a "x";\n@x;': '@namespace a "x";\n@x;'.encode(), '@namespace a "x";\na {\n x: 1\n }': '@namespace a "x";\na {\n x: 1\n }'.encode(), """@namespace url("e1"); @namespace url("e2"); @namespace x url("x1"); @namespace x url("x2"); test{color: green} x|test {color: green}""": """@namespace "e2"; @namespace x "x2"; test { color: green } x|test { color: green }""".encode() # ur'\1 { \2: \3 }': ur'''\x01 { # \x02: \x03 # }''', # ur''' # \@ { \@: \@ } # \1 { \2: \3 } # \{{\::\;;} # ''': ur'''\@ { # \@: \@ # } # \1 { # \2: \3 # } # \{ # {\:: \; # }''' } self.do_equal_r(tests) tests = { '': None, # @charset '@charset "ascii";\n@import "x";': None, '@charset "ascii";\n@media all {}': '@charset "ascii";', '@charset "ascii";\n@x;': None, '@charset "ascii";\na {\n x: 1\n }': None, # @import '@x;\n@import "x";': None, '@import "x";\n@import "y";': None, '@import "x";\n@media all {}': '@import "x";', '@import "x";\n@x;': None, '@import "x";\na {\n x: 1\n }': None, # @namespace '@x;\n@namespace a "x";': None, '@namespace a "x";\n@namespace b "y";': None, '@import "x";\n@namespace a "x";\n@media all {}': '@import "x";\n@namespace a "x";', '@namespace a "x";\n@x;': None, '@namespace a "x";\na {\n x: 1\n }': None, """@namespace url("e1"); @namespace url("e2"); @namespace x url("x1"); @namespace x url("x2"); test{color: green} x|test {color: green}""": """@namespace "e2"; @namespace x "x2"; test { color: green } x|test { color: green }""" # ur'\1 { \2: \3 }': ur'''\x01 { # \x02: \x03 # }''', # ur''' # \@ { \@: \@ } # \1 { \2: \3 } # \{{\::\;;} # ''': ur'''\@ { # \@: \@ # } # \1 { # \2: \3 # } # \{ # {\:: \; # }''' } self.do_equal_p(tests) s = css_parser.css.CSSStyleSheet() s.cssText = '''@charset "ascii";@import "x";@namespace a "x"; @media all {/*1*/}@page {margin: 0}a {\n x: 1\n }@unknown;/*comment*/''' for r in s.cssRules: self.assertEqual(s, r.parentStyleSheet) def test_cssText_HierarchyRequestErr(self): "CSSStyleSheet.cssText HierarchyRequestErr" tests = { # @charset: only one and always 1st ' @charset "utf-8";': xml.dom.HierarchyRequestErr, '@charset "ascii";@charset "ascii";': xml.dom.HierarchyRequestErr, '/*c*/@charset "ascii";': xml.dom.HierarchyRequestErr, '@import "x"; @charset "ascii";': xml.dom.HierarchyRequestErr, '@namespace a "x"; @charset "ascii";': xml.dom.HierarchyRequestErr, '@media all {} @charset "ascii";': xml.dom.HierarchyRequestErr, '@page {} @charset "ascii";': xml.dom.HierarchyRequestErr, 'a {} @charset "ascii";': xml.dom.HierarchyRequestErr, # @import: before @namespace, @media, @page, sr '@namespace a "x"; @import "x";': xml.dom.HierarchyRequestErr, '@media all {} @import "x";': xml.dom.HierarchyRequestErr, '@page {} @import "x";': xml.dom.HierarchyRequestErr, 'a {} @import "x";': xml.dom.HierarchyRequestErr, # @namespace: before @media, @page, sr '@media all {} @namespace a "x";': xml.dom.HierarchyRequestErr, '@page {} @namespace a "x";': xml.dom.HierarchyRequestErr, 'a {} @namespace a "x";': xml.dom.HierarchyRequestErr, } self.do_raise_r(tests) self.do_raise_p(tests) def test_cssText_SyntaxErr(self): """CSSStyleSheet.cssText SyntaxErr for single {, } or ; """ tests = { '{': xml.dom.SyntaxErr, '}': xml.dom.SyntaxErr, ';': xml.dom.SyntaxErr, '@charset "ascii";{': xml.dom.SyntaxErr, '@charset "ascii";}': xml.dom.SyntaxErr, '@charset "ascii";;': xml.dom.SyntaxErr, } self.do_raise_r(tests) self.do_raise_p(tests) def test_encoding(self): "CSSStyleSheet.encoding" self.s.cssText = '' self.assertEqual('utf-8', self.s.encoding) self.s.encoding = 'ascii' self.assertEqual('ascii', self.s.encoding) self.assertEqual(1, self.s.cssRules.length) self.assertEqual('ascii', self.s.cssRules[0].encoding) self.s.encoding = None self.assertEqual('utf-8', self.s.encoding) self.assertEqual(0, self.s.cssRules.length) self.s.encoding = 'UTF-8' self.assertEqual('utf-8', self.s.encoding) self.assertEqual(1, self.s.cssRules.length) self.assertRaises(xml.dom.SyntaxErr, self.s._setEncoding, 'INVALID ENCODING') self.assertEqual('utf-8', self.s.encoding) self.assertEqual(1, self.s.cssRules.length) def test_namespaces1(self): "CSSStyleSheet.namespaces.namespaces" # tests for namespaces internal methods s = css_parser.css.CSSStyleSheet() self.assertEqual(0, len(s.namespaces)) css = '''@namespace "default"; @namespace ex "example"; @namespace ex2 "example"; ex2|x { top: 0 }''' expcss = '''@namespace "default"; @namespace ex2 "example"; ex2|x { top: 0 }''' s.cssText = css self.assertEqual(s.cssText, expcss.encode()) self.assertEqual(s.namespaces.namespaces, {'': 'default', 'ex2': 'example'}) # __contains__ self.assertTrue('' in s.namespaces) self.assertTrue('ex2' in s.namespaces) self.assertFalse('NOTSET' in s.namespaces) # __delitem__ self.assertRaises(xml.dom.NoModificationAllowedErr, s.namespaces.__delitem__, 'ex2') s.namespaces['del'] = 'del' del s.namespaces['del'] self.assertRaises(xml.dom.NamespaceErr, s.namespaces.__getitem__, 'del') # __getitem__ self.assertEqual('default', s.namespaces['']) self.assertEqual('example', s.namespaces['ex2']) self.assertRaises(xml.dom.NamespaceErr, s.namespaces.__getitem__, 'UNSET') # __iter__ self.assertEqual(['', 'ex2'], sorted(list(s.namespaces))) # __len__ self.assertEqual(2, len(s.namespaces)) # __setitem__ self.assertRaises(xml.dom.NoModificationAllowedErr, s.namespaces.__setitem__, 'ex2', 'NEWURI') s.namespaces['n1'] = 'new' self.assertEqual(s.namespaces.namespaces, {'': 'default', 'ex2': 'example', 'n1': 'new'}) s.namespaces['n'] = 'new' # replaces prefix! self.assertEqual(s.namespaces.namespaces, {'': 'default', 'ex2': 'example', 'n': 'new'}) # prefixForNamespaceURI self.assertEqual('', s.namespaces.prefixForNamespaceURI('default')) self.assertEqual('ex2', s.namespaces.prefixForNamespaceURI('example')) self.assertRaises(IndexError, s.namespaces.prefixForNamespaceURI, 'UNSET') # .keys self.assertEqual(set(s.namespaces.keys()), set(['', 'ex2', 'n'])) # .get self.assertEqual('x', s.namespaces.get('UNKNOWN', 'x')) self.assertEqual('example', s.namespaces.get('ex2', 'not used defa')) def test_namespaces2(self): "CSSStyleSheet.namespaces" # tests using CSSStyleSheet.namespaces s = css_parser.css.CSSStyleSheet() css = '@namespace n "new";' # doubles will be removed s.insertRule(css + css) self.assertEqual(s.cssText, css.encode()) r = css_parser.css.CSSNamespaceRule(prefix='ex2', namespaceURI='example') s.insertRule(r) r = css_parser.css.CSSNamespaceRule(namespaceURI='default') s.insertRule(r) expcss = '''@namespace n "new"; @namespace ex2 "example"; @namespace "default";''' self.assertEqual(s.cssText, expcss.encode()) r.prefix = 'DEFAULT' expcss = '''@namespace n "new"; @namespace ex2 "example"; @namespace DEFAULT "default";''' self.assertEqual(s.cssText, expcss.encode()) # CSSMediaRule self.assertRaises(xml.dom.NamespaceErr, s.add, '@media all {x|a {left: 0}}') mcss = '@media all {\n ex2|SEL1 {\n left: 0\n }\n }' s.add(mcss) expcss += '\n' + mcss self.assertEqual(s.cssText, expcss.encode()) # CSSStyleRule self.assertRaises(xml.dom.NamespaceErr, s.add, 'x|a {top: 0}') scss = 'n|SEL2 {\n top: 0\n }' s.add(scss) expcss += '\n' + scss self.assertEqual(s.cssText, expcss.encode()) mr = s.cssRules[3] sr = s.cssRules[4] # SelectorList @media self.assertRaises(xml.dom.NamespaceErr, mr.cssRules[0]._setSelectorText, 'x|b') oldsel, newsel = mr.cssRules[0].selectorText, 'n|SEL3, a' mr.cssRules[0].selectorText = newsel expcss = expcss.replace(oldsel, newsel) self.assertEqual(s.cssText, expcss.encode()) # SelectorList stylerule self.assertRaises(xml.dom.NamespaceErr, sr._setSelectorText, 'x|b') oldsel, newsel = sr.selectorText, 'ex2|SEL4, a' sr.selectorText = newsel expcss = expcss.replace(oldsel, newsel) self.assertEqual(s.cssText, expcss.encode()) # Selector @media self.assertRaises(xml.dom.NamespaceErr, mr.cssRules[0].selectorList.append, 'x|b') oldsel, newsel = mr.cssRules[0].selectorText, 'ex2|SELMR' mr.cssRules[0].selectorList.append(newsel) expcss = expcss.replace(oldsel, oldsel + ', ' + newsel) self.assertEqual(s.cssText, expcss.encode()) # Selector stylerule self.assertRaises(xml.dom.NamespaceErr, sr.selectorList.append, 'x|b') oldsel, newsel = sr.selectorText, 'ex2|SELSR' sr.selectorList.append(newsel) expcss = expcss.replace(oldsel, oldsel + ', ' + newsel) self.assertEqual(s.cssText, expcss.encode()) self.assertEqual(s.cssText, '''@namespace n "new"; @namespace ex2 "example"; @namespace DEFAULT "default"; @media all { n|SEL3, a, ex2|SELMR { left: 0 } } ex2|SEL4, a, ex2|SELSR { top: 0 }'''.encode()) def test_namespaces3(self): "CSSStyleSheet.namespaces 3" # tests setting namespaces with new {} s = css_parser.css.CSSStyleSheet() css = 'h|a { top: 0 }' self.assertRaises(xml.dom.NamespaceErr, s.add, css) s.add('@namespace x "html";') self.assertTrue(s.namespaces['x'] == 'html') r = css_parser.css.CSSStyleRule() r.cssText = ((css, {'h': 'html'})) s.add(r) # uses x as set before! h is temporary only self.assertEqual(s.cssText, '@namespace x "html";\nx|a {\n top: 0\n }'.encode()) # prefix is now "x"! self.assertRaises(xml.dom.NamespaceErr, r.selectorList.append, 'h|b') self.assertRaises(xml.dom.NamespaceErr, r.selectorList.append, 'y|b') s.namespaces['y'] = 'html' r.selectorList.append('y|b') self.assertEqual(s.cssText, '@namespace y "html";\ny|a, y|b {\n top: 0\n }'.encode()) self.assertRaises(xml.dom.NoModificationAllowedErr, s.namespaces.__delitem__, 'y') self.assertEqual(s.cssText, '@namespace y "html";\ny|a, y|b {\n top: 0\n }'.encode()) s.cssRules[0].prefix = '' self.assertEqual(s.cssText, '@namespace "html";\na, b {\n top: 0\n }'.encode()) # remove need of namespace s.cssRules[0].prefix = 'x' s.cssRules[1].selectorText = 'a, b' self.assertEqual(s.cssText, '@namespace x "html";\na, b {\n top: 0\n }'.encode()) def test_namespaces4(self): "CSSStyleSheet.namespaces 4" # tests setting namespaces with new {} s = css_parser.css.CSSStyleSheet() self.assertEqual({}, s.namespaces.namespaces) s.namespaces.namespaces['a'] = 'no setting possible' self.assertEqual({}, s.namespaces.namespaces) s.namespaces[None] = 'default' self.assertEqual({'': 'default'}, s.namespaces.namespaces) del s.namespaces[''] self.assertEqual({}, s.namespaces.namespaces) s.namespaces[''] = 'default' self.assertEqual({'': 'default'}, s.namespaces.namespaces) del s.namespaces[None] self.assertEqual({}, s.namespaces.namespaces) s.namespaces['p'] = 'uri' # cannot use namespaces.namespaces del s.namespaces.namespaces['p'] self.assertEqual({'p': 'uri'}, s.namespaces.namespaces) self.assertRaisesMsg(xml.dom.NamespaceErr, "Prefix undefined not found.", s.namespaces.__delitem__, 'undefined') def test_namespaces5(self): "CSSStyleSheet.namespaces 5" # unknown namespace s = css_parser.parseString('a|a { color: red }') self.assertEqual(s.cssText, ''.encode()) s = css_parser.css.CSSStyleSheet() self.assertRaisesMsg(xml.dom.NamespaceErr, "Prefix a not found.", s._setCssText, 'a|a { color: red }') def test_deleteRuleIndex(self): "CSSStyleSheet.deleteRule(index)" self.s.cssText = '@charset "ascii"; @import "x"; @x; a {\n x: 1\n }@y;' self.assertEqual(5, self.s.cssRules.length) self.assertRaises(xml.dom.IndexSizeErr, self.s.deleteRule, 5) # end -1 # check parentStyleSheet r = self.s.cssRules[-1] self.assertEqual(self.s, r.parentStyleSheet) self.s.deleteRule(-1) self.assertEqual(None, r.parentStyleSheet) self.assertEqual(4, self.s.cssRules.length) self.assertEqual( '@charset "ascii";\n@import "x";\n@x;\na {\n x: 1\n }'.encode(), self.s.cssText) # beginning self.s.deleteRule(0) self.assertEqual(3, self.s.cssRules.length) self.assertEqual('@import "x";\n@x;\na {\n x: 1\n }'.encode(), self.s.cssText) # middle self.s.deleteRule(1) self.assertEqual(2, self.s.cssRules.length) self.assertEqual('@import "x";\na {\n x: 1\n }'.encode(), self.s.cssText) # end self.s.deleteRule(1) self.assertEqual(1, self.s.cssRules.length) self.assertEqual('@import "x";'.encode(), self.s.cssText) def test_deleteRule(self): "CSSStyleSheet.deleteRule(rule)" s = css_parser.css.CSSStyleSheet() s.cssText = ''' @namespace x "http://example.com"; a { color: red; } b { color: blue; } c { color: green; } ''' n, s1, s2, s3 = s.cssRules r = css_parser.css.CSSStyleRule() self.assertRaises(xml.dom.IndexSizeErr, s.deleteRule, r) self.assertEqual(4, s.cssRules.length) s.deleteRule(n) self.assertEqual(3, s.cssRules.length) self.assertEqual( s.cssText, 'a {\n color: red\n }\nb {\n color: blue\n }\nc {\n color: green\n }'.encode()) self.assertRaises(xml.dom.IndexSizeErr, s.deleteRule, n) s.deleteRule(s2) self.assertEqual(2, s.cssRules.length) self.assertEqual(s.cssText, 'a {\n color: red\n }\nc {\n color: green\n }'.encode()) self.assertRaises(xml.dom.IndexSizeErr, s.deleteRule, s2) def _gets(self): # complete self.cr = css_parser.css.CSSCharsetRule('ascii') self.c = css_parser.css.CSSComment('/*c*/') self.ur = css_parser.css.CSSUnknownRule('@x;') self.ir = css_parser.css.CSSImportRule('x') self.nr = css_parser.css.CSSNamespaceRule('uri', 'p') self.mr = css_parser.css.CSSMediaRule() self.mr.cssText = '@media all { @m; }' self.pr = css_parser.css.CSSPageRule() self.pr.style = 'margin: 0;' self.sr = css_parser.css.CSSStyleRule() self.sr.cssText = 'a {\n x: 1\n }' s = css_parser.css.CSSStyleSheet() s.insertRule(self.cr) # 0 s.insertRule(self.ir) # 1 s.insertRule(self.nr) # 2 s.insertRule(self.mr) # 3 s.insertRule(self.sr) # 4 s.insertRule(self.mr) # 5 s.insertRule(self.pr) # 6 s.insertRule(self.sr) # 7 self.assertEqual( '@charset "ascii";\n@import url(x);\n@namespace p "uri";\n@media all {\n @m;\n }\na {\n x: 1\n }\n@media all {\n @m;\n }\n@page {\n margin: 0\n }\na {\n x: 1\n }'.encode(), s.cssText) return s, s.cssRules.length def test_add(self): "CSSStyleSheet.add()" full = css_parser.css.CSSStyleSheet() sheet = css_parser.css.CSSStyleSheet() css = ['@charset "ascii";', '@import "x";', '@namespace p "u";', '@page {\n left: 0\n }', '@font-face {\n src: local(x)\n }', '@media all {\n a {\n color: red\n }\n }', 'a {\n color: green\n }', '/*comment*/', '@x;' ] fullcss = '\n'.join(css) full.cssText = fullcss self.assertEqual(full.cssText, fullcss.encode()) for i, line in enumerate(css): # sheet without same ruletype before = css[:i] after = css[i+1:] sheet.cssText = ''.join(before + after) index = sheet.add(line) if i < 3: # specific insertion point self.assertEqual(fullcss.encode(), sheet.cssText) self.assertEqual(i, index) else: # end of sheet expected = before expected.extend(after) expected.append(line) self.assertEqual('\n'.join(expected).encode(), sheet.cssText) self.assertEqual(len(expected)-1, index) # no same rule present # sheet with the same ruletype if i == 1: line = '@import "x2";' if i == 2: line = '@namespace p2 "u2";' full.cssText = fullcss index = full.add(line) if i < 1: self.assertEqual(fullcss.encode(), sheet.cssText) self.assertEqual(i, index) # no same rule present else: if i < 3: # in order expected = css[:i+1] # including same rule expected.append(line) expected.extend(css[i+1:]) expectedindex = i+1 else: # just appended as no order needed expected = css[:] expected.append(line) expectedindex = len(expected) - 1 self.assertEqual('\n'.join(expected).encode(), full.cssText) self.assertEqual(expectedindex, index) # no same rule present def test_addimport(self): p = css_parser.CSSParser(fetcher=lambda url: (None, '/**/')) cssrulessheet = p.parseString('@import "example.css";') imports = ( '@import "example.css";', # string css_parser.css.CSSImportRule(href="example.css"), # CSSRule cssrulessheet.cssRules # CSSRuleList ) for imp in imports: sheet = p.parseString('', href='http://example.com') sheet.add(imp) added = sheet.cssRules[0] self.assertEqual(sheet, added.parentStyleSheet) self.assertEqual('example.css', added.href) self.assertEqual('utf-8', added.styleSheet.encoding) self.assertEqual('/**/'.encode(), added.styleSheet.cssText) cssrulessheet = p.parseString('@import "example.css";') imports = ( ('@import "example.css";', 'ascii'), # string (css_parser.css.CSSImportRule(href="example.css"), 'ascii'), # CSSRule # got encoding from old parent already (cssrulessheet.cssRules, 'utf-8') # CSSRuleList ) for imp, enc in imports: sheet = p.parseString('', href='http://example.com', encoding='ascii') sheet.add(imp) added = sheet.cssRules[1] self.assertEqual(sheet, added.parentStyleSheet) self.assertEqual('example.css', added.href) self.assertEqual(enc, added.styleSheet.encoding) if enc == 'ascii': self.assertEqual('@charset "ascii";\n/**/'.encode(), added.styleSheet.cssText) else: self.assertEqual('/**/'.encode(), added.styleSheet.cssText) # if styleSheet is already there encoding is not set new impsheet = p.parseString('@import "example.css";') imp = impsheet.cssRules[0] sheet = p.parseString('', href='http://example.com', encoding='ascii') sheet.add(imp) added = sheet.cssRules[1] self.assertEqual(sheet, added.parentStyleSheet) self.assertEqual('example.css', added.href) self.assertEqual('utf-8', added.styleSheet.encoding) self.assertEqual('/**/'.encode(), added.styleSheet.cssText) def test_insertRule(self): "CSSStyleSheet.insertRule()" s, L = self._gets() # INVALID index self.assertRaises(xml.dom.IndexSizeErr, s.insertRule, self.sr, -1) self.assertRaises(xml.dom.IndexSizeErr, s.insertRule, self.sr, s.cssRules.length + 1) # check if rule is really not in self.assertEqual(L, s.cssRules.length) # insert string s.insertRule('a {}') self.assertEqual(L+1, s.cssRules.length) # insert rule s.insertRule(self.sr) self.assertEqual(L+2, s.cssRules.length) # insert rulelist s2, L2 = self._gets() rulelist = s2.cssRules del rulelist[:-2] s.insertRule(rulelist) self.assertEqual(L+2 + 2, s.cssRules.length) def _insertRule(self, rules, notbefore, notafter, anywhere, checkdoubles=True): """ helper test if any rule in rules cannot be inserted before rules in before or after rules in after but before and after rules in anywhere """ for rule in rules: for r in notbefore: s = css_parser.css.CSSStyleSheet() s.insertRule(r) self.assertRaises(xml.dom.HierarchyRequestErr, s.insertRule, rule, 0) s = css_parser.css.CSSStyleSheet() s.add(r) self.assertRaises(xml.dom.HierarchyRequestErr, s.insertRule, rule, 0) for r in notafter: s = css_parser.css.CSSStyleSheet() s.insertRule(r) self.assertRaises(xml.dom.HierarchyRequestErr, s.insertRule, rule, 1) s = css_parser.css.CSSStyleSheet() s.add(r) s.add(rule) # never raises self.assertEqual(s, r.parentStyleSheet) for r in anywhere: s = css_parser.css.CSSStyleSheet() s.insertRule(r) s.insertRule(rule, 0) # before s.insertRule(rule) # after if checkdoubles: self.assertEqual(s.cssRules.length, 3) self.assertEqual(s, r.parentStyleSheet) def test_insertRule_charset(self): "CSSStyleSheet.insertRule(@charset)" s, L = self._gets() notbefore = (self.cr,) notafter = (self.cr, self.ir, self.nr, self.mr, self.pr, self.sr, self.c, self.ur) anywhere = () self._insertRule((self.cr,), notbefore, notafter, anywhere) def test_insertRule_import(self): "CSSStyleSheet.insertRule(@import)" s, L = self._gets() notbefore = (self.cr,) notafter = (self.nr, self.pr, self.mr, self.sr) anywhere = (self.c, self.ur, self.ir) self._insertRule((self.ir,), notbefore, notafter, anywhere) def test_insertRule_namespace(self): "CSSStyleSheet.insertRule(@namespace)" s, L = self._gets() notbefore = (self.cr, self.ir) notafter = (self.pr, self.mr, self.sr) anywhere = (self.c, self.ur, self.nr) self._insertRule((self.nr,), notbefore, notafter, anywhere, checkdoubles=False) def test_insertRule_media_page_style(self): "CSSStyleSheet.insertRule(@media, @page, stylerule)" s, L = self._gets() notbefore = (self.cr, self.ir, self.nr) notafter = () anywhere = (self.c, self.ur, self.mr, self.pr, self.sr) self._insertRule((self.pr, self.mr, self.sr), notbefore, notafter, anywhere) def test_insertRule_unknownandcomment(self): "CSSStyleSheet.insertRule(@ unknown, comment)" s, L = self._gets() notbefore = (self.cr,) notafter = () anywhere = (self.c, self.ur, self.ir, self.nr, self.mr, self.pr, self.sr) self._insertRule((self.ur,), notbefore, notafter, anywhere) def test_HTMLComments(self): "CSSStyleSheet CDO CDC" css = '''body { color: red } body { color: blue } body { color: pink } body { color: green } ''' exp = '''body { color: red } body { color: pink }''' sheet = css_parser.parseString(css) self.assertEqual(sheet.cssText, exp.encode()) def test_incomplete(self): "CSSStyleRule (incomplete)" tests = { '@import "a': '@import "a";', # no } 'a { x: 1': 'a {\n x: 1\n }', # no } 'a { font-family: "arial sans': # no " 'a {\n font-family: "arial sans"\n }', } self.do_equal_p(tests) # parse def test_NoModificationAllowedErr(self): "CSSStyleSheet NoModificationAllowedErr" css = css_parser.css.CSSStyleSheet(readonly=True) self.assertEqual(True, css._readonly) # internal... self.assertRaises(xml.dom.NoModificationAllowedErr, css._setCssText, '@x;') self.assertRaises(xml.dom.NoModificationAllowedErr, css.insertRule, self.rule) self.assertRaises(xml.dom.NoModificationAllowedErr, css.insertRule, self.rule, 0) self.assertRaises(xml.dom.NoModificationAllowedErr, css.deleteRule, 0) def test_reprANDstr(self): "CSSStyleSheet.__repr__(), .__str__()" href = 'file:foo.css' title = 'title-of-css' s = css_parser.css.CSSStyleSheet(href=href, title=title) self.assertTrue(href in str(s)) self.assertTrue(title in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(href == s2.href) self.assertTrue(title == s2.title) def test_valid(self): cases = [ ('body { color: red; }', True), ('body { color: asd; }', False), ('body { foo: 12px; }', False), ] for case, expected in cases: sheet = css_parser.parseString(case) msg = "%r should be %s" % (case, 'valid' if expected else 'invalid') self.assertEqual(sheet.valid, expected, msg) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssunknownrule.py0000644000175000017500000001232600000000000022717 0ustar00kovidkovid"""testcases for css_parser.css.CSSUnkownRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class CSSUnknownRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSUnknownRuleTestCase, self).setUp() self.r = css_parser.css.CSSUnknownRule() self.rRO = css_parser.css.CSSUnknownRule(readonly=True) self.r_type = css_parser.css.CSSUnknownRule.UNKNOWN_RULE self.r_typeString = 'UNKNOWN_RULE' def tearDown(self): css_parser.ser.prefs.useDefaults() def test_init(self): "CSSUnknownRule.type and init" super(CSSUnknownRuleTestCase, self).test_init() self.assertFalse(self.r.wellformed) # only name r = css_parser.css.CSSUnknownRule(cssText='@init;') self.assertEqual('@init', r.atkeyword) self.assertEqual('@init;', r.cssText) self.assertTrue(r.wellformed) # @-... not allowed? r = css_parser.css.CSSUnknownRule(cssText='@-init;') self.assertEqual('@-init;', r.cssText) self.assertEqual('@-init', r.atkeyword) self.assertTrue(r.wellformed) r = css_parser.css.CSSUnknownRule(cssText='@_w-h-a-012;') self.assertEqual('@_w-h-a-012;', r.cssText) self.assertEqual('@_w-h-a-012', r.atkeyword) self.assertTrue(r.wellformed) # name and content r = css_parser.css.CSSUnknownRule(cssText='@init xxx;') self.assertEqual('@init', r.atkeyword) self.assertEqual('@init xxx;', r.cssText) self.assertTrue(r.wellformed) # name and block r = css_parser.css.CSSUnknownRule(cssText='@init { xxx }') self.assertEqual('@init', r.atkeyword) self.assertEqual('@init {\n xxx\n }', r.cssText) self.assertTrue(r.wellformed) # name and content and block r = css_parser.css.CSSUnknownRule(cssText='@init xxx { yyy }') self.assertEqual('@init', r.atkeyword) self.assertEqual('@init xxx {\n yyy\n }', r.cssText) self.assertTrue(r.wellformed) def test_cssText(self): "CSSUnknownRule.cssText" tests = { # not normal rules! '@font-facex{}': '@font-facex {\n }', '@importurl(x.css);': '@importurl (x . css);', '@mediaAll{}': '@mediaall {\n }', '@namespacep"x";': '@namespacep "x";', '@pageX{}': '@pagex {\n }', '@xbottom { content: counter(page) }': '@xbottom {\n content: counter(page)\n }', '@xbottom { content: "x" counter(page) "y"}': '@xbottom {\n content: "x" counter(page) "y"\n }' } self.do_equal_p(tests) # expects the same atkeyword for self.r so do a new one each test oldr = self.r for t, e in tests.items(): self.r = css_parser.css.CSSUnknownRule() self.do_equal_r({t: e}) self.r = oldr tests = { '@x;': None, '@x {}': '@x {\n }', '@x{ \n \t \f\r}': '@x {\n }', '@x {\n [()]([ {\n }]) {\n }\n }': None, '@x {\n @b;\n }': None, '''@x { @b { x: 1x; y: 2y; } }''': None, '@x "string" url(x);': None, # comments '@x/*1*//*2*/"str"/*3*//*4*/url("x");': '@x /*1*/ /*2*/ "str" /*3*/ /*4*/ url(x);', # WS '@x"string"url("x");': '@x "string" url(x);', '@x\n\r\t\f "string"\n\r\t\f url(\n\r\t\f "x"\n\r\t\f )\n\r\t\f ;': '@x "string" url(x);', } self.do_equal_p(tests) self.do_equal_r(tests) tests = { '@;': xml.dom.InvalidModificationErr, '@{}': xml.dom.InvalidModificationErr, '@ ;': xml.dom.InvalidModificationErr, '@ {};': xml.dom.InvalidModificationErr, '@x ;{}': xml.dom.SyntaxErr, '@x ;;': xml.dom.SyntaxErr, '@x } ': xml.dom.SyntaxErr, '@x } ;': xml.dom.SyntaxErr, '@x { ': xml.dom.SyntaxErr, '@x { ;': xml.dom.SyntaxErr, '@x ': xml.dom.SyntaxErr, '@x (;': xml.dom.SyntaxErr, '@x );': xml.dom.SyntaxErr, '@x [;': xml.dom.SyntaxErr, '@x ];': xml.dom.SyntaxErr, '@x {[(]()}': xml.dom.SyntaxErr, # trailing '@x{}{}': xml.dom.SyntaxErr, '@x{};': xml.dom.SyntaxErr, '@x{}1': xml.dom.SyntaxErr, '@x{} ': xml.dom.SyntaxErr, '@x{}/**/': xml.dom.SyntaxErr, '@x;1': xml.dom.SyntaxErr, '@x; ': xml.dom.SyntaxErr, '@x;/**/': xml.dom.SyntaxErr, } self.do_raise_r(tests) def test_InvalidModificationErr(self): "CSSUnknownRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@unknown') def test_reprANDstr(self): "CSSUnknownRule.__repr__(), .__str__()" s = css_parser.css.CSSUnknownRule(cssText='@x;') s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1623124610.0 css-parser-1.0.7/css_parser_tests/test_cssutils.py0000644000175000017500000004355700000000000021502 0ustar00kovidkovid# -*- coding: utf-8 -*- """Testcases for css_parser.css.CSSCharsetRule""" from __future__ import with_statement, unicode_literals from __future__ import absolute_import from . import basetest import codecs import css_parser import os import sys import tempfile class CSSutilsTestCase(basetest.BaseTestCase): def setUp(self): css_parser.ser.prefs.useDefaults() def tearDown(self): css_parser.ser.prefs.useDefaults() exp = '''@import "import/import2.css"; .import { /* ./import.css */ background-image: url(images/example.gif) }''' def test_import_from_above(self): def fetch(url): self.assertEqual(url, '../test2.css') return None, '' p = css_parser.CSSParser(fetcher=fetch) s = p.parseString("@import url('../test2.css'); a { background-image: url(../test.jpg); }", href='test.css') self.assertEqual( s.cssRules[1].style.getPropertyCSSValue('background-image')[0].absoluteUri, '../test.jpg' ) def fetch2(url): self.assertEqual(url, 'a/test2.css') return None, '' p = css_parser.CSSParser(fetcher=fetch2) s = p.parseString("@import url('../test2.css'); a { background-image: url(../test.jpg); }", href='a/b/test.css') self.assertEqual( s.cssRules[1].style.getPropertyCSSValue('background-image')[0].absoluteUri, 'a/test.jpg' ) results = [('a/test.css', '@import url(../test.css);'), ('test.css', 'p { background-image: url(a/test.jpg) }')] def fetch3(url): e, text = results.pop(0) self.assertEqual(e, url) return None, text p = css_parser.CSSParser(fetcher=fetch3) s = p.parseString("@import url('../test.css'); a { background-image: url(../test.jpg); }", href='a/b/test.css') style = s.cssRules[0].styleSheet.cssRules[0].styleSheet.cssRules[0].style self.assertEqual(style.getPropertyCSSValue('background-image')[0].absoluteUri, 'a/test.jpg') def test_parseString(self): "css_parser.parseString()" s = css_parser.parseString( self.exp, media='handheld, screen', title='from string') self.assertTrue(isinstance(s, css_parser.css.CSSStyleSheet)) self.assertEqual(None, s.href) self.assertEqual(self.exp.encode(), s.cssText) self.assertEqual('utf-8', s.encoding) self.assertEqual('handheld, screen', s.media.mediaText) self.assertEqual('from string', s.title) self.assertEqual(self.exp.encode(), s.cssText) ir = s.cssRules[0] self.assertEqual('import/import2.css', ir.href) irs = ir.styleSheet self.assertEqual(css_parser.css.CSSStyleSheet, type(irs)) href = basetest.get_sheet_filename('import.css') href = css_parser.helper.path2url(href) s = css_parser.parseString(self.exp, href=href) self.assertEqual(href, s.href) ir = s.cssRules[0] self.assertEqual('import/import2.css', ir.href) irs = ir.styleSheet self.assertTrue(isinstance(irs, css_parser.css.CSSStyleSheet)) self.assertEqual( irs.cssText, '@import "../import3.css";\n@import "import-impossible.css" print;\n.import2 {\n /* sheets/import2.css */\n background: url(http://example.com/images/example.gif);\n background: url(//example.com/images/example.gif);\n background: url(/images/example.gif);\n background: url(images2/example.gif);\n background: url(./images2/example.gif);\n background: url(../images/example.gif);\n background: url(./../images/example.gif)\n }' # noqa .encode()) tests = { 'a {color: red}': 'a {\n color: red\n }', 'a {color: rgb(1,2,3)}': 'a {\n color: rgb(1, 2, 3)\n }' } self.do_equal_p(tests) def test_parseFile(self): "css_parser.parseFile()" # name if used with open, href used for @import resolving name = basetest.get_sheet_filename('import.css') href = css_parser.helper.path2url(name) s = css_parser.parseFile( name, href=href, media='screen', title='from file') self.assertTrue(isinstance(s, css_parser.css.CSSStyleSheet)) if sys.platform.startswith('java'): # on Jython only file: self.assertTrue(s.href.startswith('file:')) else: # normally file:/// on win and file:/ on unix self.assertTrue(s.href.startswith('file:/')) self.assertTrue(s.href.endswith('/sheets/import.css')) self.assertEqual('utf-8', s.encoding) self.assertEqual('screen', s.media.mediaText) self.assertEqual('from file', s.title) self.assertEqual(self.exp.encode(), s.cssText) ir = s.cssRules[0] self.assertEqual('import/import2.css', ir.href) irs = ir.styleSheet self.assertTrue(isinstance(irs, css_parser.css.CSSStyleSheet)) self.assertEqual( irs.cssText, '@import "../import3.css";\n@import "import-impossible.css" print;\n.import2 {\n /* sheets/import2.css */\n background: url(http://example.com/images/example.gif);\n background: url(//example.com/images/example.gif);\n background: url(/images/example.gif);\n background: url(images2/example.gif);\n background: url(./images2/example.gif);\n background: url(../images/example.gif);\n background: url(./../images/example.gif)\n }' # noqa .encode()) # name is used for open and setting of href automatically # test needs to be relative to this test file! os.chdir(os.path.dirname(__file__)) name = basetest.get_sheet_filename('import.css') s = css_parser.parseFile(name, media='screen', title='from file') self.assertTrue(isinstance(s, css_parser.css.CSSStyleSheet)) if sys.platform.startswith('java'): # on Jython only file: self.assertTrue(s.href.startswith('file:')) else: # normally file:/// on win and file:/ on unix self.assertTrue(s.href.startswith('file:/')) self.assertTrue(s.href.endswith('/sheets/import.css')) self.assertEqual('utf-8', s.encoding) self.assertEqual('screen', s.media.mediaText) self.assertEqual('from file', s.title) self.assertEqual(self.exp.encode(), s.cssText) ir = s.cssRules[0] self.assertEqual('import/import2.css', ir.href) irs = ir.styleSheet self.assertTrue(isinstance(irs, css_parser.css.CSSStyleSheet)) self.assertEqual( irs.cssText, '@import "../import3.css";\n@import "import-impossible.css" print;\n.import2 {\n /* sheets/import2.css */\n background: url(http://example.com/images/example.gif);\n background: url(//example.com/images/example.gif);\n background: url(/images/example.gif);\n background: url(images2/example.gif);\n background: url(./images2/example.gif);\n background: url(../images/example.gif);\n background: url(./../images/example.gif)\n }' # noqa .encode()) # next test css = 'a:after { content: "羊蹄€\u2020" }' fd, name = tempfile.mkstemp('_css_parsertest.css') t = os.fdopen(fd, 'wb') t.write(css.encode('utf-8')) t.close() self.assertRaises(UnicodeDecodeError, css_parser.parseFile, name, 'ascii') # ??? s = css_parser.parseFile(name, encoding='iso-8859-1') self.assertEqual(css_parser.css.CSSStyleSheet, type(s)) self.assertEqual(s.cssRules[1].selectorText, 'a:after') s = css_parser.parseFile(name, encoding='utf-8') self.assertEqual(css_parser.css.CSSStyleSheet, type(s)) self.assertEqual(s.cssRules[1].selectorText, 'a:after') css = '@charset "iso-8859-1"; a:after { content: "ä" }' t = codecs.open(name, 'w', 'iso-8859-1') t.write(css) t.close() self.assertRaises(UnicodeDecodeError, css_parser.parseFile, name, 'ascii') s = css_parser.parseFile(name, encoding='iso-8859-1') self.assertEqual(css_parser.css.CSSStyleSheet, type(s)) self.assertEqual(s.cssRules[1].selectorText, 'a:after') self.assertRaises(UnicodeDecodeError, css_parser.parseFile, name, 'utf-8') # clean up try: os.remove(name) except EnvironmentError: pass def test_parseUrl(self): "css_parser.parseUrl()" href = basetest.get_sheet_filename('import.css') # href = u'file:' + urllib.pathname2url(href) href = css_parser.helper.path2url(href) # href = 'http://seewhatever.de/sheets/import.css' s = css_parser.parseUrl(href, media='tv, print', title='from url') self.assertTrue(isinstance(s, css_parser.css.CSSStyleSheet)) self.assertEqual(href, s.href) self.assertEqual(self.exp.encode(), s.cssText) self.assertEqual('utf-8', s.encoding) self.assertEqual('tv, print', s.media.mediaText) self.assertEqual('from url', s.title) sr = s.cssRules[1] img = sr.style.getProperty('background-image').propertyValue[0].value self.assertEqual(img, 'images/example.gif') ir = s.cssRules[0] self.assertEqual('import/import2.css', ir.href) irs = ir.styleSheet self.assertEqual( irs.cssText, '@import "../import3.css";\n@import "import-impossible.css" print;\n.import2 {\n /* sheets/import2.css */\n background: url(http://example.com/images/example.gif);\n background: url(//example.com/images/example.gif);\n background: url(/images/example.gif);\n background: url(images2/example.gif);\n background: url(./images2/example.gif);\n background: url(../images/example.gif);\n background: url(./../images/example.gif)\n }' # noqa .encode()) ir2 = irs.cssRules[0] self.assertEqual('../import3.css', ir2.href) irs2 = ir2.styleSheet self.assertEqual( irs2.cssText, '/* import3 */\n.import3 {\n /* from ./import/../import3.css */\n background: url(images/example3.gif);\n background: url(./images/example3.gif);\n background: url(import/images2/example2.gif);\n background: url(./import/images2/example2.gif);\n background: url(import/images2/../../images/example3.gif)\n }' # noqa .encode()) def test_setCSSSerializer(self): "css_parser.setSerializer() and css_parser.ser" s = css_parser.parseString('a { left: 0 }') exp4 = '''a { left: 0 }''' exp1 = '''a { left: 0 }''' self.assertEqual(exp4.encode(), s.cssText) newser = css_parser.CSSSerializer( css_parser.serialize.Preferences(indent=' ')) css_parser.setSerializer(newser) self.assertEqual(exp1.encode(), s.cssText) newser = css_parser.CSSSerializer( css_parser.serialize.Preferences(indent=' ')) css_parser.ser = newser self.assertEqual(exp4.encode(), s.cssText) def test_parseStyle(self): "css_parser.parseStyle()" s = css_parser.parseStyle('x:0; y:red') self.assertEqual(type(s), css_parser.css.CSSStyleDeclaration) self.assertEqual(s.cssText, 'x: 0;\ny: red') s = css_parser.parseStyle('@import "x";') self.assertEqual(type(s), css_parser.css.CSSStyleDeclaration) self.assertEqual(s.cssText, '') tests = [('content: "ä"', 'iso-8859-1'), ('content: "€"', 'utf-8')] for v, e in tests: s = css_parser.parseStyle(v.encode(e), encoding=e) self.assertEqual(s.cssText, v) self.assertRaises(UnicodeDecodeError, css_parser.parseStyle, 'content: "ä"'.encode('utf-8'), 'ascii') def test_getUrls(self): "css_parser.getUrls()" css_parser.ser.prefs.keepAllProperties = True css = r''' @import "im1"; @import url(im2); @import url( im3 ); @import url( "im4" ); @import url( 'im5' ); a { background-image: url(a) !important; background-\image: url(b); background: url(c) no-repeat !important; /* issue #46 */ src: local("xx"), url("f.woff") format("woff"), url("f.otf") format("opentype"), url("f.svg#f") format("svg"); }''' urls = set(css_parser.getUrls(css_parser.parseString(css))) self.assertEqual( urls, set([ "im1", "im2", "im3", "im4", "im5", "a", "b", "c", 'f.woff', 'f.svg#f', 'f.otf' ])) css_parser.ser.prefs.keepAllProperties = False def test_replaceUrls(self): "css_parser.replaceUrls()" css_parser.ser.prefs.keepAllProperties = True css = r''' @import "im1"; @import url(im2); a { background-image: url(c) !important; background-\image: url(b); background: url(a) no-repeat !important; }''' s = css_parser.parseString(css) css_parser.replaceUrls(s, lambda old: "NEW" + old) self.assertEqual('@import "NEWim1";', s.cssRules[0].cssText) self.assertEqual('NEWim2', s.cssRules[1].href) self.assertEqual( '''background-image: url(NEWc) !important; background-\\image: url(NEWb); background: url(NEWa) no-repeat !important''', s.cssRules[2].style.cssText) css_parser.ser.prefs.keepAllProperties = False # CSSStyleDeclaration style = css_parser.parseStyle('''color: red; background-image: url(1.png), url('2.png')''') css_parser.replaceUrls(style, lambda url: 'prefix/' + url) self.assertEqual( style.cssText, '''color: red; background-image: url(prefix/1.png), url(prefix/2.png)''') def test_resolveImports(self): "css_parser.resolveImports(sheet)" self._tempSer() css_parser.ser.prefs.useMinified() a = '@charset "iso-8859-1";@import"b.css";\xe4{color:green}'.encode( 'iso-8859-1') b = '@charset "ascii";\\E4 {color:red}'.encode('ascii') # normal with self.patch_default_fetcher((None, b)): s = css_parser.parseString(a) # py3 TODO self.assertEqual(a, s.cssText) self.assertEqual(b, s.cssRules[1].styleSheet.cssText) c = css_parser.resolveImports(s) # py3 TODO self.assertEqual( '\xc3\xa4{color:red}\xc3\xa4{color:green}'.encode( 'iso-8859-1'), c.cssText) c.encoding = 'ascii' self.assertEqual( r'@charset "ascii";\E4 {color:red}\E4 {color:green}'. encode(), c.cssText) # b cannot be found with self.patch_default_fetcher((None, None)): s = css_parser.parseString(a) # py3 TODO self.assertEqual(a, s.cssText) self.assertEqual(css_parser.css.CSSStyleSheet, type(s.cssRules[1].styleSheet)) c = css_parser.resolveImports(s) # py3 TODO self.assertEqual( '@import"b.css";\xc3\xa4{color:green}'.encode( 'iso-8859-1'), c.cssText) # @import with media a = '@import"b.css";@import"b.css" print, tv ;@import"b.css" all;' b = 'a {color: red}' with self.patch_default_fetcher((None, b)): s = css_parser.parseString(a) c = css_parser.resolveImports(s) self.assertEqual( 'a{color:red}@media print,tv{a{color:red}}a{color:red}'. encode(), c.cssText) # cannot resolve with media => keep original a = '@import"b.css"print;' b = '@namespace "http://example.com";' with self.patch_default_fetcher((None, b)): s = css_parser.parseString(a) c = css_parser.resolveImports(s) self.assertEqual(a.encode(), c.cssText) # urls are adjusted too, layout: # a.css # c.css # img/img.gif # b/ # b.css # subimg/subimg.gif a = ''' @import"b/b.css"; a { x: url(/img/abs.gif); y: url(img/img.gif); z: url(b/subimg/subimg.gif); }''' def fetcher(url): c = { 'b.css': ''' @import"../c.css"; b { x: url(/img/abs.gif); y: url(../img/img.gif); z: url(subimg/subimg.gif); }''', 'c.css': ''' c { x: url(/img/abs.gif); y: url(./img/img.gif); z: url(./b/subimg/subimg.gif); }''' } return 'utf-8', c[os.path.split(url)[1]] def do(): with self.patch_default_fetcher(fetcher): s = css_parser.parseString(a) r = css_parser.resolveImports(s) return s, r s, r = do() css_parser.ser.prefs.useDefaults() css_parser.ser.prefs.keepComments = False self.assertEqual( '''c { x: url(/img/abs.gif); y: url(img/img.gif); z: url(b/subimg/subimg.gif) } b { x: url(/img/abs.gif); y: url(img/img.gif); z: url(b/subimg/subimg.gif) } a { x: url(/img/abs.gif); y: url(img/img.gif); z: url(b/subimg/subimg.gif) }'''.encode(), r.cssText) css_parser.ser.prefs.useDefaults() if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssutilsimport.py0000644000175000017500000000131300000000000022715 0ustar00kovidkovidfrom __future__ import absolute_import from __future__ import unicode_literals import unittest """Testcase for css_parser imports""" class CSSutilsImportTestCase(unittest.TestCase): def test_import_all(self): "from css_parser import *" import css_parser from css_parser import __all__ as aimp exp = { 'CSSParser': css_parser.CSSParser, # noqa 'CSSSerializer': css_parser.CSSSerializer, # noqa 'css': css_parser.css, 'stylesheets': css_parser.stylesheets, } self.assertEqual(len(aimp), len(exp)) self.assertEqual(set(aimp), set(exp)) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssvalue.py0000644000175000017500000010144500000000000021445 0ustar00kovidkovid"""Testcases for css_parser.css.CSSValue and CSSPrimitiveValue.""" # # from decimal import Decimal # maybe for later tests? #import xml.dom #import basetest #import css_parser #import types # # class CSSValueTestCase(basetest.BaseTestCase): # # def setUp(self): # self.r = css_parser.css.CSSValue() # needed for tests # # def test_init(self): # "CSSValue.__init__()" # v = css_parser.css.CSSValue() # self.assertTrue(u'' == v.cssText) # self.assertTrue(None is v.cssValueType) # self.assertTrue(None == v.cssValueTypeString) # # def test_escapes(self): # "CSSValue Escapes" # v = css_parser.css.CSSValue() # v.cssText = u'1px' # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue(v.CSS_PX == v.primitiveType) # self.assertTrue(u'1px' == v.cssText) # # v.cssText = u'1PX' # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue(v.CSS_PX == v.primitiveType) # self.assertTrue(u'1px' == v.cssText) # # v.cssText = u'1p\\x' # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue(v.CSS_PX == v.primitiveType) # self.assertTrue(u'1px' == v.cssText) # # def test_cssText(self): # "CSSValue.cssText" # v = css_parser.css.CSSValue() # v.cssText = u'1px' # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue(v.CSS_PX == v.primitiveType) # self.assertTrue(u'1px' == v.cssText) # # v = css_parser.css.CSSValue() # v.cssText = u'1px' # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue(v.CSS_PX == v.primitiveType) # self.assertTrue(u'1px' == v.cssText) # # v = css_parser.css.CSSValue() # v.cssText = u'a ,b, c ,"d or d", "e, " ' # self.assertEqual(v.CSS_PRIMITIVE_VALUE, v.cssValueType) # self.assertEqual(v.CSS_STRING, v.primitiveType) # self.assertEqual(u'a, b, c, "d or d", "e, "', v.cssText) # # v.cssText = u' 1 px ' # self.assertTrue(v.CSS_VALUE_LIST == v.cssValueType) # self.assertTrue('1 px' == v.cssText) # # v.cssText = u' normal 1px a, b, "c" end ' # self.assertTrue(v.CSS_VALUE_LIST == v.cssValueType) # self.assertEqual('normal 1px a, b, "c" end', v.cssText) # # for i, x in enumerate(v): # self.assertEqual(x.CSS_PRIMITIVE_VALUE, x.cssValueType) # if x == 0: # self.assertEqual(x.CSS_IDENT, x.primitiveType) # self.assertEqual(u'normal', x.cssText) # elif x == 1: # self.assertEqual(x.CSS_PX, x.primitiveType) # self.assertEqual(u'1px', x.cssText) # if x == 2: # self.assertEqual(x.CSS_STRING, x.primitiveType) # self.assertEqual(u'a, b, "c"', x.cssText) # if x == 3: # self.assertEqual(x.CSS_IDENT, x.primitiveType) # self.assertEqual(u'end', x.cssText) # # # v = css_parser.css.CSSValue() # v.cssText = u' 1 px ' # self.assertTrue(v.CSS_VALUE_LIST == v.cssValueType) # self.assertTrue(u'1 px' == v.cssText) # # v.cssText = u'expression(document.body.clientWidth > 972 ? "1014px": "100%" )' # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue(v.CSS_UNKNOWN == v.primitiveType) # self.assertEqual(u'expression(document.body.clientWidth > 972?"1014px": "100%")', # v.cssText) # # def test_cssText2(self): # "CSSValue.cssText 2" # tests = { # # mix # u'a()1,-1,+1,1%,-1%,1px,-1px,"a",a,url(a),#aabb44': # u'a() 1, -1, 1, 1%, -1%, 1px, -1px, "a", a, url(a), #ab4', # # # S or COMMENT # u'red': u'red', # u'red ': u'red', # u' red ': u'red', # u'/**/red': u'/**/ red', # u'red/**/': u'red /**/', # u'/**/red/**/': u'/**/ red /**/', # u'/**/ red': u'/**/ red', # u'red /**/': u'red /**/', # u'/**/ red /**/': u'/**/ red /**/', # u'red-': u'red-', # # # num / dimension # u'.0': u'0', # u'0': u'0', # u'0.0': u'0', # u'00': u'0', # u'0%': u'0%', # u'0px': u'0', # u'-.0': u'0', # u'-0': u'0', # u'-0.0': u'0', # u'-00': u'0', # u'-0%': u'0%', # u'-0px': u'0', # u'+.0': u'0', # u'+0': u'0', # u'+0.0': u'0', # u'+00': u'0', # u'+0%': u'0%', # u'+0px': u'0', # u'1': u'1', # u'1.0': u'1', # u'1px': u'1px', # u'1%': u'1%', # u'1px1': u'1px1', # u'+1': u'1', # u'-1': u'-1', # u'+1.0': u'1', # u'-1.0': u'-1', # # # string, escaped nl is removed during tokenizing # u'"x"': u'"x"', # u"'x'": u'"x"', # #ur''' "1\'2" ''': u'''"1'2"''', #??? # #ur"'x\"'": ur'"x\""', #??? # ur'''"x\ # y"''': u'''"xy"''', # # # hash and rgb/a # u'#112234': u'#112234', # u'#112233': u'#123', # u'rgb(1,2,3)': u'rgb(1, 2, 3)', # u'rgb( 1 , 2 , 3 )': u'rgb(1, 2, 3)', # u'rgb(-1,+2,0)': u'rgb(-1, 2, 0)', # u'rgba(1,2,3,4)': u'rgba(1, 2, 3, 4)', # u'rgba( 1 , 2 , 3 , 4 )': u'rgba(1, 2, 3, 4)', # u'rgba(-1,+2,0, 0)': u'rgba(-1, 2, 0, 0)', # # # FUNCTION # u'f(1,2)': u'f(1, 2)', # u'f( 1 , 2 )': u'f(1, 2)', # u'f(-1,+2)': u'f(-1, 2)', # u'f( -1 , +2 )': u'f(-1, 2)', # u'fun( -1 , +2 )': u'fun(-1, 2)', # u'local( x )': u'local(x)', # u'test(1px, #111, y, 1, 1%, "1", y(), var(x))': # u'test(1px, #111, y, 1, 1%, "1", y(), var(x))', # u'test(-1px, #111, y, -1, -1%, "1", -y())': # u'test(-1px, #111, y, -1, -1%, "1", -y())', # u'url(y) format( "x" , "y" )': u'url(y) format("x", "y")', # u'f(1 2,3 4)': u'f(1 2, 3 4)', # # # IE expression # ur'Expression()': u'Expression()', # ur'expression(-1 < +2)': u'expression(-1< + 2)', # ur'expression(document.width == "1")': u'expression(document.width=="1")', # u'alpha(opacity=80)': u'alpha(opacity=80)', # u'alpha( opacity = 80 , x=2 )': u'alpha(opacity=80, x=2)', # # # unicode-range # 'u+f': 'u+f', # 'U+ABCdef': 'u+abcdef', # # # url # 'url(a)': 'url(a)', # 'uRl(a)': 'url(a)', # 'u\\rl(a)': 'url(a)', # 'url("a")': 'url(a)', # 'url( "a" )': 'url(a)', # 'url(a)': 'url(a)', # 'url(";")': 'url(";")', # 'url(",")': 'url(",")', # 'url(")")': 'url(")")', # '''url("'")''': '''url("'")''', # '''url('"')''': '''url("\\"")''', # '''url("'")''': '''url("'")''', # # # operator # '1': '1', # '1 2': '1 2', # '1 2': '1 2', # '1,2': '1, 2', # '1, 2': '1, 2', # '1 ,2': '1, 2', # '1 , 2': '1, 2', # '1/2': '1/2', # '1/ 2': '1/2', # '1 /2': '1/2', # '1 / 2': '1/2', # # comment # '1/**/2': '1 /**/ 2', # '1 /**/2': '1 /**/ 2', # '1/**/ 2': '1 /**/ 2', # '1 /**/ 2': '1 /**/ 2', # '1 /*a*/ /*b*/ 2': '1 /*a*/ /*b*/ 2', # # , before # '1,/**/2': '1, /**/ 2', # '1 ,/**/2': '1, /**/ 2', # '1, /**/2': '1, /**/ 2', # '1 , /**/2': '1, /**/ 2', # # , after # '1/**/,2': '1 /**/, 2', # '1/**/ ,2': '1 /**/, 2', # '1/**/, 2': '1 /**/, 2', # '1/**/ , 2': '1 /**/, 2', # # all # '1/*a*/ ,/*b*/ 2': '1 /*a*/, /*b*/ 2', # '1 /*a*/, /*b*/2': '1 /*a*/, /*b*/ 2', # '1 /*a*/ , /*b*/ 2': '1 /*a*/, /*b*/ 2', # # # list # 'a b1,b2 b2,b3,b4': 'a b1, b2 b2, b3, b4', # 'a b1 , b2 b2 , b3 , b4': 'a b1, b2 b2, b3, b4', # 'u+1 , u+2-5': 'u+1, u+2-5', # u'local( x ), url(y) format( "x" , "y" )': # u'local(x), url(y) format("x", "y")', # # FUNCTION # u'attr( href )': u'attr(href)', # # PrinceXML extende FUNC syntax with nested FUNC # u'target-counter(attr(href),page)': u'target-counter(attr(href), page)' # } # # self.do_equal_r(tests) # # tests = { # u'a+': xml.dom.SyntaxErr, # u'-': xml.dom.SyntaxErr, # u'+': xml.dom.SyntaxErr, # u'-%': xml.dom.SyntaxErr, # u'+a': xml.dom.SyntaxErr, # u'--1px': xml.dom.SyntaxErr, # u'++1px': xml.dom.SyntaxErr, # u'#': xml.dom.SyntaxErr, # u'#00': xml.dom.SyntaxErr, # u'#0000': xml.dom.SyntaxErr, # u'#00000': xml.dom.SyntaxErr, # u'#0000000': xml.dom.SyntaxErr, # u'-#0': xml.dom.SyntaxErr, # # operator # u',': xml.dom.SyntaxErr, # u'1,,2': xml.dom.SyntaxErr, # u'1,/**/,2': xml.dom.SyntaxErr, # u'1 , /**/ , 2': xml.dom.SyntaxErr, # u'1,': xml.dom.SyntaxErr, # u'1, ': xml.dom.SyntaxErr, # u'1 ,': xml.dom.SyntaxErr, # u'1 , ': xml.dom.SyntaxErr, # u'1 , ': xml.dom.SyntaxErr, # u'1//2': xml.dom.SyntaxErr, # # URL # u'url(x))': xml.dom.SyntaxErr, # # string # u'"': xml.dom.SyntaxErr, # u"'": xml.dom.SyntaxErr, # # function # u'f(-)': xml.dom.SyntaxErr, # u'f(x))': xml.dom.SyntaxErr # } # self.do_raise_r(tests) # # def test_incomplete(self): # "CSSValue (incomplete)" # tests = { # u'url("a': u'url(a)', # u'url(a': u'url(a)' # } # for v, exp in tests.items(): # s = css_parser.parseString('a { background: %s' % v) # v = s.cssRules[0].style.background # self.assertEqual(v, exp) # # def test_cssValueType(self): # "CSSValue.cssValueType .cssValueTypeString" # tests = [ # ([u'inherit', u'INhe\\rit'], 'CSS_INHERIT', css_parser.css.CSSValue), # (['1', '1%', '1em', '1ex', '1px', '1cm', '1mm', '1in', '1pt', '1pc', # '1deg', '1rad', '1grad', '1ms', '1s', '1hz', '1khz', '1other', # '"string"', "'string'", 'url(x)', 'red', # 'attr(a)', 'counter(x)', 'rect(1px, 2px, 3px, 4px)', # 'rgb(0, 0, 0)', '#000', '#123456', 'rgba(0, 0, 0, 0)', # 'hsl(0, 0, 0)', 'hsla(0, 0, 0, 0)', # ], # 'CSS_PRIMITIVE_VALUE', css_parser.css.CSSPrimitiveValue), # ([u'1px 1px', 'red blue green x'], 'CSS_VALUE_LIST', css_parser.css.CSSValueList), # # what is a custom value? # #([], 'CSS_CUSTOM', css_parser.css.CSSValue) # ] # for values, name, cls in tests: # for value in values: # v = css_parser.css.CSSValue(cssText=value) # if value == "'string'": # # will be changed to " always # value = '"string"' # self.assertEqual(value, v.cssText) # self.assertEqual(name, v.cssValueTypeString) # self.assertEqual(getattr(v, name), v.cssValueType) # self.assertEqual(cls, type(v)) # # def test_readonly(self): # "(CSSValue._readonly)" # v = css_parser.css.CSSValue(cssText='inherit') # self.assertTrue(False is v._readonly) # # v = css_parser.css.CSSValue(cssText='inherit', readonly=True) # self.assertTrue(True is v._readonly) # self.assertTrue(u'inherit', v.cssText) # self.assertRaises(xml.dom.NoModificationAllowedErr, v._setCssText, u'x') # self.assertTrue(u'inherit', v.cssText) # # def test_reprANDstr(self): # "CSSValue.__repr__(), .__str__()" # cssText='inherit' # # s = css_parser.css.CSSValue(cssText=cssText) # # self.assertTrue(cssText in str(s)) # # s2 = eval(repr(s)) # self.assertTrue(isinstance(s2, s.__class__)) # self.assertTrue(cssText == s2.cssText) # # # class CSSPrimitiveValueTestCase(basetest.BaseTestCase): # # def test_init(self): # "CSSPrimitiveValue.__init__()" # v = css_parser.css.CSSPrimitiveValue(u'1') # self.assertTrue(u'1' == v.cssText) # # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue("CSS_PRIMITIVE_VALUE" == v.cssValueTypeString) # # self.assertTrue(v.CSS_NUMBER == v.primitiveType) # self.assertTrue("CSS_NUMBER" == v.primitiveTypeString) # # # DUMMY to be able to test empty constructor call # #self.assertRaises(xml.dom.SyntaxErr, v.__init__, None) # # self.assertRaises(xml.dom.InvalidAccessErr, v.getCounterValue) # self.assertRaises(xml.dom.InvalidAccessErr, v.getRGBColorValue) # self.assertRaises(xml.dom.InvalidAccessErr, v.getRectValue) # self.assertRaises(xml.dom.InvalidAccessErr, v.getStringValue) # # def test_CSS_UNKNOWN(self): # "CSSPrimitiveValue.CSS_UNKNOWN" # v = css_parser.css.CSSPrimitiveValue(u'expression(false)') # self.assertTrue(v.CSS_UNKNOWN == v.primitiveType) # self.assertTrue('CSS_UNKNOWN' == v.primitiveTypeString) # # def test_CSS_NUMBER_AND_OTHER_DIMENSIONS(self): # "CSSPrimitiveValue.CSS_NUMBER .. CSS_DIMENSION" # defs = [ # ('', 'CSS_NUMBER'), # ('%', 'CSS_PERCENTAGE'), # ('em', 'CSS_EMS'), # ('ex', 'CSS_EXS'), # ('px', 'CSS_PX'), # ('cm', 'CSS_CM'), # ('mm', 'CSS_MM'), # ('in', 'CSS_IN'), # ('pt', 'CSS_PT'), # ('pc', 'CSS_PC'), # ('deg', 'CSS_DEG'), # ('rad', 'CSS_RAD'), # ('grad', 'CSS_GRAD'), # ('ms', 'CSS_MS'), # ('s', 'CSS_S'), # ('hz', 'CSS_HZ'), # ('khz', 'CSS_KHZ'), # ('other_dimension', 'CSS_DIMENSION') # ] # for dim, name in defs: # for n in (0, 1, 1.1, -1, -1.1, -0): # v = css_parser.css.CSSPrimitiveValue('%i%s' % (n, dim)) # self.assertEqual(name, v.primitiveTypeString) # self.assertEqual(getattr(v, name), v.primitiveType) # # def test_CSS_STRING_AND_OTHER(self): # "CSSPrimitiveValue.CSS_STRING .. CSS_RGBCOLOR" # defs = [ # (('""', "''", '"some thing"', "' A\\ND '", # # comma separated lists are STRINGS FOR NOW! # 'a, b', # '"a", "b"', # ), 'CSS_STRING'), # (('url(a)', 'url("a b")', "url(' ')"), 'CSS_URI'), # (('some', 'or_anth-er'), 'CSS_IDENT'), # (('attr(a)', 'attr(b)'), 'CSS_ATTR'), # (('counter(1)', 'counter(2)'), 'CSS_COUNTER'), # (('rect(1,2,3,4)',), 'CSS_RECT'), # (('rgb(1,2,3)', 'rgb(10%, 20%, 30%)', '#123', '#123456'), # 'CSS_RGBCOLOR'), # (('rgba(1,2,3,4)','rgba(10%, 20%, 30%, 40%)', ), # 'CSS_RGBACOLOR'), # (('U+0', 'u+ffffff', 'u+000000-f', # 'u+0-f, U+ee-ff'), 'CSS_UNICODE_RANGE') # ] # # for examples, name in defs: # for x in examples: # v = css_parser.css.CSSPrimitiveValue(x) # self.assertEqual(getattr(v, name), v.primitiveType) # self.assertEqual(name, v.primitiveTypeString) # # def test_getFloat(self): # "CSSPrimitiveValue.getFloatValue()" # # NOT TESTED are float values as it seems difficult to # # compare these. Maybe use decimal.Decimal? # # v = css_parser.css.CSSPrimitiveValue(u'1px') # tests = { # '0': (v.CSS_NUMBER, 0), # '-1.1': (v.CSS_NUMBER, -1.1), # '1%': (v.CSS_PERCENTAGE, 1), # '-1%': (v.CSS_PERCENTAGE, -1), # '1em': (v.CSS_EMS, 1), # '-1.1em': (v.CSS_EMS, -1.1), # '1ex': (v.CSS_EXS, 1), # '1px': (v.CSS_PX, 1), # # '1cm': (v.CSS_CM, 1), # '1cm': (v.CSS_MM, 10), # '254cm': (v.CSS_IN, 100), # '1mm': (v.CSS_MM, 1), # '10mm': (v.CSS_CM, 1), # '254mm': (v.CSS_IN, 10), # '1in': (v.CSS_IN, 1), # '100in': (v.CSS_CM, 254), # ROUNDED!!! # '10in': (v.CSS_MM, 254), # ROUNDED!!! # # '1pt': (v.CSS_PT, 1), # '1pc': (v.CSS_PC, 1), # # '1deg': (v.CSS_DEG, 1), # '1rad': (v.CSS_RAD, 1), # '1grad': (v.CSS_GRAD, 1), # # '1ms': (v.CSS_MS, 1), # '1000ms': (v.CSS_S, 1), # '1s': (v.CSS_S, 1), # '1s': (v.CSS_MS, 1000), # # '1hz': (v.CSS_HZ, 1), # '1000hz': (v.CSS_KHZ, 1), # '1khz': (v.CSS_KHZ, 1), # '1khz': (v.CSS_HZ, 1000), # # '1DIMENSION': (v.CSS_DIMENSION, 1), # } # for cssText in tests: # v.cssText = cssText # unitType, exp = tests[cssText] # val = v.getFloatValue(unitType) # if unitType in (v.CSS_IN, v.CSS_CM): # val = round(val) # self.assertEqual(val , exp) # # def test_setFloat(self): # "CSSPrimitiveValue.setFloatValue()" # V = css_parser.css.CSSPrimitiveValue # # tests = { # # unitType, value # (V.CSS_NUMBER, 1): [ # # unitType, setvalue, # # getvalue or expected exception, msg or cssText # (V.CSS_NUMBER, 0, 0, '0'), # (V.CSS_NUMBER, 0.1, 0.1, '0.1'), # (V.CSS_NUMBER, -0, 0, '0'), # (V.CSS_NUMBER, 2, 2, '2'), # (V.CSS_NUMBER, 2.0, 2, '2'), # (V.CSS_NUMBER, 2.1, 2.1, '2.1'), # (V.CSS_NUMBER, -2.1, -2.1, '-2.1'), # # setting with string does work # (V.CSS_NUMBER, '1', 1, '1'), # (V.CSS_NUMBER, '1.1', 1.1, '1.1'), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DEG, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_RAD, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_GRAD, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_S, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_MS, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_KHZ, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_HZ, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DIMENSION, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_MM, 2, xml.dom.InvalidAccessErr, None), # # (V.CSS_NUMBER, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: floatValue 'x' is not a float"), # (V.CSS_NUMBER, '1x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: floatValue '1x' is not a float"), # # (V.CSS_STRING, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_STRING' is not a float type"), # (V.CSS_URI, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_URI' is not a float type"), # (V.CSS_ATTR, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_ATTR' is not a float type"), # (V.CSS_IDENT, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_IDENT' is not a float type"), # (V.CSS_RGBCOLOR, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_RGBCOLOR' is not a float type"), # (V.CSS_RGBACOLOR, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_RGBACOLOR' is not a float type"), # (V.CSS_RECT, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_RECT' is not a float type"), # (V.CSS_COUNTER, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_COUNTER' is not a float type"), # (V.CSS_EMS, 1, xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_NUMBER' to 'CSS_EMS'"), # (V.CSS_EXS, 1, xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_NUMBER' to 'CSS_EXS'") # ], # (V.CSS_MM, '1mm'): [ # (V.CSS_MM, 2, 2, '2mm'), # (V.CSS_MM, 0, 0, '0mm'), # (V.CSS_MM, 0.1, 0.1, '0.1mm'), # (V.CSS_MM, -0, -0, '0mm'), # (V.CSS_MM, 3.0, 3, '3mm'), # (V.CSS_MM, 3.1, 3.1, '3.1mm'), # (V.CSS_MM, -3.1, -3.1, '-3.1mm'), # (V.CSS_CM, 1, 10, '10mm'), # (V.CSS_IN, 10, 254, '254mm'), # (V.CSS_PT, 1, 1828.8, '1828.8mm'), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_NUMBER, 2, xml.dom.InvalidAccessErr, None) # ], # (V.CSS_PT, '1pt'): [ # (V.CSS_PT, 2, 2, '2pt'), # (V.CSS_PC, 12, 1, '1pt'), # (V.CSS_NUMBER, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DEG, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None) # ], # (V.CSS_KHZ, '1khz'): [ # (V.CSS_HZ, 2000, 2, '2khz'), # (V.CSS_NUMBER, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DEG, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None) # ] # } # for test in tests: # initialType, initialValue = test # pv = css_parser.css.CSSPrimitiveValue(initialValue) # for setType, setValue, exp, cssText in tests[test]: # if type(exp) == types.TypeType or\ # type(exp) == types.ClassType: # 2.4 compatibility # if cssText: # self.assertRaisesMsg( # exp, cssText, pv.setFloatValue, setType, setValue) # else: # self.assertRaises( # exp, pv.setFloatValue, setType, setValue) # else: # pv.setFloatValue(setType, setValue) # self.assertEqual(pv._value[0], cssText) # if cssText == '0mm': # cssText = '0' # self.assertEqual(pv.cssText, cssText) # self.assertEqual(pv.getFloatValue(initialType), exp) # # def test_getString(self): # "CSSPrimitiveValue.getStringValue()" # v = css_parser.css.CSSPrimitiveValue(u'1px') # self.assertTrue(v.primitiveType == v.CSS_PX) # self.assertRaises(xml.dom.InvalidAccessErr, # v.getStringValue) # # pv = css_parser.css.CSSPrimitiveValue # tests = { # pv.CSS_STRING: ("'red'", 'red'), # pv.CSS_STRING: ('"red"', 'red'), # pv.CSS_URI: ('url(http://example.com)', None), # pv.CSS_URI: ("url('http://example.com')", # u"http://example.com"), # pv.CSS_URI: ('url("http://example.com")', # u'http://example.com'), # pv.CSS_URI: ('url("http://example.com?)")', # u'http://example.com?)'), # pv.CSS_IDENT: ('red', None), # pv.CSS_ATTR: ('attr(att-name)', # u'att-name'), # the name of the attrr # } # for t in tests: # val, exp = tests[t] # if not exp: # exp = val # # v = css_parser.css.CSSPrimitiveValue(val) # self.assertEqual(v.primitiveType, t) # self.assertEqual(v.getStringValue(), exp) # # def test_setString(self): # "CSSPrimitiveValue.setStringValue()" # # CSS_STRING # v = css_parser.css.CSSPrimitiveValue(u'"a"') # self.assertTrue(v.CSS_STRING == v.primitiveType) # v.setStringValue(v.CSS_STRING, 'b') # self.assertTrue(('b', 'STRING') == v._value) # self.assertEqual('b', v.getStringValue()) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_STRING' to 'CSS_URI'", # v.setStringValue, *(v.CSS_URI, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_STRING' to 'CSS_IDENT'", # v.setStringValue, *(v.CSS_IDENT, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_STRING' to 'CSS_ATTR'", # v.setStringValue, *(v.CSS_ATTR, 'x')) # # # CSS_IDENT # v = css_parser.css.CSSPrimitiveValue('new') # v.setStringValue(v.CSS_IDENT, 'ident') # self.assertTrue(v.CSS_IDENT == v.primitiveType) # self.assertTrue(('ident', 'IDENT') == v._value) # self.assertTrue('ident' == v.getStringValue()) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_IDENT' to 'CSS_URI'", # v.setStringValue, *(v.CSS_URI, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_IDENT' to 'CSS_STRING'", # v.setStringValue, *(v.CSS_STRING, '"x"')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_IDENT' to 'CSS_ATTR'", # v.setStringValue, *(v.CSS_ATTR, 'x')) # # # CSS_URI # v = css_parser.css.CSSPrimitiveValue('url(old)') # v.setStringValue(v.CSS_URI, '(') # self.assertEqual((u'(', 'URI'), v._value) # self.assertEqual(u'(', v.getStringValue()) # # v.setStringValue(v.CSS_URI, ')') # self.assertEqual((u')', 'URI'), v._value) # self.assertEqual(u')', v.getStringValue()) # # v.setStringValue(v.CSS_URI, '"') # self.assertEqual(ur'"', v.getStringValue()) # self.assertEqual((ur'"', 'URI'), v._value) # # v.setStringValue(v.CSS_URI, "''") # self.assertEqual(ur"''", v.getStringValue()) # self.assertEqual((ur"''", 'URI'), v._value) # # v.setStringValue(v.CSS_URI, ',') # self.assertEqual(ur',', v.getStringValue()) # self.assertEqual((ur',', 'URI'), v._value) # # v.setStringValue(v.CSS_URI, ' ') # self.assertEqual((u' ', 'URI'), v._value) # self.assertEqual(u' ', v.getStringValue()) # # v.setStringValue(v.CSS_URI, 'a)') # self.assertEqual((u'a)', 'URI'), v._value) # self.assertEqual(u'a)', v.getStringValue()) # # v.setStringValue(v.CSS_URI, 'a') # self.assertTrue(v.CSS_URI == v.primitiveType) # self.assertEqual((u'a', 'URI'), v._value) # self.assertEqual(u'a', v.getStringValue()) # # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_URI' to 'CSS_IDENT'", # v.setStringValue, *(v.CSS_IDENT, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_URI' to 'CSS_STRING'", # v.setStringValue, *(v.CSS_STRING, '"x"')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_URI' to 'CSS_ATTR'", # v.setStringValue, *(v.CSS_ATTR, 'x')) # # # CSS_ATTR # v = css_parser.css.CSSPrimitiveValue('attr(old)') # v.setStringValue(v.CSS_ATTR, 'a') # self.assertTrue(v.CSS_ATTR == v.primitiveType) # self.assertTrue('a' == v.getStringValue()) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_ATTR' to 'CSS_IDENT'", # v.setStringValue, *(v.CSS_IDENT, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_ATTR' to 'CSS_STRING'", # v.setStringValue, *(v.CSS_STRING, '"x"')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_ATTR' to 'CSS_URI'", # v.setStringValue, *(v.CSS_URI, 'x')) # # # TypeError as 'x' is no valid type # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: stringType 'x' (UNKNOWN TYPE) is not a string type", # v.setStringValue, *('x', 'brown')) # # IndexError as 111 is no valid type # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: stringType 111 (UNKNOWN TYPE) is not a string type", # v.setStringValue, *(111, 'brown')) # # CSS_PX is no string type # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: stringType CSS_PX is not a string type", # v.setStringValue, *(v.CSS_PX, 'brown')) # # def test_typeRGBColor(self): # "RGBColor" # v = css_parser.css.CSSPrimitiveValue('RGB(1, 5, 10)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # self.assertEqual(u'rgb(1, 5, 10)', v.cssText) # # v = css_parser.css.CSSPrimitiveValue('rgb(1, 5, 10)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # self.assertEqual(u'rgb(1, 5, 10)', v.cssText) # # v = css_parser.css.CSSPrimitiveValue('rgb(1%, 5%, 10%)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # self.assertEqual(u'rgb(1%, 5%, 10%)', v.cssText) # # v = css_parser.css.CSSPrimitiveValue(' rgb( 1 ,5, 10 )') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # v = css_parser.css.CSSPrimitiveValue('rgb(1,5,10)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # v = css_parser.css.CSSPrimitiveValue('rgb(1%, .5%, 10.1%)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # # def test_reprANDstr(self): # "CSSPrimitiveValue.__repr__(), .__str__()" # v='111' # # s = css_parser.css.CSSPrimitiveValue(v) # # self.assertTrue(v in str(s)) # self.assertTrue('CSS_NUMBER' in str(s)) # # s2 = eval(repr(s)) # self.assertTrue(isinstance(s2, s.__class__)) # self.assertTrue(v == s2.cssText) # # # class CSSValueListTestCase(basetest.BaseTestCase): # # def test_init(self): # "CSSValueList.__init__()" # v = css_parser.css.CSSValue(cssText=u'red blue') # self.assertTrue(v.CSS_VALUE_LIST == v.cssValueType) # self.assertEqual('red blue', v.cssText) # # self.assertTrue(2 == v.length) # # item = v.item(0) # item.setStringValue(item.CSS_IDENT, 'green') # self.assertEqual('green blue', v.cssText) # # def test_numbers(self): # "CSSValueList.cssText" # tests = { # u'0 0px -0px +0px': (u'0 0 0 0', 4), # u'1 2 3 4': (None, 4), # u'-1 -2 -3 -4': (None, 4), # u'-1 2': (None, 2), # u'-1px red "x"': (None, 3), # u'a, b c': (None, 2), # u'1px1 2% 3': (u'1px1 2% 3', 3), # u'f(+1pX, -2, 5%) 1': (u'f(1px, -2, 5%) 1', 2), # u'0 f()0': (u'0 f() 0', 3), # u'f()0': (u'f() 0', 2), # u'f()1%': (u'f() 1%', 2), # u'f()1px': (u'f() 1px', 2), # u'f()"str"': (u'f() "str"', 2), # u'f()ident': (u'f() ident', 2), # u'f()#123': (u'f() #123', 2), # u'f()url()': (u'f() url()', 2), # u'f()f()': (u'f() f()', 2), # u'url(x.gif)0 0': (u'url(x.gif) 0 0', 3), # u'url(x.gif)no-repeat': (u'url(x.gif) no-repeat', 2) # } # for test in tests: # exp, num = tests[test] # if not exp: # exp = test # v = css_parser.css.CSSValue(cssText=test) # self.assertTrue(v.CSS_VALUE_LIST == v.cssValueType) # self.assertEqual(num, v.length) # self.assertEqual(exp, v.cssText) # # def test_reprANDstr(self): # "CSSValueList.__repr__(), .__str__()" # v='1px 2px' # # s = css_parser.css.CSSValue(v) # self.assertTrue(isinstance(s, css_parser.css.CSSValueList)) # # self.assertTrue('length=2' in str(s)) # self.assertTrue(v in str(s)) # # # not "eval()"able! # #s2 = eval(repr(s)) # # # if __name__ == '__main__': # import unittest # unittest.main() from __future__ import unicode_literals././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssvariablesdeclaration.py0000644000175000017500000002545300000000000024513 0ustar00kovidkovid"""Testcases for css_parser.css.cssvariablesdelaration.CSSVariablesDeclaration.""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_cssstyledeclaration.py 1869 2009-10-17 19:37:40Z cthedot $' import xml.dom from . import basetest import css_parser class CSSVariablesDeclarationTestCase(basetest.BaseTestCase): def setUp(self): self.r = css_parser.css.CSSVariablesDeclaration() css_parser.ser.prefs.useDefaults() def tearDown(self): css_parser.ser.prefs.useDefaults() def test_init(self): "CSSVariablesDeclaration.__init__()" v = css_parser.css.CSSVariablesDeclaration() self.assertEqual('', v.cssText) self.assertEqual(0, v.length) self.assertEqual(None, v.parentRule) v = css_parser.css.CSSVariablesDeclaration(cssText='x: 0') self.assertEqual('x: 0', v.cssText) self.assertEqual('0', v.getVariableValue('x')) rule = css_parser.css.CSSVariablesRule() v = css_parser.css.CSSVariablesDeclaration(cssText='x: 0', parentRule=rule) self.assertEqual(rule, v.parentRule) def test__contains__(self): "CSSVariablesDeclaration.__contains__(name)" v = css_parser.css.CSSVariablesDeclaration(cssText='x: 0; y: 2') for test in ('x', 'y'): self.assertTrue(test in v) self.assertTrue(test.upper() in v) self.assertTrue('z' not in v) def test_items(self): "CSSVariablesDeclaration[variableName]" v = css_parser.css.CSSVariablesDeclaration() value = '0' v['X'] = value self.assertEqual(value, v['X']) self.assertEqual(value, v.getVariableValue('X')) self.assertEqual(value, v['x']) self.assertEqual(value, v.getVariableValue('x')) self.assertEqual('', v['y']) self.assertEqual('', v.getVariableValue('y')) v['z'] = '1' self.assertEqual(2, v.length) items = [] # unsorted! self.assertEqual(sorted(v), ['x', 'z']) del v['z'] self.assertEqual(1, v.length) self.assertEqual(1, v.length) self.assertEqual('0', v.removeVariable('x')) self.assertEqual('', v.removeVariable('z')) self.assertEqual(0, v.length) v.cssText = 'x:0; y:1' keys = [] # unsorted! for i in range(0, v.length): keys.append(v.item(i)) self.assertEqual(sorted(keys), ['x', 'y']) def test_keys(self): "CSSVariablesDeclaration.keys()" v = css_parser.css.CSSVariablesDeclaration(cssText='x: 0; Y: 2') self.assertEqual(['x', 'y'], sorted(v.keys())) def test_cssText(self): "CSSVariablesDeclaration.cssText" # empty tests = { '': '', ' ': '', ' \t \n ': '', 'x: 1': None, 'x: "a"': None, 'x: rgb(1, 2, 3)': None, 'x: 1px 2px 3px': None, 'x:1': 'x: 1', 'x:1;': 'x: 1', 'x : 1 ': 'x: 1', 'x : 1 ; ': 'x: 1', 'x:1;y:2': 'x: 1;\ny: 2', 'x:1;y:2;': 'x: 1;\ny: 2', 'x : 1 ; y : 2 ': 'x: 1;\ny: 2', 'x : 1 ; y : 2 ; ': 'x: 1;\ny: 2', '/*x*/': '/*x*/', 'x555: 5': None, 'xxx:1;yyy:2': 'xxx: 1;\nyyy: 2', 'xxx : 1; yyy : 2': 'xxx: 1;\nyyy: 2', 'x:1;x:2;X:2': 'x: 2', 'same:1;SAME:2;': 'same: 2', '/**/x/**/:/**/1/**/;/**/y/**/:/**/2/**/': '/**/\n /**/\n /**/\n x: 1 /**/;\n/**/\n /**/\n /**/\n y: 2 /**/' } self.do_equal_r(tests) # TODO: Fix? # def test_cssText2(self): # "CSSVariablesDeclaration.cssText" # # exception # tests = { # u'top': xml.dom.SyntaxErr, # u'top:': xml.dom.SyntaxErr, # u'top : ': xml.dom.SyntaxErr, # u'top:;': xml.dom.SyntaxErr, # u'top 0': xml.dom.SyntaxErr, # u'top 0;': xml.dom.SyntaxErr, # # u':': xml.dom.SyntaxErr, # u':0': xml.dom.SyntaxErr, # u':0;': xml.dom.SyntaxErr, # u':;': xml.dom.SyntaxErr, # u': ;': xml.dom.SyntaxErr, # # u'0': xml.dom.SyntaxErr, # u'0;': xml.dom.SyntaxErr, # # u';': xml.dom.SyntaxErr, # } # self.do_raise_r(tests) def test_xVariable(self): "CSSVariablesDeclaration.xVariable()" v = css_parser.css.CSSVariablesDeclaration() # unset self.assertEqual('', v.getVariableValue('x')) # set v.setVariable('x', '0') self.assertEqual('0', v.getVariableValue('x')) self.assertEqual('0', v.getVariableValue('X')) self.assertEqual('x: 0', v.cssText) v.setVariable('X', '0') self.assertEqual('0', v.getVariableValue('x')) self.assertEqual('0', v.getVariableValue('X')) self.assertEqual('x: 0', v.cssText) # remove self.assertEqual('0', v.removeVariable('x')) self.assertEqual('', v.removeVariable('x')) self.assertEqual('', v.getVariableValue('x')) self.assertEqual('', v.cssText) def test_imports(self): "CSSVariables imports" def fetcher(url): url = url.replace('\\', '/') url = url[url.rfind('/')+1:] return (None, { '3.css': ''' @variables { over3-2-1-0: 3; over3-2-1: 3; over3-2: 3; over3-2-0: 3; over3-1: 3; over3-1-0: 3; over3-0: 3; local3: 3; } ''', '2.css': ''' @variables { over3-2-1-0: 2; over3-2-1: 2; over3-2-0: 2; over3-2: 2; over2-1: 2; over2-1-0: 2; over2-0: 2; local2: 2; } ''', '1.css': ''' @import "3.css"; @import "2.css"; @variables { over3-2-1-0: 1; over3-2-1: 1; over3-1: 1; over3-1-0: 1; over2-1: 1; over2-1-0: 1; over1-0: 1; local1: 1; } ''' }[url]) css = ''' @import "1.css"; @variables { over3-2-1-0: 0; over3-2-0: 0; over3-1-0: 0; over2-1-0: 0; over3-0: 0; over2-0: 0; over1-0: 0; local0: 0; } a { local0: var(local0); local1: var(local1); local2: var(local2); local3: var(local3); over1-0: var(over1-0); over2-0: var(over2-0); over3-0: var(over3-0); over2-1: var(over2-1); over3-1: var(over3-1); over3-2: var(over3-2); over2-1-0: var(over2-1-0); over3-2-0: var(over3-2-0); over3-2-1: var(over3-2-1); over3-2-1-0: var(over3-2-1-0); } ''' p = css_parser.CSSParser(fetcher=fetcher) s = p.parseString(css) # only these in rule of this sheet self.assertEqual(s.cssRules[1].variables.length, 8) # but all vars in s available! self.assertEqual(s.variables.length, 15) self.assertEqual(['local0', 'local1', 'local2', 'local3', 'over1-0', 'over2-0', 'over2-1', 'over2-1-0', 'over3-0', 'over3-1', 'over3-1-0', 'over3-2', 'over3-2-0', 'over3-2-1', 'over3-2-1-0'], sorted(s.variables.keys())) # test with variables rule css_parser.ser.prefs.resolveVariables = False self.assertEqual(s.cssText, '''@import "1.css"; @variables { over3-2-1-0: 0; over3-2-0: 0; over3-1-0: 0; over2-1-0: 0; over3-0: 0; over2-0: 0; over1-0: 0; local0: 0 } a { local0: var(local0); local1: var(local1); local2: var(local2); local3: var(local3); over1-0: var(over1-0); over2-0: var(over2-0); over3-0: var(over3-0); over2-1: var(over2-1); over3-1: var(over3-1); over3-2: var(over3-2); over2-1-0: var(over2-1-0); over3-2-0: var(over3-2-0); over3-2-1: var(over3-2-1); over3-2-1-0: var(over3-2-1-0) }'''.encode()) # test with resolved vars css_parser.ser.prefs.resolveVariables = True self.assertEqual(s.cssText, '''@import "1.css"; a { local0: 0; local1: 1; local2: 2; local3: 3; over1-0: 0; over2-0: 0; over3-0: 0; over2-1: 1; over3-1: 1; over3-2: 2; over2-1-0: 0; over3-2-0: 0; over3-2-1: 1; over3-2-1-0: 0 }'''.encode()) s = css_parser.resolveImports(s) self.assertEqual(s.cssText, '''/* START @import "1.css" */ /* START @import "3.css" */ /* START @import "2.css" */ a { local0: 0; local1: 1; local2: 2; local3: 3; over1-0: 0; over2-0: 0; over3-0: 0; over2-1: 1; over3-1: 1; over3-2: 2; over2-1-0: 0; over3-2-0: 0; over3-2-1: 1; over3-2-1-0: 0 }'''.encode()) def test_parentRule(self): "CSSVariablesDeclaration.parentRule" s = css_parser.parseString('@variables { a:1}') r = s.cssRules[0] d = r.variables self.assertEqual(r, d.parentRule) d2 = css_parser.css.CSSVariablesDeclaration('b: 2') r.variables = d2 self.assertEqual(r, d2.parentRule) def test_reprANDstr(self): "CSSVariablesDeclaration.__repr__(), .__str__()" s = css_parser.css.CSSVariablesDeclaration(cssText='a:1;b:2') self.assertTrue("2" in str(s)) # length s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_cssvariablesrule.py0000644000175000017500000001270000000000000023164 0ustar00kovidkovid"""Testcases for css_parser.css.CSSPageRule""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_csspagerule.py 1869 2009-10-17 19:37:40Z cthedot $' import xml.dom from . import test_cssrule import css_parser class CSSVariablesRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(CSSVariablesRuleTestCase, self).setUp() self.r = css_parser.css.CSSVariablesRule() self.rRO = css_parser.css.CSSVariablesRule(readonly=True) self.r_type = css_parser.css.CSSPageRule.VARIABLES_RULE self.r_typeString = 'VARIABLES_RULE' css_parser.ser.prefs.resolveVariables = False def test_init(self): "CSSVariablesRule.__init__()" super(CSSVariablesRuleTestCase, self).test_init() r = css_parser.css.CSSVariablesRule() self.assertEqual(css_parser.css.CSSVariablesDeclaration, type(r.variables)) self.assertEqual(r, r.variables.parentRule) # until any variables self.assertEqual('', r.cssText) # only possible to set @... similar name self.assertRaises(xml.dom.InvalidModificationErr, self.r._setAtkeyword, 'x') def test_InvalidModificationErr(self): "CSSVariablesRule.cssText InvalidModificationErr" self._test_InvalidModificationErr('@variables') tests = { '@var {}': xml.dom.InvalidModificationErr, } self.do_raise_r(tests) def test_incomplete(self): "CSSVariablesRule (incomplete)" tests = { '@variables { ': '', # no } and no content '@variables { x: red': '@variables {\n x: red\n }', # no } } self.do_equal_p(tests) # parse def test_cssText(self): "CSSVariablesRule" EXP = '@variables {\n margin: 0\n }' tests = { '@variables {}': '', '@variables {margin:0;}': EXP, '@variables {margin:0}': EXP, '@VaRIables { margin : 0 ; }': EXP, '@\\VaRIables { margin : 0 }': EXP, '@variables {a:1;b:2}': '@variables {\n a: 1;\n b: 2\n }', # comments '@variables /*1*/ {margin:0;}': '@variables /*1*/ {\n margin: 0\n }', '@variables/*1*/{margin:0;}': '@variables /*1*/ {\n margin: 0\n }', } self.do_equal_r(tests) self.do_equal_p(tests) def test_media(self): "CSSVariablesRule.media" r = css_parser.css.CSSVariablesRule() self.assertRaises(AttributeError, r.__getattribute__, 'media') self.assertRaises(AttributeError, r.__setattr__, 'media', '?') def test_variables(self): "CSSVariablesRule.variables" r = css_parser.css.CSSVariablesRule( variables=css_parser.css.CSSVariablesDeclaration('x: 1')) self.assertEqual(r, r.variables.parentRule) # cssText r = css_parser.css.CSSVariablesRule() r.cssText = '@variables { x: 1 }' vars1 = r.variables self.assertEqual(r, r.variables.parentRule) self.assertEqual(vars1, r.variables) self.assertEqual(r.variables.cssText, 'x: 1') self.assertEqual(r.cssText, '@variables {\n x: 1\n }') r.cssText = '@variables {y:2}' self.assertEqual(r, r.variables.parentRule) self.assertNotEqual(vars1, r.variables) self.assertEqual(r.variables.cssText, 'y: 2') self.assertEqual(r.cssText, '@variables {\n y: 2\n }') vars2 = r.variables # fail try: r.cssText = '@variables {$:1}' except xml.dom.DOMException as e: pass self.assertEqual(vars2, r.variables) self.assertEqual(r.variables.cssText, 'y: 2') self.assertEqual(r.cssText, '@variables {\n y: 2\n }') # var decl vars3 = css_parser.css.CSSVariablesDeclaration('z: 3') r.variables = vars3 self.assertEqual(r, r.variables.parentRule) self.assertEqual(vars3, r.variables) self.assertEqual(r.variables.cssText, 'z: 3') self.assertEqual(r.cssText, '@variables {\n z: 3\n }') # string r.variables = 'a: x' self.assertNotEqual(vars3, r.variables) self.assertEqual(r, r.variables.parentRule) self.assertEqual(r.variables.cssText, 'a: x') self.assertEqual(r.cssText, '@variables {\n a: x\n }') vars4 = r.variables # string fail try: r.variables = '$: x' except xml.dom.DOMException as e: pass self.assertEqual(vars4, r.variables) self.assertEqual(r, r.variables.parentRule) self.assertEqual(r.variables.cssText, 'a: x') self.assertEqual(r.cssText, '@variables {\n a: x\n }') def test_reprANDstr(self): "CSSVariablesRule.__repr__(), .__str__()" r = css_parser.css.CSSVariablesRule() r.cssText = '@variables { xxx: 1 }' self.assertTrue('xxx' in str(r)) r2 = eval(repr(r)) self.assertTrue(isinstance(r2, r.__class__)) self.assertTrue(r.cssText == r2.cssText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_domimplementation.py0000644000175000017500000000313600000000000023343 0ustar00kovidkovid"""Testcases for css_parser.css.DOMImplementation""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom import xml.dom.minidom import unittest import css_parser class DOMImplementationTestCase(unittest.TestCase): def setUp(self): self.domimpl = css_parser.DOMImplementationCSS() def test_createCSSStyleSheet(self): "DOMImplementationCSS.createCSSStyleSheet()" title, media = 'Test Title', css_parser.stylesheets.MediaList('all') sheet = self.domimpl.createCSSStyleSheet(title, media) self.assertEqual(True, isinstance(sheet, css_parser.css.CSSStyleSheet)) self.assertEqual(title, sheet.title) self.assertEqual(media, sheet.media) def test_createDocument(self): "DOMImplementationCSS.createDocument()" doc = self.domimpl.createDocument(None, None, None) self.assertTrue(isinstance(doc, xml.dom.minidom.Document)) def test_createDocumentType(self): "DOMImplementationCSS.createDocumentType()" doctype = self.domimpl.createDocumentType('foo', 'bar', 'raboof') self.assertTrue(isinstance(doctype, xml.dom.minidom.DocumentType)) def test_hasFeature(self): "DOMImplementationCSS.hasFeature()" tests = [ ('css', '1.0'), ('css', '2.0'), ('stylesheets', '1.0'), ('stylesheets', '2.0') ] for name, version in tests: self.assertEqual(True, self.domimpl.hasFeature(name, version)) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_encutils.py0000644000175000017500000004141200000000000021443 0ustar00kovidkovid# -*- coding: utf-8 -*- """ tests for encutils.py """ from __future__ import absolute_import, unicode_literals import sys import unittest from io import StringIO import css_parser.encutils as encutils PY2x = sys.version_info < (3, 0) if PY2x: from httplib import HTTPMessage else: from http.client import HTTPMessage # helper log log = encutils.buildlog(stream=StringIO()) class AutoEncodingTestCase(unittest.TestCase): def _fakeRes(self, content): "build a fake HTTP response" class FakeRes: def __init__(self, content): if PY2x: fp = StringIO(content) self._info = HTTPMessage(fp) else: self._info = HTTPMessage() # Adjust to testdata. la = content.split(':') if len(la) > 1: # Get the type by just # using the data at the end. t = la[-1].strip() self._info.set_type(t) def info(self): return self._info def read(self): return content return FakeRes(content) def test_getTextTypeByMediaType(self): "encutils._getTextTypeByMediaType" tests = { 'application/xml': encutils._XML_APPLICATION_TYPE, 'application/xml-dtd': encutils._XML_APPLICATION_TYPE, 'application/xml-external-parsed-entity': encutils._XML_APPLICATION_TYPE, 'application/xhtml+xml': encutils._XML_APPLICATION_TYPE, 'text/xml': encutils._XML_TEXT_TYPE, 'text/xml-external-parsed-entity': encutils._XML_TEXT_TYPE, 'text/xhtml+xml': encutils._XML_TEXT_TYPE, 'text/html': encutils._HTML_TEXT_TYPE, 'text/css': encutils._TEXT_UTF8, 'text/plain': encutils._TEXT_TYPE, 'x/x': encutils._OTHER_TYPE, 'ANYTHING': encutils._OTHER_TYPE } for test, exp in tests.items(): self.assertEqual( exp, encutils._getTextTypeByMediaType(test, log=log)) def test_getTextType(self): "encutils._getTextType" tests = { '\x00\x00\xFE\xFF""": (None, None), """""": (None, None), """""": ('text/html', None), """""": ('text/html', None), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'iso-8859-1'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """""": ('text/html', 'ascii'), """raises exception: """: (None, None), """ """: ('text/html', 'ascii'), """ """: ('text/html', 'ascii'), # py 2.7.3 fixed HTMLParser so: (None, None) """ """: ('text/html', None) } for test, exp in tests.items(): self.assertEqual(exp, encutils.getMetaInfo(test, log=log)) def test_detectXMLEncoding(self): "encutils.detectXMLEncoding" tests = ( # BOM (('utf_32_be'), '\x00\x00\xFE\xFFanything'), (('utf_32_le'), '\xFF\xFE\x00\x00anything'), (('utf_16_be'), '\xFE\xFFanything'), (('utf_16_le'), '\xFF\xFEanything'), (('utf-8'), '\xef\xbb\xbfanything'), # encoding= (('ascii'), ''), (('ascii'), ""), (('iso-8859-1'), ""), # default (('utf-8'), ''), (('utf-8'), '') ) for exp, test in tests: self.assertEqual(exp, encutils.detectXMLEncoding(test, log=log)) def test_tryEncodings(self): "encutils.tryEncodings" try: tests = [ ('ascii', 'abc'.encode('ascii')), ('windows-1252', '€'.encode('windows-1252')), ('ascii', '1'.encode('utf-8')) ] except ImportError: tests = [ ('ascii', 'abc'.encode('ascii')), ('windows-1252', '€'.encode('windows-1252')), ('iso-8859-1', 'äöüß'.encode('iso-8859-1')), ('iso-8859-1', 'äöüß'.encode('windows-1252')), # ('utf-8', u'\u1111'.encode('utf-8')) ] for exp, test in tests: self.assertEqual(exp.lower(), encutils.tryEncodings(test).lower()) def test_getEncodingInfo(self): "encutils.getEncodingInfo" # (expectedencoding, expectedmismatch): (httpheader, filecontent) tests = [ # --- application/xhtml+xml --- # header default and XML default (('utf-8', False), ( '''Content-Type: application/xhtml+xml''', ''' ''')), # XML default (('utf-8', False), ( None, ''' ''')), # meta is ignored! (('utf-8', False), ( '''Content-Type: application/xhtml+xml''', ''' ''')), # header enc and XML default (('iso-h', True), ( '''Content-Type: application/xhtml+xml;charset=iso-H''', ''' ''')), # mismatch header and XML explicit, header wins (('iso-h', True), ( '''Content-Type: application/xhtml+xml;charset=iso-H''', ''' ''')), # header == XML, meta ignored! (('iso-h', False), ( '''Content-Type: application/xhtml+xml;charset=iso-H''', ''' ''')), # XML only, meta ignored! (('iso-x', False), ( '''Content-Type: application/xhtml+xml''', ''' ''')), # no text or not enough text: (('iso-h', False), ('Content-Type: application/xml;charset=iso-h', '1')), (('utf-8', False), ('Content-Type: application/xml', None)), ((None, False), ('Content-Type: application/xml', '1')), # --- text/xml --- # default enc (('ascii', False), ( '''Content-Type: text/xml''', ''' ''')), # default as XML ignored and meta completely ignored (('ascii', False), ( '''Content-Type: text/xml''', ''' ''')), (('ascii', False), ('Content-Type: text/xml', '1')), (('ascii', False), ('Content-Type: text/xml', None)), # header enc (('iso-h', False), ( '''Content-Type: text/xml;charset=iso-H''', ''' ''')), # header only, XML and meta ignored! (('iso-h', False), ( '''Content-Type: text/xml;charset=iso-H''', ''' ''')), (('iso-h', False), ( '''Content-Type: text/xml;charset=iso-H''', ''' ''')), # --- text/html --- # default enc (('iso-8859-1', False), ('Content-Type: text/html;', '''''')), (('iso-8859-1', False), ('Content-Type: text/html;', None)), # header enc (('iso-h', False), ('Content-Type: text/html;charset=iso-H', '''''')), # meta enc (('iso-m', False), ('Content-Type: text/html', '''''')), # mismatch header and meta, header wins (('iso-h', True), ('Content-Type: text/html;charset=iso-H', '''''')), # no header: ((None, False), (None, '''''')), # no encoding at all ((None, False), (None, '''''')), ((None, False), (None, '''text''')), # --- no header --- ((None, False), (None, '')), (('iso-8859-1', False), ('''NoContentType''', '''OnlyText''')), (('iso-8859-1', False), ('Content-Type: text/html;', None)), (('iso-8859-1', False), ('Content-Type: text/html;', '1')), # XML (('utf-8', False), (None, '''''')), # meta ignored (('utf-8', False), (None, ''' ''')), (('utf-8', False), ('Content-Type: text/css;', '1')), (('iso-h', False), ('Content-Type: text/css;charset=iso-h', '1')), # only header is used by encutils (('utf-8', False), ('Content-Type: text/css', '@charset "ascii";')), ] for exp, test in tests: header, text = test if header: res = encutils.getEncodingInfo(self._fakeRes(header), text) else: res = encutils.getEncodingInfo(text=text) res = (res.encoding, res.mismatch) self.assertEqual(exp, res) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1560264006.0 css-parser-1.0.7/css_parser_tests/test_errorhandler.py0000644000175000017500000001113700000000000022305 0ustar00kovidkovid"""Tests for parsing which does not raise Exceptions normally""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_parse.py 1281 2008-06-04 21:12:29Z cthedot $' import logging import sys import xml.dom from . import basetest import css_parser class ErrorHandlerTestCase(basetest.BaseTestCase): def setUp(self): "replace default log and ignore its output" self._oldlog = css_parser.log._log self._saved = css_parser.log.raiseExceptions css_parser.log.raiseExceptions = False css_parser.log.setLog(logging.getLogger('IGNORED-CSSUTILS-TEST')) def tearDown(self): "reset default log" css_parser.log.setLog(self._oldlog) # for tests only css_parser.log.setLevel(logging.FATAL) css_parser.log.raiseExceptions = self._saved def test_calls(self): "css_parser.log.*" s = self._setHandler() css_parser.log.setLevel(logging.DEBUG) css_parser.log.debug('msg', neverraise=True) self.assertEqual(s.getvalue(), 'DEBUG msg\n') s = self._setHandler() css_parser.log.setLevel(logging.INFO) css_parser.log.info('msg', neverraise=True) self.assertEqual(s.getvalue(), 'INFO msg\n') s = self._setHandler() css_parser.log.setLevel(logging.WARNING) css_parser.log.warn('msg', neverraise=True) self.assertEqual(s.getvalue(), 'WARNING msg\n') s = self._setHandler() css_parser.log.setLevel(logging.ERROR) css_parser.log.error('msg', neverraise=True) self.assertEqual(s.getvalue(), 'ERROR msg\n') s = self._setHandler() css_parser.log.setLevel(logging.FATAL) css_parser.log.fatal('msg', neverraise=True) self.assertEqual(s.getvalue(), 'CRITICAL msg\n') s = self._setHandler() css_parser.log.setLevel(logging.CRITICAL) css_parser.log.critical('msg', neverraise=True) self.assertEqual(s.getvalue(), 'CRITICAL msg\n') s = self._setHandler() css_parser.log.setLevel(logging.CRITICAL) css_parser.log.error('msg', neverraise=True) self.assertEqual(s.getvalue(), '') def test_linecol(self): "css_parser.log line col" o = css_parser.log.raiseExceptions css_parser.log.raiseExceptions = True s = css_parser.css.CSSStyleSheet() try: s.cssText = '@import x;' except xml.dom.DOMException as e: self.assertEqual(str(e), 'CSSImportRule: Unexpected ident. [1:9: x]') self.assertEqual(e.line, 1) self.assertEqual(e.col, 9) if sys.platform.startswith('java'): self.assertEqual(e.msg, 'CSSImportRule: Unexpected ident. [1:9: x]') else: self.assertEqual(e.args, ('CSSImportRule: Unexpected ident. [1:9: x]',)) css_parser.log.raiseExceptions = o def test_handlers(self): "css_parser.log" s = self._setHandler() css_parser.log.setLevel(logging.FATAL) self.assertEqual(css_parser.log.getEffectiveLevel(), logging.FATAL) css_parser.parseString('a { color: 1 }') self.assertEqual(s.getvalue(), '') css_parser.log.setLevel(logging.DEBUG) css_parser.parseString('a { color: 1 }') self.assertEqual(s.getvalue(), 'ERROR Property: Invalid value for "CSS Level 2.1" property: 1 [1:5: color]\n') s = self._setHandler() css_parser.log.setLevel(logging.WARNING) css_parser.parseUrl('http://example.com') q = s.getvalue()[:38] if q.startswith('WARNING URLError'): pass else: self.assertEqual(q, 'ERROR Expected "text/css" mime type') def test_parsevalidation(self): style = 'color: 1' t = 'a { %s }' % style css_parser.log.setLevel(logging.DEBUG) # sheet s = self._setHandler() css_parser.parseString(t) self.assertNotEqual(len(s.getvalue()), 0) s = self._setHandler() css_parser.parseString(t, validate=False) self.assertEqual(s.getvalue(), '') # style s = self._setHandler() css_parser.parseStyle(style) self.assertNotEqual(len(s.getvalue()), 0) s = self._setHandler() css_parser.parseStyle(style, validate=True) self.assertNotEqual(len(s.getvalue()), 0) s = self._setHandler() css_parser.parseStyle(style, validate=False) self.assertEqual(s.getvalue(), '') if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_helper.py0000644000175000017500000000620400000000000021074 0ustar00kovidkovid# -*- coding: utf-8 -*- """Testcases for css_parser.helper""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_util.py 1437 2008-08-18 20:30:38Z cthedot $' from . import basetest from css_parser.helper import * class HelperTestCase(basetest.BaseTestCase): def test_normalize(self): "helper._normalize()" tests = {'abcdefg ABCDEFG äöü߀ AÖÜ': r'abcdefg abcdefg äöü߀ aöü', r'\ga\Ga\\\ ': r'gaga\ ', r'0123456789': r'0123456789', r'"\x"': r'"x"', # unicode escape seqs should have been done by # the tokenizer... } for test, exp in tests.items(): self.assertEqual(normalize(test), exp) # static too self.assertEqual(normalize(test), exp) # def test_normalnumber(self): # "helper.normalnumber()" # tests = { # '0': '0', # '00': '0', # '0.0': '0', # '00.0': '0', # '1': '1', # '01': '1', # '00.1': '0.1', # '0.00001': '0.00001', # '-0': '0', # '-00': '0', # '-0.0': '0', # '-00.0': '0', # '-1': '-1', # '-01': '-1', # '-00.1': '-0.1', # '-0.00001': '-0.00001', # } # for test, exp in tests.items(): # self.assertEqual(exp, normalnumber(test)) def test_string(self): "helper.string()" self.assertEqual('"x"', string('x')) self.assertEqual('"1 2ä€"', string('1 2ä€')) self.assertEqual(r'''"'"''', string("'")) self.assertEqual(r'"\""', string('"')) # \n = 0xa, \r = 0xd, \f = 0xc self.assertEqual(r'"\a "', string(''' ''')) self.assertEqual(r'"\c "', string('\f')) self.assertEqual(r'"\d "', string('\r')) self.assertEqual(r'"\d \a "', string('\r\n')) def test_stringvalue(self): "helper.stringvalue()" self.assertEqual('x', stringvalue('"x"')) self.assertEqual('"', stringvalue('"\\""')) self.assertEqual(r'x', stringvalue(r"\x ")) # escapes should have been done by tokenizer # so this shoule not happen at all: self.assertEqual(r'a', stringvalue(r"\a ")) def test_uri(self): "helper.uri()" self.assertEqual('url(x)', uri('x')) self.assertEqual('url("(")', uri('(')) self.assertEqual('url(")")', uri(')')) self.assertEqual('url(" ")', uri(' ')) self.assertEqual('url(";")', uri(';')) self.assertEqual('url(",")', uri(',')) self.assertEqual('url("x)x")', uri('x)x')) def test_urivalue(self): "helper.urivalue()" self.assertEqual('x', urivalue('url(x)')) self.assertEqual('x', urivalue('url("x")')) self.assertEqual(')', urivalue('url(")")')) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_marginrule.py0000644000175000017500000000656100000000000021770 0ustar00kovidkovid"""Testcases for css_parser.css.CSSPageRule""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import test_cssrule import css_parser class MarginRuleTestCase(test_cssrule.CSSRuleTestCase): def setUp(self): super(MarginRuleTestCase, self).setUp() css_parser.ser.prefs.useDefaults() self.r = css_parser.css.MarginRule() self.rRO = css_parser.css.MarginRule(readonly=True) self.r_type = css_parser.css.MarginRule.MARGIN_RULE self.r_typeString = 'MARGIN_RULE' def tearDown(self): css_parser.ser.prefs.useDefaults() def test_init(self): "MarginRule.__init__()" r = css_parser.css.MarginRule() self.assertEqual(r.margin, None) self.assertEqual(r.atkeyword, None) self.assertEqual(r._keyword, None) self.assertEqual(r.style.cssText, '') self.assertEqual(r.cssText, '') r = css_parser.css.MarginRule(margin='@TOP-left') self.assertEqual(r.margin, '@top-left') self.assertEqual(r.atkeyword, '@top-left') self.assertEqual(r._keyword, '@TOP-left') self.assertEqual(r.style.cssText, '') self.assertEqual(r.cssText, '') self.assertRaises(xml.dom.InvalidModificationErr, css_parser.css.MarginRule, '@x') def test_InvalidModificationErr(self): "MarginRule.cssText InvalidModificationErr" # TODO: !!! # self._test_InvalidModificationErr(u'@top-left') # tests = { # u'@x {}': xml.dom.InvalidModificationErr, # } # self.do_raise_r(tests) def test_incomplete(self): "MarginRule (incomplete)" # must be inside @page as not valid outside tests = { '@page { @top-left { ': '', # no } and no content '@page { @top-left { /*1*/ ': '', # no } and no content '@page { @top-left { color: red': '@page {\n @top-left {\n color: red\n }\n }' } self.do_equal_p(tests) # parse def test_cssText(self): tests = { '@top-left {}': '', '@top-left { /**/ }': '', '@top-left { color: red }': '@top-left {\n color: red\n }', '@top-left{color:red;}': '@top-left {\n color: red\n }', '@top-left{color:red}': '@top-left {\n color: red\n }', '@top-left { color: red; left: 0 }': '@top-left {\n color: red;\n left: 0\n }' } self.do_equal_r(tests) # TODO tests.update({ # false selector # u'@top-left { color:': xml.dom.SyntaxErr, # no } # u'@top-left { color': xml.dom.SyntaxErr, # no } # u'@top-left {': xml.dom.SyntaxErr, # no } # u'@top-left': xml.dom.SyntaxErr, # no } # u'@top-left;': xml.dom.SyntaxErr, # no } }) # self.do_raise_r(tests) # set cssText def test_reprANDstr(self): "MarginRule.__repr__(), .__str__()" margin = '@top-left' s = css_parser.css.MarginRule(margin=margin, style='left: 0') self.assertTrue(margin in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(margin == s2.margin) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_medialist.py0000644000175000017500000001635200000000000021575 0ustar00kovidkovid# -*- coding: iso-8859-1 -*- """Testcases for css_parser.stylesheets.MediaList""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom import logging from . import basetest import css_parser.stylesheets class MediaListTestCase(basetest.BaseTestCase): def setUp(self): super(MediaListTestCase, self).setUp() self.r = css_parser.stylesheets.MediaList() def test_set(self): "MediaList.mediaText 1" ml = css_parser.stylesheets.MediaList() self.assertEqual(0, ml.length) self.assertEqual('all', ml.mediaText) ml.mediaText = ' print , screen ' self.assertEqual(2, ml.length) self.assertEqual('print, screen', ml.mediaText) # self.assertRaisesMsg(xml.dom.InvalidModificationErr, # basetest.msg3x('''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'tv') as already specified "all" (set ``mediaText`` instead).'''), # ml._setMediaText, u' print , all , tv ') # #self.assertEqual(u'all', ml.mediaText) #self.assertEqual(1, ml.length) self.assertEqual(self.captureLog(logging.WARNING, ml.appendMedium, 'test'), 'WARNING MediaQuery: Unknown media type: "test".\n') def test_appendMedium(self): "MediaList.appendMedium() 1" ml = css_parser.stylesheets.MediaList() ml.appendMedium('print') self.assertEqual(1, ml.length) self.assertEqual('print', ml.mediaText) ml.appendMedium('screen') self.assertEqual(2, ml.length) self.assertEqual('print, screen', ml.mediaText) # automatic del and append! ml.appendMedium('print') self.assertEqual(2, ml.length) self.assertEqual('screen, print', ml.mediaText) # automatic del and append! ml.appendMedium('SCREEN') self.assertEqual(2, ml.length) self.assertEqual('print, SCREEN', ml.mediaText) # append invalid MediaQuery mq = css_parser.stylesheets.MediaQuery() ml.appendMedium(mq) self.assertEqual(2, ml.length) self.assertEqual('print, SCREEN', ml.mediaText) # append() mq = css_parser.stylesheets.MediaQuery('tv') ml.append(mq) self.assertEqual(3, ml.length) self.assertEqual('print, SCREEN, tv', ml.mediaText) # __setitem__ self.assertRaises(IndexError, ml.__setitem__, 10, 'all') ml[0] = 'handheld' self.assertEqual(3, ml.length) self.assertEqual('handheld, SCREEN, tv', ml.mediaText) def test_appendAll(self): "MediaList.append() 2" ml = css_parser.stylesheets.MediaList() ml.appendMedium('print') ml.appendMedium('tv') self.assertEqual(2, ml.length) self.assertEqual('print, tv', ml.mediaText) ml.appendMedium('all') self.assertEqual(1, ml.length) self.assertEqual('all', ml.mediaText) self.assertRaisesMsg(xml.dom.InvalidModificationErr, basetest.msg3x( '''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'tv') as already specified "all" (set ``mediaText`` instead).'''), ml.appendMedium, 'tv') self.assertEqual(1, ml.length) self.assertEqual('all', ml.mediaText) self.assertRaises(xml.dom.InvalidModificationErr, ml.appendMedium, 'test') def test_append2All(self): "MediaList all" ml = css_parser.stylesheets.MediaList() ml.appendMedium('all') self.assertRaisesMsg(xml.dom.InvalidModificationErr, basetest.msg3x( '''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'print') as already specified "all" (set ``mediaText`` instead).'''), ml.appendMedium, 'print') sheet = css_parser.parseString('@media all, print { /**/ }') self.assertEqual('@media all {\n /**/\n }'.encode(), sheet.cssText) def test_delete(self): "MediaList.deleteMedium()" ml = css_parser.stylesheets.MediaList() self.assertRaises(xml.dom.NotFoundErr, ml.deleteMedium, 'all') self.assertRaises(xml.dom.NotFoundErr, ml.deleteMedium, 'test') ml.appendMedium('print') ml.deleteMedium('print') ml.appendMedium('tV') ml.deleteMedium('Tv') self.assertEqual(0, ml.length) self.assertEqual('all', ml.mediaText) def test_item(self): "MediaList.item()" ml = css_parser.stylesheets.MediaList() ml.appendMedium('print') ml.appendMedium('screen') self.assertEqual('print', ml.item(0)) self.assertEqual('screen', ml.item(1)) self.assertEqual(None, ml.item(2)) # REMOVED special case! # def test_handheld(self): # "MediaList handheld" # ml = css_parser.stylesheets.MediaList() # ml.mediaText = u' handheld , all ' # self.assertEqual(2, ml.length) # self.assertEqual(u'handheld, all', ml.mediaText) # self.assertRaisesMsg(xml.dom.InvalidModificationErr, # basetest.msg3x('''MediaList: Ignoring new medium css_parser.stylesheets.MediaQuery(mediaText=u'handheld') as already specified "all" (set ``mediaText`` instead).'''), # ml._setMediaText, u' handheld , all , tv ') def test_mediaText(self): "MediaList.mediaText 2" tests = { 'ALL': 'ALL', 'Tv': 'Tv', 'all': None, 'all, handheld': 'all', 'tv': None, 'tv, handheld, print': None, 'tv and (color), handheld and (width: 1px) and (color)': None, 'print, amzn-mobi': None } self.do_equal_r(tests, att='mediaText') tests = { '': xml.dom.SyntaxErr, 'a,b': xml.dom.SyntaxErr, 'not': xml.dom.SyntaxErr, # known but need media 'only': xml.dom.SyntaxErr, # known but need media 'not tv,': xml.dom.SyntaxErr, # known but need media 'all;': xml.dom.SyntaxErr, 'all, and(color)': xml.dom.SyntaxErr, 'all,': xml.dom.SyntaxErr, 'all, ': xml.dom.SyntaxErr, 'all ,': xml.dom.SyntaxErr, 'all, /*1*/': xml.dom.SyntaxErr, 'all and (color),': xml.dom.SyntaxErr, 'all tv, print': xml.dom.SyntaxErr, } self.do_raise_r(tests, att='_setMediaText') def test_comments(self): "MediaList.mediaText comments" tests = { '/*1*/ tv /*2*/, /*3*/ handheld /*4*/, print': '/*1*/ tv /*2*/ /*3*/, handheld /*4*/, print', } self.do_equal_r(tests, att='mediaText') def test_reprANDstr(self): "MediaList.__repr__(), .__str__()" mediaText = 'tv, print' s = css_parser.stylesheets.MediaList(mediaText=mediaText) self.assertTrue(mediaText in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(mediaText == s2.mediaText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_mediaquery.py0000644000175000017500000000725100000000000021765 0ustar00kovidkovid# -*- coding: iso-8859-1 -*- """Testcases for css_parser.stylesheets.MediaQuery""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom import logging from . import basetest import css_parser.stylesheets class MediaQueryTestCase(basetest.BaseTestCase): def setUp(self): super(MediaQueryTestCase, self).setUp() self.r = css_parser.stylesheets.MediaQuery() def test_mediaText(self): "MediaQuery.mediaText" tests = { 'all': None, 'braille': None, 'embossed': None, 'handheld': None, 'print': None, 'projection': None, 'screen': None, 'speech': None, 'tty': None, 'tv': None, 'ALL': None, 'a\\ll': None, 'not tv': None, 'n\\ot t\\v': None, 'only tv': None, '\\only \\tv': None, 'PRINT': None, 'NOT PRINT': None, 'ONLY PRINT': None, 'tv and (color)': None, 'not tv and (color)': None, 'only tv and (color)': None, 'print and(color)': 'print and (color)', 'aural': None # xml.dom.SyntaxErr in cssutils } self.do_equal_r(tests, att='mediaText') tests = { '': xml.dom.SyntaxErr, 'two values': xml.dom.SyntaxErr, 'or even three': xml.dom.SyntaxErr, '3d': xml.dom.SyntaxErr, # a dimension } self.do_raise_r(tests, att='_setMediaText') def test_mediaType(self): "MediaQuery.mediaType" mq = css_parser.stylesheets.MediaQuery() self.assertEqual('', mq.mediaText) for mt in css_parser.stylesheets.MediaQuery.MEDIA_TYPES: mq.mediaType = mt self.assertEqual(mq.mediaType, mt) mq.mediaType = mt.upper() self.assertEqual(mq.mediaType, mt.upper()) mt = '3D-UNKOwn-MEDIAtype0123' #mq.mediaType = mt self.assertEqual(self.captureLog(logging.WARNING, mq._setMediaType, mt), 'WARNING MediaQuery: Unknown media type: "3D-UNKOwn-MEDIAtype0123".\n') #self.assertRaises(xml.dom.InvalidCharacterErr, mq._setMediaType, mt) def test_comments(self): "MediaQuery.mediaText comments" tests = { 'all': None, 'print': None, 'not print': None, 'only print': None, 'print and (color)': None, 'print and (color) and (width)': None, 'print and (color: 2)': None, 'print and (min-width: 100px)': None, 'print and (min-width: 100px) and (color: red)': None, 'not print and (min-width: 100px)': None, 'only print and (min-width: 100px)': None, '/*1*/ tv /*2*/': None, '/*0*/ only /*1*/ tv /*2*/': None, '/*0* /not /*1*/ tv /*2*/': None, '/*x*/ only /*x*/ print /*x*/ and /*x*/ (/*x*/ min-width /*x*/: /*x*/ 100px /*x*/)': None, 'print and/*1*/(color)': 'print and /*1*/ (color)' } self.do_equal_r(tests, att='mediaText') def test_reprANDstr(self): "MediaQuery.__repr__(), .__str__()" mediaText = 'tv and (color)' s = css_parser.stylesheets.MediaQuery(mediaText=mediaText) self.assertTrue(mediaText in str(s)) s2 = eval(repr(s)) self.assertEqual(mediaText, s2.mediaText) self.assertTrue(isinstance(s2, s.__class__)) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578457084.0 css-parser-1.0.7/css_parser_tests/test_parse.py0000644000175000017500000004712600000000000020737 0ustar00kovidkovid# -*- coding: utf-8 -*- """Tests for parsing which does not raise Exceptions normally""" from __future__ import absolute_import, unicode_literals, with_statement import sys import xml.dom import css_parser from . import basetest if sys.version_info.major > 2: from urllib.error import HTTPError, URLError FetchError = OSError else: from socket import error as FetchError from urllib2 import HTTPError, URLError class CSSParserTestCase(basetest.BaseTestCase): def _make_fetcher(self, encoding, content): "make an URL fetcher with specified data" def fetcher(url): return encoding, content return fetcher def setUp(self): basetest.BaseTestCase.setUp(self) self._saved = css_parser.log.raiseExceptions def tearDown(self): basetest.BaseTestCase.tearDown(self) css_parser.log.raiseExceptions = self._saved def test_init(self): "CSSParser.__init__()" self.assertEqual(True, css_parser.log.raiseExceptions) # also the default: css_parser.log.raiseExceptions = True # default non raising parser p = css_parser.CSSParser() s = p.parseString('$') self.assertEqual(s.cssText, ''.encode()) # explicit raiseExceptions=False p = css_parser.CSSParser(raiseExceptions=False) s = p.parseString('$') self.assertEqual(s.cssText, ''.encode()) # working with sheet does raise though! self.assertRaises(xml.dom.DOMException, s.__setattr__, 'cssText', '$') # ---- # raiseExceptions=True p = css_parser.CSSParser(raiseExceptions=True) self.assertRaises(xml.dom.SyntaxErr, p.parseString, '$') # working with a sheet does raise too s = css_parser.css.CSSStyleSheet() self.assertRaises(xml.dom.DOMException, s.__setattr__, 'cssText', '$') # RESET css_parser.log.raiseExceptions css_parser.log.raiseExceptions = False s = css_parser.css.CSSStyleSheet() # does not raise! s.__setattr__('cssText', '$') self.assertEqual(s.cssText, ''.encode()) def test_parseComments(self): "css_parser.CSSParser(parseComments=False)" css = '/*1*/ a { color: /*2*/ red; }' p = css_parser.CSSParser(parseComments=False) self.assertEqual( p.parseString(css).cssText, 'a {\n color: red\n }'.encode()) p = css_parser.CSSParser(parseComments=True) self.assertEqual( p.parseString(css).cssText, '/*1*/\na {\n color: /*2*/ red\n }'.encode()) def test_parseUrl(self): "CSSParser.parseUrl()" parser = css_parser.CSSParser() with self.patch_default_fetcher((None, '')): sheet = parser.parseUrl( 'http://example.com', media='tv,print', title='test') self.assertEqual(sheet.href, 'http://example.com') self.assertEqual(sheet.encoding, 'utf-8') self.assertEqual(sheet.media.mediaText, 'tv, print') self.assertEqual(sheet.title, 'test') # URL and content tests tests = { # (url, content): isSheet, encoding, cssText ('', None): (False, None, None), ('1', None): (False, None, None), ('mailto:a@bb.cd', None): (False, None, None), ('http://cthedot.de/test.css', None): (False, None, None), ('http://cthedot.de/test.css', ''): (True, 'utf-8', ''), ('http://cthedot.de/test.css', 'a'): (True, 'utf-8', ''), ('http://cthedot.de/test.css', 'a {color: red}'): (True, 'utf-8', 'a {\n color: red\n }'), ('http://cthedot.de/test.css', 'a {color: red}'): (True, 'utf-8', 'a {\n color: red\n }'), ('http://cthedot.de/test.css', '@charset "ascii";a {color: red}'): (True, 'ascii', '@charset "ascii";\na {\n color: red\n }'), } override = 'iso-8859-1' overrideprefix = '@charset "iso-8859-1";' httpencoding = None for (url, content), (isSheet, expencoding, cssText) in tests.items(): parser.setFetcher(self._make_fetcher(httpencoding, content)) sheet1 = parser.parseUrl(url) sheet2 = parser.parseUrl(url, encoding=override) if isSheet: self.assertEqual(sheet1.encoding, expencoding) self.assertEqual(sheet1.cssText, cssText.encode()) self.assertEqual(sheet2.encoding, override) if sheet1.cssText and cssText.startswith('@charset'): self.assertEqual( sheet2.cssText, (cssText.replace('ascii', override).encode())) elif sheet1.cssText: self.assertEqual( sheet2.cssText, (overrideprefix + '\n' + cssText).encode()) else: self.assertEqual(sheet2.cssText, (overrideprefix + cssText).encode()) else: self.assertEqual(sheet1, None) self.assertEqual(sheet2, None) parser.setFetcher(None) self.assertRaises(ValueError, parser.parseUrl, '../not-valid-in-urllib') # we'll get an URLError if no network connection self.assertRaises( (HTTPError, URLError, FetchError), parser.parseUrl, 'https://github.com/ebook-utils/css-parser/not-found.css') def test_parseString(self): "CSSParser.parseString()" tests = { # (byte) string, encoding: encoding, cssText ('/*a*/', None): ('utf-8', '/*a*/'.encode('utf-8')), ('/*a*/', 'ascii'): ('ascii', '@charset "ascii";\n/*a*/'.encode('ascii')), # org # ('/*\xc3\xa4*/', None): (u'utf-8', u'/*\xc3\xa4*/'.encode('utf-8')), # ('/*\xc3\xa4*/', 'utf-8'): (u'utf-8', u'@charset "utf-8";\n/*\xc3\xa4*/'.encode('utf-8')), # new for 2.x and 3.x ('/*\xe4*/'.encode('utf-8'), None): ('utf-8', '/*\xe4*/'.encode('utf-8')), ('/*\xe4*/'.encode('utf-8'), 'utf-8'): ('utf-8', '@charset "utf-8";\n/*\xe4*/'.encode('utf-8')), ('@charset "ascii";/*a*/', None): ('ascii', '@charset "ascii";\n/*a*/'.encode('ascii')), ('@charset "utf-8";/*a*/', None): ('utf-8', '@charset "utf-8";\n/*a*/'.encode('utf-8')), ('@charset "iso-8859-1";/*a*/', None): ('iso-8859-1', '@charset "iso-8859-1";\n/*a*/'.encode('iso-8859-1')), # unicode string, no encoding: encoding, cssText ('/*€*/', None): ('utf-8', '/*€*/'.encode('utf-8')), ('@charset "iso-8859-1";/*ä*/', None): ('iso-8859-1', '@charset "iso-8859-1";\n/*ä*/'.encode('iso-8859-1')), ('@charset "utf-8";/*€*/', None): ('utf-8', '@charset "utf-8";\n/*€*/'.encode('utf-8')), ('@charset "utf-16";/**/', None): ('utf-16', '@charset "utf-16";\n/**/'.encode('utf-16')), # unicode string, encoding utf-8: encoding, cssText ('/*€*/', 'utf-8'): ('utf-8', '@charset "utf-8";\n/*€*/'.encode('utf-8')), ('@charset "iso-8859-1";/*ä*/', 'utf-8'): ('utf-8', '@charset "utf-8";\n/*ä*/'.encode('utf-8')), ('@charset "utf-8";/*€*/', 'utf-8'): ('utf-8', '@charset "utf-8";\n/*€*/'.encode('utf-8')), ('@charset "utf-16";/**/', 'utf-8'): ('utf-8', '@charset "utf-8";\n/**/'.encode('utf-8')), # probably not what is wanted but does not raise: ('/*€*/', 'ascii'): ('ascii', '@charset "ascii";\n/*\\20AC */'.encode('utf-8')), ('/*€*/', 'iso-8859-1'): ('iso-8859-1', '@charset "iso-8859-1";\n/*\\20AC */'.encode('utf-8')), } for test in tests: css, encoding = test sheet = css_parser.parseString(css, encoding=encoding) encoding, cssText = tests[test] self.assertEqual(encoding, sheet.encoding) self.assertEqual(cssText, sheet.cssText) tests = [ # encoded css, overiding encoding ('/*€*/'.encode('utf-16'), 'utf-8'), ('/*ä*/'.encode('iso-8859-1'), 'ascii'), ('/*€*/'.encode('utf-8'), 'ascii'), ('a'.encode('ascii'), 'utf-16'), ] for test in tests: self.assertRaises(UnicodeDecodeError, css_parser.parseString, test[0], test[1]) def test_validate(self): """CSSParser(validate)""" style = 'color: red' t = 'a { %s }' % style # helper s = css_parser.parseString(t) self.assertEqual(s.validating, True) s = css_parser.parseString(t, validate=False) self.assertEqual(s.validating, False) s = css_parser.parseString(t, validate=True) self.assertEqual(s.validating, True) d = css_parser.parseStyle(style) self.assertEqual(d.validating, True) d = css_parser.parseStyle(style, validate=True) self.assertEqual(d.validating, True) d = css_parser.parseStyle(style, validate=False) self.assertEqual(d.validating, False) # parser p = css_parser.CSSParser() s = p.parseString(t) self.assertEqual(s.validating, True) s = p.parseString(t, validate=False) self.assertEqual(s.validating, False) s = p.parseString(t, validate=True) self.assertEqual(s.validating, True) d = p.parseStyle(style) self.assertEqual(d.validating, True) p = css_parser.CSSParser(validate=True) s = p.parseString(t) self.assertEqual(s.validating, True) s = p.parseString(t, validate=False) self.assertEqual(s.validating, False) s = p.parseString(t, validate=True) self.assertEqual(s.validating, True) d = p.parseStyle(style) self.assertEqual(d.validating, True) p = css_parser.CSSParser(validate=False) s = p.parseString(t) self.assertEqual(s.validating, False) s = p.parseString(t, validate=False) self.assertEqual(s.validating, False) s = p.parseString(t, validate=True) self.assertEqual(s.validating, True) d = p.parseStyle(style) self.assertEqual(d.validating, False) # url p = css_parser.CSSParser(validate=False) p.setFetcher(self._make_fetcher('utf-8', t)) u = 'url' s = p.parseUrl(u) self.assertEqual(s.validating, False) s = p.parseUrl(u, validate=False) self.assertEqual(s.validating, False) s = p.parseUrl(u, validate=True) self.assertEqual(s.validating, True) # check if it raises see log test def test_fetcher(self): """CSSParser.fetcher order: 0. explicity given encoding OVERRIDE (css_parser only) 1. An HTTP "charset" parameter in a "Content-Type" field (or similar parameters in other protocols) 2. BOM and/or @charset (see below) 3. or other metadata from the linking mechanism (if any) 4. charset of referring style sheet or document (if any) 5. Assume UTF-8 """ tests = { # css, encoding, (mimetype, encoding, importcss): # encoding, importIndex, importEncoding, importText # 0/0 override/override => ASCII/ASCII ('@charset "utf-16"; @import "x";', 'ASCII', ('iso-8859-1', '@charset "latin1";/*t*/')): ('ascii', 1, 'ascii', '@charset "ascii";\n/*t*/'.encode()), # 1/1 not tested her but same as next # 2/1 @charset/HTTP => UTF-16/ISO-8859-1 ('@charset "UTF-16"; @import "x";', None, ('ISO-8859-1', '@charset "latin1";/*t*/')): ('utf-16', 1, 'iso-8859-1', '@charset "iso-8859-1";\n/*t*/'.encode('iso-8859-1')), # 2/2 @charset/@charset => UTF-16/ISO-8859-1 ('@charset "UTF-16"; @import "x";', None, (None, '@charset "ISO-8859-1";/*t*/')): ('utf-16', 1, 'iso-8859-1', '@charset "iso-8859-1";\n/*t*/'.encode('iso-8859-1')), # 2/4 @charset/referrer => ASCII/ASCII ('@charset "ASCII"; @import "x";', None, (None, '/*t*/')): ('ascii', 1, 'ascii', '@charset "ascii";\n/*t*/'.encode()), # 5/5 default/default or referrer ('@import "x";', None, (None, '/*t*/')): ('utf-8', 0, 'utf-8', '/*t*/'.encode()), # 0/0 override/override+unicode ('@charset "utf-16"; @import "x";', 'ASCII', (None, '@charset "latin1";/*\u0287*/')): ('ascii', 1, 'ascii', '@charset "ascii";\n/*\\287 */'.encode()), # 2/1 @charset/HTTP+unicode ('@charset "ascii"; @import "x";', None, ('iso-8859-1', '/*\u0287*/')): ('ascii', 1, 'iso-8859-1', '@charset "iso-8859-1";\n/*\\287 */'.encode()), # 2/4 @charset/referrer+unicode ('@charset "ascii"; @import "x";', None, (None, '/*\u0287*/')): ('ascii', 1, 'ascii', '@charset "ascii";\n/*\\287 */'.encode()), # 5/1 default/HTTP+unicode ('@import "x";', None, ('ascii', '/*\u0287*/')): ('utf-8', 0, 'ascii', '@charset "ascii";\n/*\\287 */'.encode()), # 5/5 default+unicode/default+unicode ('@import "x";', None, (None, '/*\u0287*/')): ('utf-8', 0, 'utf-8', '/*\u0287*/'.encode('utf-8')) } parser = css_parser.CSSParser() for test in tests: css, encoding, fetchdata = test sheetencoding, importIndex, importEncoding, importText = tests[ test] # use setFetcher parser.setFetcher(self._make_fetcher(*fetchdata)) # use init parser2 = css_parser.CSSParser( fetcher=self._make_fetcher(*fetchdata)) sheet = parser.parseString(css, encoding=encoding) sheet2 = parser2.parseString(css, encoding=encoding) # sheet self.assertEqual(sheet.encoding, sheetencoding) self.assertEqual(sheet2.encoding, sheetencoding) # imported sheet self.assertEqual(sheet.cssRules[importIndex].styleSheet.encoding, importEncoding) self.assertEqual(sheet2.cssRules[importIndex].styleSheet.encoding, importEncoding) self.assertEqual(sheet.cssRules[importIndex].styleSheet.cssText, importText) self.assertEqual(sheet2.cssRules[importIndex].styleSheet.cssText, importText) def test_roundtrip(self): "css_parser encodings" css1 = '''@charset "utf-8"; /* ä */''' s = css_parser.parseString(css1) css2 = s.cssText if isinstance(css2, bytes): css2 = css2.decode('utf-8') self.assertEqual(css1, css2) s = css_parser.parseString(css2) s.cssRules[0].encoding = 'ascii' css3 = r'''@charset "ascii"; /* \E4 */''' ans = s.cssText if isinstance(ans, bytes): ans = ans.decode('utf-8') self.assertEqual(css3, ans) def test_escapes(self): "css_parser escapes" css = r'\43\x { \43\x: \43\x !import\41nt }' sheet = css_parser.parseString(css) self.assertEqual(sheet.cssText, r'''C\x { c\x: C\x !important }'''.encode()) css = r'\ x{\ x :\ x ;y:1} ' sheet = css_parser.parseString(css) self.assertEqual(sheet.cssText, r'''\ x { \ x: \ x; y: 1 }'''.encode()) def test_invalidstring(self): "css_parser.parseString(INVALID_STRING)" validfromhere = '@namespace "x";' csss = ('''@charset "ascii ;''' + validfromhere, '''@charset 'ascii ;''' + validfromhere, '''@namespace "y ;''' + validfromhere, '''@import "y ;''' + validfromhere, '''@import url('a );''' + validfromhere, '''@unknown "y ;''' + validfromhere) for css in csss: s = css_parser.parseString(css) self.assertEqual(validfromhere.encode(), s.cssText) csss = ('''a { font-family: "Courier ; }''', r'''a { content: "\"; } ''', r'''a { content: "\\\"; } ''') for css in csss: self.assertEqual(''.encode(), css_parser.parseString(css).cssText) def test_invalid(self): "css_parser.parseString(INVALID_CSS)" tests = { 'a {color: blue}} a{color: red} a{color: green}': '''a { color: blue } a { color: green }''', 'p @here {color: red} p {color: green}': 'p {\n color: green\n }' } for css in tests: exp = tests[css] if exp is None: exp = css s = css_parser.parseString(css) self.assertEqual(exp.encode(), s.cssText) def test_nesting(self): "css_parser.parseString nesting" # examples from csslist 27.11.2007 tests = { '@1; div{color:green}': 'div {\n color: green\n }', '@1 []; div{color:green}': 'div {\n color: green\n }', '@1 [{}]; div { color:green; }': 'div {\n color: green\n }', '@media all { @ } div{color:green}': 'div {\n color: green\n }', # should this be u''? '@1 { [ } div{color:green}': '', # red was eaten: '@1 { [ } ] div{color:red}div{color:green}': 'div {\n color: green\n }', } for css, exp in tests.items(): self.assertEqual(exp.encode(), css_parser.parseString(css).cssText) def test_specialcases(self): "css_parser.parseString(special_case)" tests = { ''' a[title="a not s\ o very long title"] {/*...*/}''': '''a[title="a not so very long title"] { /*...*/ }''' } for css in tests: exp = tests[css] if exp is None: exp = css s = css_parser.parseString(css) self.assertEqual(exp.encode(), s.cssText) def test_iehack(self): "IEhack: $property (not since 0.9.5b3)" # $color is not color! css = 'a { color: green; $color: red; }' s = css_parser.parseString(css) p1 = s.cssRules[0].style.getProperty('color') self.assertEqual('color', p1.name) self.assertEqual('color', p1.literalname) self.assertEqual('', s.cssRules[0].style.getPropertyValue('$color')) p2 = s.cssRules[0].style.getProperty('$color') self.assertEqual(None, p2) self.assertEqual('green', s.cssRules[0].style.getPropertyValue('color')) self.assertEqual('green', s.cssRules[0].style.color) def test_attributes(self): "css_parser.parseString(href, media)" s = css_parser.parseString( "a{}", href="file:foo.css", media="screen, projection, tv") self.assertEqual(s.href, "file:foo.css") self.assertEqual(s.media.mediaText, "screen, projection, tv") s = css_parser.parseString( "a{}", href="file:foo.css", media=["screen", "projection", "tv"]) self.assertEqual(s.media.mediaText, "screen, projection, tv") if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_prodparser.py0000644000175000017500000003616300000000000022005 0ustar00kovidkovid"""Testcases for css_parser.css.CSSCharsetRule""" from __future__ import absolute_import, unicode_literals import sys import xml.dom from css_parser.prodparser import (Choice, Exhausted, ParseError, PreDef, Prod, ProdParser, Sequence) from . import basetest __version__ = '$Id: test_csscharsetrule.py 1356 2008-07-13 17:29:09Z cthedot $' if sys.version_info.major > 2: basestring = str class ProdTestCase(basetest.BaseTestCase): def test_init(self): "Prod.__init__(...)" p = Prod('min', lambda t, v: t == 1 and v == 2) self.assertEqual(str(p), 'min') self.assertEqual(p.toStore, None) self.assertEqual(p.optional, False) p = Prod('optional', lambda t, v: True, optional=True) self.assertEqual(p.optional, True) def test_initMatch(self): "Prod.__init__(...match=...)" p = Prod('min', lambda t, v: t == 1 and v == 2) self.assertEqual(p.match(1, 2), True) self.assertEqual(p.match(2, 2), False) self.assertEqual(p.match(1, 1), False) def test_initToSeq(self): "Prod.__init__(...toSeq=...)" # simply saves p = Prod('all', lambda t, tokens: True, toSeq=None) self.assertEqual(p.toSeq([1, 2], None), (1, 2)) # simply saves self.assertEqual(p.toSeq(['s1', 's2'], None), ('s1', 's2')) # simply saves # saves callback(val) p = Prod('all', lambda t, v: True, toSeq=lambda t, tokens: (1 == t[0], 3 == t[1])) self.assertEqual(p.toSeq([1, 3], None), (True, True)) self.assertEqual(p.toSeq([2, 4], None), (False, False)) def test_initToStore(self): "Prod.__init__(...toStore=...)" p = Prod('all', lambda t, v: True, toStore='key') # save as key s = {} p.toStore(s, 1) self.assertEqual(s['key'], 1) # append to key s = {'key': []} p.toStore(s, 1) p.toStore(s, 2) self.assertEqual(s['key'], [1, 2]) # callback def doubleToStore(key): def toStore(store, item): store[key] = item * 2 return toStore p = Prod('all', lambda t, v: True, toStore=doubleToStore('key')) s = {'key': []} p.toStore(s, 1) self.assertEqual(s['key'], 2) def test_matches(self): "Prod.matches(token)" p1 = Prod('p1', lambda t, v: t == 1 and v == 2) p2 = Prod('p2', lambda t, v: t == 1 and v == 2, optional=True) self.assertEqual(p1.matches([1, 2, 0, 0]), True) self.assertEqual(p2.matches([1, 2, 0, 0]), True) self.assertEqual(p1.matches([0, 0, 0, 0]), False) self.assertEqual(p2.matches([0, 0, 0, 0]), False) class SequenceTestCase(basetest.BaseTestCase): def test_init(self): "Sequence.__init__()" p1 = Prod('p1', lambda t, v: t == 1) seq = Sequence(p1, p1) self.assertEqual(1, seq._min) self.assertEqual(1, seq._max) def test_initminmax(self): "Sequence.__init__(...minmax=...)" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) s = Sequence(p1, p2, minmax=lambda: (2, 3)) self.assertEqual(2, s._min) self.assertEqual(3, s._max) s = Sequence(p1, p2, minmax=lambda: (0, None)) self.assertEqual(0, s._min) try: # py2.6/3 m = sys.maxsize except AttributeError: # py<1.6 m = sys.maxsize self.assertEqual(m, s._max) def test_optional(self): "Sequence.optional" p1 = Prod('p1', lambda t, v: t == 1) s = Sequence(p1, minmax=lambda: (1, 3)) self.assertEqual(False, s.optional) s = Sequence(p1, minmax=lambda: (0, 3)) self.assertEqual(True, s.optional) s = Sequence(p1, minmax=lambda: (0, None)) self.assertEqual(True, s.optional) def test_reset(self): "Sequence.reset()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) seq = Sequence(p1, p2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) self.assertEqual(p1, seq.nextProd(t1)) self.assertEqual(p2, seq.nextProd(t2)) self.assertRaises(Exhausted, seq.nextProd, t1) seq.reset() self.assertEqual(p1, seq.nextProd(t1)) def test_matches(self): "Sequence.matches()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2, optional=True) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) t3 = (3, 0, 0, 0) s = Sequence(p1, p2) self.assertEqual(True, s.matches(t1)) self.assertEqual(False, s.matches(t2)) s = Sequence(p2, p1) self.assertEqual(True, s.matches(t1)) self.assertEqual(True, s.matches(t2)) s = Sequence(Choice(p1, p2)) self.assertEqual(True, s.matches(t1)) self.assertEqual(True, s.matches(t2)) self.assertEqual(False, s.matches(t3)) def test_nextProd(self): "Sequence.nextProd()" p1 = Prod('p1', lambda t, v: t == 1, optional=True) p2 = Prod('p2', lambda t, v: t == 2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) tests = { # seq: list of list of (token, prod or error msg) (p1, ): ([(t1, p1)], [(t2, 'Extra token')], # as p1 optional [(t1, p1), (t1, 'Extra token')], [(t1, p1), (t2, 'Extra token')] ), (p2, ): ([(t2, p2)], [(t2, p2), (t2, 'Extra token')], [(t2, p2), (t1, 'Extra token')], [(t1, 'Missing token for production p2')] ), (p1, p2): ([(t1, p1), (t2, p2)], [(t1, p1), (t1, 'Missing token for production p2')] ) } for seqitems, results in tests.items(): for result in results: seq = Sequence(*seqitems) for t, p in result: if isinstance(p, basestring): self.assertRaisesMsg(ParseError, p, seq.nextProd, t) else: self.assertEqual(p, seq.nextProd(t)) tests = { # seq: list of list of (token, prod or error msg) # as p1 optional! (p1, p1): ([(t1, p1)], [(t1, p1), (t1, p1)], [(t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1), (t1, p1), (t1, 'Extra token')], ), (p1, ): ([(t1, p1)], [(t2, 'Extra token')], [(t1, p1), (t1, p1)], [(t1, p1), (t2, 'Extra token')], [(t1, p1), (t1, p1), (t1, 'Extra token')], [(t1, p1), (t1, p1), (t2, 'Extra token')] ), # as p2 NOT optional (p2, ): ([(t2, p2)], [(t1, 'Missing token for production p2')], [(t2, p2), (t2, p2)], [(t2, p2), (t1, 'No match for (1, 0, 0, 0) in Sequence(p2)')], [(t2, p2), (t2, p2), (t2, 'Extra token')], [(t2, p2), (t2, p2), (t1, 'Extra token')] ), (p1, p2): ([(t1, p1), (t1, 'Missing token for production p2')], [(t2, p2), (t2, p2)], [(t2, p2), (t1, p1), (t2, p2)], [(t1, p1), (t2, p2), (t2, p2)], [(t1, p1), (t2, p2), (t1, p1), (t2, p2)], [(t2, p2), (t2, p2), (t2, 'Extra token')], [(t2, p2), (t1, p1), (t2, p2), (t1, 'Extra token')], [(t2, p2), (t1, p1), (t2, p2), (t2, 'Extra token')], [(t1, p1), (t2, p2), (t2, p2), (t1, 'Extra token')], [(t1, p1), (t2, p2), (t2, p2), (t2, 'Extra token')], [(t1, p1), (t2, p2), (t1, p1), (t2, p2), (t1, 'Extra token')], [(t1, p1), (t2, p2), (t1, p1), (t2, p2), (t2, 'Extra token')], ) } for seqitems, results in tests.items(): for result in results: seq = Sequence(minmax=lambda: (1, 2), *seqitems) for t, p in result: if isinstance(p, basestring): self.assertRaisesMsg(ParseError, p, seq.nextProd, t) else: self.assertEqual(p, seq.nextProd(t)) class ChoiceTestCase(basetest.BaseTestCase): def test_init(self): "Choice.__init__()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) t0 = (0, 0, 0, 0) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(p1, p2) self.assertRaisesMsg(ParseError, 'No match for (0, 0, 0, 0) in Choice(p1, p2)', ch.nextProd, t0) self.assertEqual(p1, ch.nextProd(t1)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) ch = Choice(p1, p2) self.assertEqual(p2, ch.nextProd(t2)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t2) ch = Choice(p2, p1) self.assertRaisesMsg(ParseError, 'No match for (0, 0, 0, 0) in Choice(p2, p1)', ch.nextProd, t0) self.assertEqual(p1, ch.nextProd(t1)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) ch = Choice(p2, p1) self.assertEqual(p2, ch.nextProd(t2)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t2) def test_matches(self): "Choice.matches()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2, optional=True) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) t3 = (3, 0, 0, 0) c = Choice(p1, p2) self.assertEqual(True, c.matches(t1)) self.assertEqual(True, c.matches(t2)) self.assertEqual(False, c.matches(t3)) c = Choice(Sequence(p1), Sequence(p2)) self.assertEqual(True, c.matches(t1)) self.assertEqual(True, c.matches(t2)) self.assertEqual(False, c.matches(t3)) def test_nested(self): "Choice with nested Sequence" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) s1 = Sequence(p1, p1) s2 = Sequence(p2, p2) t0 = (0, 0, 0, 0) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(s1, s2) self.assertRaisesMsg( ParseError, 'No match for (0, 0, 0, 0) in Choice(Sequence(p1, p1), Sequence(p2, p2))', ch.nextProd, t0) self.assertEqual(s1, ch.nextProd(t1)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) ch = Choice(s1, s2) self.assertEqual(s2, ch.nextProd(t2)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) def test_reset(self): "Choice.reset()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(p1, p2) self.assertEqual(p1, ch.nextProd(t1)) self.assertRaises(Exhausted, ch.nextProd, t1) ch.reset() self.assertEqual(p2, ch.nextProd(t2)) class ProdParserTestCase(basetest.BaseTestCase): def setUp(self): pass def test_parse_keepS(self): "ProdParser.parse(keepS)" p = ProdParser() # text, name, productions, store=None def prods(): return Sequence(PreDef.char(';', ';'), PreDef.char(':', ':') ) w, seq, store, unused = p.parse('; :', 'test', prods(), keepS=True) self.assertTrue(w) self.assertEqual(3, len(seq)) w, seq, store, unused = p.parse('; :', 'test', prods(), keepS=False) self.assertTrue(w) self.assertEqual(2, len(seq)) def test_combi(self): "ProdParser.parse() 2" p1 = Prod('p1', lambda t, v: v == '1') p2 = Prod('p2', lambda t, v: v == '2') p3 = Prod('p3', lambda t, v: v == '3') tests = {'1 2': True, '1 2 1 2': True, '3': True, # '': 'No match in Choice(Sequence(p1, p2), p3)', '1': 'Missing token for production p2', '1 2 1': 'Missing token for production p2', '1 2 1 2 x': "No match: ('IDENT', 'x', 1, 9)", '1 2 1 2 1': "No match: ('NUMBER', '1', 1, 9)", '3 x': "No match: ('IDENT', 'x', 1, 3)", '3 3': "No match: ('NUMBER', '3', 1, 3)", } for text, exp in tests.items(): if sys.version_info.major == 2 and hasattr(exp, 'replace'): exp = exp.replace("('", "(u'").replace(" '", " u'") prods = Choice(Sequence(p1, p2, minmax=lambda: (1, 2)), p3) if exp is True: wellformed, seq, store, unused = ProdParser().parse(text, 'T', prods) self.assertEqual(wellformed, exp) else: self.assertRaisesMsg(xml.dom.SyntaxErr, 'T: %s' % exp, ProdParser().parse, text, 'T', prods) tests = {'1 3': True, '1 1 3': True, '2 3': True, '1': 'Missing token for production p3', '1 1': 'Missing token for production p3', '1 3 3': "No match: ('NUMBER', '3', 1, 5)", '1 1 3 3': "No match: ('NUMBER', '3', 1, 7)", '2 3 3': "No match: ('NUMBER', '3', 1, 5)", '2': 'Missing token for production p3', '3': "Missing token for production Choice(Sequence(p1), p2): ('NUMBER', '3', 1, 1)", } for text, exp in tests.items(): if sys.version_info.major == 2 and hasattr(exp, 'replace'): exp = exp.replace("('", "(u'").replace(" '", " u'") prods = Sequence(Choice(Sequence(p1, minmax=lambda: (1, 2)), p2), p3) if exp is True: wellformed, seq, store, unused = ProdParser().parse(text, 'T', prods) self.assertEqual(wellformed, exp) else: self.assertRaisesMsg(xml.dom.SyntaxErr, 'T: %s' % exp, ProdParser().parse, text, 'T', prods) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_profiles.py0000644000175000017500000005545200000000000021451 0ustar00kovidkovid"""Testcases for css_parser.css.CSSValue and CSSPrimitiveValue.""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_cssvalue.py 1443 2008-08-31 13:54:39Z cthedot $' import sys import platform from . import basetest import css_parser CSS2 = (css_parser.profile.CSS_LEVEL_2,) C3BUI = (css_parser.profile.CSS3_BASIC_USER_INTERFACE,) C3BB = (css_parser.profile.CSS3_BACKGROUNDS_AND_BORDERS,) CM3 = (css_parser.profile.CSS3_COLOR,) FM3 = (css_parser.profile.CSS3_FONTS,) C3PM = (css_parser.profile.CSS3_PAGED_MEDIA,) C3T = (css_parser.profile.CSS3_TEXT,) FM3FF = (css_parser.profile.CSS3_FONT_FACE,) CSS2_CM3 = (CM3[0], CSS2[0]) CSS2_FM3 = (FM3[0], CSS2[0]) class ProfilesTestCase(basetest.BaseTestCase): M1 = { 'testvalue': 'x' } P1 = { '-test-tokenmacro': '({num}{w}){1,2}', '-test-macro': '{ident}|{percentage}', '-test-custommacro': '{testvalue}', # custom validation function '-test-funcval': lambda v: int(v) > 0 } def test_knownNames(self): "Profiles.knownNames" p = css_parser.profiles.Profiles() p.removeProfile(all=True) p.addProfile('test', self.P1, self.M1) self.assertEqual(list(p.knownNames), list(self.P1.keys())) p.removeProfile(all=True) self.assertEqual(p.knownNames, []) def test_profiles(self): "Profiles.profiles" p = css_parser.profiles.Profiles() p.removeProfile(all=True) p.addProfile('test', self.P1, self.M1) self.assertEqual(p.profiles, ['test']) p.removeProfile(all=True) self.assertEqual(p.profiles, []) def test_validate2(self): "Profiles.validate()" # save saved = css_parser.profile # test p = css_parser.profiles.Profiles() css_parser.profile = p pvs = [('color', 'red'), ('color', 'rgba(0,0,0,0)'), ('color', 'XXX') ] def check(*results): for i, pv in enumerate(pvs): self.assertEqual(p.validate(*pv), results[i]) check(True, True, False) p.removeProfile(p.CSS3_COLOR) check(True, False, False) css_parser.profile.addProfile('test', {}, {'color': 'XXX'}) check(False, False, True) p.removeProfile(all=True) check(False, False, False) # TODO: validateWithProfile # restore css_parser.profile = saved def test_addProfile(self): "Profiles.addProfile with custom validation function" # unknown profile self.assertRaises(css_parser.profiles.NoSuchProfileException, lambda: list(css_parser.profile.propertiesByProfile('NOTSET'))) # new profile css_parser.profile.addProfile('test', self.P1, self.M1) props = list(self.P1.keys()) props.sort() self.assertEqual(props, list(css_parser.profile.propertiesByProfile('test'))) css_parser.log.raiseExceptions = False tests = { ('-test-tokenmacro', '1'): True, ('-test-tokenmacro', '1 -2'): True, ('-test-tokenmacro', '1 2 3'): False, ('-test-tokenmacro', 'a'): False, ('-test-macro', 'a'): True, ('-test-macro', '0.1%'): True, ('-test-custommacro', 'x'): True, ('-test-custommacro', '1'): False, ('-test-custommacro', 'y'): False, ('-test-funcval', '1'): True, ('-test-funcval', '-1'): False, ('-test-funcval', 'x'): False } for test, v in tests.items(): self.assertEqual(v, css_parser.profile.validate(*test)) self.assertEqual((v, v, ['test']), css_parser.profile.validateWithProfile(*test)) css_parser.log.raiseExceptions = True # raises: expmsg = "invalid literal for int() with base 10: 'x'" # Python upto 2.4 and Jython have different msg format... if sys.version_info[0:2] == (2, 4): expmsg = "invalid literal for int(): x" elif sys.platform.startswith('java'): expmsg = "invalid literal for int() with base 10: x" # PyPy adds the u prefix, but only in versions lower than Python 3 elif (platform.python_implementation() == "PyPy" and sys.version_info < (3, 0)): expmsg = "invalid literal for int() with base 10: u'x'" self.assertRaisesMsg(Exception, expmsg, css_parser.profile.validate, '-test-funcval', 'x') def test_removeProfile(self): "Profiles.removeProfile()" p = css_parser.profiles.Profiles() self.assertEqual(9, len(p.profiles)) p.removeProfile(p.CSS_LEVEL_2) self.assertEqual(8, len(p.profiles)) p.removeProfile(all=True) self.assertEqual(0, len(p.profiles)) # TODO: FIX # def test_validateWithProfile(self): # "Profiles.validate(), Profiles.validateWithProfile()" # p = css_parser.profiles.Profiles() # tests = { # ('color', 'red', None): (True, True, [p.CSS_LEVEL_2]), # ('color', 'red', p.CSS_LEVEL_2): (True, True,[p.CSS_LEVEL_2]), # ('color', 'red', p.CSS3_COLOR): (True, False, [p.CSS_LEVEL_2]), # ('color', 'rgba(0,0,0,0)', None): (True, True, [p.CSS3_COLOR]), # ('color', 'rgba(0,0,0,0)', p.CSS_LEVEL_2): (True, False, [p.CSS3_COLOR]), # ('color', 'rgba(0,0,0,0)', p.CSS3_COLOR): (True, True, [p.CSS3_COLOR]), # ('color', '1px', None): (False, False, [p.CSS3_COLOR, p.CSS_LEVEL_2]), # ('color', '1px', p.CSS_LEVEL_2): (False, False, [p.CSS3_COLOR, p.CSS_LEVEL_2]), # ('color', '1px', p.CSS3_COLOR): (False, False, [p.CSS3_COLOR, p.CSS_LEVEL_2]), # ('color', 'aliceblue', None): (True, True, [p.CSS_LEVEL_2]), # # ('opacity', '1', None): (True, True, [p.CSS3_COLOR]), # ('opacity', '1', p.CSS_LEVEL_2): (True, False, [p.CSS3_COLOR]), # ('opacity', '1', p.CSS3_COLOR): (True, True, [p.CSS3_COLOR]), # ('opacity', '1px', None): (False, False, [p.CSS3_COLOR]), # ('opacity', '1px', p.CSS_LEVEL_2): (False, False, [p.CSS3_COLOR]), # ('opacity', '1px', p.CSS3_COLOR): (False, False, [p.CSS3_COLOR]), # # ('-x', '1', None): (False, False, []), # ('-x', '1', p.CSS_LEVEL_2): (False, False, []), # ('-x', '1', p.CSS3_COLOR): (False, False, []), # } # for test, r in tests.items(): # self.assertEqual(p.validate(test[0], test[1]), r[0]) # self.assertEqual(p.validateWithProfile(*test), r) def test_propertiesByProfile(self): "Profiles.propertiesByProfile" self.assertEqual(['opacity'], # 'color', list(css_parser.profile.propertiesByProfile( css_parser.profile.CSS3_COLOR))) def test_csscolorlevel3(self): "CSS Color Module Level 3" # (propname, propvalue): (valid, validprofile) namedcolors = '''transparent, orange, aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white, yellow''' for color in namedcolors.split(','): color = color.strip() self.assertEqual(True, css_parser.profile.validate('color', color)) self.assertEqual((True, True, list(CSS2)), css_parser.profile.validateWithProfile('color', color)) # CSS2 only: uicolor = 'ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText' # noqa for color in uicolor.split('|'): self.assertEqual(False, css_parser.profile.validate('color', color)) # TODO: Fix # self.assertEqual((True, True, list(CSS2)), # css_parser.profile.validateWithProfile('color', color)) def test_validate(self): "Profiles.validate()" tests = { # name, values: valid, matching, profile # background-position ('background-position', ('inherit', '0', '1%', '1px', '0 0', '1% 1%', '1px 1px', '1px 1%', 'top', 'bottom', 'left', 'right', 'center center', 'center', 'top left', 'top center', 'top right', 'bottom left', 'bottom center', 'bottom right', 'center left', 'center center', 'center right', '0 center', 'center 0', '0 top', '10% bottom', 'left 0', 'right 10%', '1% center', 'center 1%' )): (True, True, CSS2), ('background-position', ('0 left', 'top 0' )): (False, False, CSS2), ('border-top-right-radius', ('1px', '1%', '1% -1px', '1% 0', )): (True, True, C3BB), ('border-top-right-radius', ('1px 2px 2px', '/ 1px', 'black')): (False, False, C3BB), ('border-radius', ('1px', '1%', '0', '1px 1px', '1px/ 1px', '1px /1px', '1px / 1px', '1px 1px 1px 1px', '1px 1px 1px 1px / 1px 1px 1px 1px', )): (True, True, C3BB), ('border-radius', ('1px /', '/ 1px', '1px / 1px / 1px', '1px 1px 1px 1px 1px', '1px / 1px 1px 1px 1px 1px', 'black')): (False, False, C3BB), ('border', ('1px', 'solid', 'red', '1px solid red', '1px red solid', 'red 1px solid', 'red solid 1px', 'solid 1px red', 'solid red 1px', )): (True, True, C3BB), ('border', ('1px 1px', 'red red 1px', )): (False, False, C3BB), ('box-shadow', ('none', '1px 1px', '1px 1px 1px', '1px 1px 1px 1px', '1px 1px 1px 1px red', 'inset 1px 1px', 'inset 1px 1px 1px 1px black')): (True, True, C3BB), ('box-shadow', ('1px', '1px 1px 1px 1px 1px', 'x 1px 1px', 'inset', '1px black', 'black')): (False, False, C3BB), # color ('color', ('x', '#', '#0', '#00', '#0000', '#00000', '#0000000', '#00j', '#j00000', 'rgb(0.0,1,1)', 'rgb(0)', 'rgb(0, 1)', 'rgb(0, 1, 1, 1)', 'rgb(0, 1, 0%)', 'rgba(0)', 'rgba(0, 1, 1.0, 1)', 'rgba(0, 1)', 'rgba(0, 1, 1, 1, 1)', 'rgba(100%, 0%, 0%, 1%)', 'rgba(100%, 0%, 0, 1)', 'hsl(1.5,1%,1%)', 'hsl(1,1,1%)', 'hsl(1,1%,1)', 'hsla(1.5,1%,1%, 1)', 'hsla(1,1,1%, 1)', 'hsla(1,1%,1, 1)', 'hsla(1,1%,1%, 1%)' )): (False, False, CSS2_CM3), ('color', ('inherit', 'black', '#000', '#000000', 'rgb(0,1,1)', 'rgb( 0 , 1 , 1 )', 'rgb(-10,555,1)', 'rgb(100%, 1.5%, 0%)', 'rgb(150%, -20%, 0%)', )): (True, True, CSS2), ('color', ('currentcolor', 'aliceblue', 'rgba(1,1,1,1)', 'rgba( 1 , 1 , 1 , 1 )', 'rgba(100%, 0%, 0%, 1)', 'hsl(1,1%,1%)', 'hsl( 1 , 1% , 1% )', 'hsl(-1000,555.5%,-61.5%)', 'hsla(1,1%,1%,1)', 'hsla( 1, 1% , 1% , 1 )', 'hsla(-1000,555.5%,-61.5%, 0.5)' )): (True, True, CM3), # TODO?: # ('color', 'rgb(/**/ 0 /**/ , /**/ 1 /**/ , /**/ 1 /**/ )'): (True, True, CSS2), # content ('content', ('none', 'normal', '""', "'x'", )): (True, True, CSS2), ('cursor', ('url(1), auto', 'url(1) 2 3, help', 'wait', 'inherit', 'none')): (True, True, C3BUI), ('cursor', ('url(1), auto, wait', 'url(1) 2, help', '1')): (False, False, C3BUI), # FONTS ('font-family', ('serif, x', )): (True, True, CSS2), # CSS2_FM3), ('font-family', ('inherit', 'a, b', 'a,b,c', 'a, "b", c', '"a", b, "c"', '"a", "b", "c"', '"x y"', 'serif', '"serif"', # valid but CSS2: font with name serif, CSS3: same as `serif` 'a b', # should use quotes but valid 'a, b b, d', )): (True, True, CSS2), ('font-weight', ('normal', 'bold', 'bolder', 'lighter', 'inherit', '100', '200', '300', '400', '500', '600', '700', '800', '900' )): (True, True, CSS2), ('font-stretch', ('normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded', 'inherit' )): (True, True, FM3), ('font-style', ('normal', 'italic', 'oblique', 'inherit' )): (True, True, CSS2), ('font-variant', ('normal', 'small-caps', 'inherit' )): (True, True, CSS2), ('font-size', ('-1em', )): (False, False, CSS2), ('font-size', ('xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'larger', 'smaller', '1em', '1%', 'inherit' )): (True, True, CSS2), ('font-size-adjust', ('1.0', 'none', 'inherit' )): (True, True, FM3), ('font', ('italic small-caps bold 1px/3 a, "b", serif', '12pt/14pt sans-serif', '80% sans-serif', 'x-large/110% "new century schoolbook", serif', 'bold italic large Palatino, serif', 'normal small-caps 120%/120% fantasy', 'oblique 12pt "Helvetica Nue", serif', 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', 'inherit' )): (True, True, CSS2), ('nav-index', ('1', 'auto', 'inherit')): (True, True, C3BUI), ('nav-index', ('x', '1 2', '1px')): (False, False, C3BUI), ('opacity', ('inherit', '0', '0.0', '0.42342', '1', '1.0', # should be clipped but valid '-0', '-0.1', '-10', '2' )): (True, True, CM3), ('opacity', ('a', '#000', '+1')): (False, False, CM3), ('outline', ('red dotted 1px', 'dotted 1px red', '1px red dotted', 'red', '1px', 'dotted', 'red 1px', '1px dotted', 'red dotted', 'inherit' )): (True, True, C3BUI), ('outline', ('red #fff', 'solid dotted', 'Url(x)', '1px 1px' )): (False, False, C3BUI), ('outline-color', ('red', '#fff', 'inherit' )): (True, True, C3BUI), ('outline-color', ('0', '1em' )): (False, False, C3BUI), ('outline-offset', ('0', '1em', 'inherit' )): (True, True, C3BUI), ('outline-offset', ('1%', 'red' )): (False, False, C3BUI), ('outline-style', ('auto', 'dotted', 'inherit' )): (True, True, C3BUI), ('outline-style', ('0', '1em', 'red' )): (False, False, C3BUI), ('outline-width', ('0', '1em', 'inherit' )): (True, True, C3BUI), ('outline-width', ('auto', 'red', 'dotted' )): (False, False, C3BUI), ('resize', ('none', 'both', 'horizontal', 'vertical', 'inherit')): (True, True, C3BUI), ('resize', ('1', 'auto', '1px', '2%')): (False, False, C3BUI), ('size', ('1cm', '1mm 20cm', 'auto', 'landscape letter', 'a4 portrait', 'landscape', 'a5', # 'inherit' )): (True, True, C3PM), ('size', ( 'portrait landscape', 'a5 letter', '2%')): (False, False, C3PM), ('src', ('url( a )', 'local( x )', 'local("x")', 'local( "x" )', 'url(../fonts/LateefRegAAT.ttf) format( "truetype-aat" )', 'url(a) format( "123x" , "a" )', 'url(a) format( "123x" , "a" ), url(a) format( "123x" , "a" )', 'local(HiraKakuPro-W3), local(Meiryo), local(IPAPGothic)', 'local(Gentium), url(/fonts/Gentium.ttf)', 'local("Gentium"), url("/fonts/Gentium.ttf")', 'local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg")', )): (True, True, FM3FF), ('text-shadow', ('none', '1px 1px', '1px 1px 1px', '1px 1px 1px 1px', '1px 1px 1px 1px red', 'inset 1px 1px', 'inset 1px 1px 1px 1px black')): (True, True, C3T), ('text-shadow', ('1px', '1px 1px 1px 1px 1px', 'x 1px 1px', 'inset', '1px black', 'black')): (False, False, C3T), ('unicode-range', ('u+1', 'U+111111-ffffff', 'u+123456 , U+1-f' )): (True, True, FM3FF), } # TODO!!! for (name, values), (valid, matching, profile) in tests.items(): for value in values: self.assertEqual(valid, css_parser.profile.validate(name, value)) # if (valid, matching, list(profile)) != css_parser.profile.validateWithProfile(name, value): # print # print '###############', name, value # print (valid, matching, list(profile)), css_parser.profile.validateWithProfile(name, value) # TODO: fix # def test_validateByProfile(self): # "Profiles.validateByProfile()" # # testing for valid values overwritten in a profile # tests = { # (FM3FF, 'font-family', ('y', '"y"' # => name should be "y"!!! # )): (True, True, FM3FF), # (FM3FF, 'font-family', ('"y", "a"', 'a, b', 'a a' # )): (True, False, CSS2), # (FM3FF, 'font-stretch', ('normal', 'wider', 'narrower', 'inherit' # )): (True, False, FM3), # (FM3FF, 'font-style', ('inherit', # )): (True, False, CSS2), # (FM3FF, 'font-weight', ('bolder', 'lighter', 'inherit', # )): (True, False, CSS2), # } # for (profiles, name, values), (v, m, p) in tests.items(): # for value in values: # self.assertEqual((v, m, list(p)), # css_parser.profile.validateWithProfile(name, # value, # profiles)) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578455265.0 css-parser-1.0.7/css_parser_tests/test_properties.py0000644000175000017500000002033200000000000022007 0ustar00kovidkovid"""Testcases for css_parser.css.property._Property.""" from __future__ import absolute_import, print_function, unicode_literals import copy import sys import css_parser from css_parser.css.property import Property from . import basetest __version__ = '$Id: test_property.py 1529 2008-11-30 15:12:01Z cthedot $' debug = False if sys.version_info.major > 2: basestring = str class PropertiesTestCase(basetest.BaseTestCase): def setUp(self): "init test values" V = { '0': ('0', '-0'), # , '+0'), 'NUMBER': ('0', '-0', '100.1', '-100.1'), # , '+0', '+100.1'), 'PERCENTAGE': ('0%', '-0%', '100.1%', '-100.1%'), # , '+0%', '+100.1%'), 'EM': '1.2em', 'EX': '1.2ex', 'PX': '1.2px', 'CM': '1.2cm', 'MM': '1.2mm', 'IN': '1.2in', 'PT': '1.2pt', 'PC': '1.2pc', 'ANGLES': ('1deg', '1rad', '1grad'), 'TIMES': ('1s', '1ms'), 'FREQUENCIES': ('1hz', '1khz'), 'DIMENSION': ('1dimension', '1_dimension', '1dimension2'), 'STRING': ('"string"', "'STRING'"), 'URI': ('url(x)', 'URL("x")', "url(')')"), 'IDENT': ('ident', 'IDENT', '_IDENT', '_2', 'i-2'), # 'AUTO': 'auto', # explicit in list as an IDENT too # 'INHERIT': 'inherit', # explicit in list as an IDENT too 'ATTR': ('attr(x)'), 'RECT': ('rect(1,2,3,4)'), # ? 'CLIP': ('rect(1,2,3,4)'), 'FUNCTION': (), 'HEX3': '#123', 'HEX6': '#123abc', 'RGB': 'rgb(1,2,3)', 'RGB100': 'rgb(1%,2%,100%)', 'RGBA': 'rgba(1,2,3, 1)', 'RGBA100': 'rgba(1%,2%,100%, 0)', 'HSL': 'hsl(1,2%,3%)', 'HSLA': 'hsla(1,2%,3%, 1.0)' } def expanded(*keys): r = [] for k in keys: if isinstance(V[k], basestring): r.append(V[k]) else: r.extend(list(V[k])) return r # before adding combined self.V = V self.ALL = list(self._valuesofkeys(V.keys())) # combined values, only keys of V may be used! self.V['LENGTHS'] = expanded('0', 'EM', 'EX', 'PX', 'CM', 'MM', 'IN', 'PT', 'PC') self.V['COLORS'] = expanded('HEX3', 'HEX6', 'RGB', 'RGB100') self.V['COLORS3'] = expanded('RGBA', 'RGBA100', 'HSL', 'HSLA') def _allvalues(self): "Return list of **all** possible values as simple list" return copy.copy(self.ALL) def _valuesofkeys(self, keys): "Generate all distinct values in given keys of self.V" done = [] for key in keys: if isinstance(key, list): # not a key but a list of values, return directly for v in key: yield v else: v = self.V[key] if isinstance(v, basestring): # single value if v not in done: done.append(v) yield v else: # a list of values for value in v: if value not in done: done.append(value) yield value def _check(self, name, keys): """ Check each value in values if for property name p.name==exp. """ notvalid = self._allvalues() for value in self._valuesofkeys(keys): if name == debug: print('+True?', Property(name, value).valid, value) self.assertEqual(True, Property(name, value).valid) if value in notvalid: notvalid.remove(value) for value in notvalid: if name == debug: print('-False?', Property(name, value).valid, value) self.assertEqual(False, Property(name, value).valid) def test_properties(self): "properties" tests = { # propname: key or [list of values] 'color': ('COLORS', 'COLORS3', ['inherit', 'red']), 'fit': (['fill', 'hidden', 'meet', 'slice'],), 'fit-position': ('LENGTHS', 'PERCENTAGE', ['auto', 'top left', '0% 50%', '1cm 5em', 'bottom']), 'font-family': ('STRING', 'IDENT', ['a, b', '"a", "b"', 'a, "b"', '"a", b', r'a\{b', r'a\ b', 'a b' 'a b, c d , e' ]), # 'src': ('STRING',), 'font-weight': (['normal', 'bold', 'bolder', 'lighter', 'inherit', '100', '200', '300', '400', '500', '600', '700', '800', '900'],), 'font-stretch': (['normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded', 'inherit'],), 'font-style': (['normal', 'italic', 'oblique', 'inherit'],), 'font-variant': (['normal', 'small-caps', 'inherit'],), 'font-size': ('LENGTHS', 'PERCENTAGE', ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'larger', 'smaller', '1em', '1%', 'inherit']), 'font-size-adjust': ('NUMBER', ['none', 'inherit']), # 'font': (['italic small-caps bold 1px/3 a, "b", serif', # 'caption', 'icon', 'menu', 'message-box', 'small-caption', # 'status-bar', 'inherit'],), 'image-orientation': ('0', 'ANGLES', ['auto']), 'left': ('LENGTHS', 'PERCENTAGE', ['inherit', 'auto']), 'opacity': ('NUMBER', ['inherit']), 'orphans': ('0', ['1', '99999', 'inherit']), 'page': ('IDENT',), 'page-break-inside': (['auto', 'inherit', 'avoid'],), 'break-inside': (['auto', 'inherit', 'avoid', 'avoid-page', 'avoid-column', 'avoid-region'],), 'size': ('LENGTHS', ['auto', '1em 1em', 'a4 portrait', 'b4 landscape', 'A5 PORTRAIT']), 'widows': ('0', ['1', '99999', 'inherit']) } for name, keys in tests.items(): # keep track of valid keys self._check(name, keys) def test_validate(self): "Property.validate() and Property.valid" tests = { # (default L2, no default, no profile, L2, Color L3) 'red': (True, True, True, True, True), 'rgba(1,2,3,1)': (False, True, True, False, True), '1': (False, False, False, False, False) } for v, rs in tests.items(): p = Property('color', v) # TODO: Fix # css_parser.profile.defaultProfiles = \ # css_parser.profile.CSS_LEVEL_2 # self.assertEqual(rs[0], p.valid) css_parser.profile.defaultProfiles = None self.assertEqual(rs[1], p.valid) self.assertEqual(rs[2], p.validate()) # self.assertEqual(rs[3], p.validate( # profiles=css_parser.profile.CSS_LEVEL_2)) # self.assertEqual(rs[4], p.validate( # css_parser.profile.CSS3_COLOR)) if __name__ == '__main__': debug = 'font-family' import logging import unittest css_parser.log.setLevel(logging.FATAL) # debug = True unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1611677859.0 css-parser-1.0.7/css_parser_tests/test_property.py0000644000175000017500000002211400000000000021477 0ustar00kovidkovid"""Testcases for css_parser.css.property._Property.""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser class PropertyTestCase(basetest.BaseTestCase): def setUp(self): self.r = css_parser.css.property.Property('top', '1px') # , 'important') def test_init(self): "Property.__init__()" p = css_parser.css.property.Property('top', '1px') self.assertEqual('top: 1px', p.cssText) self.assertEqual('top', p.literalname) self.assertEqual('top', p.name) self.assertEqual('1px', p.value) #self.assertEqual('1px', p.cssValue.cssText) self.assertEqual('1px', p.propertyValue.cssText) self.assertEqual('', p.priority) self.assertEqual(True, p.valid) self.assertEqual(True, p.wellformed) self.assertEqual(['top'], p.seqs[0]) self.assertEqual(type(css_parser.css.PropertyValue(cssText="2px")), type(p.seqs[1])) self.assertEqual([], p.seqs[2]) self.assertEqual(True, p.valid) # Prop of MediaQuery p = css_parser.css.property.Property('top', _mediaQuery=True) self.assertEqual('top', p.cssText) self.assertEqual('top', p.literalname) self.assertEqual('top', p.name) self.assertEqual('', p.value) #self.assertEqual('', p.cssValue.cssText) self.assertEqual('', p.propertyValue.cssText) self.assertEqual('', p.priority) self.assertEqual(False, p.valid) #p.cssValue.cssText = '1px' p.propertyValue.cssText = '1px' self.assertEqual('top: 1px', p.cssText) #p.cssValue = '' p.propertyValue = '' self.assertEqual('top', p.cssText) self.assertRaises(xml.dom.SyntaxErr, css_parser.css.property.Property, 'top', '') self.assertRaises(xml.dom.SyntaxErr, css_parser.css.property.Property, 'top') p = css_parser.css.property.Property('top', '0') self.assertEqual('0', p.value) self.assertEqual(True, p.wellformed) self.assertRaises(xml.dom.SyntaxErr, p._setValue, '') self.assertEqual('0', p.value) self.assertEqual(True, p.wellformed) # self.assertEqual(True, p.valid) # def test_valid(self): # "Property.valid" # # context property must be set # tests = [ # ('color', r'INHe\rIT', True), # ('color', '1', False), # ('color', 'red', True), # ('left', '1', False), # ('left', '1px', True), # ('font', 'normal 1em/1.5 serif', True), # ('background', 'url(x.gif) 1 0', False) # ] # for n, v, exp in tests: # v = css_parser.css.CSSValue(cssText=v) # self.assertTrue(v.wellformed, True) def test_cssText(self): "Property.cssText" p = css_parser.css.property.Property() tests = { 'a: 1': None, 'a: 1px 2px': None, 'a: 1 !important': None, 'a: 1 !IMPORTANT': 'a: 1 !important', 'a: 1 !impor\\tant': 'a: 1 !important', # TODO: important with unicode escapes! 'font: normal 1em/1.5 serif': None, 'font: normal 1em/serif': None } self.do_equal_r(tests) tests = { '': (xml.dom.SyntaxErr, 'Property: No property name found: '), ':': (xml.dom.SyntaxErr, 'Property: No property name found: : [1:1: :]'), 'a': (xml.dom.SyntaxErr, 'Property: No ":" after name found: a [1:1: a]'), 'b !': (xml.dom.SyntaxErr, 'Property: No ":" after name found: b ! [1:3: !]'), '/**/x': (xml.dom.SyntaxErr, 'Property: No ":" after name found: /**/x [1:5: x]'), 'c:': (xml.dom.SyntaxErr, "Property: No property value found: c: [1:2: :]"), 'd: ': (xml.dom.SyntaxErr, "No content to parse."), 'e:!important': (xml.dom.SyntaxErr, "No content to parse."), 'f: 1!': (xml.dom.SyntaxErr, 'Property: Invalid priority: !'), 'g: 1!importantX': (xml.dom.SyntaxErr, "Property: No CSS priority value: importantx"), # TODO? # u'a: 1;': (xml.dom.SyntaxErr, # u'''CSSValue: No match: ('CHAR', u';', 1, 5)''') } for test in tests: ecp, msg = tests[test] self.assertRaisesMsg(ecp, msg, p._setCssText, test) def test_name(self): "Property.name" p = css_parser.css.property.Property('top', '1px') p.name = 'left' self.assertEqual('left', p.name) tests = { 'top': None, ' top': 'top', 'top ': 'top', ' top ': 'top', '/*x*/ top ': 'top', ' top /*x*/': 'top', '/*x*/top/*x*/': 'top', '\\x': 'x', 'a\\010': 'a\x10', 'a\\01': 'a\x01' } self.do_equal_r(tests, att='name') tests = { '': xml.dom.SyntaxErr, ' ': xml.dom.SyntaxErr, '"\n': xml.dom.SyntaxErr, '/*x*/': xml.dom.SyntaxErr, ':': xml.dom.SyntaxErr, ';': xml.dom.SyntaxErr, 'top:': xml.dom.SyntaxErr, 'top;': xml.dom.SyntaxErr, 'color: #xyz': xml.dom.SyntaxErr, } self.do_raise_r(tests, att='_setName') p = css_parser.css.property.Property(r'c\olor', 'red') self.assertEqual(r'c\olor', p.literalname) self.assertEqual('color', p.name) def test_literalname(self): "Property.literalname" p = css_parser.css.property.Property(r'c\olor', 'red') self.assertEqual(r'c\olor', p.literalname) self.assertRaisesMsgSubstring(AttributeError, "can't set attribute", p.__setattr__, 'literalname', 'color') def test_validate(self): "Property.valid" p = css_parser.css.property.Property('left', '1px', '') self.assertEqual(p.valid, True) p.name = 'color' self.assertEqual(p.valid, False) p.name = 'top' self.assertEqual(p.valid, True) p.value = 'red' self.assertEqual(p.valid, False) # def test_cssValue(self): # "Property.cssValue" # pass # # DEPRECATED def test_priority(self): "Property.priority" p = css_parser.css.property.Property('top', '1px', 'important') for prio in (None, ''): p.priority = prio self.assertEqual('', p.priority) self.assertEqual('', p.literalpriority) for prio in ('!important', '! important', '!/* x */ important', '!/* x */ important /**/', 'important', 'IMPORTANT', r'im\portant' ): p.priority = prio self.assertEqual('important', p.priority) if prio.startswith('!'): prio = prio[1:].strip() if '/*' in prio: check = 'important' else: check = prio self.assertEqual(check, p.literalpriority) tests = { ' ': xml.dom.SyntaxErr, '"\n': xml.dom.SyntaxErr, # u'important': xml.dom.SyntaxErr, ';': xml.dom.SyntaxErr, '!important !important': xml.dom.SyntaxErr } self.do_raise_r(tests, att='_setPriority') def test_value(self): "Property.value" p = css_parser.css.property.Property('top', '1px') self.assertEqual('1px', p.value) p.value = '2px' self.assertEqual('2px', p.value) tests = { '1px': None, ' 2px': '2px', '3px ': '3px', ' 4px ': '4px', '5px 1px': '5px 1px', } self.do_equal_r(tests, att='value') tests = { # no value None: xml.dom.SyntaxErr, '': xml.dom.SyntaxErr, ' ': xml.dom.SyntaxErr, '"\n': xml.dom.SyntaxErr, '/*x*/': xml.dom.SyntaxErr, # not allowed: ':': xml.dom.SyntaxErr, ';': xml.dom.SyntaxErr, '!important': xml.dom.SyntaxErr, } self.do_raise_r(tests, att='_setValue') def test_reprANDstr(self): "Property.__repr__(), .__str__()" name = "color" value = "red" priority = "important" s = css_parser.css.property.Property(name=name, value=value, priority=priority) self.assertTrue(name in str(s)) self.assertTrue(value in str(s)) self.assertTrue(priority in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(name == s2.name) self.assertTrue(value == s2.value) self.assertTrue(priority == s2.priority) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_scripts_csscombine.py0000644000175000017500000000547400000000000023521 0ustar00kovidkovid"""Testcases for css_parser.scripts.csscombine""" from __future__ import absolute_import from __future__ import unicode_literals from css_parser.script import csscombine from . import basetest import css_parser import os class CSSCombine(basetest.BaseTestCase): C = '@namespace s2"uri";s2|sheet-1{top:1px}s2|sheet-2{top:2px}proxy{top:3px}' def setUp(self): self._saved = css_parser.log.raiseExceptions def tearDown(self): css_parser.log.raiseExceptions = self._saved def test_combine(self): "scripts.csscombine()" # path, SHOULD be keyword argument! csspath = basetest.get_sheet_filename('csscombine-proxy.css') combined = csscombine(csspath) self.assertEqual(combined, self.C.encode()) combined = csscombine(path=csspath, targetencoding='ascii') self.assertEqual(combined, ('@charset "ascii";' + self.C).encode()) # url cssurl = css_parser.helper.path2url(csspath) combined = csscombine(url=cssurl) self.assertEqual(combined, self.C.encode()) combined = csscombine(url=cssurl, targetencoding='ascii') self.assertEqual(combined, ('@charset "ascii";' + self.C).encode()) # cssText # TODO: really need binary or can handle str too? f = open(csspath, mode="rb") cssText = f.read() f.close() combined = csscombine(cssText=cssText, href=cssurl) self.assertEqual(combined, self.C.encode()) combined = csscombine(cssText=cssText, href=cssurl, targetencoding='ascii') self.assertEqual(combined, ('@charset "ascii";' + self.C).encode()) def test_combine_resolveVariables(self): "scripts.csscombine(minify=..., resolveVariables=...)" # no actual imports but checking if minify and resolveVariables work cssText = ''' @variables { c: #0f0; } a { color: var(c); } ''' # default minify self.assertEqual(csscombine(cssText=cssText, resolveVariables=False), '@variables{c:#0f0}a{color:var(c)}'.encode()) self.assertEqual(csscombine(cssText=cssText), 'a{color:#0f0}'.encode()) # no minify self.assertEqual(csscombine(cssText=cssText, minify=False, resolveVariables=False), '@variables {\n c: #0f0\n }\na {\n color: var(c)\n }'.encode()) self.assertEqual(csscombine(cssText=cssText, minify=False), 'a {\n color: #0f0\n }'.encode()) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1611677859.0 css-parser-1.0.7/css_parser_tests/test_selector.py0000644000175000017500000004021400000000000021434 0ustar00kovidkovid"""Testcases for css_parser.css.selector.Selector. what should happen here? - star 7 hack:: x* does not validate but works in IE>5 and FF, does it??? """ from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser class SelectorTestCase(basetest.BaseTestCase): def setUp(self): self.r = css_parser.css.Selector('*') def test_init(self): "Selector.__init__()" s = css_parser.css.Selector('*') self.assertEqual((None, '*'), s.element) self.assertEqual({}, s._namespaces.namespaces) self.assertEqual(None, s.parent) self.assertEqual('*', s.selectorText) self.assertEqual((0, 0, 0, 0), s.specificity) self.assertEqual(True, s.wellformed) s = css_parser.css.Selector(('p|b', {'p': 'URI'})) self.assertEqual(('URI', 'b'), s.element) self.assertEqual({'p': 'URI'}, s._namespaces.namespaces) self.assertEqual(None, s.parent) self.assertEqual('p|b', s.selectorText) self.assertEqual((0, 0, 0, 1), s.specificity) self.assertEqual(True, s.wellformed) self.assertRaisesEx(xml.dom.NamespaceErr, css_parser.css.Selector, 'p|b') def test_element(self): "Selector.element (TODO: RESOLVE)" tests = { '*': (None, '*'), 'x': (None, 'x'), '\\x': (None, '\\x'), '|x': ('', 'x'), '*|x': (css_parser._ANYNS, 'x'), 'ex|x': ('example', 'x'), 'a x': (None, 'x'), 'a+x': (None, 'x'), 'a>x': (None, 'x'), 'a~x': (None, 'x'), 'a+b~c x': (None, 'x'), 'x[href]': (None, 'x'), 'x[href="123"]': (None, 'x'), 'x:hover': (None, 'x'), 'x:first-letter': (None, 'x'), # TODO: Really? 'x::first-line': (None, 'x'), # TODO: Really? 'x:not(href)': (None, 'x'), # TODO: Really? '#id': None, '.c': None, 'x#id': (None, 'x'), 'x.c': (None, 'x') } for test, ele in tests.items(): s = css_parser.css.Selector((test, {'ex': 'example'})) self.assertEqual(ele, s.element) def test_namespaces(self): "Selector.namespaces" namespaces = [ {'p': 'other'}, # no default {'': 'default', 'p': 'other'}, # with default {'': 'default', 'p': 'default'} # same default ] tests = { # selector: with default, no default, same default '*': ('*', '*', '*'), 'x': ('x', 'x', 'x'), '|*': ('|*', '|*', '|*'), '|x': ('|x', '|x', '|x'), '*|*': ('*|*', '*|*', '*|*'), '*|x': ('*|x', '*|x', '*|x'), 'p|*': ('p|*', 'p|*', '*'), 'p|x': ('p|x', 'p|x', 'x'), 'x[a][|a][*|a][p|a]': ('x[a][a][*|a][p|a]', 'x[a][a][*|a][p|a]', 'x[a][a][*|a][a]') } for sel, exp in tests.items(): for i, result in enumerate(exp): s = css_parser.css.Selector((sel, namespaces[i])) self.assertEqual(result, s.selectorText) # add to CSSStyleSheet sheet = css_parser.css.CSSStyleSheet() sheet.cssText = '@namespace p "u"; a { color: green }' r = sheet.cssRules[1] self.assertEqual(r.selectorText, 'a') # add default namespace sheet.namespaces[''] = 'a' self.assertEqual(r.selectorText, '|a') del sheet.namespaces[''] self.assertEqual(r.selectorText, 'a') # r.selectorList.append('a') # self.assertEqual(r.selectorText, u'|a, a') # r.selectorList.append('*|a') # self.assertEqual(r.selectorText, u'|a, a, *|a') def test_default_namespace(self): "Selector.namespaces default" css = '''@namespace "default"; a[att] { color:green; } ''' sheet = css_parser.css.CSSStyleSheet() sheet.cssText = css self.assertEqual(sheet.cssText, '@namespace "default";\na[att] {\n color: green\n }'.encode()) # use a prefix for default namespace, does not goes for atts! sheet.namespaces['p'] = 'default' self.assertEqual(sheet.cssText, '@namespace p "default";\np|a[att] {\n color: green\n }'.encode()) def test_parent(self): "Selector.parent" sl = css_parser.css.SelectorList('a, b') for sel in sl: self.assertEqual(sl, sel.parent) newsel = css_parser.css.Selector('x') sl.append(newsel) self.assertEqual(sl, newsel.parent) newsel = css_parser.css.Selector('y') sl.appendSelector(newsel) self.assertEqual(sl, newsel.parent) def test_selectorText(self): "Selector.selectorText" tests = { # combinators 'a+b>c~e f': 'a + b > c ~ e f', 'a + b > c ~ e f': 'a + b > c ~ e f', 'a+b': 'a + b', 'a + b': 'a + b', 'a\n +\t b': 'a + b', 'a~b': 'a ~ b', 'a b': None, 'a b': 'a b', 'a\nb': 'a b', 'a\tb': 'a b', 'a #b': 'a #b', 'a .b': 'a .b', 'a * b': None, # > 'a>b': 'a > b', 'a> b': 'a > b', 'a >b': 'a > b', 'a > b': 'a > b', # + 'a+b': 'a + b', 'a+ b': 'a + b', 'a +b': 'a + b', 'a + b': 'a + b', # ~ 'a~b': 'a ~ b', 'a~ b': 'a ~ b', 'a ~b': 'a ~ b', 'a ~ b': 'a ~ b', # type selector 'a': None, 'h1-a_x__--': None, 'a-a': None, 'a_a': None, '-a': None, '_': None, '-_': None, r'-\72': '-r', # ur'\25': u'%', # TODO: should be escaped! '.a a': None, 'a1': None, 'a1-1': None, '.a1-1': None, # universal '*': None, '*/*x*/': None, '* /*x*/': None, '*:hover': None, '* :hover': None, '*:lang(fr)': None, '* :lang(fr)': None, '*::first-line': None, '* ::first-line': None, '*[lang=fr]': None, '[lang=fr]': None, # HASH '''#a''': None, '''#a1''': None, '''#1a''': None, # valid to grammar but not for HTML '''#1''': None, # valid to grammar but not for HTML '''a#b''': None, '''a #b''': None, '''a#b.c''': None, '''a.c#b''': None, '''a #b.c''': None, '''a .c#b''': None, # class 'ab': 'ab', 'a.b': None, 'a.b.c': None, '.a1._1': None, # attrib '''[x]''': None, '''*[x]''': None, '''a[x]''': None, '''a[ x]''': 'a[x]', '''a[x ]''': 'a[x]', '''a [x]''': 'a [x]', '''* [x]''': None, # is really * *[x] '''a[x="1"]''': None, '''a[x ="1"]''': 'a[x="1"]', '''a[x= "1"]''': 'a[x="1"]', '''a[x = "1"]''': 'a[x="1"]', '''a[ x = "1"]''': 'a[x="1"]', '''a[x = "1" ]''': 'a[x="1"]', '''a[ x = "1" ]''': 'a[x="1"]', '''a [ x = "1" ]''': 'a [x="1"]', '''a[x~=a1]''': None, '''a[x ~=a1]''': 'a[x~=a1]', '''a[x~= a1]''': 'a[x~=a1]', '''a[x ~= a1]''': 'a[x~=a1]', '''a[ x ~= a1]''': 'a[x~=a1]', '''a[x ~= a1 ]''': 'a[x~=a1]', '''a[ x ~= a1 ]''': 'a[x~=a1]', '''a [ x ~= a1 ]''': 'a [x~=a1]', # same as next! '''a *[ x ~= a1 ]''': 'a *[x~=a1]', '''a[x|=en]''': None, '''a[x|= en]''': 'a[x|=en]', '''a[x |=en]''': 'a[x|=en]', '''a[x |= en]''': 'a[x|=en]', '''a[ x |= en]''': 'a[x|=en]', '''a[x |= en ]''': 'a[x|=en]', '''a[ x |= en]''': 'a[x|=en]', '''a [ x |= en]''': 'a [x|=en]', # CSS3 '''a[x^=en]''': None, '''a[x$=en]''': None, '''a[x*=en]''': None, '''a[/*1*/x/*2*/]''': None, '''a[/*1*/x/*2*/=/*3*/a/*4*/]''': None, '''a[/*1*/x/*2*/~=/*3*/a/*4*/]''': None, '''a[/*1*/x/*2*/|=/*3*/a/*4*/]''': None, # pseudo-elements 'a x:first-line': None, 'a x:first-letter': None, 'a x:before': None, 'a x:after': None, 'a x::selection': None, 'a:hover+b:hover>c:hover~e:hover f:hover': 'a:hover + b:hover > c:hover ~ e:hover f:hover', 'a:hover + b:hover > c:hover ~ e:hover f:hover': 'a:hover + b:hover > c:hover ~ e:hover f:hover', 'a::selection+b::selection>c::selection~e::selection f::selection': 'a::selection + b::selection > c::selection ~ e::selection f::selection', 'a::selection + b::selection > c::selection ~ e::selection f::selection': 'a::selection + b::selection > c::selection ~ e::selection f::selection', 'x:lang(de) y': None, 'x:nth-child(odd) y': None, # functional pseudo 'x:func(a + b-2px22.3"s"i)': None, 'x:func(1 + 1)': None, 'x:func(1+1)': 'x:func(1+1)', 'x:func(1 + 1)': 'x:func(1 + 1)', 'x:func(1-1)': 'x:func(1-1)', 'x:func(1 - 1)': 'x:func(1 -1)', # TODO: FIX! 'x:func(a-1)': 'x:func(a-1)', 'x:func(a -1px)': 'x:func(a -1px)', 'x:func(1px)': None, 'x:func(23.4)': None, 'x:func("s")': None, 'x:func(i)': None, # negation ':not(y)': None, ':not( y \t\n)': ':not(y)', '*:not(y)': None, 'x:not(y)': None, '.x:not(y)': None, ':not(*)': None, ':not(#a)': None, ':not(.a)': None, ':not([a])': None, ':not(:first-letter)': None, ':not(::first-letter)': None, # escapes r'\74\72 td': 'trtd', r'\74\72 td': 'tr td', r'\74\000072 td': 'trtd', r'\74\000072 td': 'tr td', # comments 'a/**/ b': None, 'a /**/b': None, 'a /**/ b': None, 'a /**/ b': 'a /**/ b', 'a /**/ b': 'a /**/ b', # namespaces '|e': None, '*|e': None, '*|*': None, ('p|*', (('p', 'uri'),)): 'p|*', ('p|e', (('p', 'uri'),)): 'p|e', ('-a_x12|e', (('-a_x12', 'uri'),)): '-a_x12|e', ('*|b[p|a]', (('p', 'uri'),)): '*|b[p|a]', # case 'elemenT.clasS#iD[atT="valuE"]:noT(x)::firsT-linE': 'elemenT.clasS#iD[atT="valuE"]:not(x)::first-line' } # do not parse as not complete self.do_equal_r(tests, att='selectorText') tests = { 'x|a': xml.dom.NamespaceErr, ('p|*', (('x', 'uri'),)): xml.dom.NamespaceErr, '': xml.dom.SyntaxErr, '1': xml.dom.SyntaxErr, '-1': xml.dom.SyntaxErr, 'a*b': xml.dom.SyntaxErr, 'a *b': xml.dom.SyntaxErr, 'a* b': xml.dom.SyntaxErr, 'a/**/b': xml.dom.SyntaxErr, '#': xml.dom.SyntaxErr, '|': xml.dom.SyntaxErr, ':': xml.dom.SyntaxErr, '::': xml.dom.SyntaxErr, ': a': xml.dom.SyntaxErr, ':: a': xml.dom.SyntaxErr, ':a()': xml.dom.SyntaxErr, # no value '::a()': xml.dom.SyntaxErr, # no value ':::a': xml.dom.SyntaxErr, ':1': xml.dom.SyntaxErr, '#.x': xml.dom.SyntaxErr, '.': xml.dom.SyntaxErr, '.1': xml.dom.SyntaxErr, '.a.1': xml.dom.SyntaxErr, '[a': xml.dom.SyntaxErr, 'a]': xml.dom.SyntaxErr, '[a b]': xml.dom.SyntaxErr, '[=b]': xml.dom.SyntaxErr, '[a=]': xml.dom.SyntaxErr, '[a|=]': xml.dom.SyntaxErr, '[a~=]': xml.dom.SyntaxErr, '[a=1]': xml.dom.SyntaxErr, 'a +': xml.dom.SyntaxErr, 'a >': xml.dom.SyntaxErr, 'a ++ b': xml.dom.SyntaxErr, 'a + > b': xml.dom.SyntaxErr, # functional pseudo '*:lang(': xml.dom.SyntaxErr, '*:lang()': xml.dom.SyntaxErr, # no arg # negation 'not(x)': xml.dom.SyntaxErr, # no valid function ':not()': xml.dom.SyntaxErr, # no arg ':not(x': xml.dom.SyntaxErr, # no ) ':not(-': xml.dom.SyntaxErr, # not allowed ':not(+': xml.dom.SyntaxErr, # not allowed # only one selector! ',': xml.dom.InvalidModificationErr, ',a': xml.dom.InvalidModificationErr, 'a,': xml.dom.InvalidModificationErr, # @ 'p @here': xml.dom.SyntaxErr, # not allowed } # only set as not complete self.do_raise_r(tests, att='_setSelectorText') def test_specificity(self): "Selector.specificity" selector = css_parser.css.Selector() # readonly def _set(): selector.specificity = 1 self.assertRaisesMsgSubstring(AttributeError, "can't set attribute", _set) tests = { '*': (0, 0, 0, 0), 'li': (0, 0, 0, 1), 'li:first-line': (0, 0, 0, 2), 'ul li': (0, 0, 0, 2), 'ul ol+li': (0, 0, 0, 3), 'h1 + *[rel=up]': (0, 0, 1, 1), 'ul ol li.red': (0, 0, 1, 3), 'li.red.level': (0, 0, 2, 1), '#x34y': (0, 1, 0, 0), 'UL OL LI.red': (0, 0, 1, 3), 'LI.red.level': (0, 0, 2, 1), '#s12:not(FOO)': (0, 1, 0, 1), 'button:not([DISABLED])': (0, 0, 1, 1), # ? '*:not(FOO)': (0, 0, 0, 1), # elements 'a+b': (0, 0, 0, 2), 'a>b': (0, 0, 0, 2), 'a b': (0, 0, 0, 2), '* a': (0, 0, 0, 1), 'a *': (0, 0, 0, 1), 'a * b': (0, 0, 0, 2), 'a:hover': (0, 0, 0, 1), 'a:first-line': (0, 0, 0, 2), 'a:first-letter': (0, 0, 0, 2), 'a:before': (0, 0, 0, 2), 'a:after': (0, 0, 0, 2), # classes and attributes '.a': (0, 0, 1, 0), '*.a': (0, 0, 1, 0), 'a.a': (0, 0, 1, 1), '.a.a': (0, 0, 2, 0), # IE<7 False (0,0,1,0) 'a.a.a': (0, 0, 2, 1), '.a.b': (0, 0, 2, 0), 'a.a.b': (0, 0, 2, 1), '.a .a': (0, 0, 2, 0), '*[x]': (0, 0, 1, 0), '*[x]': (0, 0, 1, 0), '*[x]': (0, 0, 1, 0), '*[x=a]': (0, 0, 1, 0), '*[x~=a]': (0, 0, 1, 0), '*[x|=a]': (0, 0, 1, 0), '*[x^=a]': (0, 0, 1, 0), '*[x*=a]': (0, 0, 1, 0), '*[x$=a]': (0, 0, 1, 0), '*[x][y]': (0, 0, 2, 0), # ids '#a': (0, 1, 0, 0), '*#a': (0, 1, 0, 0), 'x#a': (0, 1, 0, 1), '.x#a': (0, 1, 1, 0), 'a.x#a': (0, 1, 1, 1), '#a#a': (0, 2, 0, 0), # e.g. html:id + xml:id '#a#b': (0, 2, 0, 0), '#a #b': (0, 2, 0, 0), } for text in tests: selector.selectorText = text self.assertEqual(tests[text], selector.specificity) def test_reprANDstr(self): "Selector.__repr__(), .__str__()" sel = 'a + b' s = css_parser.css.Selector(selectorText=sel) self.assertTrue(sel in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(sel == s2.selectorText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1604683295.0 css-parser-1.0.7/css_parser_tests/test_selectorlist.py0000644000175000017500000001111600000000000022327 0ustar00kovidkovid"""Testcases for css_parser.css.selectorlist.SelectorList.""" from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser from css_parser.css.selectorlist import SelectorList class SelectorListTestCase(basetest.BaseTestCase): def setUp(self): basetest.BaseTestCase.setUp(self) self.r = SelectorList() def test_init(self): "SelectorList.__init__() and .length" s = SelectorList() self.assertEqual(0, s.length) s = SelectorList('a, b') self.assertEqual(2, s.length) self.assertEqual('a, b', s.selectorText) s = SelectorList(selectorText='a') self.assertEqual(1, s.length) self.assertEqual('a', s.selectorText) s = SelectorList(selectorText=('p|a', {'p': 'uri'})) # n-dict self.assertEqual(1, s.length) self.assertEqual('p|a', s.selectorText) s = SelectorList(selectorText=('p|a', (('p', 'uri'),))) # n-tuples self.assertEqual(1, s.length) self.assertEqual('p|a', s.selectorText) def test_parentRule(self): "Selector.parentRule" def check(style): self.assertEqual(style, style.selectorList.parentRule) for sel in style.selectorList: self.assertEqual(style.selectorList, sel.parent) style = css_parser.css.CSSStyleRule('a, b') check(style) # add new selector style.selectorList.append(css_parser.css.Selector('x')) check(style) # replace selectorList style.selectorList = css_parser.css.SelectorList('x') check(style) # replace selectorText style.selectorText = ('x, y') check(style) def test_appendSelector(self): "SelectorList.appendSelector() and .length" s = SelectorList() s.appendSelector('a') self.assertEqual(1, s.length) self.assertRaises(xml.dom.InvalidModificationErr, s.appendSelector, 'b,') self.assertEqual(1, s.length) self.assertEqual('a', s.selectorText) s.append('b') self.assertEqual(2, s.length) self.assertEqual('a, b', s.selectorText) s.append('a') self.assertEqual(2, s.length) self.assertEqual('b, a', s.selectorText) # __setitem__ self.assertRaises(IndexError, s.__setitem__, 4, 'x') s[1] = 'c' self.assertEqual(2, s.length) self.assertEqual('b, c', s.selectorText) # TODO: remove duplicates? # s[0] = 'c' # self.assertEqual(1, s.length) # self.assertEqual(u'c', s.selectorText) s = SelectorList() s.appendSelector(('p|a', {'p': 'uri', 'x': 'xxx'})) self.assertEqual('p|a', s.selectorText) # x gets lost as not used self.assertRaises(xml.dom.NamespaceErr, s.append, 'x|a') # not set at all self.assertRaises(xml.dom.NamespaceErr, s.append, 'y|a') # but p is retained s.append('p|b') self.assertEqual('p|a, p|b', s.selectorText) def test_selectorText(self): "SelectorList.selectorText" s = SelectorList() s.selectorText = 'a, b' self.assertEqual('a, b', s.selectorText) self.assertRaises(xml.dom.SyntaxErr, s._setSelectorText, ',') # not changed as invalid! self.assertEqual('a, b', s.selectorText) tests = { '*': None, '/*1*/*': None, '/*1*/*, a': None, 'a, b': None, 'a ,b': 'a, b', 'a , b': 'a, b', 'a, b, c': 'a, b, c', '#a, x#a, .b, x.b': '#a, x#a, .b, x.b', ('[p|a], p|*', (('p', 'uri'),)): '[p|a], p|*', } # do not parse as not complete self.do_equal_r(tests, att='selectorText') tests = { 'x|*': xml.dom.NamespaceErr, '': xml.dom.SyntaxErr, ' ': xml.dom.SyntaxErr, ',': xml.dom.SyntaxErr, 'a,': xml.dom.SyntaxErr, ',a': xml.dom.SyntaxErr, '/* 1 */,a': xml.dom.SyntaxErr, } # only set as not complete self.do_raise_r(tests, att='_setSelectorText') def test_reprANDstr(self): "SelectorList.__repr__(), .__str__()" sel = ('a, p|b', {'p': 'uri'}) s = css_parser.css.SelectorList(selectorText=sel) self.assertTrue(sel[0] in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertEqual(sel[0], s2.selectorText) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1563593644.0 css-parser-1.0.7/css_parser_tests/test_serialize.py0000644000175000017500000007153300000000000021613 0ustar00kovidkovid# -*- coding: utf-8 -*- """Testcases for css_parser.CSSSerializer""" from __future__ import absolute_import from __future__ import unicode_literals from . import basetest import css_parser class PreferencesTestCase(basetest.BaseTestCase): """ testcases for css_parser.serialize.Preferences """ def setUp(self): css_parser.ser.prefs.useDefaults() def tearDown(self): css_parser.ser.prefs.useDefaults() # def testkeepUnkownAtRules(self): # "Preferences.keepUnkownAtRules" # # py >=2.6 only # # v = sys.version_info; if v[0]*10+v[1] >= 26: # from warnings import catch_warnings # with catch_warnings(record=True) as log: # x = css_parser.ser.prefs.keepUnkownAtRules # # if log: # # unpack the only member of log # warning, = log # self.assertEqual(warning.category, DeprecationWarning) def test_resolveVariables(self): "Preferences.resolveVariables" self.assertEqual(css_parser.ser.prefs.resolveVariables, True) css_parser.ser.prefs.resolveVariables = False vars = ''' @variables { c1: red; c2: #0f0; px: 1px 2px; } ''' tests = { '''a {\n color: var(c1)\n }''': '''a {\n color: red\n }''', '''a {\n color: var(c1)\n; color: var( c2 ) }''': '''a {\n color: red;\n color: #0f0\n }''', '''a {\n margin: var(px)\n }''': '''a {\n margin: 1px 2px\n }''', '''@media all { a { margin: var(px) var(px); color: var(c1); left: var(unknown) } }''': '''@media all {\n a {\n margin: 1px 2px 1px 2px;\n color: red;\n left: var(unknown)\n }\n }''', } css_parser.ser.prefs.resolveVariables = True for test, exp in tests.items(): s = css_parser.parseString(vars + test) self.assertEqual(exp.encode(), s.cssText) css_parser.ser.prefs.resolveVariables = True def test_useDefaults(self): "Preferences.useDefaults()" css_parser.ser.prefs.useMinified() css_parser.ser.prefs.useDefaults() self.assertEqual(css_parser.ser.prefs.defaultAtKeyword, True) self.assertEqual(css_parser.ser.prefs.defaultPropertyName, True) self.assertEqual(css_parser.ser.prefs.defaultPropertyPriority, True) self.assertEqual(css_parser.ser.prefs.formatUnknownAtRules, True) self.assertEqual(css_parser.ser.prefs.importHrefFormat, None) self.assertEqual(css_parser.ser.prefs.indent, 4 * ' ') self.assertEqual(css_parser.ser.prefs.indentClosingBrace, True) self.assertEqual(css_parser.ser.prefs.keepAllProperties, True) self.assertEqual(css_parser.ser.prefs.keepComments, True) self.assertEqual(css_parser.ser.prefs.keepEmptyRules, False) self.assertEqual(css_parser.ser.prefs.keepUnknownAtRules, True) self.assertEqual(css_parser.ser.prefs.keepUsedNamespaceRulesOnly, False) self.assertEqual(css_parser.ser.prefs.lineNumbers, False) self.assertEqual(css_parser.ser.prefs.lineSeparator, '\n') self.assertEqual(css_parser.ser.prefs.linesAfterRules, 0 * css_parser.ser.prefs.lineSeparator) self.assertEqual(css_parser.ser.prefs.listItemSpacer, ' ') self.assertEqual(css_parser.ser.prefs.minimizeColorHash, True) self.assertEqual(css_parser.ser.prefs.omitLastSemicolon, True) self.assertEqual(css_parser.ser.prefs.omitLeadingZero, False) self.assertEqual(css_parser.ser.prefs.paranthesisSpacer, ' ') self.assertEqual(css_parser.ser.prefs.propertyNameSpacer, ' ') self.assertEqual(css_parser.ser.prefs.selectorCombinatorSpacer, ' ') self.assertEqual(css_parser.ser.prefs.spacer, ' ') self.assertEqual(css_parser.ser.prefs.validOnly, False) css = ''' /*1*/ @import url(x) tv , print; @namespace prefix "uri"; @namespace unused "unused"; @media all {} @media all { a {} } @media all { a { color: red; } } @page { left: 0; } a {} prefix|x, a + b > c ~ d , b { top : 1px ; font-family : arial ,'some' } ''' parsedcss = '''/*1*/ @import url(x) tv, print; @namespace prefix "uri"; @namespace unused "unused"; @media all { a { color: red } } @page { left: 0 } prefix|x, a + b > c ~ d, b { top: 1px; font-family: arial, "some" }''' s = css_parser.parseString(css) self.assertEqual(s.cssText, parsedcss.encode()) tests = { '0.1 .1 0.1px .1px 0.1% .1% +0.1 +.1 +0.1px +.1px +0.1% +.1% -0.1 -.1 -0.1px -.1px -0.1% -.1%': '0.1 0.1 0.1px 0.1px 0.1% 0.1% +0.1 +0.1 +0.1px +0.1px +0.1% +0.1% -0.1 -0.1 -0.1px -0.1px -0.1% -0.1%' } css_parser.ser.prefs.useDefaults() for test, exp in tests.items(): s = css_parser.parseString('a{x:%s}' % test) self.assertEqual(('a {\n x: %s\n }' % exp).encode(), s.cssText) def test_useMinified(self): "Preferences.useMinified()" css_parser.ser.prefs.useDefaults() css_parser.ser.prefs.useMinified() self.assertEqual(css_parser.ser.prefs.defaultAtKeyword, True) self.assertEqual(css_parser.ser.prefs.defaultPropertyName, True) self.assertEqual(css_parser.ser.prefs.importHrefFormat, 'string') self.assertEqual(css_parser.ser.prefs.indent, '') self.assertEqual(css_parser.ser.prefs.keepAllProperties, True) self.assertEqual(css_parser.ser.prefs.keepComments, False) self.assertEqual(css_parser.ser.prefs.keepEmptyRules, False) self.assertEqual(css_parser.ser.prefs.keepUnknownAtRules, False) self.assertEqual(css_parser.ser.prefs.keepUsedNamespaceRulesOnly, True) self.assertEqual(css_parser.ser.prefs.lineNumbers, False) self.assertEqual(css_parser.ser.prefs.lineSeparator, '') self.assertEqual(css_parser.ser.prefs.listItemSpacer, '') self.assertEqual(css_parser.ser.prefs.omitLastSemicolon, True) self.assertEqual(css_parser.ser.prefs.omitLeadingZero, True) self.assertEqual(css_parser.ser.prefs.paranthesisSpacer, '') self.assertEqual(css_parser.ser.prefs.propertyNameSpacer, '') self.assertEqual(css_parser.ser.prefs.selectorCombinatorSpacer, '') self.assertEqual(css_parser.ser.prefs.spacer, '') self.assertEqual(css_parser.ser.prefs.validOnly, False) css = ''' /*1*/ @import url(x) tv , print; @namespace prefix "uri"; @namespace unused "unused"; @media all {} @media all { a {} } @media all "name" { a { color: red; } } @page:left { left: 0 } a {} prefix|x, a + b > c ~ d , b { top : 1px ; font-family : arial , 'some' } @x x; ''' s = css_parser.parseString(css) css_parser.ser.prefs.keepUnknownAtRules = True self.assertEqual(s.cssText, '''@import"x"tv,print;@namespace prefix"uri";@media all"name"{a{color:red}}@page :left{left:0}prefix|x,a+b>c~d,b{top:1px;font-family:arial,"some"}@x x;'''.encode( ) ) css_parser.ser.prefs.keepUnknownAtRules = False self.assertEqual(s.cssText, '''@import"x"tv,print;@namespace prefix"uri";@media all"name"{a{color:red}}@page :left{left:0}prefix|x,a+b>c~d,b{top:1px;font-family:arial,"some"}'''.encode( ) ) # Values valuetests = { ' a a1 a-1 a-1a ': 'a a1 a-1 a-1a', 'a b 1 c 1em d -1em e': 'a b 1 c 1em d -1em e', ' 1em / 5 ': '1em/5', '1em/5': '1em/5', 'a 0 a .0 a 0.0 a -0 a -.0 a -0.0 a +0 a +.0 a +0.0': 'a 0 a 0 a 0 a 0 a 0 a 0 a 0 a 0 a 0', 'a 0px a .0px a 0.0px a -0px a -.0px a -0.0px a +0px a +.0px a +0.0px ': 'a 0 a 0 a 0 a 0 a 0 a 0 a 0 a 0 a 0', 'a 1 a .1 a 1.0 a 0.1 a -1 a -.1 a -1.0 a -0.1 a +1 a +.1 a +1.0': 'a 1 a .1 a 1 a .1 a -1 a -.1 a -1 a -.1 a +1 a +.1 a +1', ' url(x) f()': 'url(x) f()', '#112233': '#123', '#112234': '#112234', '#123': '#123', '#123 url() f()': '#123 url() f()', '1 +2 +3 -4': '1 +2 +3 -4', # ? '0.1 .1 0.1px .1px 0.1% .1% +0.1 +.1 +0.1px +.1px +0.1% +.1% -0.1 -.1 -0.1px -.1px -0.1% -.1%': '.1 .1 .1px .1px .1% .1% +.1 +.1 +.1px +.1px +.1% +.1% -.1 -.1 -.1px -.1px -.1% -.1%' } for test, exp in valuetests.items(): s = css_parser.parseString('a{x:%s}' % test) self.assertEqual(('a{x:%s}' % exp).encode(), s.cssText) def test_defaultAtKeyword(self): "Preferences.defaultAtKeyword" s = css_parser.parseString('@im\\port "x";') self.assertEqual('@import "x";'.encode(), s.cssText) css_parser.ser.prefs.defaultAtKeyword = True self.assertEqual('@import "x";'.encode(), s.cssText) css_parser.ser.prefs.defaultAtKeyword = False self.assertEqual('@im\\port "x";'.encode(), s.cssText) def test_defaultPropertyName(self): "Preferences.defaultPropertyName" css_parser.ser.prefs.keepAllProperties = False # does not actually work as once the name is set it is used also # if used with a backslash in it later... s = css_parser.parseString(r'a { c\olor: green; }') self.assertEqual('a {\n color: green\n }'.encode(), s.cssText) css_parser.ser.prefs.defaultPropertyName = True self.assertEqual('a {\n color: green\n }'.encode(), s.cssText) css_parser.ser.prefs.defaultPropertyName = False self.assertEqual('a {\n c\\olor: green\n }'.encode(), s.cssText) s = css_parser.parseString(r'a { color: red; c\olor: green; }') self.assertEqual('a {\n c\\olor: green\n }'.encode(), s.cssText) css_parser.ser.prefs.defaultPropertyName = False self.assertEqual('a {\n c\\olor: green\n }'.encode(), s.cssText) css_parser.ser.prefs.defaultPropertyName = True self.assertEqual('a {\n color: green\n }'.encode(), s.cssText) def test_defaultPropertyPriority(self): "Preferences.defaultPropertyPriority" css = 'a {\n color: green !IM\\portant\n }' s = css_parser.parseString(css) self.assertEqual(s.cssText, 'a {\n color: green !important\n }'.encode()) css_parser.ser.prefs.defaultPropertyPriority = False self.assertEqual(s.cssText, css.encode()) def test_formatUnknownAtRules(self): "Preferences.formatUnknownAtRules" css = '@meda screen and (max-width: 100px) { .someclass { color: blue } }' formatted = '''\ @meda screen and (max-width: 100px) { . someclass { color: blue } }''' s = css_parser.parseString(css) self.assertEqual(s.cssText, formatted.encode()) css_parser.ser.prefs.formatUnknownAtRules = False self.assertEqual(s.cssText, css.encode()) def test_importHrefFormat(self): "Preferences.importHrefFormat" r0 = css_parser.css.CSSImportRule() r0.cssText = '@import url("not");' r1 = css_parser.css.CSSImportRule() r1.cssText = '@import "str";' self.assertEqual('@import url(not);', r0.cssText) self.assertEqual('@import "str";', r1.cssText) css_parser.ser.prefs.importHrefFormat = 'string' self.assertEqual('@import "not";', r0.cssText) self.assertEqual('@import "str";', r1.cssText) css_parser.ser.prefs.importHrefFormat = 'uri' self.assertEqual('@import url(not);', r0.cssText) self.assertEqual('@import url(str);', r1.cssText) css_parser.ser.prefs.importHrefFormat = 'not defined' self.assertEqual('@import url(not);', r0.cssText) self.assertEqual('@import "str";', r1.cssText) def test_indent(self): "Preferences.ident" s = css_parser.parseString('a { left: 0 }') exp4 = '''a { left: 0 }''' exp1 = '''a { left: 0 }''' css_parser.ser.prefs.indent = ' ' self.assertEqual(exp1.encode(), s.cssText) css_parser.ser.prefs.indent = 4 * ' ' self.assertEqual(exp4.encode(), s.cssText) def test_indentClosingBrace(self): "Preferences.indentClosingBrace" s = css_parser.parseString('@media all {a {left: 0}}' 'b { top: 0 }' '@page { @top-left { content: "some content" } margin: .5em }' '@font-face { font-family: examplefont; }') expT = '''\ @media all { a { left: 0 } } b { top: 0 } @page { margin: 0.5em; @top-left { content: "some content" } } @font-face { font-family: examplefont }''' expF = '''\ @media all { a { left: 0 } } b { top: 0 } @page { margin: 0.5em; @top-left { content: "some content" } } @font-face { font-family: examplefont }''' css_parser.ser.prefs.useDefaults() self.assertEqual(expT.encode(), s.cssText) css_parser.ser.prefs.indentClosingBrace = False self.assertEqual(expF.encode(), s.cssText) def test_keepAllProperties(self): "Preferences.keepAllProperties" css = r'''a { color: pink; color: red; c\olor: blue; c\olor: green; }''' s = css_parser.parseString(css) # keep only last css_parser.ser.prefs.keepAllProperties = False self.assertEqual('a {\n color: green\n }'.encode(), s.cssText) # keep all css_parser.ser.prefs.keepAllProperties = True self.assertEqual( 'a {\n color: pink;\n color: red;\n c\\olor: blue;\n c\olor: green\n }'.encode(), s.cssText) def test_keepComments(self): "Preferences.keepComments" s = css_parser.parseString('/*1*/ a { /*2*/ }') css_parser.ser.prefs.keepComments = False self.assertEqual(''.encode(), s.cssText) css_parser.ser.prefs.keepEmptyRules = True self.assertEqual('a {}'.encode(), s.cssText) def test_keepEmptyRules(self): "Preferences.keepEmptyRules" # CSSStyleRule css = '''a {} a { /*1*/ } a { color: red }''' s = css_parser.parseString(css) css_parser.ser.prefs.useDefaults() css_parser.ser.prefs.keepEmptyRules = True self.assertEqual(css.encode(), s.cssText) css_parser.ser.prefs.keepEmptyRules = False self.assertEqual('a {\n /*1*/\n }\na {\n color: red\n }'.encode(), s.cssText) css_parser.ser.prefs.keepComments = False self.assertEqual('a {\n color: red\n }'.encode(), s.cssText) # CSSMediaRule css = '''@media tv { } @media all { /*1*/ } @media print { a {} } @media print { a { /*1*/ } } @media all { a { color: red } }''' s = css_parser.parseString(css) css_parser.ser.prefs.useDefaults() css_parser.ser.prefs.keepEmptyRules = True self.assertEqual(css.encode(), s.cssText) css_parser.ser.prefs.keepEmptyRules = False self.assertEqual('''@media all { /*1*/ } @media print { a { /*1*/ } } @media all { a { color: red } }'''.encode(), s.cssText) css_parser.ser.prefs.keepComments = False self.assertEqual('''@media all { a { color: red } }'''.encode(), s.cssText) def test_keepUnknownAtRules(self): "Preferences.keepUnknownAtRules" tests = { '''@three-dee { @background-lighting { azimuth: 30deg; elevation: 190deg; } h1 { color: red } } h1 { color: blue }''': ('''@three-dee { @background-lighting { azimuth: 30deg; elevation: 190deg; } h1 { color: red } } h1 { color: blue }''', '''h1 { color: blue }''') } for test in tests: s = css_parser.parseString(test) expwith, expwithout = tests[test] css_parser.ser.prefs.keepUnknownAtRules = True self.assertEqual(s.cssText, expwith.encode()) css_parser.ser.prefs.keepUnknownAtRules = False self.assertEqual(s.cssText, expwithout.encode()) def test_keepUsedNamespaceRulesOnly(self): "Preferences.keepUsedNamespaceRulesOnly" tests = { # default == prefix => both are combined '@namespace p "u"; @namespace "u"; p|a, a {top: 0}': ('@namespace "u";\na, a {\n top: 0\n }', '@namespace "u";\na, a {\n top: 0\n }'), '@namespace "u"; @namespace p "u"; p|a, a {top: 0}': ('@namespace p "u";\np|a, p|a {\n top: 0\n }', '@namespace p "u";\np|a, p|a {\n top: 0\n }'), # default and prefix '@namespace p "u"; @namespace "d"; p|a, a {top: 0}': ('@namespace p "u";\n@namespace "d";\np|a, a {\n top: 0\n }', '@namespace p "u";\n@namespace "d";\np|a, a {\n top: 0\n }'), # prefix only '@namespace p "u"; @namespace "d"; p|a {top: 0}': ('@namespace p "u";\n@namespace "d";\np|a {\n top: 0\n }', '@namespace p "u";\np|a {\n top: 0\n }'), # default only '@namespace p "u"; @namespace "d"; a {top: 0}': ('@namespace p "u";\n@namespace "d";\na {\n top: 0\n }', '@namespace "d";\na {\n top: 0\n }'), # prefix-ns only '@namespace p "u"; @namespace d "d"; p|a {top: 0}': ('@namespace p "u";\n@namespace d "d";\np|a {\n top: 0\n }', '@namespace p "u";\np|a {\n top: 0\n }'), } for test in tests: s = css_parser.parseString(test) expwith, expwithout = tests[test] css_parser.ser.prefs.keepUsedNamespaceRulesOnly = False self.assertEqual(s.cssText, expwith.encode()) css_parser.ser.prefs.keepUsedNamespaceRulesOnly = True self.assertEqual(s.cssText, expwithout.encode()) def test_lineNumbers(self): "Preferences.lineNumbers" s = css_parser.parseString('a {top: 1; left: 2}') exp0 = '''a { top: 1; left: 2 }''' exp1 = '''1: a { 2: top: 1; 3: left: 2 4: }''' self.assertEqual(False, css_parser.ser.prefs.lineNumbers) self.assertEqual(exp0.encode(), s.cssText) css_parser.ser.prefs.lineNumbers = True self.assertEqual(True, css_parser.ser.prefs.lineNumbers) self.assertEqual(exp1.encode(), s.cssText) def test_lineSeparator(self): "Preferences.lineSeparator" s = css_parser.parseString('a { x:1;y:2}') self.assertEqual('a {\n x: 1;\n y: 2\n }'.encode(), s.cssText) # cannot be indented as no split possible css_parser.ser.prefs.lineSeparator = '' self.assertEqual('a {x: 1;y: 2 }'.encode(), s.cssText) # no valid css but should work css_parser.ser.prefs.lineSeparator = 'XXX' self.assertEqual('a {XXX x: 1;XXX y: 2XXX }'.encode(), s.cssText) def test_linesAfterRule(self): "Preferences.linesAfterRule" s = css_parser.parseString('div {color:red;} @media screen {.aclass {width: 200px}}') expected_default = '''\ div { color: red } @media screen { .aclass { width: 200px } }''' self.assertEqual(expected_default.encode(), s.cssText) css_parser.ser.prefs.linesAfterRules = 1 * '\n' expected_changed = '''\ div { color: red } @media screen { .aclass { width: 200px } } ''' self.assertEqual(expected_changed.encode(), s.cssText) def test_listItemSpacer(self): "Preferences.listItemSpacer" css_parser.ser.prefs.keepEmptyRules = True css = ''' @import "x" print, tv; a, b {}''' s = css_parser.parseString(css) self.assertEqual('@import "x" print, tv;\na, b {}'.encode(), s.cssText) css_parser.ser.prefs.listItemSpacer = '' self.assertEqual('@import "x" print,tv;\na,b {}'.encode(), s.cssText) def test_minimizeColorHash(self): "Preferences.minimizeColorHash" css = 'a { color: #ffffff }' s = css_parser.parseString(css) self.assertEqual('a {\n color: #fff\n }'.encode(), s.cssText) css_parser.ser.prefs.minimizeColorHash = False self.assertEqual('a {\n color: #ffffff\n }'.encode(), s.cssText) def test_omitLastSemicolon(self): "Preferences.omitLastSemicolon" css = 'a { x: 1; y: 2 }' s = css_parser.parseString(css) self.assertEqual('a {\n x: 1;\n y: 2\n }'.encode(), s.cssText) css_parser.ser.prefs.omitLastSemicolon = False self.assertEqual('a {\n x: 1;\n y: 2;\n }'.encode(), s.cssText) def test_normalizedVarNames(self): "Preferences.normalizedVarNames" css_parser.ser.prefs.resolveVariables = False css = '@variables { A: 1 }' s = css_parser.parseString(css) self.assertEqual('@variables {\n a: 1\n }'.encode(), s.cssText) css_parser.ser.prefs.normalizedVarNames = False self.assertEqual('@variables {\n A: 1\n }'.encode(), s.cssText) css_parser.ser.prefs.resolveVariables = True def test_paranthesisSpacer(self): "Preferences.paranthesisSpacer" css = 'a { x: 1; y: 2 }' s = css_parser.parseString(css) self.assertEqual('a {\n x: 1;\n y: 2\n }'.encode(), s.cssText) css_parser.ser.prefs.paranthesisSpacer = '' self.assertEqual('a{\n x: 1;\n y: 2\n }'.encode(), s.cssText) def test_propertyNameSpacer(self): "Preferences.propertyNameSpacer" css = 'a { x: 1; y: 2 }' s = css_parser.parseString(css) self.assertEqual('a {\n x: 1;\n y: 2\n }'.encode(), s.cssText) css_parser.ser.prefs.propertyNameSpacer = '' self.assertEqual('a {\n x:1;\n y:2\n }'.encode(), s.cssText) def test_selectorCombinatorSpacer(self): "Preferences.selectorCombinatorSpacer" s = css_parser.css.Selector(selectorText='a+b>c~d e') self.assertEqual('a + b > c ~ d e', s.selectorText) css_parser.ser.prefs.selectorCombinatorSpacer = '' self.assertEqual('a+b>c~d e', s.selectorText) def test_spacer(self): css_parser.ser.prefs.spacer = '' tests = { '@font-face {a:1}': '@font-face {\n a: 1\n }', '@import url( a );': '@import url(a);', '@media all{a{color:red}}': '@media all {\n a {\n color: red\n }\n }', '@namespace "a";': '@namespace"a";', '@namespace a "a";': '@namespace a"a";', '@page :left { a :1 }': '@page :left {\n a: 1\n }', '@x x;': '@x x;', '@import"x"tv': '@import"x"tv;' # ? } for css, exp in tests.items(): self.assertEqual(exp.encode(), css_parser.parseString(css).cssText) def test_validOnly(self): "Preferences.validOnly" # Property p = css_parser.css.Property('color', '1px') self.assertEqual(p.cssText, 'color: 1px') p.value = '1px' css_parser.ser.prefs.validOnly = True self.assertEqual(p.cssText, '') css_parser.ser.prefs.validOnly = False self.assertEqual(p.cssText, 'color: 1px') # CSSStyleDeclaration has no actual property valid # but is empty if containing invalid Properties only s = css_parser.css.CSSStyleDeclaration() s.cssText = 'left: x;top: x' self.assertEqual(s.cssText, 'left: x;\ntop: x') css_parser.ser.prefs.validOnly = True self.assertEqual(s.cssText, '') css_parser.ser.prefs.useDefaults() css_parser.ser.prefs.keepComments = False css_parser.ser.prefs.validOnly = True tests = { 'h1 { color: red; rotation: 70minutes }': 'h1 {\n color: red;\n }', '''img { float: left } /* correct CSS 2.1 */ img { float: left here } /* "here" is not a value of 'float' */ img { background: "red" } /* keywords cannot be quoted */ img { border-width: 3 } /* a unit must be specified for length values */''': 'img {\n float: left\n }' } self.do_equal_p(tests, raising=False) class CSSSerializerTestCase(basetest.BaseTestCase): """ testcases for css_parser.CSSSerializer """ def setUp(self): css_parser.ser.prefs.useDefaults() def tearDown(self): css_parser.ser.prefs.useDefaults() def test_canonical(self): tests = { '''1''': '''1''', # => remove + '''+1''': '''+1''', # 0 => remove unit '''0''': '''0''', '''+0''': '''0''', '''-0''': '''0''', '''0.0''': '''0''', '''00.0''': '''0''', '''00.0px''': '''0''', '''00.0pc''': '''0''', '''00.0em''': '''0''', '''00.0ex''': '''0''', '''00.0cm''': '''0''', '''00.0mm''': '''0''', '''00.0in''': '''0''', # 0 => keep unit '''00.0%''': '''0%''', '''00.0ms''': '''0ms''', '''00.0s''': '''0s''', '''00.0khz''': '''0khz''', '''00.0hz''': '''0hz''', '''00.0khz''': '''0khz''', '''00.0deg''': '''0deg''', '''00.0rad''': '''0rad''', '''00.0grad''': '''0grad''', '''00.0xx''': '''0xx''', # 11. '''a, 'b"', serif''': r'''a, "b\"", serif''', # SHOULD: \[ => [ but keep! r"""url('h)i') '\[\]'""": r'''url("h)i") "\[\]"''', '''rgb(18, 52, 86)''': '''rgb(18, 52, 86)''', '''#123456''': '''#123456''', # SHOULD => #112233 '''#112233''': '''#123''', # SHOULD => #000000 # u'rgba(000001, 0, 0, 1)': u'#000' } for test, exp in tests.items(): v = css_parser.css.PropertyValue(test) self.assertEqual(exp, v.cssText) def test_CSSStyleSheet(self): "CSSSerializer.do_CSSStyleSheet" css = '/* κουÏος */' sheet = css_parser.parseString(css) ans = sheet.cssText if isinstance(ans, bytes): ans = ans.decode('utf-8') self.assertEqual(css, ans) css = '@charset "utf-8";\n/* κουÏος */' sheet = css_parser.parseString(css) ans = sheet.cssText if isinstance(ans, bytes): ans = ans.decode('utf-8') self.assertEqual(css, ans) sheet.cssRules[0].encoding = 'ascii' self.assertEqual('@charset "ascii";\n/* \\3BA \\3BF \\3C5 \\3C1 \\3BF \\3C2 */'.encode(), sheet.cssText) def test_Property(self): "CSSSerializer.do_Property" name = "color" value = "red" priority = "!important" s = css_parser.css.property.Property( name=name, value=value, priority=priority) self.assertEqual('color: red !important', css_parser.ser.do_Property(s)) s = css_parser.css.property.Property( name=name, value=value) self.assertEqual('color: red', css_parser.ser.do_Property(s)) def test_escapestring(self): "CSSSerializer._escapestring" # '"\a\22\27"' css = r'''@import url("ABC\a"); @import "ABC\a"; @import 'ABC\a'; a[href='"\a\22\27"'] { a: "\a\d\c"; b: "\a \d \c "; c: "\""; d: "\22"; e: '\''; f: "\\"; g: "2\\ 1\ 2\\"; content: '\27'; }''' # exp = ur'''@import url("ABC\a "); # @import "ABC\a"; # @import "ABC\a"; # a[href="\"\a\22\27\""] { # a: "\a\d\c"; # b: "\a \d \c "; # c: "\""; # d: "\22"; # e: "'"; # f: "\\"; # g: "2\\ 1\ 2\\"; # content: "\27" # }''' exp = r'''@import url("ABC\a "); @import "ABC\a "; @import "ABC\a "; a[href="\"\a \"'\""] { a: "\a \d \c "; b: "\a \d \c "; c: "\""; d: "\""; e: "'"; f: "\\"; g: "2\\ 1\ 2\\"; content: "'" }''' sheet = css_parser.parseString(css) self.assertEqual(sheet.cssText, exp.encode()) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_settings.py0000644000175000017500000000167600000000000021465 0ustar00kovidkovid"""Testcases for css_parser.settings""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_csscharsetrule.py 1356 2008-07-13 17:29:09Z cthedot $' from . import test_cssrule import css_parser import css_parser.settings class Settings(test_cssrule.CSSRuleTestCase): def test_set(self): "settings.set()" css_parser.ser.prefs.useMinified() text = 'a {filter: progid:DXImageTransform.Microsoft.BasicImage( rotation = 90 )}' self.assertEqual(css_parser.parseString(text).cssText, ''.encode()) css_parser.settings.set('DXImageTransform.Microsoft', True) self.assertEqual(css_parser.parseString(text).cssText, 'a{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=90)}'.encode()) css_parser.ser.prefs.useDefaults() if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_stylesheet.py0000644000175000017500000000327500000000000022013 0ustar00kovidkovid"""Testcases for css_parser.stylesheets.StyleSheet""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_csspagerule.py 1869 2009-10-17 19:37:40Z cthedot $' import xml.dom from . import basetest import css_parser class StyleSheetTestCase(basetest.BaseTestCase): def test_init(self): "StyleSheet.__init__()" s = css_parser.stylesheets.StyleSheet() self.assertEqual(s.type, 'text/css') self.assertEqual(s.href, None) self.assertEqual(s.media, None) self.assertEqual(s.title, '') self.assertEqual(s.ownerNode, None) self.assertEqual(s.parentStyleSheet, None) self.assertEqual(s.alternate, False) self.assertEqual(s.disabled, False) s = css_parser.stylesheets.StyleSheet(type='unknown', href='test.css', media=None, title='title', ownerNode=None, parentStyleSheet=None, alternate=True, disabled=True) self.assertEqual(s.type, 'unknown') self.assertEqual(s.href, 'test.css') self.assertEqual(s.media, None) self.assertEqual(s.title, 'title') self.assertEqual(s.ownerNode, None) self.assertEqual(s.parentStyleSheet, None) self.assertEqual(s.alternate, True) self.assertEqual(s.disabled, True) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1563593644.0 css-parser-1.0.7/css_parser_tests/test_tokenize2.py0000644000175000017500000010355000000000000021531 0ustar00kovidkovid# -*- coding: utf-8 -*- """Testcases for new css_parser.tokenize.Tokenizer TODO: old tests as new ones are **not complete**! """ from __future__ import absolute_import from __future__ import unicode_literals import sys import xml.dom from . import basetest import css_parser.tokenize2 as tokenize2 from css_parser.tokenize2 import * class TokenizerTestCase(basetest.BaseTestCase): testsall = { # IDENT 'äöü߀': [('IDENT', 'äöü߀', 1, 1)], ' a ': [('S', ' ', 1, 1), ('IDENT', 'a', 1, 2), ('S', ' ', 1, 3)], '_a': [('IDENT', '_a', 1, 1)], '-a': [('IDENT', '-a', 1, 1)], 'aA-_\200\377': [('IDENT', 'aA-_\200\377', 1, 1)], 'a1': [('IDENT', 'a1', 1, 1)], # escapes must end with S or max 6 digits: '\\44 b': [('IDENT', 'Db', 1, 1)], '\\44 b': [('IDENT', 'D', 1, 1), ('S', ' ', 1, 5), ('IDENT', 'b', 1, 6)], '\\44\nb': [('IDENT', 'Db', 1, 1)], '\\44\rb': [('IDENT', 'Db', 1, 1)], '\\44\fb': [('IDENT', 'Db', 1, 1)], '\\44\n*': [('IDENT', 'D', 1, 1), ('CHAR', '*', 2, 1)], '\\44 a': [('IDENT', 'D', 1, 1), ('S', ' ', 1, 5), ('IDENT', 'a', 1, 6)], # TODO: # Note that this means that a "real" space after the escape sequence # must itself either be escaped or doubled: '\\44\\ x': [('IDENT', 'D\\ x', 1, 1)], '\\44 ': [('IDENT', 'D', 1, 1), ('S', ' ', 1, 5)], r'\44': [('IDENT', 'D', 1, 1)], r'\\': [('IDENT', r'\\', 1, 1)], r'\{': [('IDENT', r'\{', 1, 1)], r'\"': [('IDENT', r'\"', 1, 1)], r'\(': [('IDENT', r'\(', 1, 1)], r'\1 \22 \333 \4444 \55555 \666666 \777777 7 \7777777': [( ('IDENT', '\x01"\u0333\u4444\U00055555\\666666 \\777777 7', 1, 1) if sys.maxunicode > 0x10000 else ('IDENT', '\x01"\u0333\u4444\\55555 \\666666 \\777777 7', 1, 1) ), ('S', ' ', 1, 43), ('IDENT', '\\7777777', 1, 44) ], # Not a function, important for media queries 'and(': [('IDENT', 'and', 1, 1), ('CHAR', '(', 1, 4)], '\\1 b': [('IDENT', '\x01b', 1, 1)], '\\44 b': [('IDENT', 'Db', 1, 1)], '\\123 b': [('IDENT', '\u0123b', 1, 1)], '\\1234 b': [('IDENT', '\u1234b', 1, 1)], '\\12345 b': [( ('IDENT', '\U00012345b', 1, 1) if sys.maxunicode > 0x10000 else ('IDENT', '\\12345 b', 1, 1) )], '\\123456 b': [('IDENT', '\\123456 b', 1, 1)], '\\1234567 b': [('IDENT', '\\1234567', 1, 1), ('S', ' ', 1, 9), ('IDENT', 'b', 1, 10)], '\\{\\}\\(\\)\\[\\]\\#\\@\\.\\,': [('IDENT', '\\{\\}\\(\\)\\[\\]\\#\\@\\.\\,', 1, 1)], # STRING ' "" ': [('S', ' ', 1, 1), ('STRING', '""', 1, 2), ('S', ' ', 1, 4)], ' "\'" ': [('S', ' ', 1, 1), ('STRING', '"\'"', 1, 2), ('S', ' ', 1, 5)], " '' ": [('S', ' ', 1, 1), ('STRING', "''", 1, 2), ('S', ' ', 1, 4)], " '' ": [('S', ' ', 1, 1), ('STRING', "''", 1, 2), ('S', ' ', 1, 4)], # until 0.9.5.x # u"'\\\n'": [('STRING', u"'\\\n'", 1, 1)], # u"'\\\n\\\n\\\n'": [('STRING', u"'\\\n\\\n\\\n'", 1, 1)], # u"'\\\f'": [('STRING', u"'\\\f'", 1, 1)], # u"'\\\r'": [('STRING', u"'\\\r'", 1, 1)], # u"'\\\r\n'": [('STRING', u"'\\\r\n'", 1, 1)], # u"'1\\\n2'": [('STRING', u"'1\\\n2'", 1, 1)], # from 0.9.6a0 escaped nl is removed from string "'\\\n'": [('STRING', "''", 1, 1)], "'\\\n\\\n\\\n'": [('STRING', "''", 1, 1)], "'\\\f'": [('STRING', "''", 1, 1)], "'\\\r'": [('STRING', "''", 1, 1)], "'1\\\n2'": [('STRING', "'12'", 1, 1)], "'1\\\r\n2'": [('STRING', "'12'", 1, 1)], # ur'"\0020|\0020"': [('STRING', u'"\\0020|\\0020"', 1, 1)], r'"\61|\0061"': [('STRING', '"a|a"', 1, 1)], # HASH ' #a ': [('S', ' ', 1, 1), ('HASH', '#a', 1, 2), ('S', ' ', 1, 4)], '#ccc': [('HASH', '#ccc', 1, 1)], '#111': [('HASH', '#111', 1, 1)], '#a1a1a1': [('HASH', '#a1a1a1', 1, 1)], '#1a1a1a': [('HASH', '#1a1a1a', 1, 1)], # NUMBER, for plus see CSS3 ' 0 ': [('S', ' ', 1, 1), ('NUMBER', '0', 1, 2), ('S', ' ', 1, 3)], ' 0.1 ': [('S', ' ', 1, 1), ('NUMBER', '0.1', 1, 2), ('S', ' ', 1, 5)], ' .0 ': [('S', ' ', 1, 1), ('NUMBER', '.0', 1, 2), ('S', ' ', 1, 4)], ' -0 ': [('S', ' ', 1, 1), #('CHAR', u'-', 1, 2), #('NUMBER', u'0', 1, 3), ('NUMBER', '-0', 1, 2), ('S', ' ', 1, 4)], # PERCENTAGE ' 0% ': [('S', ' ', 1, 1), ('PERCENTAGE', '0%', 1, 2), ('S', ' ', 1, 4)], ' .5% ': [('S', ' ', 1, 1), ('PERCENTAGE', '.5%', 1, 2), ('S', ' ', 1, 5)], # URI ' url() ': [('S', ' ', 1, 1), ('URI', 'url()', 1, 2), ('S', ' ', 1, 7)], ' url(a) ': [('S', ' ', 1, 1), ('URI', 'url(a)', 1, 2), ('S', ' ', 1, 8)], ' url("a") ': [('S', ' ', 1, 1), ('URI', 'url("a")', 1, 2), ('S', ' ', 1, 10)], ' url( a ) ': [('S', ' ', 1, 1), ('URI', 'url( a )', 1, 2), ('S', ' ', 1, 10)], # UNICODE-RANGE # CDO ' "': [('STRING', '""', 1, 7)], # CDC ' --> ': [('S', ' ', 1, 1), ('CDC', '-->', 1, 2), ('S', ' ', 1, 5)], # S ' ': [('S', ' ', 1, 1)], ' ': [('S', ' ', 1, 1)], '\r': [('S', '\r', 1, 1)], '\n': [('S', '\n', 1, 1)], '\r\n': [('S', '\r\n', 1, 1)], '\f': [('S', '\f', 1, 1)], '\r': [('S', '\r', 1, 1)], '\t': [('S', '\t', 1, 1)], '\r\n\r\n\f\t ': [('S', '\r\n\r\n\f\t ', 1, 1)], # COMMENT, for incomplete see later '/*x*/ ': [('COMMENT', '/*x*/', 1, 1), ('S', ' ', 1, 6)], # FUNCTION ' x( ': [('S', ' ', 1, 1), ('FUNCTION', 'x(', 1, 2), ('S', ' ', 1, 4)], # INCLUDES ' ~= ': [('S', ' ', 1, 1), ('INCLUDES', '~=', 1, 2), ('S', ' ', 1, 4)], '~==': [('INCLUDES', '~=', 1, 1), ('CHAR', '=', 1, 3)], # DASHMATCH ' |= ': [('S', ' ', 1, 1), ('DASHMATCH', '|=', 1, 2), ('S', ' ', 1, 4)], '|==': [('DASHMATCH', '|=', 1, 1), ('CHAR', '=', 1, 3)], # CHAR ' @ ': [('S', ' ', 1, 1), ('CHAR', '@', 1, 2), ('S', ' ', 1, 3)], # --- overwritten for CSS 2.1 --- # LBRACE ' { ': [('S', ' ', 1, 1), ('CHAR', '{', 1, 2), ('S', ' ', 1, 3)], # PLUS ' + ': [('S', ' ', 1, 1), ('CHAR', '+', 1, 2), ('S', ' ', 1, 3)], # GREATER ' > ': [('S', ' ', 1, 1), ('CHAR', '>', 1, 2), ('S', ' ', 1, 3)], # COMMA ' , ': [('S', ' ', 1, 1), ('CHAR', ',', 1, 2), ('S', ' ', 1, 3)], # class ' . ': [('S', ' ', 1, 1), ('CHAR', '.', 1, 2), ('S', ' ', 1, 3)], } tests3 = { # UNICODE-RANGE ' u+0 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+0', 1, 2), ('S', ' ', 1, 5)], ' u+01 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+01', 1, 2), ('S', ' ', 1, 6)], ' u+012 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+012', 1, 2), ('S', ' ', 1, 7)], ' u+0123 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+0123', 1, 2), ('S', ' ', 1, 8)], ' u+01234 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+01234', 1, 2), ('S', ' ', 1, 9)], ' u+012345 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+012345', 1, 2), ('S', ' ', 1, 10)], ' u+0123456 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+012345', 1, 2), ('NUMBER', '6', 1, 10), ('S', ' ', 1, 11)], ' U+123456 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'U+123456', 1, 2), ('S', ' ', 1, 10)], ' \\55+abcdef ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'U+abcdef', 1, 2), ('S', ' ', 1, 12)], ' \\75+abcdef ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+abcdef', 1, 2), ('S', ' ', 1, 12)], ' u+0-1 ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+0-1', 1, 2), ('S', ' ', 1, 7)], ' u+0-1, u+123456-abcdef ': [('S', ' ', 1, 1), ('UNICODE-RANGE', 'u+0-1', 1, 2), ('CHAR', ',', 1, 7), ('S', ' ', 1, 8), ('UNICODE-RANGE', 'u+123456-abcdef', 1, 9), ('S', ' ', 1, 24)], # specials 'c\\olor': [('IDENT', 'c\\olor', 1, 1)], # u'-1': [('CHAR', u'-', 1, 1), ('NUMBER', u'1', 1, 2)], # u'-1px': [('CHAR', u'-', 1, 1), ('DIMENSION', u'1px', 1, 2)], '-1': [('NUMBER', '-1', 1, 1)], '-1px': [('DIMENSION', '-1px', 1, 1)], # ATKEYWORD ' @x ': [('S', ' ', 1, 1), ('ATKEYWORD', '@x', 1, 2), ('S', ' ', 1, 4)], '@X': [('ATKEYWORD', '@X', 1, 1)], '@\\x': [('ATKEYWORD', '@\\x', 1, 1)], # - '@1x': [('CHAR', '@', 1, 1), ('DIMENSION', '1x', 1, 2)], # DIMENSION ' 0px ': [('S', ' ', 1, 1), ('DIMENSION', '0px', 1, 2), ('S', ' ', 1, 5)], ' 1s ': [('S', ' ', 1, 1), ('DIMENSION', '1s', 1, 2), ('S', ' ', 1, 4)], '0.2EM': [('DIMENSION', '0.2EM', 1, 1)], '1p\\x': [('DIMENSION', '1p\\x', 1, 1)], '1PX': [('DIMENSION', '1PX', 1, 1)], # NUMBER ' - 0 ': [('S', ' ', 1, 1), ('CHAR', '-', 1, 2), ('S', ' ', 1, 3), ('NUMBER', '0', 1, 4), ('S', ' ', 1, 5)], ' + 0 ': [('S', ' ', 1, 1), ('CHAR', '+', 1, 2), ('S', ' ', 1, 3), ('NUMBER', '0', 1, 4), ('S', ' ', 1, 5)], # PREFIXMATCH ' ^= ': [('S', ' ', 1, 1), ('PREFIXMATCH', '^=', 1, 2), ('S', ' ', 1, 4)], '^==': [('PREFIXMATCH', '^=', 1, 1), ('CHAR', '=', 1, 3)], # SUFFIXMATCH ' $= ': [('S', ' ', 1, 1), ('SUFFIXMATCH', '$=', 1, 2), ('S', ' ', 1, 4)], '$==': [('SUFFIXMATCH', '$=', 1, 1), ('CHAR', '=', 1, 3)], # SUBSTRINGMATCH ' *= ': [('S', ' ', 1, 1), ('SUBSTRINGMATCH', '*=', 1, 2), ('S', ' ', 1, 4)], '*==': [('SUBSTRINGMATCH', '*=', 1, 1), ('CHAR', '=', 1, 3)], # BOM only at start # u'\xFEFF ': [('BOM', u'\xfeFF', 1, 1), # ('S', u' ', 1, 1)], # u' \xFEFF ': [('S', u' ', 1, 1), # ('IDENT', u'\xfeFF', 1, 2), # ('S', u' ', 1, 5)], '\xfe\xff ': [('BOM', '\xfe\xff', 1, 1), ('S', ' ', 1, 1)], ' \xfe\xff ': [('S', ' ', 1, 1), ('IDENT', '\xfe\xff', 1, 2), ('S', ' ', 1, 4)], '\xef\xbb\xbf ': [('BOM', '\xef\xbb\xbf', 1, 1), ('S', ' ', 1, 1)], ' \xef\xbb\xbf ': [('S', ' ', 1, 1), ('IDENT', '\xef\xbb\xbf', 1, 2), ('S', ' ', 1, 5)], } tests2 = { # escapes work not for a-f! # IMPORT_SYM ' @import ': [('S', ' ', 1, 1), ('IMPORT_SYM', '@import', 1, 2), ('S', ' ', 1, 9)], '@IMPORT': [('IMPORT_SYM', '@IMPORT', 1, 1)], '@\\49\r\nMPORT': [('IMPORT_SYM', '@\\49\r\nMPORT', 1, 1)], r'@\i\m\p\o\r\t': [('IMPORT_SYM', r'@\i\m\p\o\r\t', 1, 1)], r'@\I\M\P\O\R\T': [('IMPORT_SYM', r'@\I\M\P\O\R\T', 1, 1)], r'@\49 \04d\0050\0004f\000052\54': [('IMPORT_SYM', r'@\49 \04d\0050\0004f\000052\54', 1, 1)], r'@\69 \06d\0070\0006f\000072\74': [('IMPORT_SYM', r'@\69 \06d\0070\0006f\000072\74', 1, 1)], # PAGE_SYM ' @page ': [('S', ' ', 1, 1), ('PAGE_SYM', '@page', 1, 2), ('S', ' ', 1, 7)], '@PAGE': [('PAGE_SYM', '@PAGE', 1, 1)], r'@\pa\ge': [('PAGE_SYM', r'@\pa\ge', 1, 1)], r'@\PA\GE': [('PAGE_SYM', r'@\PA\GE', 1, 1)], r'@\50\41\47\45': [('PAGE_SYM', r'@\50\41\47\45', 1, 1)], r'@\70\61\67\65': [('PAGE_SYM', r'@\70\61\67\65', 1, 1)], # MEDIA_SYM ' @media ': [('S', ' ', 1, 1), ('MEDIA_SYM', '@media', 1, 2), ('S', ' ', 1, 8)], '@MEDIA': [('MEDIA_SYM', '@MEDIA', 1, 1)], r'@\med\ia': [('MEDIA_SYM', r'@\med\ia', 1, 1)], r'@\MED\IA': [('MEDIA_SYM', r'@\MED\IA', 1, 1)], '@\\4d\n\\45\r\\44\t\\49\r\nA': [('MEDIA_SYM', '@\\4d\n\\45\r\\44\t\\49\r\nA', 1, 1)], '@\\4d\n\\45\r\\44\t\\49\r\\41\f': [('MEDIA_SYM', '@\\4d\n\\45\r\\44\t\\49\r\\41\f', 1, 1)], '@\\6d\n\\65\r\\64\t\\69\r\\61\f': [('MEDIA_SYM', '@\\6d\n\\65\r\\64\t\\69\r\\61\f', 1, 1)], # FONT_FACE_SYM ' @font-face ': [('S', ' ', 1, 1), ('FONT_FACE_SYM', '@font-face', 1, 2), ('S', ' ', 1, 12)], '@FONT-FACE': [('FONT_FACE_SYM', '@FONT-FACE', 1, 1)], r'@f\o\n\t\-face': [('FONT_FACE_SYM', r'@f\o\n\t\-face', 1, 1)], r'@F\O\N\T\-FACE': [('FONT_FACE_SYM', r'@F\O\N\T\-FACE', 1, 1)], # TODO: "-" as hex! r'@\46\4f\4e\54\-\46\41\43\45': [('FONT_FACE_SYM', r'@\46\4f\4e\54\-\46\41\43\45', 1, 1)], r'@\66\6f\6e\74\-\66\61\63\65': [('FONT_FACE_SYM', r'@\66\6f\6e\74\-\66\61\63\65', 1, 1)], # CHARSET_SYM only if "@charset "! '@charset ': [('CHARSET_SYM', '@charset ', 1, 1), ('S', ' ', 1, 10)], ' @charset ': [('S', ' ', 1, 1), ('CHARSET_SYM', '@charset ', 1, 2), # not at start ('S', ' ', 1, 11)], '@charset': [('ATKEYWORD', '@charset', 1, 1)], # no ending S '@CHARSET ': [('ATKEYWORD', '@CHARSET', 1, 1), # uppercase ('S', ' ', 1, 9)], '@cha\\rset ': [('ATKEYWORD', '@cha\\rset', 1, 1), # not literal ('S', ' ', 1, 10)], # NAMESPACE_SYM ' @namespace ': [('S', ' ', 1, 1), ('NAMESPACE_SYM', '@namespace', 1, 2), ('S', ' ', 1, 12)], r'@NAMESPACE': [('NAMESPACE_SYM', r'@NAMESPACE', 1, 1)], r'@\na\me\s\pace': [('NAMESPACE_SYM', r'@\na\me\s\pace', 1, 1)], r'@\NA\ME\S\PACE': [('NAMESPACE_SYM', r'@\NA\ME\S\PACE', 1, 1)], r'@\4e\41\4d\45\53\50\41\43\45': [('NAMESPACE_SYM', r'@\4e\41\4d\45\53\50\41\43\45', 1, 1)], r'@\6e\61\6d\65\73\70\61\63\65': [('NAMESPACE_SYM', r'@\6e\61\6d\65\73\70\61\63\65', 1, 1)], # ATKEYWORD ' @unknown ': [('S', ' ', 1, 1), ('ATKEYWORD', '@unknown', 1, 2), ('S', ' ', 1, 10)], # STRING # strings with linebreak in it ' "\\na"\na': [('S', ' ', 1, 1), ('STRING', '"\\na"', 1, 2), ('S', '\n', 1, 7), ('IDENT', 'a', 2, 1)], " '\\na'\na": [('S', ' ', 1, 1), ('STRING', "'\\na'", 1, 2), ('S', '\n', 1, 7), ('IDENT', 'a', 2, 1)], ' "\\r\\n\\t\\n\\ra"a': [('S', ' ', 1, 1), ('STRING', '"\\r\\n\\t\\n\\ra"', 1, 2), ('IDENT', 'a', 1, 15)], # IMPORTANT_SYM is not IDENT!!! ' !important ': [('S', ' ', 1, 1), ('CHAR', '!', 1, 2), ('IDENT', 'important', 1, 3), ('S', ' ', 1, 12)], '! /*1*/ important ': [ ('CHAR', '!', 1, 1), ('S', ' ', 1, 2), ('COMMENT', '/*1*/', 1, 3), ('S', ' ', 1, 8), ('IDENT', 'important', 1, 9), ('S', ' ', 1, 18)], '! important': [('CHAR', '!', 1, 1), ('S', ' ', 1, 2), ('IDENT', 'important', 1, 3)], '!\n\timportant': [('CHAR', '!', 1, 1), ('S', '\n\t', 1, 2), ('IDENT', 'important', 2, 2)], '!IMPORTANT': [('CHAR', '!', 1, 1), ('IDENT', 'IMPORTANT', 1, 2)], r'!\i\m\p\o\r\ta\n\t': [('CHAR', '!', 1, 1), ('IDENT', r'\i\m\p\o\r\ta\n\t', 1, 2)], r'!\I\M\P\O\R\Ta\N\T': [('CHAR', '!', 1, 1), ('IDENT', r'\I\M\P\O\R\Ta\N\T', 1, 2)], r'!\49\4d\50\4f\52\54\41\4e\54': [('CHAR', '!', 1, 1), ('IDENT', r'IMPORTANT', 1, 2)], r'!\69\6d\70\6f\72\74\61\6e\74': [('CHAR', '!', 1, 1), ('IDENT', r'important', 1, 2)], } # overwriting tests in testsall tests2only = { # LBRACE ' { ': [('S', ' ', 1, 1), ('LBRACE', '{', 1, 2), ('S', ' ', 1, 3)], # PLUS ' + ': [('S', ' ', 1, 1), ('PLUS', '+', 1, 2), ('S', ' ', 1, 3)], # GREATER ' > ': [('S', ' ', 1, 1), ('GREATER', '>', 1, 2), ('S', ' ', 1, 3)], # COMMA ' , ': [('S', ' ', 1, 1), ('COMMA', ',', 1, 2), ('S', ' ', 1, 3)], # class ' . ': [('S', ' ', 1, 1), ('CLASS', '.', 1, 2), ('S', ' ', 1, 3)], } testsfullsheet = { # escape ends with explicit space but \r\n as single space '\\65\r\nb': [('IDENT', 'eb', 1, 1)], # STRING r'"\""': [('STRING', r'"\""', 1, 1)], r'"\" "': [('STRING', r'"\" "', 1, 1)], """'\\''""": [('STRING', """'\\''""", 1, 1)], '''"\\""''': [('STRING', '''"\\""''', 1, 1)], ' "\na': [('S', ' ', 1, 1), ('INVALID', '"', 1, 2), ('S', '\n', 1, 3), ('IDENT', 'a', 2, 1)], # strings with linebreak in it ' "\\na\na': [('S', ' ', 1, 1), ('INVALID', '"\\na', 1, 2), ('S', '\n', 1, 6), ('IDENT', 'a', 2, 1)], ' "\\r\\n\\t\\n\\ra\na': [('S', ' ', 1, 1), ('INVALID', '"\\r\\n\\t\\n\\ra', 1, 2), ('S', '\n', 1, 14), ('IDENT', 'a', 2, 1)], # URI 'ur\\l(a)': [('URI', 'ur\\l(a)', 1, 1)], 'url(a)': [('URI', 'url(a)', 1, 1)], '\\55r\\4c(a)': [('URI', 'UrL(a)', 1, 1)], '\\75r\\6c(a)': [('URI', 'url(a)', 1, 1)], ' url())': [('S', ' ', 1, 1), ('URI', 'url()', 1, 2), ('CHAR', ')', 1, 7)], 'url("x"))': [('URI', 'url("x")', 1, 1), ('CHAR', ')', 1, 9)], "url('x'))": [('URI', "url('x')", 1, 1), ('CHAR', ')', 1, 9)], } # tests if fullsheet=False is set on tokenizer testsfullsheetfalse = { # COMMENT incomplete '/*': [('CHAR', '/', 1, 1), ('CHAR', '*', 1, 2)], # INVALID incomplete ' " ': [('S', ' ', 1, 1), ('INVALID', '" ', 1, 2)], " 'abc\"with quote\" in it": [('S', ' ', 1, 1), ('INVALID', "'abc\"with quote\" in it", 1, 2)], # URI incomplete 'url(a': [('FUNCTION', 'url(', 1, 1), ('IDENT', 'a', 1, 5)], 'url("a': [('FUNCTION', 'url(', 1, 1), ('INVALID', '"a', 1, 5)], "url('a": [('FUNCTION', 'url(', 1, 1), ('INVALID', "'a", 1, 5)], "UR\\l('a": [('FUNCTION', 'UR\\l(', 1, 1), ('INVALID', "'a", 1, 6)], } # tests if fullsheet=True is set on tokenizer testsfullsheettrue = { # COMMENT incomplete '/*': [('COMMENT', '/**/', 1, 1)], # # INVALID incomplete => STRING ' " ': [('S', ' ', 1, 1), ('STRING', '" "', 1, 2)], " 'abc\"with quote\" in it": [('S', ' ', 1, 1), ('STRING', "'abc\"with quote\" in it'", 1, 2)], # URI incomplete FUNC => URI 'url(a': [('URI', 'url(a)', 1, 1)], 'url( a': [('URI', 'url( a)', 1, 1)], 'url("a': [('URI', 'url("a")', 1, 1)], 'url( "a ': [('URI', 'url( "a ")', 1, 1)], "url('a": [('URI', "url('a')", 1, 1)], 'url("a"': [('URI', 'url("a")', 1, 1)], "url('a'": [('URI', "url('a')", 1, 1)], } def setUp(self): #log = css_parser.errorhandler.ErrorHandler() self.tokenizer = Tokenizer() # NOT USED # def test_push(self): # "Tokenizer.push()" # r = [] # def do(): # T = Tokenizer() # x = False # for t in T.tokenize('1 x 2 3'): # if not x and t[1] == 'x': # T.push(t) # x = True # r.append(t[1]) # return ''.join(r) # # # push reinserts token into token stream, so x is doubled # self.assertEqual('1 xx 2 3', do()) # def test_linenumbers(self): # "Tokenizer line + col" # pass def test_tokenize(self): "css_parser Tokenizer().tokenize()" import css_parser.cssproductions tokenizer = Tokenizer(css_parser.cssproductions.MACROS, css_parser.cssproductions.PRODUCTIONS) tests = {} tests.update(self.testsall) tests.update(self.tests2) tests.update(self.tests3) tests.update(self.testsfullsheet) tests.update(self.testsfullsheetfalse) for css in tests: # check token format tokens = tokenizer.tokenize(css) for i, actual in enumerate(tokens): expected = tests[css][i] self.assertEqual(expected, actual) # check if all same number of tokens tokens = list(tokenizer.tokenize(css)) self.assertEqual(len(tokens), len(tests[css])) def test_tokenizefullsheet(self): "css_parser Tokenizer().tokenize(fullsheet=True)" import css_parser.cssproductions tokenizer = Tokenizer(css_parser.cssproductions.MACROS, css_parser.cssproductions.PRODUCTIONS) tests = {} tests.update(self.testsall) tests.update(self.tests2) tests.update(self.tests3) tests.update(self.testsfullsheet) tests.update(self.testsfullsheettrue) for css in tests: # check token format tokens = tokenizer.tokenize(css, fullsheet=True) for i, actual in enumerate(tokens): try: expected = tests[css][i] except IndexError: # EOF is added self.assertEqual(actual[0], 'EOF') else: self.assertEqual(expected, actual) # check if all same number of tokens tokens = list(tokenizer.tokenize(css, fullsheet=True)) # EOF is added so -1 self.assertEqual(len(tokens) - 1, len(tests[css])) # -------------- def __old(self): testsOLD = { 'x x1 -x .-x #_x -': [(1, 1, tt.IDENT, 'x'), (1, 2, 'S', ' '), (1, 3, tt.IDENT, 'x1'), (1, 5, 'S', ' '), (1, 6, tt.IDENT, '-x'), (1, 8, 'S', ' '), (1, 9, tt.CLASS, '.'), (1, 10, tt.IDENT, '-x'), (1, 12, 'S', ' '), (1, 13, tt.HASH, '#_x'), (1, 16, 'S', ' '), (1, 17, 'DELIM', '-')], # num '1 1.1 -1 -1.1 .1 -.1 1.': [(1, 1, tt.NUMBER, '1'), (1, 2, 'S', ' '), (1, 3, tt.NUMBER, '1.1'), (1, 6, 'S', ' '), (1, 7, tt.NUMBER, '-1'), (1, 9, 'S', ' '), (1, 10, tt.NUMBER, '-1.1'), (1, 14, 'S', ' '), (1, 15, tt.NUMBER, '0.1'), (1, 17, 'S', ' '), (1, 18, tt.NUMBER, '-0.1'), (1, 21, 'S', ' '), (1, 22, tt.NUMBER, '1'), (1, 23, tt.CLASS, '.') ], # CSS3 pseudo '::': [(1, 1, tt.PSEUDO_ELEMENT, '::')], # SPECIALS '*+>~{},': [(1, 1, tt.UNIVERSAL, '*'), (1, 2, tt.PLUS, '+'), (1, 3, tt.GREATER, '>'), (1, 4, tt.TILDE, '~'), (1, 5, tt.LBRACE, '{'), (1, 6, tt.RBRACE, '}'), (1, 7, tt.COMMA, ',')], # DELIM '!%:&$|': [(1, 1, 'DELIM', '!'), (1, 2, 'DELIM', '%'), (1, 3, 'DELIM', ':'), (1, 4, 'DELIM', '&'), (1, 5, 'DELIM', '$'), (1, 6, 'DELIM', '|')], # DIMENSION '5em': [(1, 1, tt.DIMENSION, '5em')], ' 5em': [(1, 1, 'S', ' '), (1, 2, tt.DIMENSION, '5em')], '5em ': [(1, 1, tt.DIMENSION, '5em'), (1, 4, 'S', ' ')], '-5em': [(1, 1, tt.DIMENSION, '-5em')], ' -5em': [(1, 1, 'S', ' '), (1, 2, tt.DIMENSION, '-5em')], '-5em ': [(1, 1, tt.DIMENSION, '-5em'), (1, 5, 'S', ' ')], '.5em': [(1, 1, tt.DIMENSION, '0.5em')], ' .5em': [(1, 1, 'S', ' '), (1, 2, tt.DIMENSION, '0.5em')], '.5em ': [(1, 1, tt.DIMENSION, '0.5em'), (1, 5, 'S', ' ')], '-.5em': [(1, 1, tt.DIMENSION, '-0.5em')], ' -.5em': [(1, 1, 'S', ' '), (1, 2, tt.DIMENSION, '-0.5em')], '-.5em ': [(1, 1, tt.DIMENSION, '-0.5em'), (1, 6, 'S', ' ')], '5em5_-': [(1, 1, tt.DIMENSION, '5em5_-')], 'a a5 a5a 5 5a 5a5': [(1, 1, tt.IDENT, 'a'), (1, 2, 'S', ' '), (1, 3, tt.IDENT, 'a5'), (1, 5, 'S', ' '), (1, 6, tt.IDENT, 'a5a'), (1, 9, 'S', ' '), (1, 10, tt.NUMBER, '5'), (1, 11, 'S', ' '), (1, 12, tt.DIMENSION, '5a'), (1, 14, 'S', ' '), (1, 15, tt.DIMENSION, '5a5')], # URI 'url()': [(1, 1, tt.URI, 'url()')], 'url();': [(1, 1, tt.URI, 'url()'), (1, 6, tt.SEMICOLON, ';')], 'url("x")': [(1, 1, tt.URI, 'url("x")')], 'url( "x")': [(1, 1, tt.URI, 'url("x")')], 'url("x" )': [(1, 1, tt.URI, 'url("x")')], 'url( "x" )': [(1, 1, tt.URI, 'url("x")')], ' url("x")': [ (1, 1, 'S', ' '), (1, 2, tt.URI, 'url("x")')], 'url("x") ': [ (1, 1, tt.URI, 'url("x")'), (1, 9, 'S', ' '), ], 'url(ab)': [(1, 1, tt.URI, 'url(ab)')], 'url($#/ab)': [(1, 1, tt.URI, 'url($#/ab)')], 'url(\1233/a/b)': [(1, 1, tt.URI, 'url(\1233/a/b)')], # not URI 'url("1""2")': [ (1, 1, tt.FUNCTION, 'url('), (1, 5, tt.STRING, '"1"'), (1, 8, tt.STRING, '"2"'), (1, 11, tt.RPARANTHESIS, ')'), ], 'url(a"2")': [ (1, 1, tt.FUNCTION, 'url('), (1, 5, tt.IDENT, 'a'), (1, 6, tt.STRING, '"2"'), (1, 9, tt.RPARANTHESIS, ')'), ], 'url(a b)': [ (1, 1, tt.FUNCTION, 'url('), (1, 5, tt.IDENT, 'a'), (1, 6, 'S', ' '), (1, 7, tt.IDENT, 'b'), (1, 8, tt.RPARANTHESIS, ')'), ], # FUNCTION ' counter("x")': [ (1, 1, 'S', ' '), (1, 2, tt.FUNCTION, 'counter('), (1, 10, tt.STRING, '"x"'), (1, 13, tt.RPARANTHESIS, ')')], # HASH '# #a #_a #-a #1': [ (1, 1, 'DELIM', '#'), (1, 2, 'S', ' '), (1, 3, tt.HASH, '#a'), (1, 5, 'S', ' '), (1, 6, tt.HASH, '#_a'), (1, 9, 'S', ' '), (1, 10, tt.HASH, '#-a'), (1, 13, 'S', ' '), (1, 14, tt.HASH, '#1') ], '#1a1 ': [ (1, 1, tt.HASH, '#1a1'), (1, 5, 'S', ' '), ], '#1a1\n': [ (1, 1, tt.HASH, '#1a1'), (1, 5, 'S', '\n'), ], '#1a1{': [ (1, 1, tt.HASH, '#1a1'), (1, 5, tt.LBRACE, '{'), ], '#1a1 {': [ (1, 1, tt.HASH, '#1a1'), (1, 5, 'S', ' '), (1, 6, tt.LBRACE, '{'), ], '#1a1\n{': [ (1, 1, tt.HASH, '#1a1'), (1, 5, 'S', '\n'), (2, 1, tt.LBRACE, '{'), ], '#1a1\n {': [ (1, 1, tt.HASH, '#1a1'), (1, 5, 'S', '\n '), (2, 2, tt.LBRACE, '{'), ], '#1a1 \n{': [ (1, 1, tt.HASH, '#1a1'), (1, 5, 'S', ' \n'), (2, 1, tt.LBRACE, '{'), ], # STRINGS with NL '"x\n': [(1, 1, tt.INVALID, '"x\n')], '"x\r': [(1, 1, tt.INVALID, '"x\r')], '"x\f': [(1, 1, tt.INVALID, '"x\f')], '"x\n ': [ (1, 1, tt.INVALID, '"x\n'), (2, 1, 'S', ' ') ] } tests = { '/*a': xml.dom.SyntaxErr, '"a': xml.dom.SyntaxErr, "'a": xml.dom.SyntaxErr, "\\0 a": xml.dom.SyntaxErr, "\\00": xml.dom.SyntaxErr, "\\000": xml.dom.SyntaxErr, "\\0000": xml.dom.SyntaxErr, "\\00000": xml.dom.SyntaxErr, "\\000000": xml.dom.SyntaxErr, "\\0000001": xml.dom.SyntaxErr } # self.tokenizer.log.raiseExceptions = True #!! # for css, exception in tests.items(): # self.assertRaises(exception, self.tokenizer.tokenize, css) class TokenizerUtilsTestCase(basetest.BaseTestCase): """Tests for the util functions of tokenize""" __metaclass__ = basetest.GenerateTests def gen_test_has_at(self, string, pos, text, expected): self.assertEqual(tokenize2.has_at(string, pos, text), expected) gen_test_has_at.cases = [ ('foo', 0, 'foo', True), ('foo', 0, 'f', True), ('foo', 1, 'o', True), ('foo', 1, 'oo', True), ('foo', 4, 'foo', False), ('foo', 0, 'bar', False), ('foo', 0, 'foobar', False), ] def gen_test_suffix_eq(self, string, pos, suffix, expected): self.assertEqual(tokenize2.suffix_eq(string, pos, suffix), expected) gen_test_suffix_eq.cases = [ ('foobar', 0, 'foobar', True), ('foobar', 3, 'bar', True), ('foobar', 3, 'foo', False), ('foobar', 10, 'bar', False), ] if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_util.py0000644000175000017500000004753000000000000020601 0ustar00kovidkovid# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, with_statement import cgi import re import sys from contextlib import contextmanager from css_parser.util import Base, LazyRegex, ListSeq, _defaultFetcher, _readUrl from . import basetest if sys.version_info.major > 2: from urllib.error import URLError, HTTPError else: from urllib2 import URLError, HTTPError """Testcases for css_parser.util""" class ListSeqTestCase(basetest.BaseTestCase): def test_all(self): "util.ListSeq" ls = ListSeq() self.assertEqual(0, len(ls)) # append() self.assertRaises(NotImplementedError, ls.append, 1) # set self.assertRaises(NotImplementedError, ls.__setitem__, 0, 1) # hack: ls.seq.append(1) ls.seq.append(2) # len self.assertEqual(2, len(ls)) # __contains__ self.assertEqual(True, 1 in ls) # get self.assertEqual(1, ls[0]) self.assertEqual(2, ls[1]) # del del ls[0] self.assertEqual(1, len(ls)) self.assertEqual(False, 1 in ls) # for in for x in ls: self.assertEqual(2, x) class BaseTestCase(basetest.BaseTestCase): def test_normalize(self): "Base._normalize()" b = Base() tests = { 'abcdefg ABCDEFG äöü߀ AÖÜ': 'abcdefg abcdefg äöü߀ aöü', r'\ga\Ga\\\ ': r'gaga\ ', r'0123456789': '0123456789', # unicode escape seqs should have been done by # the tokenizer... } for test, exp in tests.items(): self.assertEqual(b._normalize(test), exp) # static too self.assertEqual(Base._normalize(test), exp) def test_tokenupto(self): "Base._tokensupto2()" # tests nested blocks of {} [] or () b = Base() tests = [ ('default', 'a[{1}]({2}) { } NOT', 'a[{1}]({2}) { }', False), ('default', 'a[{1}]({2}) { } NOT', 'a[{1}]func({2}) { }', True), ('blockstartonly', 'a[{1}]({2}) { NOT', 'a[{1}]({2}) {', False), ('blockstartonly', 'a[{1}]({2}) { NOT', 'a[{1}]func({2}) {', True), ('propertynameendonly', 'a[(2)1] { }2 : a;', 'a[(2)1] { }2 :', False), ('propertynameendonly', 'a[(2)1] { }2 : a;', 'a[func(2)1] { }2 :', True), ('propertyvalueendonly', 'a{;{;}[;](;)}[;{;}[;](;)](;{;}[;](;)) 1; NOT', 'a{;{;}[;](;)}[;{;}[;](;)](;{;}[;](;)) 1;', False), ('propertyvalueendonly', 'a{;{;}[;](;)}[;{;}[;](;)](;{;}[;](;)) 1; NOT', 'a{;{;}[;]func(;)}[;{;}[;]func(;)]func(;{;}[;]func(;)) 1;', True), ('funcendonly', 'a{[1]}([3])[{[1]}[2]([3])]) NOT', 'a{[1]}([3])[{[1]}[2]([3])])', False), ('funcendonly', 'a{[1]}([3])[{[1]}[2]([3])]) NOT', 'a{[1]}func([3])[{[1]}[2]func([3])])', True), ('selectorattendonly', '[a[()]{()}([()]{()}())] NOT', '[a[()]{()}([()]{()}())]', False), ('selectorattendonly', '[a[()]{()}([()]{()}())] NOT', '[a[func()]{func()}func([func()]{func()}func())]', True), # issue 50 ('withstarttoken [', 'a];x', '[a];', False) ] for typ, values, exp, paransasfunc in tests: def maketokens(valuelist): # returns list of tuples return [('TYPE', v, 0, 0) for v in valuelist] tokens = maketokens(list(values)) if paransasfunc: for i, t in enumerate(tokens): if '(' == t[1]: tokens[i] = ('FUNCTION', 'func(', t[2], t[3]) if 'default' == typ: restokens = b._tokensupto2(tokens) elif 'blockstartonly' == typ: restokens = b._tokensupto2( tokens, blockstartonly=True) elif 'propertynameendonly' == typ: restokens = b._tokensupto2( tokens, propertynameendonly=True) elif 'propertyvalueendonly' == typ: restokens = b._tokensupto2( tokens, propertyvalueendonly=True) elif 'funcendonly' == typ: restokens = b._tokensupto2( tokens, funcendonly=True) elif 'selectorattendonly' == typ: restokens = b._tokensupto2( tokens, selectorattendonly=True) elif 'withstarttoken [' == typ: restokens = b._tokensupto2(tokens, ('CHAR', '[', 0, 0)) res = ''.join([t[1] for t in restokens]) self.assertEqual(exp, res) class _readUrl_TestCase(basetest.BaseTestCase): """needs mock""" def test_readUrl(self): """util._readUrl()""" # for additional tests see test_parse.py url = 'http://example.com/test.css' def make_fetcher(r): # normally r == encoding, content def fetcher(url): return r return fetcher tests = { # defaultFetcher returns: readUrl returns None: (None, None, None), (None, ''): ('utf-8', 5, ''), (None, '€'.encode('utf-8')): ('utf-8', 5, '€'), ('utf-8', '€'.encode('utf-8')): ('utf-8', 1, '€'), ('ISO-8859-1', 'ä'.encode('iso-8859-1')): ('ISO-8859-1', 1, 'ä'), ('ASCII', 'a'.encode('ascii')): ('ASCII', 1, 'a') } for r, exp in tests.items(): self.assertEqual(_readUrl(url, fetcher=make_fetcher(r)), exp) tests = { # (overrideEncoding, parentEncoding, (httpencoding, content)): # readUrl returns # ===== 0. OVERRIDE WINS ===== # override + parent + http ('latin1', 'ascii', ('utf-16', ''.encode())): ('latin1', 0, ''), ('latin1', 'ascii', ('utf-16', '123'.encode())): ('latin1', 0, '123'), ('latin1', 'ascii', ('utf-16', 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'), ('latin1', 'ascii', ('utf-16', 'a'.encode('ascii'))): ('latin1', 0, 'a'), # + @charset ('latin1', 'ascii', ('utf-16', '@charset "ascii";'.encode())): ('latin1', 0, '@charset "latin1";'), ('latin1', 'ascii', ('utf-16', '@charset "utf-8";ä'.encode('latin1'))): ('latin1', 0, '@charset "latin1";ä'), ('latin1', 'ascii', ('utf-16', '@charset "utf-8";ä'.encode('utf-8'))): ('latin1', 0, '@charset "latin1";\xc3\xa4'), # read as latin1! # override only ('latin1', None, None): (None, None, None), ('latin1', None, (None, ''.encode())): ('latin1', 0, ''), ('latin1', None, (None, '123'.encode())): ('latin1', 0, '123'), ('latin1', None, (None, 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'), ('latin1', None, (None, 'a'.encode('ascii'))): ('latin1', 0, 'a'), # + @charset ('latin1', None, (None, '@charset "ascii";'.encode())): ('latin1', 0, '@charset "latin1";'), ('latin1', None, (None, '@charset "utf-8";ä'.encode('latin1'))): ('latin1', 0, '@charset "latin1";ä'), ('latin1', None, (None, '@charset "utf-8";ä'.encode('utf-8'))): ('latin1', 0, '@charset "latin1";\xc3\xa4'), # read as latin1! # override + parent ('latin1', 'ascii', None): (None, None, None), ('latin1', 'ascii', (None, ''.encode())): ('latin1', 0, ''), ('latin1', 'ascii', (None, '123'.encode())): ('latin1', 0, '123'), ('latin1', 'ascii', (None, 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'), ('latin1', 'ascii', (None, 'a'.encode('ascii'))): ('latin1', 0, 'a'), # + @charset ('latin1', 'ascii', (None, '@charset "ascii";'.encode())): ('latin1', 0, '@charset "latin1";'), ('latin1', 'ascii', (None, '@charset "utf-8";ä'.encode('latin1'))): ('latin1', 0, '@charset "latin1";ä'), ('latin1', 'ascii', (None, '@charset "utf-8";ä'.encode('utf-8'))): ('latin1', 0, '@charset "latin1";\xc3\xa4'), # read as latin1! # override + http ('latin1', None, ('utf-16', ''.encode())): ('latin1', 0, ''), ('latin1', None, ('utf-16', '123'.encode())): ('latin1', 0, '123'), ('latin1', None, ('utf-16', 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'), ('latin1', None, ('utf-16', 'a'.encode('ascii'))): ('latin1', 0, 'a'), # + @charset ('latin1', None, ('utf-16', '@charset "ascii";'.encode())): ('latin1', 0, '@charset "latin1";'), ('latin1', None, ('utf-16', '@charset "utf-8";ä'.encode('latin1'))): ('latin1', 0, '@charset "latin1";ä'), ('latin1', None, ('utf-16', '@charset "utf-8";ä'.encode('utf-8'))): ('latin1', 0, '@charset "latin1";\xc3\xa4'), # read as latin1! # override ü @charset ('latin1', None, (None, '@charset "ascii";'.encode())): ('latin1', 0, '@charset "latin1";'), ('latin1', None, (None, '@charset "utf-8";ä'.encode('latin1'))): ('latin1', 0, '@charset "latin1";ä'), ('latin1', None, (None, '@charset "utf-8";ä'.encode('utf-8'))): ('latin1', 0, '@charset "latin1";\xc3\xa4'), # read as latin1! # ===== 1. HTTP WINS ===== (None, 'ascii', ('latin1', ''.encode())): ('latin1', 1, ''), (None, 'ascii', ('latin1', '123'.encode())): ('latin1', 1, '123'), (None, 'ascii', ('latin1', 'ä'.encode('iso-8859-1'))): ('latin1', 1, 'ä'), (None, 'ascii', ('latin1', 'a'.encode('ascii'))): ('latin1', 1, 'a'), # + @charset (None, 'ascii', ('latin1', '@charset "ascii";'.encode())): ('latin1', 1, '@charset "latin1";'), (None, 'ascii', ('latin1', '@charset "utf-8";ä'.encode('latin1'))): ('latin1', 1, '@charset "latin1";ä'), (None, 'ascii', ('latin1', '@charset "utf-8";ä'.encode('utf-8'))): ('latin1', 1, '@charset "latin1";\xc3\xa4'), # read as latin1! # ===== 2. @charset WINS ===== (None, 'ascii', (None, '@charset "latin1";'.encode())): ('latin1', 2, '@charset "latin1";'), (None, 'ascii', (None, '@charset "latin1";ä'.encode('latin1'))): ('latin1', 2, '@charset "latin1";ä'), (None, 'ascii', (None, '@charset "latin1";ä'.encode('utf-8'))): ('latin1', 2, '@charset "latin1";\xc3\xa4'), # read as latin1! # ===== 2. BOM WINS ===== (None, 'ascii', (None, 'ä'.encode('utf-8-sig'))): ('utf-8-sig', 2, '\xe4'), # read as latin1! (None, 'ascii', (None, '@charset "utf-8";ä'.encode('utf-8-sig'))): ('utf-8-sig', 2, '@charset "utf-8";\xe4'), # read as latin1! (None, 'ascii', (None, '@charset "latin1";ä'.encode('utf-8-sig'))): ('utf-8-sig', 2, '@charset "utf-8";\xe4'), # read as latin1! # ===== 4. parentEncoding WINS ===== (None, 'latin1', (None, ''.encode())): ('latin1', 4, ''), (None, 'latin1', (None, '123'.encode())): ('latin1', 4, '123'), (None, 'latin1', (None, 'ä'.encode('iso-8859-1'))): ('latin1', 4, 'ä'), (None, 'latin1', (None, 'a'.encode('ascii'))): ('latin1', 4, 'a'), (None, 'latin1', (None, 'ä'.encode('utf-8'))): ('latin1', 4, '\xc3\xa4'), # read as latin1! # ===== 5. default WINS which in this case is None! ===== (None, None, (None, ''.encode())): ('utf-8', 5, ''), (None, None, (None, '123'.encode())): ('utf-8', 5, '123'), (None, None, (None, 'a'.encode('ascii'))): ('utf-8', 5, 'a'), (None, None, (None, 'ä'.encode('utf-8'))): ('utf-8', 5, 'ä'), # read as utf-8 (None, None, (None, 'ä'.encode('iso-8859-1'))): # trigger UnicodeDecodeError! ('utf-8', 5, None), } for (override, parent, r), exp in tests.items(): self.assertEqual(_readUrl(url, overrideEncoding=override, parentEncoding=parent, fetcher=make_fetcher(r)), exp) def test_defaultFetcher(self): """util._defaultFetcher""" class Response(object): """urllib2.Reponse mock""" def __init__(self, url, contenttype, content, exception=None, args=None): self.url = url mt, params = cgi.parse_header(contenttype) self.mimetype = mt self.charset = params.get('charset', None) self.text = content self.exception = exception self.args = args def geturl(self): return self.url def info(self): mimetype, charset = self.mimetype, self.charset class Info(object): # py2x def gettype(self): return mimetype def getparam(self, name=None): return charset # py 3x get_content_type = gettype get_content_charset = getparam # here always charset! return Info() def read(self): # returns fake text or raises fake exception if not self.exception: return self.text else: raise self.exception(*self.args) def urlopen(url, contenttype=None, content=None, exception=None, args=None): # return an mock which returns parameterized Response def x(*ignored): if exception: raise exception(*args) else: return Response(url, contenttype, content, exception=exception, args=args) return x # positive tests tests = { # content-type, contentstr: encoding, contentstr ('text/css', '€'.encode('utf-8')): (None, '€'.encode('utf-8')), ('text/css;charset=utf-8', '€'.encode('utf-8')): ('utf-8', '€'.encode('utf-8')), ('text/css;charset=ascii', 'a'): ('ascii', 'a') } @contextmanager def patch_urlopen(*a, **kw): import css_parser._fetch as fetch orig = fetch.urllib_urlopen fetch.urllib_urlopen = urlopen(*a, **kw) try: yield finally: fetch.urllib_urlopen = orig url = 'http://example.com/test.css' for (contenttype, content), exp in tests.items(): def do(url): return _defaultFetcher(url) with patch_urlopen(url, contenttype, content): self.assertEqual(exp, do(url)) # wrong mimetype def do(url): with patch_urlopen(url, 'text/html', 'a'): return _defaultFetcher(url) self.assertRaises(ValueError, do, url) # calling url results in fake exception # py2 ~= py3 raises error earlier than urlopen! tests = { '1': (ValueError, ['invalid value for url']), # _readUrl('mailto:a.css') 'mailto:e4': (URLError, ['urlerror']), # cannot resolve x, IOError 'http://x': (URLError, ['ioerror']), } for url, (exception, args) in tests.items(): def do(url): with patch_urlopen(url, exception=exception, args=args): return _defaultFetcher(url) self.assertRaises(exception, do, url) tests = { # _readUrl('http://cthedot.de/__UNKNOWN__.css') 'http://e2': (HTTPError, ['u', 500, 'server error', {}, None]), 'http://e3': (HTTPError, ['u', 404, 'not found', {}, None]), } for url, (exception, args) in tests.items(): def do(url): with patch_urlopen(url, exception=exception, args=args): return _defaultFetcher(url) self.assertRaises(exception, do, url) class TestLazyRegex(basetest.BaseTestCase): """Tests for css_parser.util.LazyRegex.""" def setUp(self): self.lazyre = LazyRegex('f.o') def test_public_interface(self): methods = ['search', 'match', 'split', 'sub', 'subn', 'findall', 'finditer', 'pattern', 'flags', 'groups', 'groupindex', ] for method in methods: self.assertTrue(hasattr(self.lazyre, method), 'expected %r public attribute' % method) def test_ensure(self): self.assertIsNone(self.lazyre.matcher) self.lazyre.ensure() self.assertIsNotNone(self.lazyre.matcher) def test_calling(self): self.assertIsNone(self.lazyre('bar')) match = self.lazyre('foobar') self.assertEquals(match.group(), 'foo') def test_matching(self): self.assertIsNone(self.lazyre.match('bar')) match = self.lazyre.match('foobar') self.assertEquals(match.group(), 'foo') def test_matching_with_position_parameters(self): self.assertIsNone(self.lazyre.match('foo', 1)) self.assertIsNone(self.lazyre.match('foo', 0, 2)) def test_searching(self): self.assertIsNone(self.lazyre.search('rafuubar')) match = self.lazyre.search('rafoobar') self.assertEquals(match.group(), 'foo') def test_searching_with_position_parameters(self): self.assertIsNone(self.lazyre.search('rafoobar', 3)) self.assertIsNone(self.lazyre.search('rafoobar', 0, 4)) match = self.lazyre.search('rafoofuobar', 4) self.assertEquals(match.group(), 'fuo') def test_split(self): self.assertEquals(self.lazyre.split('rafoobarfoobaz'), ['ra', 'bar', 'baz']) self.assertEquals(self.lazyre.split('rafoobarfoobaz', 1), ['ra', 'barfoobaz']) def test_findall(self): self.assertEquals(self.lazyre.findall('rafoobarfuobaz'), ['foo', 'fuo']) def test_finditer(self): result = self.lazyre.finditer('rafoobarfuobaz') self.assertEquals([m.group() for m in result], ['foo', 'fuo']) def test_sub(self): self.assertEquals(self.lazyre.sub('bar', 'foofoo'), 'barbar') self.assertEquals(self.lazyre.sub(lambda x: 'baz', 'foofoo'), 'bazbaz') def test_subn(self): subbed = self.lazyre.subn('bar', 'foofoo') self.assertEquals(subbed, ('barbar', 2)) subbed = self.lazyre.subn(lambda x: 'baz', 'foofoo') self.assertEquals(subbed, ('bazbaz', 2)) def test_groups(self): lazyre = LazyRegex('(.)(.)') self.assertIsNone(lazyre.groups) lazyre.ensure() self.assertEquals(lazyre.groups, 2) def test_groupindex(self): lazyre = LazyRegex('(?P.)') self.assertIsNone(lazyre.groupindex) lazyre.ensure() self.assertEquals(lazyre.groupindex, {'foo': 1}) def test_flags(self): self.lazyre.ensure() self.assertEquals(self.lazyre.flags, re.compile('.').flags) def test_pattern(self): self.assertEquals(self.lazyre.pattern, 'f.o') if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_value.py0000644000175000017500000013151500000000000020735 0ustar00kovidkovid"""Testcases for css_parser.css.CSSValue and CSSPrimitiveValue.""" # from decimal import Decimal # maybe for later tests? from __future__ import absolute_import from __future__ import unicode_literals import xml.dom from . import basetest import css_parser import types class PropertyValueTestCase(basetest.BaseTestCase): def setUp(self): self.r = css_parser.css.PropertyValue() def test_init(self): "PropertyValue.__init__() .item() .length" pv = css_parser.css.PropertyValue() self.assertEqual('', pv.cssText) self.assertEqual(0, pv.length) self.assertEqual('', pv.value) cssText = '0, 0/0 1px var(x) url(x)' items = ['0', '0', '0', '1px', 'var(x)', 'url(x)'] pv = css_parser.css.PropertyValue(cssText) self.assertEqual(cssText, pv.cssText) self.assertEqual(6, len(pv)) self.assertEqual(6, pv.length) # __iter__ for i, x in enumerate(pv): self.assertEqual(x.cssText, items[i]) # cssText for i, item in enumerate(items): self.assertEqual(item, pv[i].cssText) self.assertEqual(item, pv.item(i).cssText) def test_cssText(self): "PropertyValue.cssText" tests = { '0': (None, 1, None), '0 0': (None, 2, None), '0, 0': (None, 2, None), '0,0': ('0, 0', 2, None), '0 , 0': ('0, 0', 2, None), '0/0': (None, 2, None), '/**/ 0 /**/': (None, 1, '0'), '0 /**/ 0 /**/ 0': (None, 3, '0 0 0'), '0, /**/ 0, /**/ 0': (None, 3, '0, 0, 0'), '0//**/ 0//**/ 0': (None, 3, '0/0/0'), '/**/ red': (None, 1, 'red'), '/**/red': ('/**/ red', 1, 'red'), 'red /**/': (None, 1, 'red'), 'red/**/': ('red /**/', 1, 'red'), 'a()1,-1,+1,1%,-1%,1px,-1px,"a",a,url(a),#aabb44': ( 'a() 1, -1, +1, 1%, -1%, 1px, -1px, "a", a, url(a), #ab4', 12, 'a() 1, -1, +1, 1%, -1%, 1px, -1px, "a", a, url(a), #ab4'), # calc values 'calc(1)': (None, 1, 'calc(1)'), 'calc( 1)': ('calc(1)', 1, 'calc(1)'), 'calc(1 )': ('calc(1)', 1, 'calc(1)'), 'calc(1px)': (None, 1, 'calc(1px)'), 'calc(1p-x-)': (None, 1, 'calc(1p-x-)'), 'calc(1%)': (None, 1, 'calc(1%)'), 'calc(-1)': (None, 1, 'calc(-1)'), 'calc(+1)': (None, 1, 'calc(+1)'), 'calc(1 + 1px)': ('calc(1 + 1px)', 1, 'calc(1 + 1px)'), 'calc(1 - 1px)': (None, 1, 'calc(1 - 1px)'), 'calc(1*1px)': ('calc(1 * 1px)', 1, 'calc(1 * 1px)'), 'calc(1 / 1px)': ('calc(1 / 1px)', 1, 'calc(1 / 1px)'), 'calc( 1*1px)': ('calc(1 * 1px)', 1, 'calc(1 * 1px)'), 'calc( 1 / 1px)': ('calc(1 / 1px)', 1, 'calc(1 / 1px)'), 'calc(1*1px )': ('calc(1 * 1px)', 1, 'calc(1 * 1px)'), 'calc(1 / 1px )': ('calc(1 / 1px)', 1, 'calc(1 / 1px)'), 'calc( 1*1px )': ('calc(1 * 1px)', 1, 'calc(1 * 1px)'), 'calc( 1 / 1px )': ('calc(1 / 1px)', 1, 'calc(1 / 1px)'), 'calc(var(X))': (None, 1, None), 'calc(2 * var(X))': (None, 1, None), 'calc(2px + var(X))': (None, 1, None), # issue #24 'rgb(0, 10, 255)': (None, 1, 'rgb(0, 10, 255)'), 'hsl(10, 10%, 25%)': (None, 1, 'hsl(10, 10%, 25%)'), 'rgba(0, 10, 255, 0.5)': (None, 1, 'rgba(0, 10, 255, 0.5)'), 'hsla(10, 10%, 25%, 0.5)': (None, 1, 'hsla(10, 10%, 25%, 0.5)'), # issue #27 'matrix(0.000092, 0.2500010, -0.250000, 0.000092, 0, 0)': ( 'matrix(0.000092, 0.250001, -0.25, 0.000092, 0, 0)', 1, 'matrix(0.000092, 0.250001, -0.25, 0.000092, 0, 0)') } for (cssText, (c, l, v)) in tests.items(): if c is None: c = cssText if v is None: v = c pv = css_parser.css.PropertyValue(cssText) self.assertEqual(c, pv.cssText) self.assertEqual(l, pv.length) self.assertEqual(v, pv.value) tests = { '0 0px -0px +0px': ('0 0 0 0', 4), '1 2 3 4': (None, 4), '-1 -2 -3 -4': (None, 4), '-1 2': (None, 2), '-1px red "x"': (None, 3), 'a, b c': (None, 3), '1px1 2% 3': ('1px1 2% 3', 3), 'f(+1pX, -2, 5%) 1': ('f(+1px, -2, 5%) 1', 2), '0 f()0': ('0 f() 0', 3), 'f()0': ('f() 0', 2), 'f()1%': ('f() 1%', 2), 'f()1px': ('f() 1px', 2), 'f()"str"': ('f() "str"', 2), 'f()ident': ('f() ident', 2), 'f()#123': ('f() #123', 2), 'f()url()': ('f() url()', 2), 'f()f()': ('f() f()', 2), 'url(x.gif)0 0': ('url(x.gif) 0 0', 3), 'url(x.gif)no-repeat': ('url(x.gif) no-repeat', 2) } for (cssText, (c, l)) in tests.items(): if c is None: c = cssText pv = css_parser.css.PropertyValue(cssText) self.assertEqual(c, pv.cssText) self.assertEqual(l, pv.length) tests = { # hash and rgb/a '#112234': '#112234', '#112233': '#123', 'rgb(1,2,3)': 'rgb(1, 2, 3)', 'rgb( 1 , 2 , 3 )': 'rgb(1, 2, 3)', 'rgba(1,2,3,4)': 'rgba(1, 2, 3, 4)', 'rgba( 1 , 2 , 3 , 4 )': 'rgba(1, 2, 3, 4)', 'rgb(-1,+2,0)': 'rgb(-1, +2, 0)', 'rgba(-1,+2,0, 0)': 'rgba(-1, +2, 0, 0)', # FUNCTION 'f(1,2)': 'f(1, 2)', 'f( 1 , 2 )': 'f(1, 2)', 'f(-1,+2)': 'f(-1, +2)', 'f( -1 , +2 )': 'f(-1, +2)', 'fun( -1 , +2 )': 'fun(-1, +2)', 'local( x )': 'local(x)', 'test(1px, #111, y, 1, 1%, "1", y(), var(x))': 'test(1px, #111, y, 1, 1%, "1", y(), var(x))', 'test(-1px, #111, y, -1, -1%, "1", -y())': 'test(-1px, #111, y, -1, -1%, "1", -y())', 'url(y) format( "x" , "y" )': 'url(y) format("x", "y")', 'f(1 2,3 4)': 'f(1 2, 3 4)', # IE expression r'Expression()': 'Expression()', r'expression(-1 < +2)': 'expression(-1<+2)', r'expression(document.width == "1")': 'expression(document.width=="1")', 'alpha(opacity=80)': 'alpha(opacity=80)', 'alpha( opacity = 80 , x=2 )': 'alpha(opacity=80, x=2)', 'expression(eval(document.documentElement.scrollTop))': 'expression(eval(document.documentElement.scrollTop))', # TODO # u'expression((function(ele){ele.style.behavior="none";})(this))': # u'expression((function(ele){ele.style.behavior="none";})(this))', # unicode-range 'u+f': 'u+f', 'U+ABCdef': 'u+abcdef', # url 'url(a)': 'url(a)', 'uRl(a)': 'url(a)', 'u\\rl(a)': 'url(a)', 'url("a")': 'url(a)', 'url( "a" )': 'url(a)', 'url(a)': 'url(a)', 'url(";")': 'url(";")', 'url(",")': 'url(",")', 'url(")")': 'url(")")', '''url("'")''': '''url("'")''', '''url('"')''': '''url("\\"")''', '''url("'")''': '''url("'")''', # operator '1': '1', '1 2': '1 2', '1 2': '1 2', '1,2': '1, 2', '1, 2': '1, 2', '1 ,2': '1, 2', '1 , 2': '1, 2', '1/2': '1/2', '1/ 2': '1/2', '1 /2': '1/2', '1 / 2': '1/2', # comment '1/**/2': '1 /**/ 2', '1 /**/2': '1 /**/ 2', '1/**/ 2': '1 /**/ 2', '1 /**/ 2': '1 /**/ 2', '1 /*a*/ /*b*/ 2': '1 /*a*/ /*b*/ 2', # , before '1,/**/2': '1, /**/ 2', '1 ,/**/2': '1, /**/ 2', '1, /**/2': '1, /**/ 2', '1 , /**/2': '1, /**/ 2', # , after '1/**/,2': '1 /**/, 2', '1/**/ ,2': '1 /**/, 2', '1/**/, 2': '1 /**/, 2', '1/**/ , 2': '1 /**/, 2', # all '1/*a*/ ,/*b*/ 2': '1 /*a*/, /*b*/ 2', '1 /*a*/, /*b*/2': '1 /*a*/, /*b*/ 2', '1 /*a*/ , /*b*/ 2': '1 /*a*/, /*b*/ 2', # list 'a b1,b2 b2,b3,b4': 'a b1, b2 b2, b3, b4', 'a b1 , b2 b2 , b3 , b4': 'a b1, b2 b2, b3, b4', 'u+1 , u+2-5': 'u+1, u+2-5', 'local( x ), url(y) format( "x" , "y" )': 'local(x), url(y) format("x", "y")', # FUNCTION 'attr( href )': 'attr(href)', # PrinceXML extende FUNC syntax with nested FUNC 'target-counter(attr(href),page)': 'target-counter(attr(href), page)' } self.do_equal_r(tests) tests = [ 'a+', '-', '+', '-%', '+a', '--1px', '++1px', '#', '#00', '#12x', '#xyz', '#0000', '#00000', '#0000000', '-#0', # operator ',', '1,,2', '1,/**/,2', '1 , /**/ , 2', '1,', '1, ', '1 ,', '1 , ', '1 , ', '1//2', # URL 'url(x))', # string '"', "'", # function 'f(-)', 'f(x))', # calc 'calc(', 'calc(1', 'calc(1 + 1', 'calc(1+1)', 'calc(1-1)', 'calc(1 +1)', 'calc(1+ 1)', 'calc(1 -1)', 'calc(1- 1)', 'calc(+)', 'calc(+ 1)', 'calc(-)', 'calc(- 1)', 'calc(*)', 'calc(*1)', 'calc(* 2)', 'calc(/)', 'calc(/1)', 'calc(/ 2)', 'calc(1+)', 'calc(1 +)', 'calc(1 + )', 'calc(2px -)', 'calc(3px*)', 'calc(3px *)', 'calc(3px * )', 'calc(4em/)', 'calc(4em /)', 'calc(4em / )', 'calc(1 + + 1)', 'calc(1 ++ 1)' ] self.do_raise_r_list(tests, xml.dom.SyntaxErr) def test_list(self): "PropertyValue[index]" # issue #41 css = """div.one {color: rgb(255, 0, 0);} """ sheet = css_parser.parseString(css) pv = sheet.cssRules[0].style.getProperty('color').propertyValue self.assertEqual(pv.value, 'rgb(255, 0, 0)') self.assertEqual(pv[0].value, 'rgb(255, 0, 0)') # issue #42 sheet = css_parser.parseString('body { font-family: "A", b, serif }') pv = sheet.cssRules[0].style.getProperty('font-family').propertyValue self.assertEqual(3, pv.length) self.assertEqual(pv[0].value, 'A') self.assertEqual(pv[1].value, 'b') self.assertEqual(pv[2].value, 'serif') def test_comments(self): "PropertyValue with comment" # issue #45 for t in ('green', 'green /* comment */', '/* comment */green', '/* comment */green/* comment */', '/* comment */ green /* comment */', '/* comment *//**/ green /* comment *//**/', ): sheet = css_parser.parseString('body {color: %s; }' % t) p = sheet.cssRules[0].style.getProperties()[0] self.assertEqual(p.valid, True) for t in ('gree', 'gree /* comment */', '/* comment */gree', '/* comment */gree/* comment */', '/* comment */ gree /* comment */', '/* comment *//**/ gree /* comment *//**/', ): sheet = css_parser.parseString('body {color: %s; }' % t) p = sheet.cssRules[0].style.getProperties()[0] self.assertEqual(p.valid, False) def test_incomplete(self): "PropertyValue (incomplete)" tests = { 'url("a': 'url(a)', 'url(a': 'url(a)' } for v, exp in tests.items(): s = css_parser.parseString('a { background: %s' % v) v = s.cssRules[0].style.background self.assertEqual(v, exp) def test_readonly(self): "PropertyValue._readonly" v = css_parser.css.PropertyValue(cssText='inherit') self.assertTrue(False is v._readonly) v = css_parser.css.PropertyValue(cssText='inherit', readonly=True) self.assertTrue(True is v._readonly) self.assertTrue('inherit', v.cssText) self.assertRaises(xml.dom.NoModificationAllowedErr, v._setCssText, 'x') self.assertTrue('inherit', v.cssText) def test_reprANDstr(self): "PropertyValue.__repr__(), .__str__()" cssText = 'inherit' s = css_parser.css.PropertyValue(cssText=cssText) self.assertTrue(cssText in str(s)) s2 = eval(repr(s)) self.assertTrue(isinstance(s2, s.__class__)) self.assertTrue(cssText == s2.cssText) class ValueTestCase(basetest.BaseTestCase): def test_init(self): "Value.__init__()" v = css_parser.css.Value() self.assertTrue('' == v.cssText) self.assertTrue('' == v.value) self.assertTrue(None is v.type) def test_cssText(self): "Value.cssText" # HASH IDENT STRING UNICODE-RANGE tests = { '#123': ('#123', '#123', 'HASH'), '#123456': ('#123456', '#123456', 'HASH'), '#112233': ('#123', '#112233', 'HASH'), ' #112233 ': ('#123', '#112233', 'HASH'), 'red': ('red', 'red', 'IDENT'), ' red ': ('red', 'red', 'IDENT'), 'red ': ('red', 'red', 'IDENT'), ' red': ('red', 'red', 'IDENT'), 'red-': ('red-', 'red-', 'IDENT'), '-red': ('-red', '-red', 'IDENT'), '"red"': ('"red"', 'red', 'STRING'), "'red'": ('"red"', 'red', 'STRING'), ' "red" ': ('"red"', 'red', 'STRING'), r'"red\""': (r'"red\""', r'red"', 'STRING'), r"'x\"'": (r'"x\\""', r'x\"', 'STRING'), # ??? '''"x\ y"''': ('"xy"', 'xy', 'STRING'), } for (p, (r, n, t)) in tests.items(): v = css_parser.css.Value(p) self.assertEqual(r, v.cssText) self.assertEqual(t, v.type) self.assertEqual(n, v.value) class ColorValueTestCase(basetest.BaseTestCase): def test_init(self): "ColorValue.__init__()" v = css_parser.css.ColorValue() self.assertEqual(v.COLOR_VALUE, v.type) self.assertTrue('' == v.cssText) self.assertTrue('' == v.value) self.assertEqual('transparent', v.name) self.assertEqual(None, v.colorType) def test_cssText(self): "ColorValue.cssText" tests = { # HASH '#123': ('#123',), '#112233': ('#123',), # rgb 'rgb(1,2,3)': ('rgb(1, 2, 3)',), 'rgb(1%,2%,3%)': ('rgb(1%, 2%, 3%)',), 'rgb(-1,-1,-1)': ('rgb(-1, -1, -1)',), 'rgb(-1%,-2%,-3%)': ('rgb(-1%, -2%, -3%)',), 'RGB(100, 255, 3)': ('rgb(100, 255, 3)',), # rgba 'rgba(1,2,3, 0)': ('rgba(1, 2, 3, 0)',), # hsl 'hsl(1,2%,3%)': ('hsl(1, 2%, 3%)',), 'hsla(1,2%,3%, 1.0)': ('hsla(1, 2%, 3%, 1)',), 'HsLa(1,2%,3%, 1.0)': ('hsla(1, 2%, 3%, 1)',), } for (p, (r, )) in tests.items(): v = css_parser.css.ColorValue(p) self.assertEqual(v.COLOR_VALUE, v.type) self.assertEqual(r, v.cssText) self.assertEqual(r, v.value) v = css_parser.css.ColorValue() v.cssText = p self.assertEqual(v.COLOR_VALUE, v.type) self.assertEqual(r, v.cssText) self.assertEqual(r, v.value) tests = { '1': xml.dom.SyntaxErr, 'a': xml.dom.SyntaxErr, '#12': xml.dom.SyntaxErr, '#1234': xml.dom.SyntaxErr, '#1234567': xml.dom.SyntaxErr, '#12345678': xml.dom.SyntaxErr, 'rgb(1,1%,1%)': xml.dom.SyntaxErr, 'rgb(1%,1,1)': xml.dom.SyntaxErr, 'rgb(-1,-1%,-1%)': xml.dom.SyntaxErr, 'rgb(-1%,-1,-1)': xml.dom.SyntaxErr, 'rgb(1,1,1, 0)': xml.dom.SyntaxErr, 'rgb(1%,1%,1%, 0)': xml.dom.SyntaxErr, 'rgba(1,1,1)': xml.dom.SyntaxErr, 'rgba(1%,1%,1%)': xml.dom.SyntaxErr, 'rgba(1,1,1, 0%)': xml.dom.SyntaxErr, 'rgba(1%,1%,1%, 0%)': xml.dom.SyntaxErr, 'hsl(1,2%,3%, 1)': xml.dom.SyntaxErr, 'hsla(1,2%,3%)': xml.dom.SyntaxErr, 'hsl(1,2,3)': xml.dom.SyntaxErr, 'hsl(1%,2,3)': xml.dom.SyntaxErr, 'hsl(1%,2,3%)': xml.dom.SyntaxErr, 'hsl(1%,2%,3)': xml.dom.SyntaxErr, 'hsla(1,2%,3%, 0%)': xml.dom.SyntaxErr, 'hsla(1,2,3, 0.0)': xml.dom.SyntaxErr, 'hsla(1%,2,3, 0.0)': xml.dom.SyntaxErr, 'hsla(1%,2,3%, 0.0)': xml.dom.SyntaxErr, 'hsla(1%,2%,3, 0.0)': xml.dom.SyntaxErr, } self.r = css_parser.css.ColorValue() self.do_raise_r(tests) def test_rgb(self): "ColorValue.red .green .blue" tests = { ('#0A0AD2', 'rgb(10, 10, 210)'): (10, 10, 210, 1.0), # TODO: Fix rounding? ('hsl(240, 91%, 43%)', ): (10, 10, 209, 1.0), ('#ff8800', '#f80', 'rgb(255, 136, 0)', 'rgba(255, 136, 0, 1.0)'): (255, 136, 0, 1.0), ('red', '#ff0000', '#f00', 'hsl(0, 100%, 50%)', 'hsla(0, 100%, 50%, 1.0)'): (255, 0, 0, 1.0), ('lime', '#00ff00', '#0f0', 'hsl(120, 100%, 50%)'): (0, 255, 0, 1.0), ('rgba(255, 127, 0, .1)', 'rgba(100%, 50%, 0%, .1)'): (255, 127, 0, 0.1), ('transparent', 'rgba(0, 0, 0, 0)'): (0, 0, 0, 0), ('aqua',): (0, 255, 255, 1.0) } for colors, rgba in tests.items(): for color in colors: c = css_parser.css.ColorValue(color) self.assertEqual(c.red, rgba[0]) self.assertEqual(c.green, rgba[1]) self.assertEqual(c.blue, rgba[2]) self.assertEqual(c.alpha, rgba[3]) class URIValueTestCase(basetest.BaseTestCase): def test_init(self): "URIValue.__init__()" v = css_parser.css.URIValue() self.assertTrue('url()' == v.cssText) self.assertTrue('' == v.value) self.assertTrue('' == v.uri) self.assertTrue(v.URI is v.type) v.uri = '1' self.assertTrue('1' == v.value) self.assertTrue('1' == v.uri) self.assertEqual('url(1)', v.cssText) v.value = '2' self.assertTrue('2' == v.value) self.assertTrue('2' == v.uri) self.assertEqual('url(2)', v.cssText) def test_absoluteUri(self): "URIValue.absoluteUri" s = css_parser.parseString('a { background-image: url(x.gif)}', href="/path/to/x.css") v = s.cssRules[0].style.getProperty('background-image').propertyValue[0] self.assertEqual('x.gif', v.uri) self.assertEqual('/path/to/x.gif', v.absoluteUri) v = css_parser.css.URIValue('url(x.gif)') self.assertEqual('x.gif', v.uri) self.assertEqual('x.gif', v.absoluteUri) def test_cssText(self): "URIValue.cssText" tests = { 'url()': ('url()', '', 'URI'), # comments are part of the url! 'url(/**/)': ('url(/**/)', '/**/', 'URI'), 'url(/**/1)': ('url(/**/1)', '/**/1', 'URI'), 'url(1/**/)': ('url(1/**/)', '1/**/', 'URI'), 'url(/**/1/**/)': ('url(/**/1/**/)', '/**/1/**/', 'URI'), 'url(some.gif)': ('url(some.gif)', 'some.gif', 'URI'), ' url(some.gif) ': ('url(some.gif)', 'some.gif', 'URI'), 'url( some.gif )': ('url(some.gif)', 'some.gif', 'URI'), } for (p, (r, n, t)) in tests.items(): v = css_parser.css.URIValue(p) self.assertEqual(r, v.cssText) self.assertEqual(t, v.type) self.assertEqual(n, v.value) self.assertEqual(n, v.uri) v = css_parser.css.URIValue() v.cssText = p self.assertEqual(r, v.cssText) self.assertEqual(t, v.type) self.assertEqual(n, v.value) self.assertEqual(n, v.uri) tests = { 'a()': xml.dom.SyntaxErr, '1': xml.dom.SyntaxErr, 'url(': xml.dom.SyntaxErr, 'url("': xml.dom.SyntaxErr, 'url(\'': xml.dom.SyntaxErr, } self.r = css_parser.css.URIValue() self.do_raise_r(tests) class DimensionValueTestCase(basetest.BaseTestCase): def test_init(self): "DimensionValue.__init__()" v = css_parser.css.DimensionValue() self.assertTrue('' == v.cssText) self.assertTrue('' == v.value) self.assertTrue(None is v.type) self.assertTrue(None is v.dimension) def test_cssText(self): "DimensionValue.cssText" # NUMBER DIMENSION PERCENTAGE tests = { '0': ('0', 0, None, 'NUMBER'), '00': ('0', 0, None, 'NUMBER'), '.0': ('0', 0, None, 'NUMBER'), '0.0': ('0', 0, None, 'NUMBER'), '+0': ('0', 0, None, 'NUMBER'), '+00': ('0', 0, None, 'NUMBER'), '+.0': ('0', 0, None, 'NUMBER'), '+0.0': ('0', 0, None, 'NUMBER'), '-0': ('0', 0, None, 'NUMBER'), '-00': ('0', 0, None, 'NUMBER'), '-.0': ('0', 0, None, 'NUMBER'), '-0.0': ('0', 0, None, 'NUMBER'), '1': ('1', 1, None, 'NUMBER'), '1.0': ('1', 1.0, None, 'NUMBER'), '1.1': ('1.1', 1.1, None, 'NUMBER'), '+1': ('+1', 1, None, 'NUMBER'), '+1.0': ('+1', 1.0, None, 'NUMBER'), '+1.1': ('+1.1', 1.1, None, 'NUMBER'), '-1': ('-1', -1, None, 'NUMBER'), '-1.0': ('-1', -1, None, 'NUMBER'), '-1.1': ('-1.1', -1.1, None, 'NUMBER'), '0px': ('0', 0, 'px', 'DIMENSION'), '1px': ('1px', 1, 'px', 'DIMENSION'), '1.0px': ('1px', 1.0, 'px', 'DIMENSION'), '1.1px': ('1.1px', 1.1, 'px', 'DIMENSION'), '-1px': ('-1px', -1, 'px', 'DIMENSION'), '-1.1px': ('-1.1px', -1.1, 'px', 'DIMENSION'), '+1px': ('+1px', 1, 'px', 'DIMENSION'), '1px1': ('1px1', 1, 'px1', 'DIMENSION'), '0%': ('0%', 0, '%', 'PERCENTAGE'), '1%': ('1%', 1, '%', 'PERCENTAGE'), '1.1%': ('1.1%', 1.1, '%', 'PERCENTAGE'), '-1%': ('-1%', -1, '%', 'PERCENTAGE'), '-1.1%': ('-1.1%', -1.1, '%', 'PERCENTAGE'), '+1%': ('+1%', 1, '%', 'PERCENTAGE'), } for (p, (r, n, d, t)) in tests.items(): v = css_parser.css.DimensionValue(p) self.assertEqual(r, v.cssText) self.assertEqual(t, v.type) self.assertEqual(n, v.value) self.assertEqual(d, v.dimension) class CSSFunctionTestCase(basetest.BaseTestCase): def test_init(self): "CSSFunction.__init__()" v = css_parser.css.CSSFunction() self.assertEqual('', v.cssText) self.assertEqual('FUNCTION', v.type) self.assertEqual(v.value, '') def test_cssText(self): "CSSFunction.cssText" tests = { 'x(x)': ('x(x)', None), 'X( X )': ('x(X)', None), 'x(1,2)': ('x(1, 2)', None), 'x(1/**/)': ('x(1 /**/)', 'x(1)'), 'x(/**/1)': ('x(/**/ 1)', 'x(1)'), 'x(/**/1/**/)': ('x(/**/ 1 /**/)', 'x(1)'), 'x(/**/1,x/**/)': ('x(/**/ 1, x /**/)', 'x(1, x)'), 'x(1,2)': ('x(1, 2)', None), } for (f, (cssText, value)) in tests.items(): if value is None: value = cssText v = css_parser.css.CSSFunction(f) self.assertEqual(cssText, v.cssText) self.assertEqual('FUNCTION', v.type) self.assertEqual(value, v.value) class CSSVariableTestCase(basetest.BaseTestCase): def test_init(self): "CSSVariable.__init__()" v = css_parser.css.CSSVariable() self.assertEqual('', v.cssText) self.assertEqual('VARIABLE', v.type) self.assertTrue(None is v.name) self.assertTrue(None is v.value) def test_cssText(self): "CSSVariable.cssText" tests = { 'var(x)': ('var(x)', 'x', None), 'VAR( X )': ('var(X)', 'X', None), 'var(c1,rgb(14,14,14))': ('var(c1, rgb(14, 14, 14))', 'c1', 'rgb(14, 14, 14)'), 'var( L, 1px )': ('var(L, 1px)', 'L', '1px'), 'var(L,1)': ('var(L, 1)', 'L', '1'), 'var(T, calc( 2 * 1px ))': ('var(T, calc(2 * 1px))', 'T', 'calc(2 * 1px)'), 'var(U, url( example.png ) )': ('var(U, url(example.png))', 'U', 'url(example.png)'), 'var(C, #f00 )': ('var(C, #f00)', 'C', '#fff') } for (var, (cssText, name, fallback)) in tests.items(): v = css_parser.css.CSSVariable(var) self.assertEqual(cssText, v.cssText) self.assertEqual('VARIABLE', v.type) self.assertEqual(name, v.name) # not resolved so it is None self.assertEqual(None, v.value) # def test_cssValueType(self): # "CSSValue.cssValueType .cssValueTypeString" # tests = [ # ([u'inherit', u'INhe\\rit'], 'CSS_INHERIT', css_parser.css.CSSValue), # (['1', '1%', '1em', '1ex', '1px', '1cm', '1mm', '1in', '1pt', '1pc', # '1deg', '1rad', '1grad', '1ms', '1s', '1hz', '1khz', '1other', # '"string"', "'string'", 'url(x)', 'red', # 'attr(a)', 'counter(x)', 'rect(1px, 2px, 3px, 4px)', # 'rgb(0, 0, 0)', '#000', '#123456', 'rgba(0, 0, 0, 0)', # 'hsl(0, 0, 0)', 'hsla(0, 0, 0, 0)', # ], # 'CSS_PRIMITIVE_VALUE', css_parser.css.CSSPrimitiveValue), # ([u'1px 1px', 'red blue green x'], 'CSS_VALUE_LIST', css_parser.css.CSSValueList), # # what is a custom value? # #([], 'CSS_CUSTOM', css_parser.css.CSSValue) # ] # for values, name, cls in tests: # for value in values: # v = css_parser.css.CSSValue(cssText=value) # if value == "'string'": # # will be changed to " always # value = '"string"' # self.assertEqual(value, v.cssText) # self.assertEqual(name, v.cssValueTypeString) # self.assertEqual(getattr(v, name), v.cssValueType) # self.assertEqual(cls, type(v)) # class CSSPrimitiveValueTestCase(basetest.BaseTestCase): # # def test_init(self): # "CSSPrimitiveValue.__init__()" # v = css_parser.css.CSSPrimitiveValue(u'1') # self.assertTrue(u'1' == v.cssText) # # self.assertTrue(v.CSS_PRIMITIVE_VALUE == v.cssValueType) # self.assertTrue("CSS_PRIMITIVE_VALUE" == v.cssValueTypeString) # # self.assertTrue(v.CSS_NUMBER == v.primitiveType) # self.assertTrue("CSS_NUMBER" == v.primitiveTypeString) # # # DUMMY to be able to test empty constructor call # #self.assertRaises(xml.dom.SyntaxErr, v.__init__, None) # # self.assertRaises(xml.dom.InvalidAccessErr, v.getCounterValue) # self.assertRaises(xml.dom.InvalidAccessErr, v.getRGBColorValue) # self.assertRaises(xml.dom.InvalidAccessErr, v.getRectValue) # self.assertRaises(xml.dom.InvalidAccessErr, v.getStringValue) # # def test_CSS_UNKNOWN(self): # "CSSPrimitiveValue.CSS_UNKNOWN" # v = css_parser.css.CSSPrimitiveValue(u'expression(false)') # self.assertTrue(v.CSS_UNKNOWN == v.primitiveType) # self.assertTrue('CSS_UNKNOWN' == v.primitiveTypeString) # # def test_CSS_NUMBER_AND_OTHER_DIMENSIONS(self): # "CSSPrimitiveValue.CSS_NUMBER .. CSS_DIMENSION" # defs = [ # ('', 'CSS_NUMBER'), # ('%', 'CSS_PERCENTAGE'), # ('em', 'CSS_EMS'), # ('ex', 'CSS_EXS'), # ('px', 'CSS_PX'), # ('cm', 'CSS_CM'), # ('mm', 'CSS_MM'), # ('in', 'CSS_IN'), # ('pt', 'CSS_PT'), # ('pc', 'CSS_PC'), # ('deg', 'CSS_DEG'), # ('rad', 'CSS_RAD'), # ('grad', 'CSS_GRAD'), # ('ms', 'CSS_MS'), # ('s', 'CSS_S'), # ('hz', 'CSS_HZ'), # ('khz', 'CSS_KHZ'), # ('other_dimension', 'CSS_DIMENSION') # ] # for dim, name in defs: # for n in (0, 1, 1.1, -1, -1.1, -0): # v = css_parser.css.CSSPrimitiveValue('%i%s' % (n, dim)) # self.assertEqual(name, v.primitiveTypeString) # self.assertEqual(getattr(v, name), v.primitiveType) # # def test_CSS_STRING_AND_OTHER(self): # "CSSPrimitiveValue.CSS_STRING .. CSS_RGBCOLOR" # defs = [ # (('""', "''", '"some thing"', "' A\\ND '", # # comma separated lists are STRINGS FOR NOW! # 'a, b', # '"a", "b"', # ), 'CSS_STRING'), # (('url(a)', 'url("a b")', "url(' ')"), 'CSS_URI'), # (('some', 'or_anth-er'), 'CSS_IDENT'), # (('attr(a)', 'attr(b)'), 'CSS_ATTR'), # (('counter(1)', 'counter(2)'), 'CSS_COUNTER'), # (('rect(1,2,3,4)',), 'CSS_RECT'), # (('rgb(1,2,3)', 'rgb(10%, 20%, 30%)', '#123', '#123456'), # 'CSS_RGBCOLOR'), # (('rgba(1,2,3,4)','rgba(10%, 20%, 30%, 40%)', ), # 'CSS_RGBACOLOR'), # (('U+0', 'u+ffffff', 'u+000000-f', # 'u+0-f, U+ee-ff'), 'CSS_UNICODE_RANGE') # ] # # for examples, name in defs: # for x in examples: # v = css_parser.css.CSSPrimitiveValue(x) # self.assertEqual(getattr(v, name), v.primitiveType) # self.assertEqual(name, v.primitiveTypeString) # # def test_getFloat(self): # "CSSPrimitiveValue.getFloatValue()" # # NOT TESTED are float values as it seems difficult to # # compare these. Maybe use decimal.Decimal? # # v = css_parser.css.CSSPrimitiveValue(u'1px') # tests = { # '0': (v.CSS_NUMBER, 0), # '-1.1': (v.CSS_NUMBER, -1.1), # '1%': (v.CSS_PERCENTAGE, 1), # '-1%': (v.CSS_PERCENTAGE, -1), # '1em': (v.CSS_EMS, 1), # '-1.1em': (v.CSS_EMS, -1.1), # '1ex': (v.CSS_EXS, 1), # '1px': (v.CSS_PX, 1), # # '1cm': (v.CSS_CM, 1), # '1cm': (v.CSS_MM, 10), # '254cm': (v.CSS_IN, 100), # '1mm': (v.CSS_MM, 1), # '10mm': (v.CSS_CM, 1), # '254mm': (v.CSS_IN, 10), # '1in': (v.CSS_IN, 1), # '100in': (v.CSS_CM, 254), # ROUNDED!!! # '10in': (v.CSS_MM, 254), # ROUNDED!!! # # '1pt': (v.CSS_PT, 1), # '1pc': (v.CSS_PC, 1), # # '1deg': (v.CSS_DEG, 1), # '1rad': (v.CSS_RAD, 1), # '1grad': (v.CSS_GRAD, 1), # # '1ms': (v.CSS_MS, 1), # '1000ms': (v.CSS_S, 1), # '1s': (v.CSS_S, 1), # '1s': (v.CSS_MS, 1000), # # '1hz': (v.CSS_HZ, 1), # '1000hz': (v.CSS_KHZ, 1), # '1khz': (v.CSS_KHZ, 1), # '1khz': (v.CSS_HZ, 1000), # # '1DIMENSION': (v.CSS_DIMENSION, 1), # } # for cssText in tests: # v.cssText = cssText # unitType, exp = tests[cssText] # val = v.getFloatValue(unitType) # if unitType in (v.CSS_IN, v.CSS_CM): # val = round(val) # self.assertEqual(val , exp) # # def test_setFloat(self): # "CSSPrimitiveValue.setFloatValue()" # V = css_parser.css.CSSPrimitiveValue # # tests = { # # unitType, value # (V.CSS_NUMBER, 1): [ # # unitType, setvalue, # # getvalue or expected exception, msg or cssText # (V.CSS_NUMBER, 0, 0, '0'), # (V.CSS_NUMBER, 0.1, 0.1, '0.1'), # (V.CSS_NUMBER, -0, 0, '0'), # (V.CSS_NUMBER, 2, 2, '2'), # (V.CSS_NUMBER, 2.0, 2, '2'), # (V.CSS_NUMBER, 2.1, 2.1, '2.1'), # (V.CSS_NUMBER, -2.1, -2.1, '-2.1'), # # setting with string does work # (V.CSS_NUMBER, '1', 1, '1'), # (V.CSS_NUMBER, '1.1', 1.1, '1.1'), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DEG, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_RAD, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_GRAD, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_S, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_MS, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_KHZ, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_HZ, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DIMENSION, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_MM, 2, xml.dom.InvalidAccessErr, None), # # (V.CSS_NUMBER, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: floatValue 'x' is not a float"), # (V.CSS_NUMBER, '1x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: floatValue '1x' is not a float"), # # (V.CSS_STRING, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_STRING' is not a float type"), # (V.CSS_URI, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_URI' is not a float type"), # (V.CSS_ATTR, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_ATTR' is not a float type"), # (V.CSS_IDENT, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_IDENT' is not a float type"), # (V.CSS_RGBCOLOR, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_RGBCOLOR' is not a float type"), # (V.CSS_RGBACOLOR, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_RGBACOLOR' is not a float type"), # (V.CSS_RECT, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_RECT' is not a float type"), # (V.CSS_COUNTER, 'x', xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: unitType 'CSS_COUNTER' is not a float type"), # (V.CSS_EMS, 1, xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_NUMBER' to 'CSS_EMS'"), # (V.CSS_EXS, 1, xml.dom.InvalidAccessErr, # "CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_NUMBER' to 'CSS_EXS'") # ], # (V.CSS_MM, '1mm'): [ # (V.CSS_MM, 2, 2, '2mm'), # (V.CSS_MM, 0, 0, '0mm'), # (V.CSS_MM, 0.1, 0.1, '0.1mm'), # (V.CSS_MM, -0, -0, '0mm'), # (V.CSS_MM, 3.0, 3, '3mm'), # (V.CSS_MM, 3.1, 3.1, '3.1mm'), # (V.CSS_MM, -3.1, -3.1, '-3.1mm'), # (V.CSS_CM, 1, 10, '10mm'), # (V.CSS_IN, 10, 254, '254mm'), # (V.CSS_PT, 1, 1828.8, '1828.8mm'), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_NUMBER, 2, xml.dom.InvalidAccessErr, None) # ], # (V.CSS_PT, '1pt'): [ # (V.CSS_PT, 2, 2, '2pt'), # (V.CSS_PC, 12, 1, '1pt'), # (V.CSS_NUMBER, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DEG, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None) # ], # (V.CSS_KHZ, '1khz'): [ # (V.CSS_HZ, 2000, 2, '2khz'), # (V.CSS_NUMBER, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_DEG, 1, xml.dom.InvalidAccessErr, None), # (V.CSS_PX, 1, xml.dom.InvalidAccessErr, None) # ] # } # for test in tests: # initialType, initialValue = test # pv = css_parser.css.CSSPrimitiveValue(initialValue) # for setType, setValue, exp, cssText in tests[test]: # if type(exp) == types.TypeType or\ # type(exp) == types.ClassType: # 2.4 compatibility # if cssText: # self.assertRaisesMsg( # exp, cssText, pv.setFloatValue, setType, setValue) # else: # self.assertRaises( # exp, pv.setFloatValue, setType, setValue) # else: # pv.setFloatValue(setType, setValue) # self.assertEqual(pv._value[0], cssText) # if cssText == '0mm': # cssText = '0' # self.assertEqual(pv.cssText, cssText) # self.assertEqual(pv.getFloatValue(initialType), exp) # # def test_getString(self): # "CSSPrimitiveValue.getStringValue()" # v = css_parser.css.CSSPrimitiveValue(u'1px') # self.assertTrue(v.primitiveType == v.CSS_PX) # self.assertRaises(xml.dom.InvalidAccessErr, # v.getStringValue) # # pv = css_parser.css.CSSPrimitiveValue # tests = { # pv.CSS_STRING: ("'red'", 'red'), # pv.CSS_STRING: ('"red"', 'red'), # pv.CSS_URI: ('url(http://example.com)', None), # pv.CSS_URI: ("url('http://example.com')", # u"http://example.com"), # pv.CSS_URI: ('url("http://example.com")', # u'http://example.com'), # pv.CSS_URI: ('url("http://example.com?)")', # u'http://example.com?)'), # pv.CSS_IDENT: ('red', None), # pv.CSS_ATTR: ('attr(att-name)', # u'att-name'), # the name of the attrr # } # for t in tests: # val, exp = tests[t] # if not exp: # exp = val # # v = css_parser.css.CSSPrimitiveValue(val) # self.assertEqual(v.primitiveType, t) # self.assertEqual(v.getStringValue(), exp) # # def test_setString(self): # "CSSPrimitiveValue.setStringValue()" # # CSS_STRING # v = css_parser.css.CSSPrimitiveValue(u'"a"') # self.assertTrue(v.CSS_STRING == v.primitiveType) # v.setStringValue(v.CSS_STRING, 'b') # self.assertTrue(('b', 'STRING') == v._value) # self.assertEqual('b', v.getStringValue()) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_STRING' to 'CSS_URI'", # v.setStringValue, *(v.CSS_URI, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_STRING' to 'CSS_IDENT'", # v.setStringValue, *(v.CSS_IDENT, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_STRING' to 'CSS_ATTR'", # v.setStringValue, *(v.CSS_ATTR, 'x')) # # # CSS_IDENT # v = css_parser.css.CSSPrimitiveValue('new') # v.setStringValue(v.CSS_IDENT, 'ident') # self.assertTrue(v.CSS_IDENT == v.primitiveType) # self.assertTrue(('ident', 'IDENT') == v._value) # self.assertTrue('ident' == v.getStringValue()) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_IDENT' to 'CSS_URI'", # v.setStringValue, *(v.CSS_URI, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_IDENT' to 'CSS_STRING'", # v.setStringValue, *(v.CSS_STRING, '"x"')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_IDENT' to 'CSS_ATTR'", # v.setStringValue, *(v.CSS_ATTR, 'x')) # # # CSS_URI # v = css_parser.css.CSSPrimitiveValue('url(old)') # v.setStringValue(v.CSS_URI, '(') # self.assertEqual((u'(', 'URI'), v._value) # self.assertEqual(u'(', v.getStringValue()) # # v.setStringValue(v.CSS_URI, ')') # self.assertEqual((u')', 'URI'), v._value) # self.assertEqual(u')', v.getStringValue()) # # v.setStringValue(v.CSS_URI, '"') # self.assertEqual(ur'"', v.getStringValue()) # self.assertEqual((ur'"', 'URI'), v._value) # # v.setStringValue(v.CSS_URI, "''") # self.assertEqual(ur"''", v.getStringValue()) # self.assertEqual((ur"''", 'URI'), v._value) # # v.setStringValue(v.CSS_URI, ',') # self.assertEqual(ur',', v.getStringValue()) # self.assertEqual((ur',', 'URI'), v._value) # # v.setStringValue(v.CSS_URI, ' ') # self.assertEqual((u' ', 'URI'), v._value) # self.assertEqual(u' ', v.getStringValue()) # # v.setStringValue(v.CSS_URI, 'a)') # self.assertEqual((u'a)', 'URI'), v._value) # self.assertEqual(u'a)', v.getStringValue()) # # v.setStringValue(v.CSS_URI, 'a') # self.assertTrue(v.CSS_URI == v.primitiveType) # self.assertEqual((u'a', 'URI'), v._value) # self.assertEqual(u'a', v.getStringValue()) # # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_URI' to 'CSS_IDENT'", # v.setStringValue, *(v.CSS_IDENT, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_URI' to 'CSS_STRING'", # v.setStringValue, *(v.CSS_STRING, '"x"')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_URI' to 'CSS_ATTR'", # v.setStringValue, *(v.CSS_ATTR, 'x')) # # # CSS_ATTR # v = css_parser.css.CSSPrimitiveValue('attr(old)') # v.setStringValue(v.CSS_ATTR, 'a') # self.assertTrue(v.CSS_ATTR == v.primitiveType) # self.assertTrue('a' == v.getStringValue()) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_ATTR' to 'CSS_IDENT'", # v.setStringValue, *(v.CSS_IDENT, 'x')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_ATTR' to 'CSS_STRING'", # v.setStringValue, *(v.CSS_STRING, '"x"')) # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: Cannot coerce primitiveType 'CSS_ATTR' to 'CSS_URI'", # v.setStringValue, *(v.CSS_URI, 'x')) # # # TypeError as 'x' is no valid type # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: stringType 'x' (UNKNOWN TYPE) is not a string type", # v.setStringValue, *('x', 'brown')) # # IndexError as 111 is no valid type # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: stringType 111 (UNKNOWN TYPE) is not a string type", # v.setStringValue, *(111, 'brown')) # # CSS_PX is no string type # self.assertRaisesMsg(xml.dom.InvalidAccessErr, # u"CSSPrimitiveValue: stringType CSS_PX is not a string type", # v.setStringValue, *(v.CSS_PX, 'brown')) # # def test_typeRGBColor(self): # "RGBColor" # v = css_parser.css.CSSPrimitiveValue('RGB(1, 5, 10)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # self.assertEqual(u'rgb(1, 5, 10)', v.cssText) # # v = css_parser.css.CSSPrimitiveValue('rgb(1, 5, 10)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # self.assertEqual(u'rgb(1, 5, 10)', v.cssText) # # v = css_parser.css.CSSPrimitiveValue('rgb(1%, 5%, 10%)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # self.assertEqual(u'rgb(1%, 5%, 10%)', v.cssText) # # v = css_parser.css.CSSPrimitiveValue(' rgb( 1 ,5, 10 )') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # v = css_parser.css.CSSPrimitiveValue('rgb(1,5,10)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) # v = css_parser.css.CSSPrimitiveValue('rgb(1%, .5%, 10.1%)') # self.assertEqual(v.CSS_RGBCOLOR, v.primitiveType) if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563076.0 css-parser-1.0.7/css_parser_tests/test_x.py0000644000175000017500000000421400000000000020063 0ustar00kovidkovid"""Testcases for css_parser.css.CSSValue and CSSPrimitiveValue.""" from __future__ import absolute_import from __future__ import unicode_literals __version__ = '$Id: test_cssvalue.py 1473 2008-09-15 21:15:54Z cthedot $' # from decimal import Decimal # maybe for later tests? import xml.dom from . import basetest import css_parser import types class XTestCase(basetest.BaseTestCase): def setUp(self): css_parser.ser.prefs.useDefaults() def tearDown(self): css_parser.ser.prefs.useDefaults() def test_prioriy(self): "Property.priority" s = css_parser.parseString('a { color: red }') self.assertEqual(s.cssText, 'a {\n color: red\n }'.encode()) # self.assertEqual(u'', s.cssRules[0].style.getPropertyPriority('color')) # # s = css_parser.parseString('a { color: red !important }') # self.assertEqual(u'a {\n color: red !important\n }', s.cssText) # self.assertEqual(u'important', s.cssRules[0].style.getPropertyPriority('color')) # # css_parser.log.raiseExceptions = True # p = css_parser.css.Property(u'color', u'red', u'') # self.assertEqual(p.priority, u'') # p = css_parser.css.Property(u'color', u'red', u'!important') # self.assertEqual(p.priority, u'important') # self.assertRaisesMsg(xml.dom.SyntaxErr, # u'', # css_parser.css.Property, u'color', u'red', u'x') # # css_parser.log.raiseExceptions = False # p = css_parser.css.Property(u'color', u'red', u'!x') # self.assertEqual(p.priority, u'x') # p = css_parser.css.Property(u'color', u'red', u'!x') # self.assertEqual(p.priority, u'x') # css_parser.log.raiseExceptions = True # # # # invalid but kept! # css_parser.log.raiseExceptions = False ## s = css_parser.parseString('a { color: red !x }') ## self.assertEqual(u'a {\n color: red !x\n }', s.cssText) ## self.assertEqual(u'x', s.cssRules[0].style.getPropertyPriority('color')) # if __name__ == '__main__': import unittest unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1604678760.0 css-parser-1.0.7/run_tests.py0000755000175000017500000000650200000000000015222 0ustar00kovidkovid#!/usr/bin/env python # vim:fileencoding=utf-8 # License: LGPLv3 Copyright: 2017, Kovid Goyal from __future__ import (absolute_import, division, print_function, unicode_literals) import importlib import logging import os import sys import unittest self_path = os.path.abspath(__file__) base = os.path.dirname(self_path) def itertests(suite): stack = [suite] while stack: suite = stack.pop() for test in suite: if isinstance(test, unittest.TestSuite): stack.append(test) continue if test.__class__.__name__ == 'ModuleImportFailure': raise Exception('Failed to import a test module: %s' % test) yield test def filter_tests(suite, test_ok): ans = unittest.TestSuite() added = set() for test in itertests(suite): if test_ok(test) and test not in added: ans.addTest(test) added.add(test) return ans def filter_tests_by_name(suite, name): if not name.startswith('test_'): name = 'test_' + name if name.endswith('_'): def q(test): return test._testMethodName.startswith(name) else: def q(test): return test._testMethodName == name return filter_tests(suite, q) def filter_tests_by_module(suite, *names): names = frozenset(names) def q(test): m = test.__class__.__module__.rpartition('.')[-1] return m in names return filter_tests(suite, q) def find_tests(): suites = [] for f in os.listdir(os.path.join(base, 'css_parser_tests')): n, ext = os.path.splitext(f) if ext == '.py' and n.startswith('test_'): m = importlib.import_module('css_parser_tests.' + n) suite = unittest.defaultTestLoader.loadTestsFromModule(m) suites.append(suite) return unittest.TestSuite(suites) def run_tests(test_names=()): sys.path = [base, os.path.join(base, 'src')] + sys.path import css_parser tests = find_tests() suites = [] for name in test_names: if name.endswith('.'): module_name = name[:-1] if not module_name.startswith('test_'): module_name = 'test_' + module_name suites.append(filter_tests_by_module(tests, module_name)) else: suites.append(filter_tests_by_name(tests, name)) tests = unittest.TestSuite(suites) if suites else tests r = unittest.TextTestRunner css_parser.log.setLevel(logging.CRITICAL) result = r().run(tests) if not result.wasSuccessful(): raise SystemExit(1) def main(): import argparse parser = argparse.ArgumentParser( description='''\ Run the specified tests, or all tests if none are specified. Tests can be specified as either the test method name (without the leading test_) or a module name with a trailing period. ''') parser.add_argument( 'test_name', nargs='*', help=( 'Test name (either a method name or a module name with a trailing period)' '. Note that if the name ends with a trailing underscore all tests methods' ' whose names start with the specified name are run.' ) ) args = parser.parse_args() run_tests(args.test_name) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1406815 css-parser-1.0.7/setup.cfg0000644000175000017500000000020100000000000014426 0ustar00kovidkovid[flake8] max-line-length = 120 builtins = unicode,basestring [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1604675925.0 css-parser-1.0.7/setup.py0000755000175000017500000000457000000000000014337 0ustar00kovidkovid#!/usr/bin/env python # -*- coding: utf-8 -*- # License: LGPLv3 Copyright: 2019, Kovid Goyal import ast import re import sys import os from setuptools import find_packages, setup from setuptools.command.test import test # extract the version without importing the module VERSION = open('src/css_parser/version.py', 'rb').read().decode('utf-8') VERSION = '.'.join(map(str, ast.literal_eval(re.search(r'^version\s+=\s+(.+)', VERSION, flags=re.M).group(1)))) long_description = '\n' + open('README.md', 'rb').read().decode('utf-8') + '\n' # + read('CHANGELOG.txt') class Test(test): user_options = [ ('which-test=', 'w', "Specify which test to run as either" " the test method name (without the leading test_)" " or a module name with a trailing period"), ] def initialize_options(self): self.which_test = None def finalize_options(self): pass def run(self): import importlib orig = sys.path[:] try: sys.path.insert(0, os.getcwd()) m = importlib.import_module('run_tests') which_test = (self.which_test,) if self.which_test else () m.run_tests(which_test) finally: sys.path = orig setup( name='css-parser', version=VERSION, package_dir={'': 'src'}, packages=find_packages('src'), description='A CSS Cascading Style Sheets library for Python', long_description=long_description, long_description_content_type='text/markdown', cmdclass={'test': Test}, author='Various People', author_email='redacted@anonymous.net', url='https://github.com/ebook-utils/css-parser', license='LGPL 3.0 or later', keywords='CSS, Cascading Style Sheets, CSSParser, DOM Level 2 Stylesheets, DOM Level 2 CSS', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Internet', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Processing :: Markup :: HTML' ] ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.0906813 css-parser-1.0.7/src/0000755000175000017500000000000000000000000013403 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1340148 css-parser-1.0.7/src/css_parser/0000755000175000017500000000000000000000000015547 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/__init__.py0000644000175000017500000003733700000000000017675 0ustar00kovidkovid#!/usr/bin/env python from __future__ import unicode_literals, division, absolute_import, print_function from .profiles import Profiles from .serialize import CSSSerializer from .parse import CSSParser from . import css from . import errorhandler from .version import VERSION import xml.dom import os.path import sys """css_parser - CSS Cascading Style Sheets library for Python Copyright (C) 2004-2013 Christof Hoeke css_parser is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . A Python package to parse and build CSS Cascading Style Sheets. DOM only, not any rendering facilities! Based upon and partly implementing the following specifications : `CSS 2.1 `__ General CSS rules and properties are defined here `CSS 2.1 Errata `__ A few errata, mainly the definition of CHARSET_SYM tokens `CSS3 Module: Syntax `__ Used in parts since css_parser 0.9.4. css_parser tries to use the features from CSS 2.1 and CSS 3 with preference to CSS3 but as this is not final yet some parts are from CSS 2.1 `MediaQueries `__ MediaQueries are part of ``stylesheets.MediaList`` since v0.9.4, used in @import and @media rules. `Namespaces `__ Added in v0.9.1, updated to definition in CSSOM in v0.9.4, updated in 0.9.5 for dev version `CSS3 Module: Pages Media `__ Most properties of this spec are implemented including MarginRules `Selectors `__ The selector syntax defined here (and not in CSS 2.1) should be parsable with css_parser (*should* mind though ;) ) `DOM Level 2 Style CSS `__ DOM for package css. 0.9.8 removes support for CSSValue and related API, see PropertyValue and Value API for now `DOM Level 2 Style Stylesheets `__ DOM for package stylesheets `CSSOM `__ A few details (mainly the NamespaceRule DOM) is taken from here. Plan is to move implementation to the stuff defined here which is newer but still no REC so might change anytime... The css_parser tokenizer is a customized implementation of `CSS3 Module: Syntax (W3C Working Draft 13 August 2003) `__ which itself is based on the CSS 2.1 tokenizer. It tries to be as compliant as possible but uses some (helpful) parts of the CSS 2.1 tokenizer. I guess css_parser is neither CSS 2.1 nor CSS 3 compliant but tries to at least be able to parse both grammars including some more real world cases (some CSS hacks are actually parsed and serialized). Both official grammars are not final nor bugfree but still feasible. css_parser aim is not to be fully compliant to any CSS specification (the specifications seem to be in a constant flow anyway) but css_parser *should* be able to read and write as many as possible CSS stylesheets "in the wild" while at the same time implement the official APIs which are well documented. Some minor extensions are provided as well. Please visit http://cthedot.de/css_parser/ for more details. Tested with Python 2.7.6 and 3.3.3 on Windows 8.1 64bit. This library may be used ``from css_parser import *`` which import subpackages ``css`` and ``stylesheets``, CSSParser and CSSSerializer classes only. Usage may be:: >>> from css_parser import * >>> parser = CSSParser() >>> sheet = parser.parseString(u'a { color: red}') >>> print sheet.cssText a { color: red } """ __all__ = list(map(str, ('css', 'stylesheets', 'CSSParser', 'CSSSerializer'))) __docformat__ = 'restructuredtext' __author__ = 'Christof Hoeke with contributions by Walter Doerwald' __date__ = '$LastChangedDate:: $:' if sys.version_info[0] >= 3: text_type = str from urllib.parse import urlsplit as urllib_urlsplit from urllib.request import pathname2url as urllib_pathname2url else: text_type = unicode from urlparse import urlsplit as urllib_urlsplit from urllib import pathname2url as urllib_pathname2url if sys.version_info < (2, 6): bytes = str # order of imports is important (partly circular) log = errorhandler.ErrorHandler() VERSION ser = CSSSerializer() profile = Profiles(log=log) # used by Selector defining namespace prefix '*' _ANYNS = -1 class DOMImplementationCSS(object): """This interface allows the DOM user to create a CSSStyleSheet outside the context of a document. There is no way to associate the new CSSStyleSheet with a document in DOM Level 2. This class is its *own factory*, as it is given to xml.dom.registerDOMImplementation which simply calls it and receives an instance of this class then. """ _features = [ ('css', '1.0'), ('css', '2.0'), ('stylesheets', '1.0'), ('stylesheets', '2.0') ] def createCSSStyleSheet(self, title, media): """ Creates a new CSSStyleSheet. title of type DOMString The advisory title. See also the Style Sheet Interfaces section. media of type DOMString The comma-separated list of media associated with the new style sheet. See also the Style Sheet Interfaces section. returns CSSStyleSheet: A new CSS style sheet. TODO: DOMException SYNTAX_ERR: Raised if the specified media string value has a syntax error and is unparsable. """ import warnings warning = ("Deprecated, see " "https://bitbucket.org/cthedot/css_parser/issues/69#comment-30669799") warnings.warn(warning, DeprecationWarning) return css.CSSStyleSheet(title=title, media=media) def createDocument(self, *args, **kwargs): # sometimes css_parser is picked automatically for # xml.dom.getDOMImplementation, so we should provide an implementation # see https://bitbucket.org/cthedot/css_parser/issues/69 import xml.dom.minidom as minidom return minidom.DOMImplementation().createDocument(*args, **kwargs) def createDocumentType(self, *args, **kwargs): # sometimes css_parser is picked automatically for # xml.dom.getDOMImplementation, so we should provide an implementation # see https://bitbucket.org/cthedot/css_parser/issues/69 import xml.dom.minidom as minidom return minidom.DOMImplementation().createDocumentType(*args, **kwargs) def hasFeature(self, feature, version): return (feature.lower(), text_type(version)) in self._features xml.dom.registerDOMImplementation('css_parser', DOMImplementationCSS) def parseString(*a, **k): return CSSParser().parseString(*a, **k) parseString.__doc__ = CSSParser.parseString.__doc__ def parseFile(*a, **k): return CSSParser().parseFile(*a, **k) parseFile.__doc__ = CSSParser.parseFile.__doc__ def parseUrl(*a, **k): return CSSParser().parseUrl(*a, **k) parseUrl.__doc__ = CSSParser.parseUrl.__doc__ def parseStyle(*a, **k): return CSSParser().parseStyle(*a, **k) parseStyle.__doc__ = CSSParser.parseStyle.__doc__ # set "ser", default serializer def setSerializer(serializer): """Set the global serializer used by all class in css_parser.""" global ser ser = serializer def getUrls(sheet): """Retrieve all ``url(urlstring)`` values (in e.g. :class:`css_parser.css.CSSImportRule` or :class:`css_parser.css.CSSValue` objects of given `sheet`. :param sheet: :class:`css_parser.css.CSSStyleSheet` object whose URLs are yielded This function is a generator. The generated URL values exclude ``url(`` and ``)`` and surrounding single or double quotes. """ for importrule in (r for r in sheet if r.type == r.IMPORT_RULE): yield importrule.href def styleDeclarations(base): "recursive generator to find all CSSStyleDeclarations" if hasattr(base, 'cssRules'): for rule in base.cssRules: for s in styleDeclarations(rule): yield s elif hasattr(base, 'style'): yield base.style for style in styleDeclarations(sheet): for p in style.getProperties(all=True): for v in p.propertyValue: if v.type == 'URI': yield v.uri def replaceUrls(sheetOrStyle, replacer, ignoreImportRules=False): """Replace all URLs in :class:`css_parser.css.CSSImportRule` or :class:`css_parser.css.CSSValue` objects of given `sheetOrStyle`. :param sheetOrStyle: a :class:`css_parser.css.CSSStyleSheet` or a :class:`css_parser.css.CSSStyleDeclaration` which is changed in place :param replacer: a function which is called with a single argument `url` which is the current value of each url() excluding ``url(``, ``)`` and surrounding (single or double) quotes. :param ignoreImportRules: if ``True`` does not call `replacer` with URLs from @import rules. """ if not ignoreImportRules and not isinstance(sheetOrStyle, css.CSSStyleDeclaration): for importrule in (r for r in sheetOrStyle if r.type == r.IMPORT_RULE): importrule.href = replacer(importrule.href) def styleDeclarations(base): "recursive generator to find all CSSStyleDeclarations" if hasattr(base, 'cssRules'): for rule in base.cssRules: for s in styleDeclarations(rule): yield s elif hasattr(base, 'style'): yield base.style elif isinstance(sheetOrStyle, css.CSSStyleDeclaration): # base is a style already yield base for style in styleDeclarations(sheetOrStyle): for p in style.getProperties(all=True): for v in p.propertyValue: if v.type == v.URI: v.uri = replacer(v.uri) def resolveImports(sheet, target=None): """Recurcively combine all rules in given `sheet` into a `target` sheet. @import rules which use media information are tried to be wrapped into @media rules so keeping the media information. This may not work in all instances (if e.g. an @import rule itself contains an @import rule with different media infos or if it contains rules which may not be used inside an @media block like @namespace rules.). In these cases the @import rule is kept as in the original sheet and a WARNING is issued. :param sheet: in this given :class:`css_parser.css.CSSStyleSheet` all import rules are resolved and added to a resulting *flat* sheet. :param target: A :class:`css_parser.css.CSSStyleSheet` object which will be the resulting *flat* sheet if given :returns: given `target` or a new :class:`css_parser.css.CSSStyleSheet` object """ if not target: target = css.CSSStyleSheet(href=sheet.href, media=sheet.media, title=sheet.title) def getReplacer(targetbase): "Return a replacer which uses base to return adjusted URLs" basesch, baseloc, basepath, basequery, basefrag = urllib_urlsplit(targetbase) basepath, basepathfilename = os.path.split(basepath) def replacer(uri): scheme, location, path, query, fragment = urllib_urlsplit(uri) if not scheme and not location and not path.startswith('/'): # relative path, filename = os.path.split(path) combined = os.path.normpath(os.path.join(basepath, path, filename)) return urllib_pathname2url(combined) else: # keep anything absolute return uri return replacer for rule in sheet.cssRules: if rule.type == rule.CHARSET_RULE: pass elif rule.type == rule.IMPORT_RULE: log.info('Processing @import %r' % rule.href, neverraise=True) if rule.hrefFound: # add all rules of @import to current sheet target.add(css.CSSComment(cssText='/* START @import "%s" */' % rule.href)) try: # nested imports importedSheet = resolveImports(rule.styleSheet) except xml.dom.HierarchyRequestErr as e: log.warn('@import: Cannot resolve target, keeping rule: %s' % e, neverraise=True) target.add(rule) else: # adjust relative URI references log.info('@import: Adjusting paths for %r' % rule.href, neverraise=True) replaceUrls(importedSheet, getReplacer(rule.href), ignoreImportRules=True) # might have to wrap rules in @media if media given if rule.media.mediaText == 'all': mediaproxy = None else: keepimport = False for r in importedSheet: # check if rules present which may not be # combined with media if r.type not in (r.COMMENT, r.STYLE_RULE, r.IMPORT_RULE): keepimport = True break if keepimport: log.warn('Cannot combine imported sheet with' ' given media as other rules then' ' comments or stylerules found %r,' ' keeping %r' % (r, rule.cssText), neverraise=True) target.add(rule) continue # wrap in @media if media is not `all` log.info('@import: Wrapping some rules in @media ' ' to keep media: %s' % rule.media.mediaText, neverraise=True) mediaproxy = css.CSSMediaRule(rule.media.mediaText) for r in importedSheet: if mediaproxy: mediaproxy.add(r) else: # add to top sheet directly but are difficult anyway target.add(r) if mediaproxy: target.add(mediaproxy) else: # keep @import as it is log.error('Cannot get referenced stylesheet %r, keeping rule' % rule.href, neverraise=True) target.add(rule) else: target.add(rule) return target if __name__ == '__main__': print(__doc__) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/_codec2.py0000644000175000017500000005634700000000000017436 0ustar00kovidkovid#!/usr/bin/env python """Python codec for CSS.""" __docformat__ = 'restructuredtext' __author__ = 'Walter Doerwald' __version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $' import codecs import marshal # We're using bits to store all possible candidate encodings (or variants, i.e. # we have two bits for the variants of UTF-16 and two for the # variants of UTF-32). # # Prefixes for various CSS encodings # UTF-8-SIG xEF xBB xBF # UTF-16 (LE) xFF xFE ~x00|~x00 # UTF-16 (BE) xFE xFF # UTF-16-LE @ x00 @ x00 # UTF-16-BE x00 @ # UTF-32 (LE) xFF xFE x00 x00 # UTF-32 (BE) x00 x00 xFE xFF # UTF-32-LE @ x00 x00 x00 # UTF-32-BE x00 x00 x00 @ # CHARSET @ c h a ... def detectencoding_str(input, final=False): """ Detect the encoding of the byte string ``input``, which contains the beginning of a CSS file. This function returns the detected encoding (or ``None`` if it hasn't got enough data), and a flag that indicates whether that encoding has been detected explicitely or implicitely. To detect the encoding the first few bytes are used (or if ``input`` is ASCII compatible and starts with a charset rule the encoding name from the rule). "Explicit" detection means that the bytes start with a BOM or a charset rule. If the encoding can't be detected yet, ``None`` is returned as the encoding. ``final`` specifies whether more data will be available in later calls or not. If ``final`` is true, ``detectencoding_str()`` will never return ``None`` as the encoding. """ # A bit for every candidate CANDIDATE_UTF_8_SIG = 1 CANDIDATE_UTF_16_AS_LE = 2 CANDIDATE_UTF_16_AS_BE = 4 CANDIDATE_UTF_16_LE = 8 CANDIDATE_UTF_16_BE = 16 CANDIDATE_UTF_32_AS_LE = 32 CANDIDATE_UTF_32_AS_BE = 64 CANDIDATE_UTF_32_LE = 128 CANDIDATE_UTF_32_BE = 256 CANDIDATE_CHARSET = 512 candidates = 1023 # all candidates li = len(input) if li >= 1: # Check first byte c = input[0] if c != "\xef": candidates &= ~CANDIDATE_UTF_8_SIG if c != "\xff": candidates &= ~(CANDIDATE_UTF_32_AS_LE | CANDIDATE_UTF_16_AS_LE) if c != "\xfe": candidates &= ~CANDIDATE_UTF_16_AS_BE if c != "@": candidates &= ~(CANDIDATE_UTF_32_LE | CANDIDATE_UTF_16_LE | CANDIDATE_CHARSET) if c != "\x00": candidates &= ~(CANDIDATE_UTF_32_AS_BE | CANDIDATE_UTF_32_BE | CANDIDATE_UTF_16_BE) if li >= 2: # Check second byte c = input[1] if c != "\xbb": candidates &= ~CANDIDATE_UTF_8_SIG if c != "\xfe": candidates &= ~(CANDIDATE_UTF_16_AS_LE | CANDIDATE_UTF_32_AS_LE) if c != "\xff": candidates &= ~CANDIDATE_UTF_16_AS_BE if c != "\x00": candidates &= ~(CANDIDATE_UTF_16_LE | CANDIDATE_UTF_32_AS_BE | CANDIDATE_UTF_32_LE | CANDIDATE_UTF_32_BE) if c != "@": candidates &= ~CANDIDATE_UTF_16_BE if c != "c": candidates &= ~CANDIDATE_CHARSET if li >= 3: # Check third byte c = input[2] if c != "\xbf": candidates &= ~CANDIDATE_UTF_8_SIG if c != "c": candidates &= ~CANDIDATE_UTF_16_LE if c != "\x00": candidates &= ~(CANDIDATE_UTF_32_AS_LE | CANDIDATE_UTF_32_LE | CANDIDATE_UTF_32_BE) if c != "\xfe": candidates &= ~CANDIDATE_UTF_32_AS_BE if c != "h": candidates &= ~CANDIDATE_CHARSET if li >= 4: # Check fourth byte c = input[3] if input[2:4] == "\x00\x00": candidates &= ~CANDIDATE_UTF_16_AS_LE if c != "\x00": candidates &= ~(CANDIDATE_UTF_16_LE | CANDIDATE_UTF_32_AS_LE | CANDIDATE_UTF_32_LE) if c != "\xff": candidates &= ~CANDIDATE_UTF_32_AS_BE if c != "@": candidates &= ~CANDIDATE_UTF_32_BE if c != "a": candidates &= ~CANDIDATE_CHARSET if candidates == 0: return ("utf-8", False) if not (candidates & (candidates-1)): # only one candidate remaining if candidates == CANDIDATE_UTF_8_SIG and li >= 3: return ("utf-8-sig", True) elif candidates == CANDIDATE_UTF_16_AS_LE and li >= 2: return ("utf-16", True) elif candidates == CANDIDATE_UTF_16_AS_BE and li >= 2: return ("utf-16", True) elif candidates == CANDIDATE_UTF_16_LE and li >= 4: return ("utf-16-le", False) elif candidates == CANDIDATE_UTF_16_BE and li >= 2: return ("utf-16-be", False) elif candidates == CANDIDATE_UTF_32_AS_LE and li >= 4: return ("utf-32", True) elif candidates == CANDIDATE_UTF_32_AS_BE and li >= 4: return ("utf-32", True) elif candidates == CANDIDATE_UTF_32_LE and li >= 4: return ("utf-32-le", False) elif candidates == CANDIDATE_UTF_32_BE and li >= 4: return ("utf-32-be", False) elif candidates == CANDIDATE_CHARSET and li >= 4: prefix = '@charset "' if input[:len(prefix)] == prefix: pos = input.find('"', len(prefix)) if pos >= 0: return (input[len(prefix):pos], True) # if this is the last call, and we haven't determined an encoding yet, # we default to UTF-8 if final: return ("utf-8", False) return (None, False) # dont' know yet def detectencoding_unicode(input, final=False): """ Detect the encoding of the unicode string ``input``, which contains the beginning of a CSS file. The encoding is detected from the charset rule at the beginning of ``input``. If there is no charset rule, ``"utf-8"`` will be returned. If the encoding can't be detected yet, ``None`` is returned. ``final`` specifies whether more data will be available in later calls or not. If ``final`` is true, ``detectencoding_unicode()`` will never return ``None``. """ prefix = u'@charset "' if input.startswith(prefix): pos = input.find(u'"', len(prefix)) if pos >= 0: return (input[len(prefix):pos], True) elif final or not prefix.startswith(input): # if this is the last call, and we haven't determined an encoding yet, # (or the string definitely doesn't start with prefix) we default to UTF-8 return ("utf-8", False) return (None, False) # don't know yet def _fixencoding(input, encoding, final=False): """ Replace the name of the encoding in the charset rule at the beginning of ``input`` with ``encoding``. If ``input`` doesn't starts with a charset rule, ``input`` will be returned unmodified. If the encoding can't be found yet, ``None`` is returned. ``final`` specifies whether more data will be available in later calls or not. If ``final`` is true, ``_fixencoding()`` will never return ``None``. """ prefix = u'@charset "' if len(input) > len(prefix): if input.startswith(prefix): pos = input.find(u'"', len(prefix)) if pos >= 0: if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = u"utf-8" return prefix + encoding + input[pos:] # we haven't seen the end of the encoding name yet => fall through else: return input # doesn't start with prefix, so nothing to fix elif not prefix.startswith(input) or final: # can't turn out to be a @charset rule later (or there is no "later") return input if final: return input return None # don't know yet def decode(input, errors="strict", encoding=None, force=True): if encoding is None or not force: (_encoding, explicit) = detectencoding_str(input, True) if _encoding == "css": raise ValueError("css not allowed as encoding name") if (explicit and not force) or encoding is None: # Take the encoding from the input encoding = _encoding (input, consumed) = codecs.getdecoder(encoding)(input, errors) return (_fixencoding(input, unicode(encoding), True), consumed) def encode(input, errors="strict", encoding=None): consumed = len(input) if encoding is None: encoding = detectencoding_unicode(input, True)[0] if encoding.replace("_", "-").lower() == "utf-8-sig": input = _fixencoding(input, u"utf-8", True) else: input = _fixencoding(input, unicode(encoding), True) if encoding == "css": raise ValueError("css not allowed as encoding name") encoder = codecs.getencoder(encoding) return (encoder(input, errors)[0], consumed) def _bytes2int(bytes): # Helper: convert an 8 bit string into an ``int``. i = 0 for byte in bytes: i = (i << 8) + ord(byte) return i def _int2bytes(i): # Helper: convert an ``int`` into an 8-bit string. v = [] while i: v.insert(0, chr(i & 0xff)) i >>= 8 return "".join(v) if hasattr(codecs, "IncrementalDecoder"): class IncrementalDecoder(codecs.IncrementalDecoder): def __init__(self, errors="strict", encoding=None, force=True): self.decoder = None self.encoding = encoding self.force = force codecs.IncrementalDecoder.__init__(self, errors) # Store ``errors`` somewhere else, # because we have to hide it in a property self._errors = errors self.buffer = u"".encode() self.headerfixed = False def iterdecode(self, input): for part in input: result = self.decode(part, False) if result: yield result result = self.decode("", True) if result: yield result def decode(self, input, final=False): # We're doing basically the same as a ``BufferedIncrementalDecoder``, # but since the buffer is only relevant until the encoding has been # detected (in which case the buffer of the underlying codec might # kick in), we're implementing buffering ourselves to avoid some # overhead. if self.decoder is None: input = self.buffer + input # Do we have to detect the encoding from the input? if self.encoding is None or not self.force: (encoding, explicit) = detectencoding_str(input, final) if encoding is None: # no encoding determined yet self.buffer = input # retry the complete input on the next call return u"" # no encoding determined yet, so no output elif encoding == "css": raise ValueError("css not allowed as encoding name") if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input self.encoding = encoding self.buffer = "" # drop buffer, as the decoder might keep its own decoder = codecs.getincrementaldecoder(self.encoding) self.decoder = decoder(self._errors) if self.headerfixed: return self.decoder.decode(input, final) # If we haven't fixed the header yet, # the content of ``self.buffer`` is a ``unicode`` object output = self.buffer + self.decoder.decode(input, final) encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newoutput = _fixencoding(output, unicode(encoding), final) if newoutput is None: # retry fixing the @charset rule (but keep the decoded stuff) self.buffer = output return u"" self.headerfixed = True return newoutput def reset(self): codecs.IncrementalDecoder.reset(self) self.decoder = None self.buffer = u"".encode() self.headerfixed = False def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors`` must be done on the real decoder too if self.decoder is not None: self.decoder.errors = errors self._errors = errors errors = property(_geterrors, _seterrors) def getstate(self): if self.decoder is not None: state = (self.encoding, self.buffer, self.headerfixed, True, self.decoder.getstate()) else: state = (self.encoding, self.buffer, self.headerfixed, False, None) return ("", _bytes2int(marshal.dumps(state))) def setstate(self, state): state = _int2bytes(marshal.loads(state[1])) # ignore buffered input self.encoding = state[0] self.buffer = state[1] self.headerfixed = state[2] if state[3] is not None: self.decoder = codecs.getincrementaldecoder(self.encoding)(self._errors) self.decoder.setstate(state[4]) else: self.decoder = None if hasattr(codecs, "IncrementalEncoder"): class IncrementalEncoder(codecs.IncrementalEncoder): def __init__(self, errors="strict", encoding=None): self.encoder = None self.encoding = encoding codecs.IncrementalEncoder.__init__(self, errors) # Store ``errors`` somewhere else, # because we have to hide it in a property self._errors = errors self.buffer = u"" def iterencode(self, input): for part in input: result = self.encode(part, False) if result: yield result result = self.encode(u"", True) if result: yield result def encode(self, input, final=False): if self.encoder is None: input = self.buffer + input if self.encoding is not None: # Replace encoding in the @charset rule with the specified one encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newinput = _fixencoding(input, unicode(encoding), final) if newinput is None: # @charset rule incomplete => Retry next time self.buffer = input return "" input = newinput else: # Use encoding from the @charset declaration self.encoding = detectencoding_unicode(input, final)[0] if self.encoding is not None: if self.encoding == "css": raise ValueError("css not allowed as encoding name") info = codecs.lookup(self.encoding) encoding = self.encoding if self.encoding.replace("_", "-").lower() == "utf-8-sig": input = _fixencoding(input, u"utf-8", True) self.encoder = info.incrementalencoder(self._errors) self.buffer = u"" else: self.buffer = input return "" return self.encoder.encode(input, final) def reset(self): codecs.IncrementalEncoder.reset(self) self.encoder = None self.buffer = u"" def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors ``must be done on the real encoder too if self.encoder is not None: self.encoder.errors = errors self._errors = errors errors = property(_geterrors, _seterrors) def getstate(self): if self.encoder is not None: state = (self.encoding, self.buffer, True, self.encoder.getstate()) else: state = (self.encoding, self.buffer, False, None) return _bytes2int(marshal.dumps(state)) def setstate(self, state): state = _int2bytes(marshal.loads(state)) self.encoding = state[0] self.buffer = state[1] if state[2] is not None: self.encoder = codecs.getincrementalencoder(self.encoding)(self._errors) self.encoder.setstate(state[4]) else: self.encoder = None class StreamWriter(codecs.StreamWriter): def __init__(self, stream, errors="strict", encoding=None, header=False): codecs.StreamWriter.__init__(self, stream, errors) self.streamwriter = None self.encoding = encoding self._errors = errors self.buffer = u"" def encode(self, input, errors='strict'): li = len(input) if self.streamwriter is None: input = self.buffer + input li = len(input) if self.encoding is not None: # Replace encoding in the @charset rule with the specified one encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newinput = _fixencoding(input, unicode(encoding), False) if newinput is None: # @charset rule incomplete => Retry next time self.buffer = input return ("", 0) input = newinput else: # Use encoding from the @charset declaration self.encoding = detectencoding_unicode(input, False)[0] if self.encoding is not None: if self.encoding == "css": raise ValueError("css not allowed as encoding name") self.streamwriter = codecs.getwriter(self.encoding)(self.stream, self._errors) encoding = self.encoding if self.encoding.replace("_", "-").lower() == "utf-8-sig": input = _fixencoding(input, u"utf-8", True) self.buffer = u"" else: self.buffer = input return ("", 0) return (self.streamwriter.encode(input, errors)[0], li) def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors`` must be done on the streamwriter too if self.streamwriter is not None: self.streamwriter.errors = errors self._errors = errors errors = property(_geterrors, _seterrors) class StreamReader(codecs.StreamReader): def __init__(self, stream, errors="strict", encoding=None, force=True): codecs.StreamReader.__init__(self, stream, errors) self.streamreader = None self.encoding = encoding self.force = force self._errors = errors def decode(self, input, errors='strict'): if self.streamreader is None: if self.encoding is None or not self.force: (encoding, explicit) = detectencoding_str(input, False) if encoding is None: # no encoding determined yet return (u"", 0) # no encoding determined yet, so no output elif encoding == "css": raise ValueError("css not allowed as encoding name") if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input self.encoding = encoding streamreader = codecs.getreader(self.encoding) streamreader = streamreader(self.stream, self._errors) (output, consumed) = streamreader.decode(input, errors) encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newoutput = _fixencoding(output, unicode(encoding), False) if newoutput is not None: self.streamreader = streamreader return (newoutput, consumed) return (u"", 0) # we will create a new streamreader on the next call return self.streamreader.decode(input, errors) def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors`` must be done on the streamreader too if self.streamreader is not None: self.streamreader.errors = errors self._errors = errors errors = property(_geterrors, _seterrors) if hasattr(codecs, "CodecInfo"): # We're running on Python 2.5 or better def search_function(name): if name == "css": return codecs.CodecInfo( name="css", encode=encode, decode=decode, incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, streamreader=StreamReader, ) else: # If we're running on Python 2.4, define the utf-8-sig codec here def utf8sig_encode(input, errors='strict'): return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input)) def utf8sig_decode(input, errors='strict'): prefix = 0 if input[:3] == codecs.BOM_UTF8: input = input[3:] prefix = 3 (output, consumed) = codecs.utf_8_decode(input, errors, True) return (output, consumed+prefix) class UTF8SigStreamWriter(codecs.StreamWriter): def reset(self): codecs.StreamWriter.reset(self) try: del self.encode except AttributeError: pass def encode(self, input, errors='strict'): self.encode = codecs.utf_8_encode return utf8sig_encode(input, errors) class UTF8SigStreamReader(codecs.StreamReader): def reset(self): codecs.StreamReader.reset(self) try: del self.decode except AttributeError: pass def decode(self, input, errors='strict'): if len(input) < 3 and codecs.BOM_UTF8.startswith(input): # not enough data to decide if this is a BOM # => try again on the next call return (u"", 0) self.decode = codecs.utf_8_decode return utf8sig_decode(input, errors) def search_function(name): import encodings name = encodings.normalize_encoding(name) if name == "css": return (encode, decode, StreamReader, StreamWriter) elif name == "utf_8_sig": return (utf8sig_encode, utf8sig_decode, UTF8SigStreamReader, UTF8SigStreamWriter) codecs.register(search_function) # Error handler for CSS escaping def cssescape(exc): if not isinstance(exc, UnicodeEncodeError): raise TypeError("don't know how to handle %r" % exc) return (u"".join(u"\\%06x" % ord(c) for c in exc.object[exc.start:exc.end]), exc.end) codecs.register_error("cssescape", cssescape) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/_codec3.py0000644000175000017500000005753500000000000017437 0ustar00kovidkovid#!/usr/bin/env python """Python codec for CSS.""" __docformat__ = 'restructuredtext' __author__ = 'Walter Doerwald' __version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $' import codecs import marshal # We're using bits to store all possible candidate encodings (or variants, i.e. # we have two bits for the variants of UTF-16 and two for the # variants of UTF-32). # # Prefixes for various CSS encodings # UTF-8-SIG xEF xBB xBF # UTF-16 (LE) xFF xFE ~x00|~x00 # UTF-16 (BE) xFE xFF # UTF-16-LE @ x00 @ x00 # UTF-16-BE x00 @ # UTF-32 (LE) xFF xFE x00 x00 # UTF-32 (BE) x00 x00 xFE xFF # UTF-32-LE @ x00 x00 x00 # UTF-32-BE x00 x00 x00 @ # CHARSET @ c h a ... def chars(bytestring): return ''.join(chr(byte) for byte in bytestring) def detectencoding_str(input, final=False): """ Detect the encoding of the byte string ``input``, which contains the beginning of a CSS file. This function returns the detected encoding (or ``None`` if it hasn't got enough data), and a flag that indicates whether that encoding has been detected explicitely or implicitely. To detect the encoding the first few bytes are used (or if ``input`` is ASCII compatible and starts with a charset rule the encoding name from the rule). "Explicit" detection means that the bytes start with a BOM or a charset rule. If the encoding can't be detected yet, ``None`` is returned as the encoding. ``final`` specifies whether more data will be available in later calls or not. If ``final`` is true, ``detectencoding_str()`` will never return ``None`` as the encoding. """ # A bit for every candidate CANDIDATE_UTF_8_SIG = 1 CANDIDATE_UTF_16_AS_LE = 2 CANDIDATE_UTF_16_AS_BE = 4 CANDIDATE_UTF_16_LE = 8 CANDIDATE_UTF_16_BE = 16 CANDIDATE_UTF_32_AS_LE = 32 CANDIDATE_UTF_32_AS_BE = 64 CANDIDATE_UTF_32_LE = 128 CANDIDATE_UTF_32_BE = 256 CANDIDATE_CHARSET = 512 candidates = 1023 # all candidates # input = chars(input) li = len(input) if li >= 1: # Check first byte c = input[0] if c != b"\xef"[0]: candidates &= ~CANDIDATE_UTF_8_SIG if c != b"\xff"[0]: candidates &= ~(CANDIDATE_UTF_32_AS_LE | CANDIDATE_UTF_16_AS_LE) if c != b"\xfe"[0]: candidates &= ~CANDIDATE_UTF_16_AS_BE if c != b"@"[0]: candidates &= ~(CANDIDATE_UTF_32_LE | CANDIDATE_UTF_16_LE | CANDIDATE_CHARSET) if c != b"\x00"[0]: candidates &= ~(CANDIDATE_UTF_32_AS_BE | CANDIDATE_UTF_32_BE | CANDIDATE_UTF_16_BE) if li >= 2: # Check second byte c = input[1] if c != b"\xbb"[0]: candidates &= ~CANDIDATE_UTF_8_SIG if c != b"\xfe"[0]: candidates &= ~(CANDIDATE_UTF_16_AS_LE | CANDIDATE_UTF_32_AS_LE) if c != b"\xff"[0]: candidates &= ~CANDIDATE_UTF_16_AS_BE if c != b"\x00"[0]: candidates &= ~(CANDIDATE_UTF_16_LE | CANDIDATE_UTF_32_AS_BE | CANDIDATE_UTF_32_LE | CANDIDATE_UTF_32_BE) if c != b"@"[0]: candidates &= ~CANDIDATE_UTF_16_BE if c != b"c"[0]: candidates &= ~CANDIDATE_CHARSET if li >= 3: # Check third byte c = input[2] if c != b"\xbf"[0]: candidates &= ~CANDIDATE_UTF_8_SIG if c != b"c"[0]: candidates &= ~CANDIDATE_UTF_16_LE if c != b"\x00"[0]: candidates &= ~(CANDIDATE_UTF_32_AS_LE | CANDIDATE_UTF_32_LE | CANDIDATE_UTF_32_BE) if c != b"\xfe"[0]: candidates &= ~CANDIDATE_UTF_32_AS_BE if c != b"h"[0]: candidates &= ~CANDIDATE_CHARSET if li >= 4: # Check fourth byte c = input[3] if input[2:4] == b"\x00\x00"[0:2]: candidates &= ~CANDIDATE_UTF_16_AS_LE if c != b"\x00"[0]: candidates &= ~(CANDIDATE_UTF_16_LE | CANDIDATE_UTF_32_AS_LE | CANDIDATE_UTF_32_LE) if c != b"\xff"[0]: candidates &= ~CANDIDATE_UTF_32_AS_BE if c != b"@"[0]: candidates &= ~CANDIDATE_UTF_32_BE if c != b"a"[0]: candidates &= ~CANDIDATE_CHARSET if candidates == 0: return ("utf-8", False) if not (candidates & (candidates-1)): # only one candidate remaining if candidates == CANDIDATE_UTF_8_SIG and li >= 3: return ("utf-8-sig", True) elif candidates == CANDIDATE_UTF_16_AS_LE and li >= 2: return ("utf-16", True) elif candidates == CANDIDATE_UTF_16_AS_BE and li >= 2: return ("utf-16", True) elif candidates == CANDIDATE_UTF_16_LE and li >= 4: return ("utf-16-le", False) elif candidates == CANDIDATE_UTF_16_BE and li >= 2: return ("utf-16-be", False) elif candidates == CANDIDATE_UTF_32_AS_LE and li >= 4: return ("utf-32", True) elif candidates == CANDIDATE_UTF_32_AS_BE and li >= 4: return ("utf-32", True) elif candidates == CANDIDATE_UTF_32_LE and li >= 4: return ("utf-32-le", False) elif candidates == CANDIDATE_UTF_32_BE and li >= 4: return ("utf-32-be", False) elif candidates == CANDIDATE_CHARSET and li >= 4: prefix = '@charset "' charsinput = chars(input) if charsinput[:len(prefix)] == prefix: pos = charsinput.find('"', len(prefix)) if pos >= 0: # TODO: return str and not bytes! return (charsinput[len(prefix):pos], True) # if this is the last call, and we haven't determined an encoding yet, # we default to UTF-8 if final: return ("utf-8", False) return (None, False) # dont' know yet def detectencoding_unicode(input, final=False): """ Detect the encoding of the unicode string ``input``, which contains the beginning of a CSS file. The encoding is detected from the charset rule at the beginning of ``input``. If there is no charset rule, ``"utf-8"`` will be returned. If the encoding can't be detected yet, ``None`` is returned. ``final`` specifies whether more data will be available in later calls or not. If ``final`` is true, ``detectencoding_unicode()`` will never return ``None``. """ prefix = '@charset "' if input.startswith(prefix): pos = input.find('"', len(prefix)) if pos >= 0: return (input[len(prefix):pos], True) elif final or not prefix.startswith(input): # if this is the last call, and we haven't determined an encoding yet, # (or the string definitely doesn't start with prefix) we default to UTF-8 return ("utf-8", False) return (None, False) # don't know yet def _fixencoding(input, encoding, final=False): """ Replace the name of the encoding in the charset rule at the beginning of ``input`` with ``encoding``. If ``input`` doesn't starts with a charset rule, ``input`` will be returned unmodified. If the encoding can't be found yet, ``None`` is returned. ``final`` specifies whether more data will be available in later calls or not. If ``final`` is true, ``_fixencoding()`` will never return ``None``. """ prefix = '@charset "' if len(input) > len(prefix): if input.startswith(prefix): pos = input.find('"', len(prefix)) if pos >= 0: if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" return prefix + encoding + input[pos:] # we haven't seen the end of the encoding name yet => fall through else: return input # doesn't start with prefix, so nothing to fix elif not prefix.startswith(input) or final: # can't turn out to be a @charset rule later (or there is no "later") return input if final: return input return None # don't know yet def decode(input, errors="strict", encoding=None, force=True): try: # py 3 only, memory?! object to bytes input = input.tobytes() except AttributeError: pass if encoding is None or not force: (_encoding, explicit) = detectencoding_str(input, True) if _encoding == "css": raise ValueError("css not allowed as encoding name") if (explicit and not force) or encoding is None: # Take the encoding from the input encoding = _encoding # NEEDS: change in parse.py (str to bytes!) (input, consumed) = codecs.getdecoder(encoding)(input, errors) return (_fixencoding(input, str(encoding), True), consumed) def encode(input, errors="strict", encoding=None): consumed = len(input) if encoding is None: encoding = detectencoding_unicode(input, True)[0] if encoding.replace("_", "-").lower() == "utf-8-sig": input = _fixencoding(input, "utf-8", True) else: input = _fixencoding(input, str(encoding), True) if encoding == "css": raise ValueError("css not allowed as encoding name") encoder = codecs.getencoder(encoding) return (encoder(input, errors)[0], consumed) def _bytes2int(bytes): # Helper: convert an 8 bit string into an ``int``. i = 0 for byte in bytes: i = (i << 8) + ord(byte) return i def _int2bytes(i): # Helper: convert an ``int`` into an 8-bit string. v = [] while i: v.insert(0, chr(i & 0xff)) i >>= 8 return "".join(v) if hasattr(codecs, "IncrementalDecoder"): class IncrementalDecoder(codecs.IncrementalDecoder): def __init__(self, errors="strict", encoding=None, force=True): self.decoder = None self.encoding = encoding self.force = force codecs.IncrementalDecoder.__init__(self, errors) # Store ``errors`` somewhere else, # because we have to hide it in a property self._errors = errors self.buffer = b"" self.headerfixed = False def iterdecode(self, input): for part in input: result = self.decode(part, False) if result: yield result result = self.decode("", True) if result: yield result def decode(self, input, final=False): # We're doing basically the same as a ``BufferedIncrementalDecoder``, # but since the buffer is only relevant until the encoding has been # detected (in which case the buffer of the underlying codec might # kick in), we're implementing buffering ourselves to avoid some # overhead. if self.decoder is None: input = self.buffer + input # Do we have to detect the encoding from the input? if self.encoding is None or not self.force: (encoding, explicit) = detectencoding_str(input, final) if encoding is None: # no encoding determined yet self.buffer = input # retry the complete input on the next call return "" # no encoding determined yet, so no output elif encoding == "css": raise ValueError("css not allowed as encoding name") if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input self.encoding = encoding self.buffer = "" # drop buffer, as the decoder might keep its own decoder = codecs.getincrementaldecoder(self.encoding) self.decoder = decoder(self._errors) if self.headerfixed: return self.decoder.decode(input, final) # If we haven't fixed the header yet, # the content of ``self.buffer`` is a ``unicode`` object output = self.buffer + self.decoder.decode(input, final) encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newoutput = _fixencoding(output, str(encoding), final) if newoutput is None: # retry fixing the @charset rule (but keep the decoded stuff) self.buffer = output return "" self.headerfixed = True return newoutput def reset(self): codecs.IncrementalDecoder.reset(self) self.decoder = None self.buffer = b"" self.headerfixed = False def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors`` must be done on the real decoder too if self.decoder is not None: self.decoder.errors = errors self._errors = errors errors = property(_geterrors, _seterrors) def getstate(self): if self.decoder is not None: state = (self.encoding, self.buffer, self.headerfixed, True, self.decoder.getstate()) else: state = (self.encoding, self.buffer, self.headerfixed, False, None) return ("", _bytes2int(marshal.dumps(state))) def setstate(self, state): state = _int2bytes(marshal.loads(state[1])) # ignore buffered input self.encoding = state[0] self.buffer = state[1] self.headerfixed = state[2] if state[3] is not None: self.decoder = codecs.getincrementaldecoder(self.encoding)(self._errors) self.decoder.setstate(state[4]) else: self.decoder = None if hasattr(codecs, "IncrementalEncoder"): class IncrementalEncoder(codecs.IncrementalEncoder): def __init__(self, errors="strict", encoding=None): self.encoder = None self.encoding = encoding codecs.IncrementalEncoder.__init__(self, errors) # Store ``errors`` somewhere else, # because we have to hide it in a property self._errors = errors self.buffer = "" def iterencode(self, input): for part in input: result = self.encode(part, False) if result: yield result result = self.encode("", True) if result: yield result def encode(self, input, final=False): if self.encoder is None: input = self.buffer + input if self.encoding is not None: # Replace encoding in the @charset rule with the specified one encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newinput = _fixencoding(input, str(encoding), final) if newinput is None: # @charset rule incomplete => Retry next time self.buffer = input return "" input = newinput else: # Use encoding from the @charset declaration self.encoding = detectencoding_unicode(input, final)[0] if self.encoding is not None: if self.encoding == "css": raise ValueError("css not allowed as encoding name") info = codecs.lookup(self.encoding) encoding = self.encoding if self.encoding.replace("_", "-").lower() == "utf-8-sig": input = _fixencoding(input, "utf-8", True) self.encoder = info.incrementalencoder(self._errors) self.buffer = "" else: self.buffer = input return "" return self.encoder.encode(input, final) def reset(self): codecs.IncrementalEncoder.reset(self) self.encoder = None self.buffer = "" def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors ``must be done on the real encoder too if self.encoder is not None: self.encoder.errors = errors self._errors = errors errors = property(_geterrors, _seterrors) def getstate(self): if self.encoder is not None: state = (self.encoding, self.buffer, True, self.encoder.getstate()) else: state = (self.encoding, self.buffer, False, None) return _bytes2int(marshal.dumps(state)) def setstate(self, state): state = _int2bytes(marshal.loads(state)) self.encoding = state[0] self.buffer = state[1] if state[2] is not None: self.encoder = codecs.getincrementalencoder(self.encoding)(self._errors) self.encoder.setstate(state[4]) else: self.encoder = None class StreamWriter(codecs.StreamWriter): def __init__(self, stream, errors="strict", encoding=None, header=False): codecs.StreamWriter.__init__(self, stream, errors) self.streamwriter = None self.encoding = encoding self._errors = errors self.buffer = "" def encode(self, input, errors='strict'): li = len(input) if self.streamwriter is None: input = self.buffer + input li = len(input) if self.encoding is not None: # Replace encoding in the @charset rule with the specified one encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newinput = _fixencoding(input, str(encoding), False) if newinput is None: # @charset rule incomplete => Retry next time self.buffer = input return ("", 0) input = newinput else: # Use encoding from the @charset declaration self.encoding = detectencoding_unicode(input, False)[0] if self.encoding is not None: if self.encoding == "css": raise ValueError("css not allowed as encoding name") self.streamwriter = codecs.getwriter(self.encoding)(self.stream, self._errors) encoding = self.encoding if self.encoding.replace("_", "-").lower() == "utf-8-sig": input = _fixencoding(input, "utf-8", True) self.buffer = "" else: self.buffer = input return ("", 0) return (self.streamwriter.encode(input, errors)[0], li) def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors`` must be done on the streamwriter too try: if self.streamwriter is not None: self.streamwriter.errors = errors except AttributeError: # TODO: py3 only exception? pass self._errors = errors errors = property(_geterrors, _seterrors) class StreamReader(codecs.StreamReader): def __init__(self, stream, errors="strict", encoding=None, force=True): codecs.StreamReader.__init__(self, stream, errors) self.streamreader = None self.encoding = encoding self.force = force self._errors = errors def decode(self, input, errors='strict'): if self.streamreader is None: if self.encoding is None or not self.force: (encoding, explicit) = detectencoding_str(input, False) if encoding is None: # no encoding determined yet return ("", 0) # no encoding determined yet, so no output elif encoding == "css": raise ValueError("css not allowed as encoding name") if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input self.encoding = encoding streamreader = codecs.getreader(self.encoding) streamreader = streamreader(self.stream, self._errors) (output, consumed) = streamreader.decode(input, errors) encoding = self.encoding if encoding.replace("_", "-").lower() == "utf-8-sig": encoding = "utf-8" newoutput = _fixencoding(output, str(encoding), False) if newoutput is not None: self.streamreader = streamreader return (newoutput, consumed) return ("", 0) # we will create a new streamreader on the next call return self.streamreader.decode(input, errors) def _geterrors(self): return self._errors def _seterrors(self, errors): # Setting ``errors`` must be done on the streamreader too try: if self.streamreader is not None: self.streamreader.errors = errors except AttributeError: # TODO: py3 only exception? pass self._errors = errors errors = property(_geterrors, _seterrors) if hasattr(codecs, "CodecInfo"): # We're running on Python 2.5 or better def search_function(name): if name == "css": return codecs.CodecInfo( name="css", encode=encode, decode=decode, incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, streamreader=StreamReader, ) else: # If we're running on Python 2.4, define the utf-8-sig codec here def utf8sig_encode(input, errors='strict'): return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input)) def utf8sig_decode(input, errors='strict'): prefix = 0 if input[:3] == codecs.BOM_UTF8: input = input[3:] prefix = 3 (output, consumed) = codecs.utf_8_decode(input, errors, True) return (output, consumed+prefix) class UTF8SigStreamWriter(codecs.StreamWriter): def reset(self): codecs.StreamWriter.reset(self) try: del self.encode except AttributeError: pass def encode(self, input, errors='strict'): self.encode = codecs.utf_8_encode return utf8sig_encode(input, errors) class UTF8SigStreamReader(codecs.StreamReader): def reset(self): codecs.StreamReader.reset(self) try: del self.decode except AttributeError: pass def decode(self, input, errors='strict'): if len(input) < 3 and codecs.BOM_UTF8.startswith(input): # not enough data to decide if this is a BOM # => try again on the next call return ("", 0) self.decode = codecs.utf_8_decode return utf8sig_decode(input, errors) def search_function(name): import encodings name = encodings.normalize_encoding(name) if name == "css": return (encode, decode, StreamReader, StreamWriter) elif name == "utf_8_sig": return (utf8sig_encode, utf8sig_decode, UTF8SigStreamReader, UTF8SigStreamWriter) codecs.register(search_function) # Error handler for CSS escaping def cssescape(exc): if not isinstance(exc, UnicodeEncodeError): raise TypeError("don't know how to handle %r" % exc) return ("".join("\\%06x" % ord(c) for c in exc.object[exc.start:exc.end]), exc.end) codecs.register_error("cssescape", cssescape) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/_fetch.py0000644000175000017500000000430500000000000017353 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function from . import errorhandler import css_parser.encutils as encutils from css_parser.version import VERSION """Default URL reading functions""" __all__ = ['_defaultFetcher'] __docformat__ = 'restructuredtext' __version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' import sys if sys.version_info[0] >= 3: from urllib.request import urlopen as urllib_urlopen from urllib.request import Request as urllib_Request from urllib.error import HTTPError as urllib_HTTPError from urllib.error import URLError as urllib_URLError else: from urllib2 import urlopen as urllib_urlopen from urllib2 import Request as urllib_Request from urllib2 import HTTPError as urllib_HTTPError from urllib2 import URLError as urllib_URLError log = errorhandler.ErrorHandler() def _defaultFetcher(url): """Retrieve data from ``url``. css_parser default implementation of fetch URL function. Returns ``(encoding, string)`` or ``None`` """ try: request = urllib_Request(url) request.add_header('User-agent', 'css_parser %s (http://www.cthedot.de/css_parser/)' % VERSION) res = urllib_urlopen(request) except urllib_HTTPError as e: # http error, e.g. 404, e can be raised log.warn('HTTPError opening url=%s: %s %s' % (url, e.code, e.msg), error=e) except urllib_URLError as e: # URLError like mailto: or other IO errors, e can be raised log.warn('URLError, %s' % e.reason, error=e) except OSError as e: # e.g if file URL and not found log.warn(e, error=OSError) except ValueError as e: # invalid url, e.g. "1" log.warn('ValueError, %s' % e.args[0], error=ValueError) else: if res: mimeType, encoding = encutils.getHTTPInfo(res) if mimeType != 'text/css': log.error('Expected "text/css" mime type for url=%r but found: %r' % (url, mimeType), error=ValueError) content = res.read() if hasattr(res, 'close'): res.close() return encoding, content ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/_fetchgae.py0000644000175000017500000000516200000000000020032 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function from . import errorhandler import cgi from google.appengine.api import urlfetch """GAE specific URL reading functions""" import sys PY3 = sys.version_info[0] >= 3 __all__ = ['_defaultFetcher'] __docformat__ = 'restructuredtext' __version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' # raises ImportError of not on GAE log = errorhandler.ErrorHandler() def _defaultFetcher(url): """ uses GoogleAppEngine (GAE) fetch(url, payload=None, method=GET, headers={}, allow_truncated=False) Response content The body content of the response. content_was_truncated True if the allow_truncated parameter to fetch() was True and the response exceeded the maximum response size. In this case, the content attribute contains the truncated response. status_code The HTTP status code. headers The HTTP response headers, as a mapping of names to values. Exceptions exception InvalidURLError() The URL of the request was not a valid URL, or it used an unsupported method. Only http and https URLs are supported. exception DownloadError() There was an error retrieving the data. This exception is not raised if the server returns an HTTP error code: In that case, the response data comes back intact, including the error code. exception ResponseTooLargeError() The response data exceeded the maximum allowed size, and the allow_truncated parameter passed to fetch() was False. """ # from google.appengine.api import urlfetch try: r = urlfetch.fetch(url, method=urlfetch.GET) except urlfetch.Error as e: log.warn('Error opening url=%r: %s' % (url, e), error=IOError) else: if r.status_code == 200: # find mimetype and encoding mimetype = 'application/octet-stream' try: mimetype, params = cgi.parse_header(r.headers['content-type']) encoding = params['charset'] except KeyError: encoding = None if mimetype != 'text/css': log.error('Expected "text/css" mime type for url %r but found: %r' % (url, mimetype), error=ValueError) return encoding, r.content else: # TODO: 301 etc log.warn('Error opening url=%r: HTTP status %s' % (url, r.status_code), error=IOError) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/codec.py0000644000175000017500000000071700000000000017203 0ustar00kovidkovid#!/usr/bin/env python """Python codec for CSS.""" from __future__ import absolute_import __docformat__ = 'restructuredtext' __author__ = 'Walter Doerwald' __version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $' import sys if sys.version_info < (3,): from ._codec2 import * # noqa # for tests from ._codec2 import _fixencoding # noqa else: from ._codec3 import * # noqa # for tests from ._codec3 import _fixencoding # noqa ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1373482 css-parser-1.0.7/src/css_parser/css/0000755000175000017500000000000000000000000016337 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/__init__.py0000644000175000017500000000426500000000000020457 0ustar00kovidkovid"""Implements Document Object Model Level 2 CSS http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/css.html currently implemented - CSSStyleSheet - CSSRuleList - CSSRule - CSSComment (css_parser addon) - CSSCharsetRule - CSSFontFaceRule - CSSImportRule - CSSMediaRule - CSSNamespaceRule (WD) - CSSPageRule - CSSStyleRule - CSSUnkownRule - Selector and SelectorList - CSSStyleDeclaration - CSS2Properties - CSSValue - CSSPrimitiveValue - CSSValueList - CSSVariablesRule - CSSVariablesDeclaration todo - RGBColor, Rect, Counter """ from __future__ import division, absolute_import, print_function __all__ = [ 'CSSStyleSheet', 'CSSRuleList', 'CSSRule', 'CSSComment', 'CSSCharsetRule', 'CSSFontFaceRule', 'CSSImportRule', 'CSSMediaRule', 'CSSNamespaceRule', 'CSSPageRule', 'MarginRule', 'CSSStyleRule', 'CSSUnknownRule', 'CSSVariablesRule', 'CSSVariablesDeclaration', 'Selector', 'SelectorList', 'CSSStyleDeclaration', 'Property', # 'CSSValue', 'CSSPrimitiveValue', 'CSSValueList' 'PropertyValue', 'Value', 'ColorValue', 'DimensionValue', 'URIValue', 'CSSFunction', 'CSSVariable', 'MSValue', ] __docformat__ = 'restructuredtext' __version__ = '$Id$' from .cssstylesheet import CSSStyleSheet from .cssrulelist import CSSRuleList from .cssrule import CSSRule from .csscomment import CSSComment from .csscharsetrule import CSSCharsetRule from .cssfontfacerule import CSSFontFaceRule from .cssimportrule import CSSImportRule from .cssmediarule import CSSMediaRule from .cssnamespacerule import CSSNamespaceRule from .csspagerule import CSSPageRule from .marginrule import MarginRule from .cssstylerule import CSSStyleRule from .cssvariablesrule import CSSVariablesRule from .cssunknownrule import CSSUnknownRule from .selector import Selector from .selectorlist import SelectorList from .cssstyledeclaration import CSSStyleDeclaration from .cssvariablesdeclaration import CSSVariablesDeclaration from .property import Property from .value import PropertyValue, Value, ColorValue, DimensionValue, URIValue, CSSFunction, CSSVariable, MSValue ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/colors.py0000644000175000017500000001465000000000000020220 0ustar00kovidkovid# -*- coding: utf-8 -*- """ Built from something like this: print [ ( row[2].text_content().strip(), eval(row[4].text_content().strip()) ) for row in lxml.html.parse('http://www.w3.org/TR/css3-color/') .xpath("//*[@class='colortable']//tr[position()>1]") ] by Simon Sapin """ from __future__ import unicode_literals, division, absolute_import, print_function COLORS = { 'transparent': (0, 0, 0, 0.0), 'black': (0, 0, 0, 1.0), 'silver': (192, 192, 192, 1.0), 'gray': (128, 128, 128, 1.0), 'white': (255, 255, 255, 1.0), 'maroon': (128, 0, 0, 1.0), 'red': (255, 0, 0, 1.0), 'purple': (128, 0, 128, 1.0), 'fuchsia': (255, 0, 255, 1.0), 'green': (0, 128, 0, 1.0), 'lime': (0, 255, 0, 1.0), 'olive': (128, 128, 0, 1.0), 'yellow': (255, 255, 0, 1.0), 'navy': (0, 0, 128, 1.0), 'blue': (0, 0, 255, 1.0), 'teal': (0, 128, 128, 1.0), 'aqua': (0, 255, 255, 1.0), 'aliceblue': (240, 248, 255, 1.0), 'antiquewhite': (250, 235, 215, 1.0), 'aqua': (0, 255, 255, 1.0), 'aquamarine': (127, 255, 212, 1.0), 'azure': (240, 255, 255, 1.0), 'beige': (245, 245, 220, 1.0), 'bisque': (255, 228, 196, 1.0), 'black': (0, 0, 0, 1.0), 'blanchedalmond': (255, 235, 205, 1.0), 'blue': (0, 0, 255, 1.0), 'blueviolet': (138, 43, 226, 1.0), 'brown': (165, 42, 42, 1.0), 'burlywood': (222, 184, 135, 1.0), 'cadetblue': (95, 158, 160, 1.0), 'chartreuse': (127, 255, 0, 1.0), 'chocolate': (210, 105, 30, 1.0), 'coral': (255, 127, 80, 1.0), 'cornflowerblue': (100, 149, 237, 1.0), 'cornsilk': (255, 248, 220, 1.0), 'crimson': (220, 20, 60, 1.0), 'cyan': (0, 255, 255, 1.0), 'darkblue': (0, 0, 139, 1.0), 'darkcyan': (0, 139, 139, 1.0), 'darkgoldenrod': (184, 134, 11, 1.0), 'darkgray': (169, 169, 169, 1.0), 'darkgreen': (0, 100, 0, 1.0), 'darkgrey': (169, 169, 169, 1.0), 'darkkhaki': (189, 183, 107, 1.0), 'darkmagenta': (139, 0, 139, 1.0), 'darkolivegreen': (85, 107, 47, 1.0), 'darkorange': (255, 140, 0, 1.0), 'darkorchid': (153, 50, 204, 1.0), 'darkred': (139, 0, 0, 1.0), 'darksalmon': (233, 150, 122, 1.0), 'darkseagreen': (143, 188, 143, 1.0), 'darkslateblue': (72, 61, 139, 1.0), 'darkslategray': (47, 79, 79, 1.0), 'darkslategrey': (47, 79, 79, 1.0), 'darkturquoise': (0, 206, 209, 1.0), 'darkviolet': (148, 0, 211, 1.0), 'deeppink': (255, 20, 147, 1.0), 'deepskyblue': (0, 191, 255, 1.0), 'dimgray': (105, 105, 105, 1.0), 'dimgrey': (105, 105, 105, 1.0), 'dodgerblue': (30, 144, 255, 1.0), 'firebrick': (178, 34, 34, 1.0), 'floralwhite': (255, 250, 240, 1.0), 'forestgreen': (34, 139, 34, 1.0), 'fuchsia': (255, 0, 255, 1.0), 'gainsboro': (220, 220, 220, 1.0), 'ghostwhite': (248, 248, 255, 1.0), 'gold': (255, 215, 0, 1.0), 'goldenrod': (218, 165, 32, 1.0), 'gray': (128, 128, 128, 1.0), 'green': (0, 128, 0, 1.0), 'greenyellow': (173, 255, 47, 1.0), 'grey': (128, 128, 128, 1.0), 'honeydew': (240, 255, 240, 1.0), 'hotpink': (255, 105, 180, 1.0), 'indianred': (205, 92, 92, 1.0), 'indigo': (75, 0, 130, 1.0), 'ivory': (255, 255, 240, 1.0), 'khaki': (240, 230, 140, 1.0), 'lavender': (230, 230, 250, 1.0), 'lavenderblush': (255, 240, 245, 1.0), 'lawngreen': (124, 252, 0, 1.0), 'lemonchiffon': (255, 250, 205, 1.0), 'lightblue': (173, 216, 230, 1.0), 'lightcoral': (240, 128, 128, 1.0), 'lightcyan': (224, 255, 255, 1.0), 'lightgoldenrodyellow': (250, 250, 210, 1.0), 'lightgray': (211, 211, 211, 1.0), 'lightgreen': (144, 238, 144, 1.0), 'lightgrey': (211, 211, 211, 1.0), 'lightpink': (255, 182, 193, 1.0), 'lightsalmon': (255, 160, 122, 1.0), 'lightseagreen': (32, 178, 170, 1.0), 'lightskyblue': (135, 206, 250, 1.0), 'lightslategray': (119, 136, 153, 1.0), 'lightslategrey': (119, 136, 153, 1.0), 'lightsteelblue': (176, 196, 222, 1.0), 'lightyellow': (255, 255, 224, 1.0), 'lime': (0, 255, 0, 1.0), 'limegreen': (50, 205, 50, 1.0), 'linen': (250, 240, 230, 1.0), 'magenta': (255, 0, 255, 1.0), 'maroon': (128, 0, 0, 1.0), 'mediumaquamarine': (102, 205, 170, 1.0), 'mediumblue': (0, 0, 205, 1.0), 'mediumorchid': (186, 85, 211, 1.0), 'mediumpurple': (147, 112, 219, 1.0), 'mediumseagreen': (60, 179, 113, 1.0), 'mediumslateblue': (123, 104, 238, 1.0), 'mediumspringgreen': (0, 250, 154, 1.0), 'mediumturquoise': (72, 209, 204, 1.0), 'mediumvioletred': (199, 21, 133, 1.0), 'midnightblue': (25, 25, 112, 1.0), 'mintcream': (245, 255, 250, 1.0), 'mistyrose': (255, 228, 225, 1.0), 'moccasin': (255, 228, 181, 1.0), 'navajowhite': (255, 222, 173, 1.0), 'navy': (0, 0, 128, 1.0), 'oldlace': (253, 245, 230, 1.0), 'olive': (128, 128, 0, 1.0), 'olivedrab': (107, 142, 35, 1.0), 'orange': (255, 165, 0, 1.0), 'orangered': (255, 69, 0, 1.0), 'orchid': (218, 112, 214, 1.0), 'palegoldenrod': (238, 232, 170, 1.0), 'palegreen': (152, 251, 152, 1.0), 'paleturquoise': (175, 238, 238, 1.0), 'palevioletred': (219, 112, 147, 1.0), 'papayawhip': (255, 239, 213, 1.0), 'peachpuff': (255, 218, 185, 1.0), 'peru': (205, 133, 63, 1.0), 'pink': (255, 192, 203, 1.0), 'plum': (221, 160, 221, 1.0), 'powderblue': (176, 224, 230, 1.0), 'purple': (128, 0, 128, 1.0), 'red': (255, 0, 0, 1.0), 'rosybrown': (188, 143, 143, 1.0), 'royalblue': (65, 105, 225, 1.0), 'saddlebrown': (139, 69, 19, 1.0), 'salmon': (250, 128, 114, 1.0), 'sandybrown': (244, 164, 96, 1.0), 'seagreen': (46, 139, 87, 1.0), 'seashell': (255, 245, 238, 1.0), 'sienna': (160, 82, 45, 1.0), 'silver': (192, 192, 192, 1.0), 'skyblue': (135, 206, 235, 1.0), 'slateblue': (106, 90, 205, 1.0), 'slategray': (112, 128, 144, 1.0), 'slategrey': (112, 128, 144, 1.0), 'snow': (255, 250, 250, 1.0), 'springgreen': (0, 255, 127, 1.0), 'steelblue': (70, 130, 180, 1.0), 'tan': (210, 180, 140, 1.0), 'teal': (0, 128, 128, 1.0), 'thistle': (216, 191, 216, 1.0), 'tomato': (255, 99, 71, 1.0), 'turquoise': (64, 224, 208, 1.0), 'violet': (238, 130, 238, 1.0), 'wheat': (245, 222, 179, 1.0), 'white': (255, 255, 255, 1.0), 'whitesmoke': (245, 245, 245, 1.0), 'yellow': (255, 255, 0, 1.0), 'yellowgreen': (154, 205, 50, 1.0), } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/csscharsetrule.py0000644000175000017500000001376300000000000021755 0ustar00kovidkovid"""CSSCharsetRule implements DOM Level 2 CSS CSSCharsetRule.""" from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSCharsetRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import codecs from . import cssrule import css_parser import xml.dom class CSSCharsetRule(cssrule.CSSRule): """ The CSSCharsetRule interface represents an @charset rule in a CSS style sheet. The value of the encoding attribute does not affect the encoding of text data in the DOM objects; this encoding is always UTF-16 (also in Python?). After a stylesheet is loaded, the value of the encoding attribute is the value found in the @charset rule. If there was no @charset in the original document, then no CSSCharsetRule is created. The value of the encoding attribute may also be used as a hint for the encoding used on serialization of the style sheet. The value of the @charset rule (and therefore of the CSSCharsetRule) may not correspond to the encoding the document actually came in; character encoding information e.g. in an HTTP header, has priority (see CSS document representation) but this is not reflected in the CSSCharsetRule. This rule is not really needed anymore as setting :attr:`CSSStyleSheet.encoding` is much easier. Format:: charsetrule: CHARSET_SYM S* STRING S* ';' BUT: Only valid format is (single space, double quotes!):: @charset "ENCODING"; """ def __init__(self, encoding=None, parentRule=None, parentStyleSheet=None, readonly=False): """ :param encoding: a valid character encoding :param readonly: defaults to False, not used yet """ super(CSSCharsetRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@charset' if encoding: self.encoding = encoding else: self._encoding = None self._readonly = readonly def __repr__(self): return "css_parser.css.%s(encoding=%r)" % ( self.__class__.__name__, self.encoding) def __str__(self): return "" % ( self.__class__.__name__, self.encoding, id(self)) def _getCssText(self): """The parsable textual representation.""" return css_parser.ser.do_CSSCharsetRule(self) def _setCssText(self, cssText): """ :param cssText: A parsable DOMString. :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSCharsetRule, self)._setCssText(cssText) wellformed = True tokenizer = self._tokenize2(cssText) if self._type(self._nexttoken(tokenizer)) != self._prods.CHARSET_SYM: wellformed = False self._log.error('CSSCharsetRule must start with "@charset "', error=xml.dom.InvalidModificationErr) encodingtoken = self._nexttoken(tokenizer) encodingtype = self._type(encodingtoken) encoding = self._stringtokenvalue(encodingtoken) if self._prods.STRING != encodingtype or not encoding: wellformed = False self._log.error('CSSCharsetRule: no encoding found; %r.' % self._valuestr(cssText)) semicolon = self._tokenvalue(self._nexttoken(tokenizer)) EOFtype = self._type(self._nexttoken(tokenizer)) if ';' != semicolon or EOFtype not in ('EOF', None): wellformed = False self._log.error('CSSCharsetRule: Syntax Error: %r.' % self._valuestr(cssText)) if wellformed: self.encoding = encoding cssText = property(fget=_getCssText, fset=_setCssText, doc="(DOM) The parsable textual representation.") def _setEncoding(self, encoding): """ :param encoding: a valid encoding to be used. Currently only valid Python encodings are allowed. :exceptions: - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this encoding rule is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified encoding value has a syntax error and is unparsable. """ self._checkReadonly() tokenizer = self._tokenize2(encoding) encodingtoken = self._nexttoken(tokenizer) unexpected = self._nexttoken(tokenizer) if not encodingtoken or unexpected or\ self._prods.IDENT != self._type(encodingtoken): self._log.error('CSSCharsetRule: Syntax Error in encoding value ' '%r.' % encoding) else: try: codecs.lookup(encoding) except LookupError: self._log.error('CSSCharsetRule: Unknown (Python) encoding %r.' % encoding) else: self._encoding = encoding.lower() encoding = property(lambda self: self._encoding, _setEncoding, doc="(DOM)The encoding information used in this @charset rule.") type = property(lambda self: self.CHARSET_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") wellformed = property(lambda self: bool(self.encoding)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/csscomment.py0000644000175000017500000000557600000000000021101 0ustar00kovidkovid"""CSSComment is not defined in DOM Level 2 at all but a css_parser defined class only. Implements CSSRule which is also extended for a CSSComment rule type. """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSComment'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from . import cssrule import css_parser import xml.dom class CSSComment(cssrule.CSSRule): """ Represents a CSS comment (css_parser only). Format:: /*...*/ """ def __init__(self, cssText=None, parentRule=None, parentStyleSheet=None, readonly=False): super(CSSComment, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._cssText = None if cssText: self._setCssText(cssText) self._readonly = readonly def __repr__(self): return "css_parser.css.%s(cssText=%r)" % ( self.__class__.__name__, self.cssText) def __str__(self): return "" % ( self.__class__.__name__, self.cssText, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSComment(self) def _setCssText(self, cssText): """ :param cssText: textual text to set or tokenlist which is not tokenized anymore. May also be a single token for this rule :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSComment, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) commenttoken = self._nexttoken(tokenizer) unexpected = self._nexttoken(tokenizer) if not commenttoken or\ self._type(commenttoken) != self._prods.COMMENT or\ unexpected: self._log.error('CSSComment: Not a CSSComment: %r' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: self._cssText = self._tokenvalue(commenttoken) cssText = property(_getCssText, _setCssText, doc="The parsable textual representation of this rule.") type = property(lambda self: self.COMMENT, doc="The type of this rule, as defined by a CSSRule " "type constant.") # constant but needed: wellformed = property(lambda self: True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssfontfacerule.py0000644000175000017500000001601100000000000022076 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser from . import cssrule from .cssstyledeclaration import CSSStyleDeclaration """CSSFontFaceRule implements DOM Level 2 CSS CSSFontFaceRule. From css_parser 0.9.6 additions from CSS Fonts Module Level 3 are added http://www.w3.org/TR/css3-fonts/. """ __all__ = ['CSSFontFaceRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class CSSFontFaceRule(cssrule.CSSRule): """ The CSSFontFaceRule interface represents a @font-face rule in a CSS style sheet. The @font-face rule is used to hold a set of font descriptions. Format:: font_face : FONT_FACE_SYM S* '{' S* declaration [ ';' S* declaration ]* '}' S* ; css_parser uses a :class:`~css_parser.css.CSSStyleDeclaration` to represent the font descriptions. For validation a specific profile is used though were some properties have other valid values than when used in e.g. a :class:`~css_parser.css.CSSStyleRule`. """ def __init__(self, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ If readonly allows setting of properties in constructor only. :param style: CSSStyleDeclaration used to hold any font descriptions for this CSSFontFaceRule """ super(CSSFontFaceRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@font-face' if style: self.style = style else: self.style = CSSStyleDeclaration() self._readonly = readonly def __repr__(self): return "css_parser.css.%s(style=%r)" % ( self.__class__.__name__, self.style.cssText) def __str__(self): return "" % ( self.__class__.__name__, self.style.cssText, self.valid, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSFontFaceRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSFontFaceRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if self._type(attoken) != self._prods.FONT_FACE_SYM: self._log.error('CSSFontFaceRule: No CSSFontFaceRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: newStyle = CSSStyleDeclaration(parentRule=self) ok = True beforetokens, brace = self._tokensupto2(tokenizer, blockstartonly=True, separateEnd=True) if self._tokenvalue(brace) != '{': ok = False self._log.error('CSSFontFaceRule: No start { of style ' 'declaration found: %r' % self._valuestr(cssText), brace) # parse stuff before { which should be comments and S only new = {'wellformed': True} newseq = self._tempSeq() beforewellformed, expected = self._parse(expected=':', seq=newseq, tokenizer=self._tokenize2(beforetokens), productions={}) ok = ok and beforewellformed and new['wellformed'] styletokens, braceorEOFtoken = self._tokensupto2(tokenizer, blockendonly=True, separateEnd=True) val, type_ = self._tokenvalue(braceorEOFtoken),\ self._type(braceorEOFtoken) if val != '}' and type_ != 'EOF': ok = False self._log.error('CSSFontFaceRule: No "}" after style ' 'declaration found: %r' % self._valuestr(cssText)) nonetoken = self._nexttoken(tokenizer) if nonetoken: ok = False self._log.error('CSSFontFaceRule: Trailing content found.', token=nonetoken) if 'EOF' == type_: # add again as style needs it styletokens.append(braceorEOFtoken) # SET, may raise: newStyle.cssText = styletokens if ok: # contains probably comments only (upto ``{``) self._setSeq(newseq) self.style = newStyle cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this " "rule.") def _setStyle(self, style): """ :param style: a CSSStyleDeclaration or string """ self._checkReadonly() # under Python 2.X this was basestring but given unicode literals ... if isinstance(style, string_type): self._style = CSSStyleDeclaration(cssText=style, parentRule=self) else: style._parentRule = self self._style = style style = property(lambda self: self._style, _setStyle, doc="(DOM) The declaration-block of this rule set, " "a :class:`~css_parser.css.CSSStyleDeclaration`.") type = property(lambda self: self.FONT_FACE_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") def _getValid(self): needed = ['font-family', 'src'] for p in self.style.getProperties(all=True): if not p.valid: return False try: needed.remove(p.name) except ValueError: pass return not bool(needed) valid = property(_getValid, doc="CSSFontFace is valid if properties `font-family` " "and `src` are set and all properties are valid.") # constant but needed: wellformed = property(lambda self: True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1603522300.0 css-parser-1.0.7/src/css_parser/css/cssimportrule.py0000644000175000017500000003541700000000000021636 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom """CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the ``name`` property from http://www.w3.org/TR/css3-cascade/#cascading.""" __all__ = ['CSSImportRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from . import cssrule from ..util import urljoin, string_type import css_parser import os class CSSImportRule(cssrule.CSSRule): """ Represents an @import rule within a CSS style sheet. The @import rule is used to import style rules from other style sheets. Format:: import : IMPORT_SYM S* [STRING|URI] S* [ medium [ COMMA S* medium]* ]? S* STRING? S* ';' S* ; """ def __init__(self, href=None, mediaText=None, name=None, parentRule=None, parentStyleSheet=None, readonly=False): """ If readonly allows setting of properties in constructor only :param href: location of the style sheet to be imported. :param mediaText: A list of media types for which this style sheet may be used as a string :param name: Additional name of imported style sheet """ super(CSSImportRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@import' self._styleSheet = None # string or uri used for reserialization self.hreftype = None # prepare seq seq = self._tempSeq() seq.append(None, 'href') # seq.append(None, 'media') seq.append(None, 'name') self._setSeq(seq) # 1. media if mediaText: self.media = mediaText else: # must be all for @import self.media = css_parser.stylesheets.MediaList(mediaText='all') # 2. name self.name = name # 3. href and styleSheet self.href = href self._readonly = readonly def __repr__(self): if self._usemedia: mediaText = self.media.mediaText else: mediaText = None return "css_parser.css.%s(href=%r, mediaText=%r, name=%r)" % ( self.__class__.__name__, self.href, mediaText, self.name) def __str__(self): if self._usemedia: mediaText = self.media.mediaText else: mediaText = None return ""\ % (self.__class__.__name__, self.href, mediaText, self.name, id(self)) _usemedia = property(lambda self: self.media.mediaText not in ('', 'all'), doc="if self.media is used (or simply empty)") def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSImportRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. """ super(CSSImportRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if self._type(attoken) != self._prods.IMPORT_SYM: self._log.error('CSSImportRule: No CSSImportRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: # for closures: must be a mutable new = {'keyword': self._tokenvalue(attoken), 'href': None, 'hreftype': None, 'media': None, 'name': None, 'wellformed': True } def __doname(seq, token): # called by _string or _ident new['name'] = self._stringtokenvalue(token) seq.append(new['name'], 'name') return ';' def _string(expected, seq, token, tokenizer=None): if 'href' == expected: # href new['href'] = self._stringtokenvalue(token) new['hreftype'] = 'string' seq.append(new['href'], 'href') return 'media name ;' elif 'name' in expected: # name return __doname(seq, token) else: new['wellformed'] = False self._log.error( 'CSSImportRule: Unexpected string.', token) return expected def _uri(expected, seq, token, tokenizer=None): # href if 'href' == expected: uri = self._uritokenvalue(token) new['hreftype'] = 'uri' new['href'] = uri seq.append(new['href'], 'href') return 'media name ;' else: new['wellformed'] = False self._log.error( 'CSSImportRule: Unexpected URI.', token) return expected def _ident(expected, seq, token, tokenizer=None): # medialist ending with ; which is checked upon too if expected.startswith('media'): mediatokens = self._tokensupto2( tokenizer, importmediaqueryendonly=True) mediatokens.insert(0, token) # push found token last = mediatokens.pop() # retrieve ; lastval, lasttyp = self._tokenvalue(last), self._type(last) if lastval != ';' and lasttyp not in ('EOF', self._prods.STRING): new['wellformed'] = False self._log.error('CSSImportRule: No ";" found: %s' % self._valuestr(cssText), token=token) newMedia = css_parser.stylesheets.MediaList(parentRule=self) newMedia.mediaText = mediatokens if newMedia.wellformed: new['media'] = newMedia seq.append(newMedia, 'media') else: new['wellformed'] = False self._log.error('CSSImportRule: Invalid MediaList: %s' % self._valuestr(cssText), token=token) if lasttyp == self._prods.STRING: # name return __doname(seq, last) else: return 'EOF' # ';' is token "last" else: new['wellformed'] = False self._log.error('CSSImportRule: Unexpected ident.', token) return expected def _char(expected, seq, token, tokenizer=None): # final ; val = self._tokenvalue(token) if expected.endswith(';') and ';' == val: return 'EOF' else: new['wellformed'] = False self._log.error( 'CSSImportRule: Unexpected char.', token) return expected # import : IMPORT_SYM S* [STRING|URI] # S* [ medium [ ',' S* medium]* ]? ';' S* # STRING? # see http://www.w3.org/TR/css3-cascade/#cascading # ; newseq = self._tempSeq() wellformed, expected = self._parse(expected='href', seq=newseq, tokenizer=tokenizer, productions={'STRING': _string, 'URI': _uri, 'IDENT': _ident, 'CHAR': _char}, new=new) # wellformed set by parse ok = wellformed and new['wellformed'] # post conditions if not new['href']: ok = False self._log.error('CSSImportRule: No href found: %s' % self._valuestr(cssText)) if expected != 'EOF': ok = False self._log.error('CSSImportRule: No ";" found: %s' % self._valuestr(cssText)) # set all if ok: self._setSeq(newseq) self.atkeyword = new['keyword'] self.hreftype = new['hreftype'] self.name = new['name'] if new['media']: self.media = new['media'] else: # must be all for @import self.media = css_parser.stylesheets.MediaList(mediaText='all') # needs new self.media self.href = new['href'] cssText = property(fget=_getCssText, fset=_setCssText, doc="(DOM) The parsable textual representation of this rule.") def _setHref(self, href): # set new href self._href = href # update seq for i, item in enumerate(self.seq): type_ = item.type if 'href' == type_: self._seq[i] = (href, type_, item.line, item.col) break importedSheet = css_parser.css.CSSStyleSheet(media=self.media, ownerRule=self, title=self.name) self.hrefFound = False # set styleSheet if href and self.parentStyleSheet: # loading errors are all catched! # relative href parentHref = self.parentStyleSheet.href if parentHref is None: # use cwd instead parentHref = css_parser.helper.path2url(os.getcwd()) + '/' fullhref = urljoin(parentHref, self.href) # all possible exceptions are ignored try: usedEncoding, enctype, cssText = \ self.parentStyleSheet._resolveImport(fullhref) if cssText is None: # catched in next except below! raise IOError('Cannot read Stylesheet.') # contentEncoding with parentStyleSheet.overrideEncoding, # HTTP or parent encodingOverride, encoding = None, None if enctype == 0: encodingOverride = usedEncoding elif 0 < enctype < 5: encoding = usedEncoding # inherit fetcher for @imports in styleSheet importedSheet._href = fullhref importedSheet._setFetcher(self.parentStyleSheet._fetcher) importedSheet._setCssTextWithEncodingOverride( cssText, encodingOverride=encodingOverride, encoding=encoding) except (OSError, IOError, ValueError) as e: self._log.warn('CSSImportRule: While processing imported ' 'style sheet href=%s: %r' % (self.href, e), neverraise=True) else: # used by resolveImports if to keep unprocessed href self.hrefFound = True self._styleSheet = importedSheet _href = None # needs to be set href = property(lambda self: self._href, _setHref, doc="Location of the style sheet to be imported.") def _setMedia(self, media): """ :param media: a :class:`~css_parser.stylesheets.MediaList` or string """ self._checkReadonly() # Under Python 2.X this was basestring but given unicode literals ... if isinstance(media, string_type): self._media = css_parser.stylesheets.MediaList(mediaText=media, parentRule=self) else: media._parentRule = self self._media = media # update seq ihref = 0 for i, item in enumerate(self.seq): if item.type == 'href': ihref = i elif item.type == 'media': self.seq[i] = (self._media, 'media', None, None) break else: # if no media until now add after href self.seq.insert(ihref+1, self._media, 'media', None, None) media = property(lambda self: self._media, _setMedia, doc="(DOM) A list of media types for this rule " "of type :class:`~css_parser.stylesheets.MediaList`.") def _setName(self, name=''): """Raises xml.dom.SyntaxErr if name is not a string.""" # Under Python 2.X this was basestring but given unicode literals ... if name is None or isinstance(name, string_type): # "" or '' handled as None if not name: name = None # save name self._name = name # update seq for i, item in enumerate(self.seq): typ = item.type if 'name' == typ: self._seq[i] = (name, typ, item.line, item.col) break # set title of imported sheet if self.styleSheet: self.styleSheet.title = name else: self._log.error('CSSImportRule: Not a valid name: %s' % name) name = property(lambda self: self._name, _setName, doc="An optional name for the imported sheet.") styleSheet = property(lambda self: self._styleSheet, doc="(readonly) The style sheet referred to by this " "rule.") type = property(lambda self: self.IMPORT_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") def _getWellformed(self): "Depending on if media is used at all." if self._usemedia: return bool(self.href and self.media.wellformed) else: return bool(self.href) wellformed = property(_getWellformed) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssmediarule.py0000644000175000017500000003115500000000000021376 0ustar00kovidkovid"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.""" from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSMediaRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from . import cssrule import css_parser import xml.dom import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class CSSMediaRule(cssrule.CSSRuleRules): """ Objects implementing the CSSMediaRule interface can be identified by the MEDIA_RULE constant. On these objects the type attribute must return the value of that constant. Format:: : MEDIA_SYM S* medium [ COMMA S* medium ]* STRING? # the name LBRACE S* ruleset* '}' S*; ``cssRules`` All Rules in this media rule, a :class:`~css_parser.css.CSSRuleList`. """ def __init__(self, mediaText='all', name=None, parentRule=None, parentStyleSheet=None, readonly=False): """constructor""" super(CSSMediaRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@media' # 1. media if mediaText: self.media = mediaText else: self.media = css_parser.stylesheets.MediaList() self.name = name self._readonly = readonly def __repr__(self): return "css_parser.css.%s(mediaText=%r)" % ( self.__class__.__name__, self.media.mediaText) def __str__(self): return "" % ( self.__class__.__name__, self.media.mediaText, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSMediaRule(self) def _setCssText(self, cssText): """ :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) :Exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if a specified selector uses an unknown namespace prefix. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ # media "name"? { cssRules } super(CSSMediaRule, self)._setCssText(cssText) # might be (cssText, namespaces) cssText, namespaces = self._splitNamespacesOff(cssText) tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if self._type(attoken) != self._prods.MEDIA_SYM: self._log.error('CSSMediaRule: No CSSMediaRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: # save if parse goes wrong oldMedia = self._media oldCssRules = self._cssRules ok = True # media mediatokens, end = self._tokensupto2(tokenizer, mediaqueryendonly=True, separateEnd=True) if '{' == self._tokenvalue(end)\ or self._prods.STRING == self._type(end): self.media = css_parser.stylesheets.MediaList(parentRule=self) # TODO: remove special case self.media.mediaText = mediatokens ok = ok and self.media.wellformed else: ok = False # name (optional) name = None nameseq = self._tempSeq() if self._prods.STRING == self._type(end): name = self._stringtokenvalue(end) # TODO: for now comments are lost after name nametokens, end = self._tokensupto2(tokenizer, blockstartonly=True, separateEnd=True) wellformed, expected = self._parse(None, nameseq, nametokens, {}) if not wellformed: ok = False self._log.error('CSSMediaRule: Syntax Error: %s' % self._valuestr(cssText)) # check for { if '{' != self._tokenvalue(end): self._log.error('CSSMediaRule: No "{" found: %s' % self._valuestr(cssText)) return # cssRules cssrulestokens, braceOrEOF = self._tokensupto2(tokenizer, mediaendonly=True, separateEnd=True) nonetoken = self._nexttoken(tokenizer, None) if 'EOF' == self._type(braceOrEOF): # HACK!!! # TODO: Not complete, add EOF to rule and } to @media cssrulestokens.append(braceOrEOF) braceOrEOF = ('CHAR', '}', 0, 0) self._log.debug('CSSMediaRule: Incomplete, adding "}".', token=braceOrEOF, neverraise=True) if '}' != self._tokenvalue(braceOrEOF): self._log.error('CSSMediaRule: No "}" found.', token=braceOrEOF) elif nonetoken: self._log.error('CSSMediaRule: Trailing content found.', token=nonetoken) else: # for closures: must be a mutable new = {'wellformed': True} def COMMENT(expected, seq, token, tokenizer=None): self.insertRule(css_parser.css.CSSComment( [token], parentRule=self, parentStyleSheet=self.parentStyleSheet)) return expected def ruleset(expected, seq, token, tokenizer): rule = css_parser.css.CSSStyleRule( parentRule=self, parentStyleSheet=self.parentStyleSheet) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) return expected def atrule(expected, seq, token, tokenizer): # TODO: get complete rule! tokens = self._tokensupto2(tokenizer, token) atval = self._tokenvalue(token) factories = { '@page': css_parser.css.CSSPageRule, '@media': CSSMediaRule, } if atval in ('@charset ', '@font-face', '@import', '@namespace', '@variables'): self._log.error('CSSMediaRule: This rule is not ' 'allowed in CSSMediaRule - ignored: ' '%s.' % self._valuestr(tokens), token=token, error=xml.dom.HierarchyRequestErr) elif atval in factories: rule = factories[atval]( parentRule=self, parentStyleSheet=self.parentStyleSheet) rule.cssText = tokens if rule.wellformed: self.insertRule(rule) else: rule = css_parser.css.CSSUnknownRule( tokens, parentRule=self, parentStyleSheet=self.parentStyleSheet) if rule.wellformed: self.insertRule(rule) return expected # save for possible reset oldCssRules = self.cssRules self.cssRules = css_parser.css.CSSRuleList() seq = [] # not used really tokenizer = iter(cssrulestokens) wellformed, expected = self._parse(braceOrEOF, seq, tokenizer, { 'COMMENT': COMMENT, 'CHARSET_SYM': atrule, 'FONT_FACE_SYM': atrule, 'IMPORT_SYM': atrule, 'NAMESPACE_SYM': atrule, 'PAGE_SYM': atrule, 'MEDIA_SYM': atrule, 'ATKEYWORD': atrule }, default=ruleset, new=new) ok = ok and wellformed if ok: self.name = name self._setSeq(nameseq) else: self._media = oldMedia self._cssRules = oldCssRules cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this " "rule.") def _setName(self, name): # Under Python 2.x this was basestring but given unicode literals ... if isinstance(name, string_type) or name is None: # "" or '' if not name: name = None self._name = name else: self._log.error('CSSImportRule: Not a valid name: %s' % name) name = property(lambda self: self._name, _setName, doc="An optional name for this media rule.") def _setMedia(self, media): """ :param media: a :class:`~css_parser.stylesheets.MediaList` or string """ self._checkReadonly() # Under Python 2.x this was basestring but given unicode literals ... if isinstance(media, string_type): self._media = css_parser.stylesheets.MediaList( mediaText=media, parentRule=self) else: media._parentRule = self self._media = media # NOT IN @media seq at all?! # # update seq # for i, item in enumerate(self.seq): # if item.type == 'media': # self._seq[i] = (self._media, 'media', None, None) # break # else: # # insert after @media if not in seq at all # self.seq.insert(0, # self._media, 'media', None, None) media = property(lambda self: self._media, _setMedia, doc="(DOM) A list of media types for this rule " "of type :class:`~css_parser.stylesheets.MediaList`.") def insertRule(self, rule, index=None): """Implements base ``insertRule``.""" rule, index = self._prepareInsertRule(rule, index) if rule is False or rule is True: # done or error return # check hierarchy if isinstance(rule, css_parser.css.CSSCharsetRule) or \ isinstance(rule, css_parser.css.CSSFontFaceRule) or \ isinstance(rule, css_parser.css.CSSImportRule) or \ isinstance(rule, css_parser.css.CSSNamespaceRule) or \ isinstance(rule, css_parser.css.MarginRule): self._log.error('%s: This type of rule is not allowed here: %s' % (self.__class__.__name__, rule.cssText), error=xml.dom.HierarchyRequestErr) return return self._finishInsertRule(rule, index) type = property(lambda self: self.MEDIA_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") wellformed = property(lambda self: self.media.wellformed) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssnamespacerule.py0000644000175000017500000002653500000000000022261 0ustar00kovidkovid"""CSSNamespaceRule currently implements http://dev.w3.org/csswg/css3-namespace/ """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSNamespaceRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from . import cssrule import css_parser import xml.dom class CSSNamespaceRule(cssrule.CSSRule): """ Represents an @namespace rule within a CSS style sheet. The @namespace at-rule declares a namespace prefix and associates it with a given namespace (a string). This namespace prefix can then be used in namespace-qualified names such as those described in the Selectors Module [SELECT] or the Values and Units module [CSS3VAL]. Dealing with these rules directly is not needed anymore, easier is the use of :attr:`css_parser.css.CSSStyleSheet.namespaces`. Format:: namespace : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* ; namespace_prefix : IDENT ; """ def __init__(self, namespaceURI=None, prefix=None, cssText=None, parentRule=None, parentStyleSheet=None, readonly=False): """ :Parameters: namespaceURI The namespace URI (a simple string!) which is bound to the given prefix. If no prefix is set (``CSSNamespaceRule.prefix==''``) the namespace defined by namespaceURI is set as the default namespace prefix The prefix used in the stylesheet for the given ``CSSNamespaceRule.uri``. cssText if no namespaceURI is given cssText must be given to set a namespaceURI as this is readonly later on parentStyleSheet sheet where this rule belongs to Do not use as positional but as keyword parameters only! If readonly allows setting of properties in constructor only format namespace:: namespace : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* ; namespace_prefix : IDENT ; """ super(CSSNamespaceRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@namespace' self._prefix = '' self._namespaceURI = None if namespaceURI: self.namespaceURI = namespaceURI self.prefix = prefix tempseq = self._tempSeq() tempseq.append(self.prefix, 'prefix') tempseq.append(self.namespaceURI, 'namespaceURI') self._setSeq(tempseq) elif cssText is not None: self.cssText = cssText if parentStyleSheet: self._parentStyleSheet = parentStyleSheet self._readonly = readonly def __repr__(self): return "css_parser.css.%s(namespaceURI=%r, prefix=%r)" % ( self.__class__.__name__, self.namespaceURI, self.prefix) def __str__(self): return "" % ( self.__class__.__name__, self.namespaceURI, self.prefix, id(self)) def _getCssText(self): """Return serialized property cssText""" return css_parser.ser.do_CSSNamespaceRule(self) def _setCssText(self, cssText): """ :param cssText: initial value for this rules cssText which is parsed :exceptions: - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. """ super(CSSNamespaceRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if self._type(attoken) != self._prods.NAMESPACE_SYM: self._log.error('CSSNamespaceRule: No CSSNamespaceRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: # for closures: must be a mutable new = {'keyword': self._tokenvalue(attoken), 'prefix': '', 'uri': None, 'wellformed': True } def _ident(expected, seq, token, tokenizer=None): # the namespace prefix, optional if 'prefix or uri' == expected: new['prefix'] = self._tokenvalue(token) seq.append(new['prefix'], 'prefix') return 'uri' else: new['wellformed'] = False self._log.error( 'CSSNamespaceRule: Unexpected ident.', token) return expected def _string(expected, seq, token, tokenizer=None): # the namespace URI as a STRING if expected.endswith('uri'): new['uri'] = self._stringtokenvalue(token) seq.append(new['uri'], 'namespaceURI') return ';' else: new['wellformed'] = False self._log.error( 'CSSNamespaceRule: Unexpected string.', token) return expected def _uri(expected, seq, token, tokenizer=None): # the namespace URI as URI which is DEPRECATED if expected.endswith('uri'): uri = self._uritokenvalue(token) new['uri'] = uri seq.append(new['uri'], 'namespaceURI') return ';' else: new['wellformed'] = False self._log.error( 'CSSNamespaceRule: Unexpected URI.', token) return expected def _char(expected, seq, token, tokenizer=None): # final ; val = self._tokenvalue(token) if ';' == expected and ';' == val: return 'EOF' else: new['wellformed'] = False self._log.error( 'CSSNamespaceRule: Unexpected char.', token) return expected # "NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*" newseq = self._tempSeq() wellformed, expected = self._parse(expected='prefix or uri', seq=newseq, tokenizer=tokenizer, productions={'IDENT': _ident, 'STRING': _string, 'URI': _uri, 'CHAR': _char}, new=new) # wellformed set by parse wellformed = wellformed and new['wellformed'] # post conditions if new['uri'] is None: wellformed = False self._log.error('CSSNamespaceRule: No namespace URI found: %s' % self._valuestr(cssText)) if expected != 'EOF': wellformed = False self._log.error('CSSNamespaceRule: No ";" found: %s' % self._valuestr(cssText)) # set all if wellformed: self.atkeyword = new['keyword'] self._prefix = new['prefix'] self.namespaceURI = new['uri'] self._setSeq(newseq) cssText = property(fget=_getCssText, fset=_setCssText, doc="(DOM) The parsable textual representation of this " "rule.") def _setNamespaceURI(self, namespaceURI): """ :param namespaceURI: the initial value for this rules namespaceURI :exceptions: - :exc:`~xml.dom.NoModificationAllowedErr`: (CSSRule) Raised if this rule is readonly or a namespaceURI is already set in this rule. """ self._checkReadonly() if not self._namespaceURI: # initial setting self._namespaceURI = namespaceURI tempseq = self._tempSeq() tempseq.append(namespaceURI, 'namespaceURI') self._setSeq(tempseq) # makes seq readonly! elif self._namespaceURI != namespaceURI: self._log.error('CSSNamespaceRule: namespaceURI is readonly.', error=xml.dom.NoModificationAllowedErr) namespaceURI = property(lambda self: self._namespaceURI, _setNamespaceURI, doc="URI (handled as simple string) of the defined namespace.") def _replaceNamespaceURI(self, namespaceURI): """Used during parse of new sheet only! :param namespaceURI: the new value for this rules namespaceURI """ self._namespaceURI = namespaceURI for i, x in enumerate(self._seq): if 'namespaceURI' == x.type: self._seq._readonly = False self._seq.replace(i, namespaceURI, 'namespaceURI') self._seq._readonly = True break def _setPrefix(self, prefix=None): """ :param prefix: the new prefix :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() if not prefix: prefix = '' else: tokenizer = self._tokenize2(prefix) prefixtoken = self._nexttoken(tokenizer, None) if not prefixtoken or self._type(prefixtoken) != self._prods.IDENT: self._log.error('CSSNamespaceRule: No valid prefix "%s".' % self._valuestr(prefix), error=xml.dom.SyntaxErr) return else: prefix = self._tokenvalue(prefixtoken) # update seq for i, x in enumerate(self._seq): if x == self._prefix: self._seq[i] = (prefix, 'prefix', None, None) break else: # put prefix at the beginning! self._seq[0] = (prefix, 'prefix', None, None) # set new prefix self._prefix = prefix prefix = property(lambda self: self._prefix, _setPrefix, doc="Prefix used for the defined namespace.") type = property(lambda self: self.NAMESPACE_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") wellformed = property(lambda self: self.namespaceURI is not None) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/csspagerule.py0000644000175000017500000003762100000000000021237 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser from . import cssrule from .marginrule import MarginRule from .cssstyledeclaration import CSSStyleDeclaration from itertools import chain """CSSPageRule implements DOM Level 2 CSS CSSPageRule.""" __all__ = ['CSSPageRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring def as_list(p): if isinstance(p, list): return p return list(p) class CSSPageRule(cssrule.CSSRuleRules): """ The CSSPageRule interface represents a @page rule within a CSS style sheet. The @page rule is used to specify the dimensions, orientation, margins, etc. of a page box for paged media. Format:: page : PAGE_SYM S* IDENT? pseudo_page? S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* ; pseudo_page : ':' [ "left" | "right" | "first" ] ; margin : margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* ; margin_sym : TOPLEFTCORNER_SYM | TOPLEFT_SYM | TOPCENTER_SYM | TOPRIGHT_SYM | TOPRIGHTCORNER_SYM | BOTTOMLEFTCORNER_SYM | BOTTOMLEFT_SYM | BOTTOMCENTER_SYM | BOTTOMRIGHT_SYM | BOTTOMRIGHTCORNER_SYM | LEFTTOP_SYM | LEFTMIDDLE_SYM | LEFTBOTTOM_SYM | RIGHTTOP_SYM | RIGHTMIDDLE_SYM | RIGHTBOTTOM_SYM ; `cssRules` contains a list of `MarginRule` objects. """ def __init__(self, selectorText=None, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ If readonly allows setting of properties in constructor only. :param selectorText: type string :param style: CSSStyleDeclaration for this CSSStyleRule """ super(CSSPageRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@page' self._specificity = (0, 0, 0) tempseq = self._tempSeq() if selectorText: self.selectorText = selectorText tempseq.append(self.selectorText, 'selectorText') else: self._selectorText = self._tempSeq() if style: self.style = style else: self.style = CSSStyleDeclaration() tempseq.append(self.style, 'style') self._setSeq(tempseq) self._readonly = readonly def __repr__(self): return "css_parser.css.%s(selectorText=%r, style=%r)" % ( self.__class__.__name__, self.selectorText, self.style.cssText) def __str__(self): return ("") % ( self.__class__.__name__, self.selectorText, self.specificity, self.style.cssText, len(self.cssRules), id(self)) def __contains__(self, margin): """Check if margin is set in the rule.""" return margin in as_list(self.keys()) def keys(self): "Return list of all set margins (MarginRule)." return as_list(r.margin for r in self.cssRules) def __getitem__(self, margin): """Retrieve the style (of MarginRule) for `margin` (which must be normalized). """ for r in self.cssRules: if r.margin == margin: return r.style def __setitem__(self, margin, style): """Set the style (of MarginRule) for `margin` (which must be normalized). """ for i, r in enumerate(self.cssRules): if r.margin == margin: r.style = style return i else: return self.add(MarginRule(margin, style)) def __delitem__(self, margin): """Delete the style (the MarginRule) for `margin` (which must be normalized). """ for r in self.cssRules: if r.margin == margin: self.deleteRule(r) def __parseSelectorText(self, selectorText): """ Parse `selectorText` which may also be a list of tokens and returns (selectorText, seq). see _setSelectorText for details """ # for closures: must be a mutable new = {'wellformed': True, 'last-S': False, 'name': 0, 'first': 0, 'lr': 0} def _char(expected, seq, token, tokenizer=None): # pseudo_page, :left, :right or :first val = self._tokenvalue(token) if not new['last-S'] and expected in ['page', ': or EOF']\ and ':' == val: try: identtoken = next(tokenizer) except StopIteration: self._log.error( 'CSSPageRule selectorText: No IDENT found.', token) else: ival, ityp = self._tokenvalue(identtoken),\ self._type(identtoken) if self._prods.IDENT != ityp: self._log.error('CSSPageRule selectorText: Expected ' 'IDENT but found: %r' % ival, token) else: if ival not in ('first', 'left', 'right'): self._log.warn('CSSPageRule: Unknown @page ' 'selector: %r' % (':'+ival,), neverraise=True) if ival == 'first': new['first'] = 1 else: new['lr'] = 1 seq.append(val + ival, 'pseudo') return 'EOF' return expected else: new['wellformed'] = False self._log.error('CSSPageRule selectorText: Unexpected CHAR: %r' % val, token) return expected def S(expected, seq, token, tokenizer=None): "Does not raise if EOF is found." if expected == ': or EOF': # pseudo must directly follow IDENT if given new['last-S'] = True return expected def IDENT(expected, seq, token, tokenizer=None): "" val = self._tokenvalue(token) if 'page' == expected: if self._normalize(val) == 'auto': self._log.error('CSSPageRule selectorText: Invalid pagename.', token) else: new['name'] = 1 seq.append(val, 'IDENT') return ': or EOF' else: new['wellformed'] = False self._log.error('CSSPageRule selectorText: Unexpected IDENT: ' '%r' % val, token) return expected def COMMENT(expected, seq, token, tokenizer=None): "Does not raise if EOF is found." seq.append(css_parser.css.CSSComment([token]), 'COMMENT') return expected newseq = self._tempSeq() wellformed, expected = self._parse(expected='page', seq=newseq, tokenizer=self._tokenize2(selectorText), productions={'CHAR': _char, 'IDENT': IDENT, 'COMMENT': COMMENT, 'S': S}, new=new) wellformed = wellformed and new['wellformed'] # post conditions if expected == 'ident': self._log.error( 'CSSPageRule selectorText: No valid selector: %r' % self._valuestr(selectorText)) return wellformed, newseq, (new['name'], new['first'], new['lr']) def __parseMarginAndStyle(self, tokens): "tokens is a list, no generator (yet)" g = iter(tokens) styletokens = [] # new rules until parse done cssRules = [] for token in g: if token[0] == 'ATKEYWORD' and \ self._normalize(token[1]) in MarginRule.margins: # MarginRule m = MarginRule(parentRule=self, parentStyleSheet=self.parentStyleSheet) m.cssText = chain([token], g) # merge if margin set more than once for r in cssRules: if r.margin == m.margin: for p in m.style: r.style.setProperty(p, replace=False) break else: cssRules.append(m) continue # TODO: Properties? styletokens.append(token) return cssRules, styletokens def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSPageRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSPageRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) if self._type(self._nexttoken(tokenizer)) != self._prods.PAGE_SYM: self._log.error('CSSPageRule: No CSSPageRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: newStyle = CSSStyleDeclaration(parentRule=self) ok = True selectortokens, startbrace = self._tokensupto2(tokenizer, blockstartonly=True, separateEnd=True) styletokens, braceorEOFtoken = self._tokensupto2(tokenizer, blockendonly=True, separateEnd=True) nonetoken = self._nexttoken(tokenizer) if self._tokenvalue(startbrace) != '{': ok = False self._log.error('CSSPageRule: No start { of style declaration ' 'found: %r' % self._valuestr(cssText), startbrace) elif nonetoken: ok = False self._log.error('CSSPageRule: Trailing content found.', token=nonetoken) selok, newselseq, specificity = self.__parseSelectorText(selectortokens) ok = ok and selok val, type_ = self._tokenvalue(braceorEOFtoken),\ self._type(braceorEOFtoken) if val != '}' and type_ != 'EOF': ok = False self._log.error( 'CSSPageRule: No "}" after style declaration found: %r' % self._valuestr(cssText)) else: if 'EOF' == type_: # add again as style needs it styletokens.append(braceorEOFtoken) # filter pagemargin rules out first cssRules, styletokens = self.__parseMarginAndStyle(styletokens) # SET, may raise: newStyle.cssText = styletokens if ok: self._selectorText = newselseq self._specificity = specificity self.style = newStyle self.cssRules = css_parser.css.CSSRuleList() for r in cssRules: self.cssRules.append(r) cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this rule.") def _getSelectorText(self): """Wrapper for css_parser Selector object.""" return css_parser.ser.do_CSSPageRuleSelector(self._selectorText) def _setSelectorText(self, selectorText): """Wrapper for css_parser Selector object. :param selectorText: DOM String, in CSS 2.1 one of - :first - :left - :right - empty :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() # may raise SYNTAX_ERR wellformed, newseq, specificity = self.__parseSelectorText(selectorText) if wellformed: self._selectorText = newseq self._specificity = specificity selectorText = property(_getSelectorText, _setSelectorText, doc="(DOM) The parsable textual representation of " "the page selector for the rule.") def _setStyle(self, style): """ :param style: a CSSStyleDeclaration or string """ self._checkReadonly() # Under Python2.x this was basestring but given unicode literals ... if isinstance(style, string_type): self._style = CSSStyleDeclaration(cssText=style, parentRule=self) else: style._parentRule = self self._style = style style = property(lambda self: self._style, _setStyle, doc="(DOM) The declaration-block of this rule set, " "a :class:`~css_parser.css.CSSStyleDeclaration`.") def insertRule(self, rule, index=None): """Implements base ``insertRule``.""" rule, index = self._prepareInsertRule(rule, index) if rule is False or rule is True: # done or error return # check hierarchy if isinstance(rule, css_parser.css.CSSCharsetRule) or \ isinstance(rule, css_parser.css.CSSFontFaceRule) or \ isinstance(rule, css_parser.css.CSSImportRule) or \ isinstance(rule, css_parser.css.CSSNamespaceRule) or \ isinstance(rule, CSSPageRule) or \ isinstance(rule, css_parser.css.CSSMediaRule): self._log.error('%s: This type of rule is not allowed here: %s' % (self.__class__.__name__, rule.cssText), error=xml.dom.HierarchyRequestErr) return return self._finishInsertRule(rule, index) specificity = property(lambda self: self._specificity, doc="""Specificity of this page rule (READONLY). Tuple of (f, g, h) where: - if the page selector has a named page, f=1; else f=0 - if the page selector has a ':first' pseudo-class, g=1; else g=0 - if the page selector has a ':left' or ':right' pseudo-class, h=1; else h=0 """) type = property(lambda self: self.PAGE_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") # constant but needed: wellformed = property(lambda self: True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssproperties.py0000644000175000017500000001212500000000000021617 0ustar00kovidkovid"""CSS2Properties (partly!) implements DOM Level 2 CSS CSS2Properties used by CSSStyleDeclaration TODO: CSS2Properties If an implementation does implement this interface, it is expected to understand the specific syntax of the shorthand properties, and apply their semantics; when the margin property is set, for example, the marginTop, marginRight, marginBottom and marginLeft properties are actually being set by the underlying implementation. When dealing with CSS "shorthand" properties, the shorthand properties should be decomposed into their component longhand properties as appropriate, and when querying for their value, the form returned should be the shortest form exactly equivalent to the declarations made in the ruleset. However, if there is no shorthand declaration that could be added to the ruleset without changing in any way the rules already declared in the ruleset (i.e., by adding longhand rules that were previously not declared in the ruleset), then the empty string should be returned for the shorthand property. For example, querying for the font property should not return "normal normal normal 14pt/normal Arial, sans-serif", when "14pt Arial, sans-serif" suffices. (The normals are initial values, and are implied by use of the longhand property.) If the values for all the longhand properties that compose a particular string are the initial values, then a string consisting of all the initial values should be returned (e.g. a border-width value of "medium" should be returned as such, not as ""). For some shorthand properties that take missing values from other sides, such as the margin, padding, and border-[width|style|color] properties, the minimum number of sides possible should be used; i.e., "0px 10px" will be returned instead of "0px 10px 0px 10px". If the value of a shorthand property can not be decomposed into its component longhand properties, as is the case for the font property with a value of "menu", querying for the values of the component longhand properties should return the empty string. TODO: CSS2Properties DOMImplementation The interface found within this section are not mandatory. A DOM application can use the hasFeature method of the DOMImplementation interface to determine whether it is supported or not. The feature string for this extended interface listed in this section is "CSS2" and the version is "2.0". """ __all__ = ['CSS2Properties'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import css_parser.profiles import re class CSS2Properties(object): """The CSS2Properties interface represents a convenience mechanism for retrieving and setting properties within a CSSStyleDeclaration. The attributes of this interface correspond to all the properties specified in CSS2. Getting an attribute of this interface is equivalent to calling the getPropertyValue method of the CSSStyleDeclaration interface. Setting an attribute of this interface is equivalent to calling the setProperty method of the CSSStyleDeclaration interface. css_parser actually also allows usage of ``del`` to remove a CSS property from a CSSStyleDeclaration. This is an abstract class, the following functions need to be present in inheriting class: - ``_getP`` - ``_setP`` - ``_delP`` """ # actual properties are set after the class definition! def _getP(self, CSSname): pass def _setP(self, CSSname, value): pass def _delP(self, CSSname): pass _reCSStoDOMname = re.compile('-[a-z]', re.I) def _toDOMname(CSSname): """Returns DOMname for given CSSname e.g. for CSSname 'font-style' returns 'fontStyle'. """ def _doCSStoDOMname2(m): return m.group(0)[1].capitalize() return _reCSStoDOMname.sub(_doCSStoDOMname2, CSSname) _reDOMtoCSSname = re.compile('([A-Z])[a-z]+') def _toCSSname(DOMname): """Return CSSname for given DOMname e.g. for DOMname 'fontStyle' returns 'font-style'. """ def _doDOMtoCSSname2(m): return '-' + m.group(0).lower() return _reDOMtoCSSname.sub(_doDOMtoCSSname2, DOMname) # add list of DOMname properties to CSS2Properties # used for CSSStyleDeclaration to check if allowed properties # but somehow doubled, any better way? CSS2Properties._properties = [] for group in css_parser.profiles.properties: for name in css_parser.profiles.properties[group]: CSS2Properties._properties.append(_toDOMname(name)) # add CSS2Properties to CSSStyleDeclaration: def __named_property_def(DOMname): """ Closure to keep name known in each properties accessor function DOMname is converted to CSSname here, so actual calls use CSSname. """ CSSname = _toCSSname(DOMname) def _get(self): return self._getP(CSSname) def _set(self, value): self._setP(CSSname, value) def _del(self): self._delP(CSSname) return _get, _set, _del # add all CSS2Properties to CSSStyleDeclaration for DOMname in CSS2Properties._properties: setattr(CSS2Properties, DOMname, property(*__named_property_def(DOMname))) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssrule.py0000644000175000017500000003011100000000000020365 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser """CSSRule implements DOM Level 2 CSS CSSRule.""" __all__ = ['CSSRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class CSSRule(css_parser.util.Base2): """Abstract base interface for any type of CSS statement. This includes both rule sets and at-rules. An implementation is expected to preserve all rules specified in a CSS style sheet, even if the rule is not recognized by the parser. Unrecognized rules are represented using the :class:`CSSUnknownRule` interface. """ """ CSSRule type constants. An integer indicating which type of rule this is. """ UNKNOWN_RULE = 0 ":class:`css_parser.css.CSSUnknownRule` (not used in CSSOM anymore)" STYLE_RULE = 1 ":class:`css_parser.css.CSSStyleRule`" CHARSET_RULE = 2 ":class:`css_parser.css.CSSCharsetRule` (not used in CSSOM anymore)" IMPORT_RULE = 3 ":class:`css_parser.css.CSSImportRule`" MEDIA_RULE = 4 ":class:`css_parser.css.CSSMediaRule`" FONT_FACE_RULE = 5 ":class:`css_parser.css.CSSFontFaceRule`" PAGE_RULE = 6 ":class:`css_parser.css.CSSPageRule`" NAMESPACE_RULE = 10 """:class:`css_parser.css.CSSNamespaceRule`, Value has changed in 0.9.7a3 due to a change in the CSSOM spec.""" COMMENT = 1001 # was -1, css_parser only """:class:`css_parser.css.CSSComment` - not in the offical spec, Value has changed in 0.9.7a3""" VARIABLES_RULE = 1008 """:class:`css_parser.css.CSSVariablesRule` - experimental rule not in the offical spec""" MARGIN_RULE = 1006 """:class:`css_parser.css.MarginRule` - experimental rule not in the offical spec""" _typestrings = {UNKNOWN_RULE: 'UNKNOWN_RULE', STYLE_RULE: 'STYLE_RULE', CHARSET_RULE: 'CHARSET_RULE', IMPORT_RULE: 'IMPORT_RULE', MEDIA_RULE: 'MEDIA_RULE', FONT_FACE_RULE: 'FONT_FACE_RULE', PAGE_RULE: 'PAGE_RULE', NAMESPACE_RULE: 'NAMESPACE_RULE', COMMENT: 'COMMENT', VARIABLES_RULE: 'VARIABLES_RULE', MARGIN_RULE: 'MARGIN_RULE' } def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False): """Set common attributes for all rules.""" super(CSSRule, self).__init__() self._parent = parentRule self._parentRule = parentRule self._parentStyleSheet = parentStyleSheet self._setSeq(self._tempSeq()) # self._atkeyword = None # must be set after initialization of #inheriting rule is done self._readonly = False def _setAtkeyword(self, keyword): """Check if new keyword fits the rule it is used for.""" atkeyword = self._normalize(keyword) if not self.atkeyword or (self.atkeyword == atkeyword): self._atkeyword = atkeyword self._keyword = keyword else: self._log.error('%s: Invalid atkeyword for this rule: %r' % (self.atkeyword, keyword), error=xml.dom.InvalidModificationErr) atkeyword = property(lambda self: self._atkeyword, _setAtkeyword, doc="Normalized keyword of an @rule (e.g. ``@import``).") def _setCssText(self, cssText): """ :param cssText: A parsable DOMString. :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ self._checkReadonly() cssText = property(lambda self: '', _setCssText, doc="(DOM) The parsable textual representation of the " "rule. This reflects the current state of the rule " "and not its initial value.") parent = property(lambda self: self._parent, doc="The Parent Node of this CSSRule or None.") parentRule = property(lambda self: self._parentRule, doc="If this rule is contained inside another rule " "(e.g. a style rule inside an @media block), this " "is the containing rule. If this rule is not nested " "inside any other rules, this returns None.") def _getParentStyleSheet(self): # rules contained in other rules (@media) use that rules parent if (self.parentRule): return self.parentRule._parentStyleSheet else: return self._parentStyleSheet parentStyleSheet = property(_getParentStyleSheet, doc="The style sheet that contains this rule.") type = property(lambda self: self.UNKNOWN_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") typeString = property(lambda self: CSSRule._typestrings[self.type], doc="Descriptive name of this rule's type.") wellformed = property(lambda self: False, doc="If the rule is wellformed.") class CSSRuleRules(CSSRule): """Abstract base interface for rules that contain other rules like @media or @page. Methods may be overwritten if a rule has specific stuff to do like checking the order of insertion like @media does. """ def __init__(self, parentRule=None, parentStyleSheet=None): super(CSSRuleRules, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self.cssRules = css_parser.css.CSSRuleList() def __iter__(self): """Generator iterating over these rule's cssRules.""" for rule in self._cssRules: yield rule def _setCssRules(self, cssRules): "Set new cssRules and update contained rules refs." cssRules.append = self.insertRule cssRules.extend = self.insertRule cssRules.__delitem__ == self.deleteRule for rule in cssRules: rule._parentRule = self rule._parentStyleSheet = None self._cssRules = cssRules cssRules = property(lambda self: self._cssRules, _setCssRules, "All Rules in this style sheet, a " ":class:`~css_parser.css.CSSRuleList`.") def deleteRule(self, index): """ Delete the rule at `index` from rules ``cssRules``. :param index: The `index` of the rule to be removed from the rules cssRules list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is raised but rules for normal Python lists are used. E.g. ``deleteRule(-1)`` removes the last rule in cssRules. `index` may also be a CSSRule object which will then be removed. :Exceptions: - :exc:`~xml.dom.IndexSizeErr`: Raised if the specified index does not correspond to a rule in the media rule list. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this media rule is readonly. """ self._checkReadonly() if isinstance(index, CSSRule): for i, r in enumerate(self.cssRules): if index == r: index = i break else: raise xml.dom.IndexSizeErr("%s: Not a rule in " "this rule'a cssRules list: %s" % (self.__class__.__name__, index)) try: # detach self._cssRules[index]._parentRule = None del self._cssRules[index] except IndexError: raise xml.dom.IndexSizeErr('%s: %s is not a valid index ' 'in the rulelist of length %i' % (self.__class__.__name__, index, self._cssRules.length)) def _prepareInsertRule(self, rule, index=None): "return checked `index` and optional parsed `rule`" self._checkReadonly() # check index if index is None: index = len(self._cssRules) elif index < 0 or index > self._cssRules.length: raise xml.dom.IndexSizeErr('%s: Invalid index %s for ' 'CSSRuleList with a length of %s.' % (self.__class__.__name__, index, self._cssRules.length)) # check and optionally parse rule # Under Python 2.x this was basestring but ... if isinstance(rule, string_type): tempsheet = css_parser.css.CSSStyleSheet() tempsheet.cssText = rule if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and not isinstance(tempsheet.cssRules[0], css_parser.css.CSSRule)): self._log.error('%s: Invalid Rule: %s' % (self.__class__.__name__, rule)) return False, False rule = tempsheet.cssRules[0] elif isinstance(rule, css_parser.css.CSSRuleList): # insert all rules for i, r in enumerate(rule): self.insertRule(r, index + i) return True, True elif not isinstance(rule, css_parser.css.CSSRule): self._log.error('%s: Not a CSSRule: %s' % (rule, self.__class__.__name__)) return False, False return rule, index def _finishInsertRule(self, rule, index): "add `rule` at `index`" rule._parentRule = self rule._parentStyleSheet = None self._cssRules.insert(index, rule) return index def add(self, rule): """Add `rule` to page rule. Same as ``insertRule(rule)``.""" return self.insertRule(rule) def insertRule(self, rule, index=None): """ Insert `rule` into the rules ``cssRules``. :param rule: the parsable text representing the `rule` to be inserted. For rule sets this contains both the selector and the style declaration. For at-rules, this specifies both the at-identifier and the rule content. css_parser also allows rule to be a valid :class:`~css_parser.css.CSSRule` object. :param index: before the `index` the specified `rule` will be inserted. If the specified `index` is equal to the length of the rules rule collection, the rule will be added to the end of the rule. If index is not given or None rule will be appended to rule list. :returns: the index of the newly inserted rule. :exceptions: - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the `rule` cannot be inserted at the specified `index`, e.g., if an @import rule is inserted after a standard rule set or other at-rule. - :exc:`~xml.dom.IndexSizeErr`: Raised if the specified `index` is not a valid insertion point. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified `rule` has a syntax error and is unparsable. """ return self._prepareInsertRule(rule, index) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssrulelist.py0000644000175000017500000000407700000000000021275 0ustar00kovidkovid"""CSSRuleList implements DOM Level 2 CSS CSSRuleList. Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist.""" from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSRuleList'] __docformat__ = 'restructuredtext' __version__ = '$Id$' class CSSRuleList(list): """The CSSRuleList object represents an (ordered) list of statements. The items in the CSSRuleList are accessible via an integral index, starting from 0. Subclasses a standard Python list so theoretically all standard list methods are available. Setting methods like ``__init__``, ``append``, ``extend`` or ``__setslice__`` are added later on instances of this class if so desired. E.g. CSSStyleSheet adds ``append`` which is not available in a simple instance of this class! """ def __init__(self, *ignored): "Nothing is set as this must also be defined later." pass def __notimplemented(self, *ignored): "Implemented in class using a CSSRuleList only." raise NotImplementedError( 'Must be implemented by class using an instance of this class.') append = extend = __setitem__ = __setslice__ = __notimplemented def item(self, index): """(DOM) Retrieve a CSS rule by ordinal `index`. The order in this collection represents the order of the rules in the CSS style sheet. If index is greater than or equal to the number of rules in the list, this returns None. Returns CSSRule, the style rule at the index position in the CSSRuleList, or None if that is not a valid index. """ try: return self[index] except IndexError: return None length = property(lambda self: len(self), doc="(DOM) The number of CSSRules in the list.") def rulesOfType(self, type): """Yield the rules which have the given `type` only, one of the constants defined in :class:`css_parser.css.CSSRule`.""" for r in self: if r.type == type: yield r ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssstyledeclaration.py0000644000175000017500000006556400000000000023010 0ustar00kovidkovid"""CSSStyleDeclaration implements DOM Level 2 CSS CSSStyleDeclaration and extends CSS2Properties see http://www.w3.org/TR/1998/REC-CSS2-19980512/syndata.html#parsing-errors Unknown properties ------------------ User agents must ignore a declaration with an unknown property. For example, if the style sheet is:: H1 { color: red; rotation: 70minutes } the user agent will treat this as if the style sheet had been:: H1 { color: red } css_parser gives a message about any unknown properties but keeps any property (if syntactically correct). Illegal values -------------- User agents must ignore a declaration with an illegal value. For example:: IMG { float: left } /* correct CSS2 */ IMG { float: left here } /* "here" is not a value of 'float' */ IMG { background: "red" } /* keywords cannot be quoted in CSS2 */ IMG { border-width: 3 } /* a unit must be specified for length values */ A CSS2 parser would honor the first rule and ignore the rest, as if the style sheet had been:: IMG { float: left } IMG { } IMG { } IMG { } css_parser again will issue a message (WARNING in this case) about invalid CSS2 property values. TODO: This interface is also used to provide a read-only access to the computed values of an element. See also the ViewCSS interface. - return computed values and not literal values - simplify unit pairs/triples/quadruples 2px 2px 2px 2px -> 2px for border/padding... - normalize compound properties like: background: no-repeat left url() #fff -> background: #fff url() no-repeat left """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSStyleDeclaration', 'Property'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from .cssproperties import CSS2Properties from .property import Property import css_parser class CSSStyleDeclaration(CSS2Properties, css_parser.util.Base2): """The CSSStyleDeclaration class represents a single CSS declaration block. This class may be used to determine the style properties currently set in a block or to set style properties explicitly within the block. While an implementation may not recognize all CSS properties within a CSS declaration block, it is expected to provide access to all specified properties in the style sheet through the CSSStyleDeclaration interface. Furthermore, implementations that support a specific level of CSS should correctly handle CSS shorthand properties for that level. For a further discussion of shorthand properties, see the CSS2Properties interface. Additionally the CSS2Properties interface is implemented. $css2propertyname All properties defined in the CSS2Properties class are available as direct properties of CSSStyleDeclaration with their respective DOM name, so e.g. ``fontStyle`` for property 'font-style'. These may be used as:: >>> style = CSSStyleDeclaration(cssText='color: red') >>> style.color = 'green' >>> print style.color green >>> del style.color >>> print style.color Format:: [Property: Value Priority?;]* [Property: Value Priority?]? """ def __init__(self, cssText='', parentRule=None, readonly=False, validating=None): """ :param cssText: Shortcut, sets CSSStyleDeclaration.cssText :param parentRule: The CSS rule that contains this declaration block or None if this CSSStyleDeclaration is not attached to a CSSRule. :param readonly: defaults to False :param validating: a flag defining if this sheet should be validated on change. Defaults to None, which means defer to the parent stylesheet. """ super(CSSStyleDeclaration, self).__init__() self._parentRule = parentRule self.validating = validating self.cssText = cssText self._readonly = readonly def __contains__(self, nameOrProperty): """Check if a property (or a property with given name) is in style. :param name: a string or Property, uses normalized name and not literalname """ if isinstance(nameOrProperty, Property): name = nameOrProperty.name else: name = self._normalize(nameOrProperty) return name in self.__nnames() def __iter__(self): """Iterator of set Property objects with different normalized names.""" def properties(): for name in self.__nnames(): yield self.getProperty(name) return properties() def keys(self): """Analoguous to standard dict returns property names which are set in this declaration.""" return list(self.__nnames()) def __getitem__(self, CSSName): """Retrieve the value of property ``CSSName`` from this declaration. ``CSSName`` will be always normalized. """ return self.getPropertyValue(CSSName) def __setitem__(self, CSSName, value): """Set value of property ``CSSName``. ``value`` may also be a tuple of (value, priority), e.g. style['color'] = ('red', 'important') ``CSSName`` will be always normalized. """ priority = None if isinstance(value, tuple): value, priority = value return self.setProperty(CSSName, value, priority) def __delitem__(self, CSSName): """Delete property ``CSSName`` from this declaration. If property is not in this declaration return u'' just like removeProperty. ``CSSName`` will be always normalized. """ return self.removeProperty(CSSName) def __setattr__(self, n, v): """Prevent setting of unknown properties on CSSStyleDeclaration which would not work anyway. For these ``CSSStyleDeclaration.setProperty`` MUST be called explicitly! TODO: implementation of known is not really nice, any alternative? """ known = ['_tokenizer', '_log', '_ttypes', '_seq', 'seq', 'parentRule', '_parentRule', 'cssText', 'valid', 'wellformed', 'validating', '_readonly', '_profiles', '_validating'] known.extend(CSS2Properties._properties) if n in known: super(CSSStyleDeclaration, self).__setattr__(n, v) else: raise AttributeError('Unknown CSS Property, ' '``CSSStyleDeclaration.setProperty("%s", ' '...)`` MUST be used.' % n) def __repr__(self): return "css_parser.css.%s(cssText=%r)" % ( self.__class__.__name__, self.getCssText(separator=' ')) def __str__(self): return "" % ( self.__class__.__name__, self.length, len(self.getProperties(all=True)), id(self)) def __nnames(self): """Return iterator for all different names in order as set if names are set twice the last one is used (double reverse!) """ names = [] for item in reversed(self.seq): val = item.value if isinstance(val, Property) and val.name not in names: names.append(val.name) return reversed(names) # overwritten accessor functions for CSS2Properties' properties def _getP(self, CSSName): """(DOM CSS2Properties) Overwritten here and effectively the same as ``self.getPropertyValue(CSSname)``. Parameter is in CSSname format ('font-style'), see CSS2Properties. Example:: >>> style = CSSStyleDeclaration(cssText='font-style:italic;') >>> print style.fontStyle italic """ return self.getPropertyValue(CSSName) def _setP(self, CSSName, value): """(DOM CSS2Properties) Overwritten here and effectively the same as ``self.setProperty(CSSname, value)``. Only known CSS2Properties may be set this way, otherwise an AttributeError is raised. For these unknown properties ``setPropertyValue(CSSname, value)`` has to be called explicitly. Also setting the priority of properties needs to be done with a call like ``setPropertyValue(CSSname, value, priority)``. Example:: >>> style = CSSStyleDeclaration() >>> style.fontStyle = 'italic' >>> # or >>> style.setProperty('font-style', 'italic', '!important') """ self.setProperty(CSSName, value) # TODO: Shorthand ones def _delP(self, CSSName): """(css_parser only) Overwritten here and effectively the same as ``self.removeProperty(CSSname)``. Example:: >>> style = CSSStyleDeclaration(cssText='font-style:italic;') >>> del style.fontStyle >>> print style.fontStyle """ self.removeProperty(CSSName) def children(self): """Generator yielding any known child in this declaration including *all* properties, comments or CSSUnknownrules. """ for item in self._seq: yield item.value def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_css_CSSStyleDeclaration(self) def _setCssText(self, cssText): """Setting this attribute will result in the parsing of the new value and resetting of all the properties in the declaration block including the removal or addition of properties. :exceptions: - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this declaration is readonly or a property is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. """ self._checkReadonly() tokenizer = self._tokenize2(cssText) def ident(expected, seq, token, tokenizer=None): # a property tokens = self._tokensupto2(tokenizer, starttoken=token, semicolon=True) if self._tokenvalue(tokens[-1]) == ';': tokens.pop() property = Property(parent=self) property.cssText = tokens if property.wellformed: seq.append(property, 'Property') else: self._log.error('CSSStyleDeclaration: Syntax Error in ' 'Property: %s' % self._valuestr(tokens)) # does not matter in this case return expected def unexpected(expected, seq, token, tokenizer=None): # error, find next ; or } to omit upto next property ignored = self._tokenvalue(token) + self._valuestr( self._tokensupto2(tokenizer, propertyvalueendonly=True)) self._log.error('CSSStyleDeclaration: Unexpected token, ignoring ' 'upto %r.' % ignored, token) # does not matter in this case return expected def char(expected, seq, token, tokenizer=None): # a standalone ; or error... if self._tokenvalue(token) == ';': self._log.info('CSSStyleDeclaration: Stripped standalone semicolon' ': %s' % self._valuestr([token]), neverraise=True) return expected else: return unexpected(expected, seq, token, tokenizer) # [Property: Value;]* Property: Value? newseq = self._tempSeq() wellformed, expected = self._parse(expected=None, seq=newseq, tokenizer=tokenizer, productions={'IDENT': ident, 'CHAR': char}, default=unexpected) # wellformed set by parse for item in newseq: item.value._parent = self # do not check wellformed as invalid things are removed anyway self._setSeq(newseq) cssText = property(_getCssText, _setCssText, doc="(DOM) A parsable textual representation of the " "declaration block excluding the surrounding curly " "braces.") def getCssText(self, separator=None): """ :returns: serialized property cssText, each property separated by given `separator` which may e.g. be ``u''`` to be able to use cssText directly in an HTML style attribute. ``;`` is part of each property (except the last one) and **cannot** be set with separator! """ return css_parser.ser.do_css_CSSStyleDeclaration(self, separator) def _setParentRule(self, parentRule): self._parentRule = parentRule # for x in self.children(): # x.parent = self parentRule = property(lambda self: self._parentRule, _setParentRule, doc="(DOM) The CSS rule that contains this declaration block or " "None if this CSSStyleDeclaration is not attached to a CSSRule.") def getProperties(self, name=None, all=False): """ :param name: optional `name` of properties which are requested. Only properties with this **always normalized** `name` are returned. If `name` is ``None`` all properties are returned (at least one for each set name depending on parameter `all`). :param all: if ``False`` (DEFAULT) only the effective properties are returned. If name is given a list with only one property is returned. if ``True`` all properties including properties set multiple times with different values or priorities for different UAs are returned. The order of the properties is fully kept as in the original stylesheet. :returns: a list of :class:`~css_parser.css.Property` objects set in this declaration. """ if name and not all: # single prop but list p = self.getProperty(name) if p: return [p] else: return [] elif not all: # effective Properties in name order return [self.getProperty(name_) for name_ in self.__nnames()] else: # all properties or all with this name nname = self._normalize(name) properties = [] for item in self.seq: val = item.value if isinstance(val, Property) and ( (bool(nname) is False) or (val.name == nname)): properties.append(val) return properties def getProperty(self, name, normalize=True): r""" :param name: of the CSS property, always lowercase (even if not normalized) :param normalize: if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent If ``False`` may return **NOT** the effective value but the effective for the unnormalized name. :returns: the effective :class:`~css_parser.css.Property` object. """ nname = self._normalize(name) found = None for item in reversed(self.seq): val = item.value if isinstance(val, Property): if (normalize and nname == val.name) or name == val.literalname: if val.priority: return val elif not found: found = val return found def getPropertyCSSValue(self, name, normalize=True): r""" :param name: of the CSS property, always lowercase (even if not normalized) :param normalize: if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent If ``False`` may return **NOT** the effective value but the effective for the unnormalized name. :returns: :class:`~css_parser.css.CSSValue`, the value of the effective property if it has been explicitly set for this declaration block. (DOM) Used to retrieve the object representation of the value of a CSS property if it has been explicitly set within this declaration block. Returns None if the property has not been set. (This method returns None if the property is a shorthand property. Shorthand property values can only be accessed and modified as strings, using the getPropertyValue and setProperty methods.) **css_parser currently always returns a CSSValue if the property is set.** for more on shorthand properties see http://www.dustindiaz.com/css-shorthand/ """ nname = self._normalize(name) if nname in self._SHORTHANDPROPERTIES: self._log.info('CSSValue for shorthand property "%s" should be ' 'None, this may be implemented later.' % nname, neverraise=True) p = self.getProperty(name, normalize) if p: return p.propertyValue else: return None def getPropertyValue(self, name, normalize=True): r""" :param name: of the CSS property, always lowercase (even if not normalized) :param normalize: if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent If ``False`` may return **NOT** the effective value but the effective for the unnormalized name. :returns: the value of the effective property if it has been explicitly set for this declaration block. Returns the empty string if the property has not been set. """ p = self.getProperty(name, normalize) if p: return p.value else: return '' def getPropertyPriority(self, name, normalize=True): r""" :param name: of the CSS property, always lowercase (even if not normalized) :param normalize: if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent If ``False`` may return **NOT** the effective value but the effective for the unnormalized name. :returns: the priority of the effective CSS property (e.g. the "important" qualifier) if the property has been explicitly set in this declaration block. The empty string if none exists. """ p = self.getProperty(name, normalize) if p: return p.priority else: return '' def removeProperty(self, name, normalize=True): r""" (DOM) Used to remove a CSS property if it has been explicitly set within this declaration block. :param name: of the CSS property :param normalize: if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent. The effective Property value is returned and *all* Properties with ``Property.name == name`` are removed. If ``False`` may return **NOT** the effective value but the effective for the unnormalized `name` only. Also only the Properties with the literal name `name` are removed. :returns: the value of the property if it has been explicitly set for this declaration block. Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property :exceptions: - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this declaration is readonly or the property is readonly. """ self._checkReadonly() r = self.getPropertyValue(name, normalize=normalize) newseq = self._tempSeq() if normalize: # remove all properties with name == nname nname = self._normalize(name) for item in self.seq: if not (isinstance(item.value, Property) and item.value.name == nname): newseq.appendItem(item) else: # remove all properties with literalname == name for item in self.seq: if not (isinstance(item.value, Property) and item.value.literalname == name): newseq.appendItem(item) self._setSeq(newseq) return r def setProperty(self, name, value=None, priority='', normalize=True, replace=True): r"""(DOM) Set a property value and priority within this declaration block. :param name: of the CSS property to set (in W3C DOM the parameter is called "propertyName"), always lowercase (even if not normalized) If a property with this `name` is present it will be reset. css_parser also allowed `name` to be a :class:`~css_parser.css.Property` object, all other parameter are ignored in this case :param value: the new value of the property, ignored if `name` is a Property. :param priority: the optional priority of the property (e.g. "important"), ignored if `name` is a Property. :param normalize: if True (DEFAULT) `name` will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent :param replace: if True (DEFAULT) the given property will replace a present property. If False a new property will be added always. The difference to `normalize` is that two or more properties with the same name may be set, useful for e.g. stuff like:: background: red; background: rgba(255, 0, 0, 0.5); which defines the same property but only capable UAs use the last property value, older ones use the first value. :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this declaration is readonly or the property is readonly. """ self._checkReadonly() if isinstance(name, Property): newp = name name = newp.literalname elif not value: # empty string or None effectively removed property return self.removeProperty(name) else: newp = Property(name, value, priority, parent=self) if newp.wellformed: if replace: # check if update nname = self._normalize(name) properties = self.getProperties(name, all=(not normalize)) for property in reversed(properties): if normalize and property.name == nname: property.propertyValue = newp.propertyValue.cssText property.priority = newp.priority return elif property.literalname == name: property.propertyValue = newp.propertyValue.cssText property.priority = newp.priority return # not yet set or forced omit replace newp.parent = self self.seq._readonly = False self.seq.append(newp, 'Property') self.seq._readonly = True else: self._log.warn('Invalid Property: %s: %s %s' % (name, value, priority)) def item(self, index): r"""(DOM) Retrieve the properties that have been explicitly set in this declaration block. The order of the properties retrieved using this method does not have to be the order in which they were set. This method can be used to iterate over all properties in this declaration block. :param index: of the property to retrieve, negative values behave like negative indexes on Python lists, so -1 is the last element :returns: the name of the property at this ordinal position. The empty string if no property exists at this position. **ATTENTION:** Only properties with different names are counted. If two properties with the same name are present in this declaration only the effective one is included. :meth:`item` and :attr:`length` work on the same set here. """ names = list(self.__nnames()) try: return names[index] except IndexError: return '' length = property(lambda self: len(list(self.__nnames())), doc="(DOM) The number of distinct properties that have " "been explicitly in this declaration block. The " "range of valid indices is 0 to length-1 inclusive. " "These are properties with a different ``name`` " "only. :meth:`item` and :attr:`length` work on the " "same set here.") def _getValidating(self): try: # CSSParser.parseX() sets validating of stylesheet return self.parentRule.parentStyleSheet.validating except AttributeError: # CSSParser.parseStyle() sets validating of declaration if self._validating is not None: return self._validating # default return True def _setValidating(self, validating): self._validating = validating validating = property(_getValidating, _setValidating, doc="If ``True`` this declaration validates " "contained properties. The parent StyleSheet " "validation setting does *always* win though so " "even if validating is True it may not validate " "if the StyleSheet defines else!") def _getValid(self): """Check each contained property for validity.""" return all(prop.valid for prop in self.getProperties()) valid = property(_getValid, doc='``True`` if each property is valid.') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssstylerule.py0000644000175000017500000002260600000000000021460 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser from . import cssrule from .selectorlist import SelectorList from .cssstyledeclaration import CSSStyleDeclaration """CSSStyleRule implements DOM Level 2 CSS CSSStyleRule.""" __all__ = ['CSSStyleRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class CSSStyleRule(cssrule.CSSRule): """The CSSStyleRule object represents a ruleset specified (if any) in a CSS style sheet. It provides access to a declaration block as well as to the associated group of selectors. Format:: : selector [ COMMA S* selector ]* LBRACE S* declaration [ ';' S* declaration ]* '}' S* ; """ def __init__(self, selectorText=None, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ :Parameters: selectorText string parsed into selectorList style string parsed into CSSStyleDeclaration for this CSSStyleRule readonly if True allows setting of properties in constructor only """ super(CSSStyleRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self.selectorList = SelectorList() if selectorText: self.selectorText = selectorText if style: self.style = style else: self.style = CSSStyleDeclaration() self._readonly = readonly def __repr__(self): if self._namespaces: st = (self.selectorText, self._namespaces) else: st = self.selectorText return "css_parser.css.%s(selectorText=%r, style=%r)" % ( self.__class__.__name__, st, self.style.cssText) def __str__(self): return "" % (self.__class__.__name__, self.selectorText, self.style.cssText, self._namespaces, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSStyleRule(self) def _setCssText(self, cssText): """ :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) :exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSStyleRule, self)._setCssText(cssText) # might be (cssText, namespaces) cssText, namespaces = self._splitNamespacesOff(cssText) try: # use parent style sheet ones if available namespaces = self.parentStyleSheet.namespaces except AttributeError: pass tokenizer = self._tokenize2(cssText) selectortokens = self._tokensupto2(tokenizer, blockstartonly=True) styletokens = self._tokensupto2(tokenizer, blockendonly=True) trail = self._nexttoken(tokenizer) if trail: self._log.error('CSSStyleRule: Trailing content: %s' % self._valuestr(cssText), token=trail) elif not selectortokens: self._log.error('CSSStyleRule: No selector found: %r' % self._valuestr(cssText)) elif self._tokenvalue(selectortokens[0]).startswith('@'): self._log.error('CSSStyleRule: No style rule: %r' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: newSelectorList = SelectorList(parentRule=self) newStyle = CSSStyleDeclaration(parentRule=self) ok = True bracetoken = selectortokens.pop() if self._tokenvalue(bracetoken) != '{': ok = False self._log.error( 'CSSStyleRule: No start { of style declaration found: %r' % self._valuestr(cssText), bracetoken) elif not selectortokens: ok = False self._log.error('CSSStyleRule: No selector found: %r.' % self._valuestr(cssText), bracetoken) # SET newSelectorList.selectorText = (selectortokens, namespaces) if not styletokens: ok = False self._log.error( 'CSSStyleRule: No style declaration or "}" found: %r' % self._valuestr(cssText)) else: braceorEOFtoken = styletokens.pop() val, typ = self._tokenvalue(braceorEOFtoken),\ self._type(braceorEOFtoken) if val != '}' and typ != 'EOF': ok = False self._log.error('CSSStyleRule: No "}" after style ' 'declaration found: %r' % self._valuestr(cssText)) else: if 'EOF' == typ: # add again as style needs it styletokens.append(braceorEOFtoken) # SET, may raise: newStyle.cssText = styletokens if ok: self.selectorList = newSelectorList self.style = newStyle cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this " "rule.") def __getNamespaces(self): """Uses children namespaces if not attached to a sheet, else the sheet's ones.""" try: return self.parentStyleSheet.namespaces except AttributeError: return self.selectorList._namespaces _namespaces = property(__getNamespaces, doc="If this Rule is attached to a CSSStyleSheet " "the namespaces of that sheet are mirrored " "here. While the Rule is not attached the " "namespaces of selectorList are used.""") def _setSelectorList(self, selectorList): """ :param selectorList: A SelectorList which replaces the current selectorList object """ self._checkReadonly() selectorList._parentRule = self self._selectorList = selectorList _selectorList = None selectorList = property(lambda self: self._selectorList, _setSelectorList, doc="The SelectorList of this rule.") def _setSelectorText(self, selectorText): """ wrapper for css_parser SelectorList object :param selectorText: of type string, might also be a comma separated list of selectors :exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() sl = SelectorList(selectorText=selectorText, parentRule=self) if sl.wellformed: self._selectorList = sl selectorText = property(lambda self: self._selectorList.selectorText, _setSelectorText, doc="(DOM) The textual representation of the " "selector for the rule set.") def _setStyle(self, style): """ :param style: A string or CSSStyleDeclaration which replaces the current style object. """ self._checkReadonly() if isinstance(style, string_type): self._style = CSSStyleDeclaration(cssText=style, parentRule=self) else: style._parentRule = self self._style = style style = property(lambda self: self._style, _setStyle, doc="(DOM) The declaration-block of this rule set.") type = property(lambda self: self.STYLE_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") wellformed = property(lambda self: self.selectorList.wellformed) def _getValid(self): """Return whether the style declaration is valid.""" return self.style.valid valid = property(_getValid, doc='``True`` when the style declaration is true.') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1603855770.0 css-parser-1.0.7/src/css_parser/css/cssstylesheet.py0000644000175000017500000010255600000000000021624 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser.stylesheets from .cssvariablesdeclaration import CSSVariablesDeclaration from .cssrule import CSSRule from css_parser.util import _Namespaces, _readUrl from css_parser.helper import Deprecated """CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet. Partly also: - http://dev.w3.org/csswg/cssom/#the-cssstylesheet - http://www.w3.org/TR/2006/WD-css3-namespace-20060828/ TODO: - ownerRule and ownerNode """ __all__ = ['CSSStyleSheet'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring def as_list(p): if isinstance(p, list): return p return list(p) class CSSStyleSheet(css_parser.stylesheets.StyleSheet): """CSSStyleSheet represents a CSS style sheet. Format:: stylesheet : [ CHARSET_SYM S* STRING S* ';' ]? [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* [ namespace [S|CDO|CDC]* ]* # according to @namespace WD [ [ ruleset | media | page ] [S|CDO|CDC]* ]* ``cssRules`` All Rules in this style sheet, a :class:`~css_parser.css.CSSRuleList`. """ def __init__(self, href=None, media=None, title='', disabled=None, ownerNode=None, parentStyleSheet=None, readonly=False, ownerRule=None, validating=True): """ For parameters see :class:`~css_parser.stylesheets.StyleSheet` """ super(CSSStyleSheet, self).__init__( 'text/css', href, media, title, disabled, ownerNode, parentStyleSheet, validating=validating) self._ownerRule = ownerRule self.cssRules = css_parser.css.CSSRuleList() self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log) self._variables = CSSVariablesDeclaration() self._readonly = readonly # used only during setting cssText by parse*() self.__encodingOverride = None self._fetcher = None def __iter__(self): "Generator which iterates over cssRules." for rule in self._cssRules: yield rule def __repr__(self): if self.media: mediaText = self.media.mediaText else: mediaText = None return "css_parser.css.%s(href=%r, media=%r, title=%r)" % ( self.__class__.__name__, self.href, mediaText, self.title) def __str__(self): if self.media: mediaText = self.media.mediaText else: mediaText = None return "" % ( self.__class__.__name__, self.encoding, self.href, mediaText, self.title, self.namespaces.namespaces, id(self)) def _cleanNamespaces(self): "Remove all namespace rules with same namespaceURI but last." rules = self.cssRules namespaceitems = as_list(self.namespaces.items()) i = 0 while i < len(rules): rule = rules[i] if rule.type == rule.NAMESPACE_RULE and \ (rule.prefix, rule.namespaceURI) not in namespaceitems: self.deleteRule(i) else: i += 1 def _getUsedURIs(self): "Return set of URIs used in the sheet." useduris = set() for r1 in self: if r1.STYLE_RULE == r1.type: useduris.update(r1.selectorList._getUsedUris()) elif r1.MEDIA_RULE == r1.type: for r2 in r1: if r2.type == r2.STYLE_RULE: useduris.update(r2.selectorList._getUsedUris()) return useduris def _setCssRules(self, cssRules): "Set new cssRules and update contained rules refs." cssRules.append = self.insertRule cssRules.extend = self.insertRule cssRules.__delitem__ = self.deleteRule for rule in cssRules: rule._parentStyleSheet = self self._cssRules = cssRules cssRules = property(lambda self: self._cssRules, _setCssRules, "All Rules in this style sheet, a " ":class:`~css_parser.css.CSSRuleList`.") def _getCssText(self): "Textual representation of the stylesheet (a byte string)." return css_parser.ser.do_CSSStyleSheet(self) def _setCssText(self, cssText): """Parse `cssText` and overwrites the whole stylesheet. :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) :exceptions: - :exc:`~xml.dom.NamespaceErr`: If a namespace prefix is found which is not declared. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. """ self._checkReadonly() cssText, namespaces = self._splitNamespacesOff(cssText) tokenizer = self._tokenize2(cssText) def S(expected, seq, token, tokenizer=None): # @charset must be at absolute beginning of style sheet # or 0 for py3 return max(1, expected or 0) def COMMENT(expected, seq, token, tokenizer=None): "special: sets parent*" self.insertRule(css_parser.css.CSSComment([token], parentStyleSheet=self)) # or 0 for py3 return max(1, expected or 0) def charsetrule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSCharsetRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if (expected or 0) > 0: self._log.error('CSSStylesheet: CSSCharsetRule only allowed ' 'at beginning of stylesheet.', token, xml.dom.HierarchyRequestErr) return expected elif rule.wellformed: self.insertRule(rule) return 1 def importrule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSImportRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if (expected or 0) > 1: self._log.error('CSSStylesheet: CSSImportRule not allowed ' 'here.', token, xml.dom.HierarchyRequestErr) return expected elif rule.wellformed: self.insertRule(rule) return 1 def namespacerule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSNamespaceRule( cssText=self._tokensupto2(tokenizer, token), parentStyleSheet=self) if (expected or 0) > 2: self._log.error('CSSStylesheet: CSSNamespaceRule not allowed ' 'here.', token, xml.dom.HierarchyRequestErr) return expected elif rule.wellformed: if rule.prefix not in self.namespaces: # add new if not same prefix self.insertRule(rule, _clean=False) else: # same prefix => replace namespaceURI for r in self.cssRules.rulesOfType(rule.NAMESPACE_RULE): if r.prefix == rule.prefix: r._replaceNamespaceURI(rule.namespaceURI) self._namespaces[rule.prefix] = rule.namespaceURI return 2 def variablesrule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSVariablesRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if (expected or 0) > 2: self._log.error('CSSStylesheet: CSSVariablesRule not allowed ' 'here.', token, xml.dom.HierarchyRequestErr) return expected elif rule.wellformed: self.insertRule(rule) self._updateVariables() return 2 def fontfacerule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSFontFaceRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) return 3 def mediarule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSMediaRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) return 3 def pagerule(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSPageRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) return 3 def unknownrule(expected, seq, token, tokenizer): # parse and consume tokens in any case if token[1] in css_parser.css.MarginRule.margins: self._log.error('CSSStylesheet: MarginRule out CSSPageRule.', token, neverraise=True) rule = css_parser.css.MarginRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) else: self._log.warn('CSSStylesheet: Unknown @rule found.', token, neverraise=True) rule = css_parser.css.CSSUnknownRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) # or 0 for py3 return max(1, expected or 0) def ruleset(expected, seq, token, tokenizer): # parse and consume tokens in any case rule = css_parser.css.CSSStyleRule(parentStyleSheet=self) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) return 3 # save for possible reset oldCssRules = self.cssRules oldNamespaces = self._namespaces self.cssRules = css_parser.css.CSSRuleList() # simple during parse self._namespaces = namespaces self._variables = CSSVariablesDeclaration() # not used?! newseq = [] # ['CHARSET', 'IMPORT', ('VAR', NAMESPACE'), ('PAGE', 'MEDIA', ruleset)] wellformed, expected = self._parse(0, newseq, tokenizer, {'S': S, 'COMMENT': COMMENT, 'CDO': lambda *ignored: None, 'CDC': lambda *ignored: None, 'CHARSET_SYM': charsetrule, 'FONT_FACE_SYM': fontfacerule, 'IMPORT_SYM': importrule, 'NAMESPACE_SYM': namespacerule, 'PAGE_SYM': pagerule, 'MEDIA_SYM': mediarule, 'VARIABLES_SYM': variablesrule, 'ATKEYWORD': unknownrule }, default=ruleset) if wellformed: # use proper namespace object self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log) self._cleanNamespaces() else: # reset self._cssRules = oldCssRules self._namespaces = oldNamespaces self._updateVariables() self._cleanNamespaces() cssText = property(_getCssText, _setCssText, "Textual representation of the stylesheet (a byte string)") def _resolveImport(self, url): """Read (encoding, enctype, decodedContent) from `url` for @import sheets.""" try: # only available during parsing of a complete sheet parentEncoding = self.__newEncoding except AttributeError: try: # explicit @charset parentEncoding = self._cssRules[0].encoding except (IndexError, AttributeError): # default not UTF-8 but None! parentEncoding = None return _readUrl(url, fetcher=self._fetcher, overrideEncoding=self.__encodingOverride, parentEncoding=parentEncoding) def _setCssTextWithEncodingOverride(self, cssText, encodingOverride=None, encoding=None): """Set `cssText` but use `encodingOverride` to overwrite detected encoding. This is used by parse and @import during setting of cssText. If `encoding` is given use this but do not save as `encodingOverride`. """ if encodingOverride: # encoding during resolving of @import self.__encodingOverride = encodingOverride if encoding: # save for nested @import self.__newEncoding = encoding self.cssText = cssText if encodingOverride: # set encodingOverride explicit again! self.encoding = self.__encodingOverride # del? self.__encodingOverride = None elif encoding: # may e.g. be httpEncoding self.encoding = encoding try: del self.__newEncoding except AttributeError: pass def _setFetcher(self, fetcher=None): """Set @import URL loader, if None the default is used.""" self._fetcher = fetcher def _getEncoding(self): """Encoding set in :class:`~css_parser.css.CSSCharsetRule` or if ``None`` resulting in default ``utf-8`` encoding being used.""" try: return self._cssRules[0].encoding except (IndexError, AttributeError): return 'utf-8' def _setEncoding(self, encoding): """Set `encoding` of charset rule if present in sheet or insert a new :class:`~css_parser.css.CSSCharsetRule` with given `encoding`. If `encoding` is None removes charsetrule if present resulting in default encoding of utf-8. """ try: rule = self._cssRules[0] except IndexError: rule = None if rule and rule.CHARSET_RULE == rule.type: if encoding: rule.encoding = encoding else: self.deleteRule(0) elif encoding: self.insertRule(css_parser.css.CSSCharsetRule(encoding=encoding), 0) encoding = property(_getEncoding, _setEncoding, "(css_parser) Reflect encoding of an @charset rule or 'utf-8' " "(default) if set to ``None``") namespaces = property(lambda self: self._namespaces, doc="All Namespaces used in this CSSStyleSheet.") def _updateVariables(self): """Updates self._variables, called when @import or @variables rules is added to sheet. """ for r in self.cssRules.rulesOfType(CSSRule.IMPORT_RULE): s = r.styleSheet if s: for var in s.variables: self._variables.setVariable(var, s.variables[var]) # for r in self.cssRules.rulesOfType(CSSRule.IMPORT_RULE): # for vr in r.styleSheet.cssRules.rulesOfType(CSSRule.VARIABLES_RULE): # for var in vr.variables: # self._variables.setVariable(var, vr.variables[var]) for vr in self.cssRules.rulesOfType(CSSRule.VARIABLES_RULE): for var in vr.variables: self._variables.setVariable(var, vr.variables[var]) variables = property(lambda self: self._variables, doc="A :class:`css_parser.css.CSSVariablesDeclaration` " "containing all available variables in this " "CSSStyleSheet including the ones defined in " "imported sheets.") def add(self, rule): """Add `rule` to style sheet at appropriate position. Same as ``insertRule(rule, inOrder=True)``. """ return self.insertRule(rule, index=None, inOrder=True) def deleteRule(self, index): """Delete rule at `index` from the style sheet. :param index: The `index` of the rule to be removed from the StyleSheet's rule list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is raised but rules for normal Python lists are used. E.g. ``deleteRule(-1)`` removes the last rule in cssRules. `index` may also be a CSSRule object which will then be removed from the StyleSheet. :exceptions: - :exc:`~xml.dom.IndexSizeErr`: Raised if the specified index does not correspond to a rule in the style sheet's rule list. - :exc:`~xml.dom.NamespaceErr`: Raised if removing this rule would result in an invalid StyleSheet - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this style sheet is readonly. """ self._checkReadonly() if isinstance(index, CSSRule): for i, r in enumerate(self.cssRules): if index == r: index = i break else: raise xml.dom.IndexSizeErr("CSSStyleSheet: Not a rule in" " this sheets'a cssRules list: %s" % index) try: rule = self._cssRules[index] except IndexError: raise xml.dom.IndexSizeErr( 'CSSStyleSheet: %s is not a valid index in the rulelist of ' 'length %i' % (index, self._cssRules.length)) else: if rule.type == rule.NAMESPACE_RULE: # check all namespacerules if used uris = [r.namespaceURI for r in self if r.type == r.NAMESPACE_RULE] useduris = self._getUsedURIs() if rule.namespaceURI in useduris and\ uris.count(rule.namespaceURI) == 1: raise xml.dom.NoModificationAllowedErr( 'CSSStyleSheet: NamespaceURI defined in this rule is ' 'used, cannot remove.') return rule._parentStyleSheet = None # detach del self._cssRules[index] # delete from StyleSheet def insertRule(self, rule, index=None, inOrder=False, _clean=True): """ Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade. :param rule: a parsable DOMString, in css_parser also a :class:`~css_parser.css.CSSRule` or :class:`~css_parser.css.CSSRuleList` :param index: of the rule before the new rule will be inserted. If the specified `index` is equal to the length of the StyleSheet's rule collection, the rule will be added to the end of the style sheet. If `index` is not given or ``None`` rule will be appended to rule list. :param inOrder: if ``True`` the rule will be put to a proper location while ignoring `index` and without raising :exc:`~xml.dom.HierarchyRequestErr`. The resulting index is returned nevertheless. :returns: The index within the style sheet's rule collection :Exceptions: - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at the specified `index` e.g. if an @import rule is inserted after a standard rule set or other at-rule. - :exc:`~xml.dom.IndexSizeErr`: Raised if the specified `index` is not a valid insertion point. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this style sheet is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified rule has a syntax error and is unparsable. """ self._checkReadonly() # check position if index is None: index = len(self._cssRules) elif index < 0 or index > self._cssRules.length: raise xml.dom.IndexSizeErr( 'CSSStyleSheet: Invalid index %s for CSSRuleList with a ' 'length of %s.' % (index, self._cssRules.length)) return if isinstance(rule, string_type): # init a temp sheet which has the same properties as self tempsheet = CSSStyleSheet(href=self.href, media=self.media, title=self.title, parentStyleSheet=self.parentStyleSheet, ownerRule=self.ownerRule) tempsheet._ownerNode = self.ownerNode tempsheet._fetcher = self._fetcher # prepend encoding if in this sheet to be able to use it in # @import rules encoding resolution # do not add if new rule startswith "@charset" (which is exact!) if not rule.startswith('@charset') and (self._cssRules and self._cssRules[0].type == self._cssRules[0].CHARSET_RULE): # rule 0 is @charset! newrulescount, newruleindex = 2, 1 rule = self._cssRules[0].cssText + rule else: newrulescount, newruleindex = 1, 0 # parse the new rule(s) tempsheet.cssText = (rule, self._namespaces) if len(tempsheet.cssRules) != newrulescount or (not isinstance( tempsheet.cssRules[newruleindex], css_parser.css.CSSRule)): self._log.error('CSSStyleSheet: Not a CSSRule: %s' % rule) return rule = tempsheet.cssRules[newruleindex] rule._parentStyleSheet = None # done later? # TODO: # tempsheet._namespaces = self._namespaces # variables? elif isinstance(rule, css_parser.css.CSSRuleList): # insert all rules for i, r in enumerate(rule): self.insertRule(r, index + i) return index if not rule.wellformed: self._log.error('CSSStyleSheet: Invalid rules cannot be added.') return # CHECK HIERARCHY # @charset if rule.type == rule.CHARSET_RULE: if inOrder: index = 0 # always first and only if (self._cssRules and self._cssRules[0].type == rule.CHARSET_RULE): self._cssRules[0].encoding = rule.encoding else: self._cssRules.insert(0, rule) elif index != 0 or (self._cssRules and self._cssRules[0].type == rule.CHARSET_RULE): self._log.error( 'CSSStylesheet: @charset only allowed once at the' ' beginning of a stylesheet.', error=xml.dom.HierarchyRequestErr) return else: self._cssRules.insert(index, rule) # @unknown or comment elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder: if index == 0 and self._cssRules and\ self._cssRules[0].type == rule.CHARSET_RULE: self._log.error( 'CSSStylesheet: @charset must be the first rule.', error=xml.dom.HierarchyRequestErr) return else: self._cssRules.insert(index, rule) # @import elif rule.type == rule.IMPORT_RULE: if inOrder: # automatic order if rule.type in (r.type for r in self): # find last of this type for i, r in enumerate(reversed(self._cssRules)): if r.type == rule.type: index = len(self._cssRules) - i break else: # find first point to insert if self._cssRules and\ self._cssRules[0].type in (rule.CHARSET_RULE, rule.COMMENT): index = 1 else: index = 0 else: # after @charset if index == 0 and self._cssRules and\ self._cssRules[0].type == rule.CHARSET_RULE: self._log.error( 'CSSStylesheet: Found @charset at index 0.', error=xml.dom.HierarchyRequestErr) return # before @namespace @variables @page @font-face @media stylerule for r in self._cssRules[:index]: if r.type in (r.NAMESPACE_RULE, r.VARIABLES_RULE, r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, r.FONT_FACE_RULE): self._log.error( 'CSSStylesheet: Cannot insert @import here,' ' found @namespace, @variables, @media, @page or' ' CSSStyleRule before index %s.' % index, error=xml.dom.HierarchyRequestErr) return self._cssRules.insert(index, rule) self._updateVariables() # @namespace elif rule.type == rule.NAMESPACE_RULE: if inOrder: if rule.type in (r.type for r in self): # find last of this type for i, r in enumerate(reversed(self._cssRules)): if r.type == rule.type: index = len(self._cssRules) - i break else: # find first point to insert for i, r in enumerate(self._cssRules): if r.type in (r.VARIABLES_RULE, r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, r.FONT_FACE_RULE, r.UNKNOWN_RULE, r.COMMENT): index = i # before these break else: # after @charset and @import for r in self._cssRules[index:]: if r.type in (r.CHARSET_RULE, r.IMPORT_RULE): self._log.error( 'CSSStylesheet: Cannot insert @namespace here,' ' found @charset or @import after index %s.' % index, error=xml.dom.HierarchyRequestErr) return # before @variables @media @page @font-face and stylerule for r in self._cssRules[:index]: if r.type in (r.VARIABLES_RULE, r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, r.FONT_FACE_RULE): self._log.error( 'CSSStylesheet: Cannot insert @namespace here,' ' found @variables, @media, @page or CSSStyleRule' ' before index %s.' % index, error=xml.dom.HierarchyRequestErr) return if not (rule.prefix in self.namespaces and self.namespaces[rule.prefix] == rule.namespaceURI): # no doublettes self._cssRules.insert(index, rule) if _clean: self._cleanNamespaces() # @variables elif rule.type == rule.VARIABLES_RULE: if inOrder: if rule.type in (r.type for r in self): # find last of this type for i, r in enumerate(reversed(self._cssRules)): if r.type == rule.type: index = len(self._cssRules) - i break else: # find first point to insert for i, r in enumerate(self._cssRules): if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, r.FONT_FACE_RULE, r.UNKNOWN_RULE, r.COMMENT): index = i # before these break else: # after @charset @import @namespace for r in self._cssRules[index:]: if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE): self._log.error( 'CSSStylesheet: Cannot insert @variables here,' ' found @charset, @import or @namespace after' ' index %s.' % index, error=xml.dom.HierarchyRequestErr) return # before @media @page @font-face and stylerule for r in self._cssRules[:index]: if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, r.FONT_FACE_RULE): self._log.error( 'CSSStylesheet: Cannot insert @variables here,' ' found @media, @page or CSSStyleRule' ' before index %s.' % index, error=xml.dom.HierarchyRequestErr) return self._cssRules.insert(index, rule) self._updateVariables() # all other where order is not important else: if inOrder: # simply add to end as no specific order self._cssRules.append(rule) index = len(self._cssRules) - 1 else: for r in self._cssRules[index:]: if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE): self._log.error( 'CSSStylesheet: Cannot insert rule here, found ' '@charset, @import or @namespace before index %s.' % index, error=xml.dom.HierarchyRequestErr) return self._cssRules.insert(index, rule) # post settings rule._parentStyleSheet = self if rule.IMPORT_RULE == rule.type and not rule.hrefFound: # try loading the imported sheet which has new relative href now rule.href = rule.href return index ownerRule = property(lambda self: self._ownerRule, doc='A ref to an @import rule if it is imported, ' 'else ``None``.') def _getValid(self): """Check if each contained rule is valid.""" for rule in self.cssRules: # Not all rules can be checked for validity if hasattr(rule, 'valid') and not rule.valid: return False return True valid = property(_getValid, doc='``True`` if all contained rules are valid') @Deprecated('Use ``css_parser.setSerializer(serializer)`` instead.') def setSerializer(self, cssserializer): """Set the css_parser global Serializer used for all output.""" if isinstance(cssserializer, css_parser.CSSSerializer): css_parser.ser = cssserializer else: raise ValueError('Serializer must be an instance of ' 'css_parser.CSSSerializer.') @Deprecated('Set pref in ``css_parser.ser.prefs`` instead.') def setSerializerPref(self, pref, value): """Set a Preference of CSSSerializer used for output. See :class:`css_parser.serialize.Preferences` for possible preferences to be set. """ css_parser.ser.prefs.__setattr__(pref, value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssunknownrule.py0000644000175000017500000002104200000000000022010 0ustar00kovidkovid"""CSSUnknownRule implements DOM Level 2 CSS CSSUnknownRule.""" from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSUnknownRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from . import cssrule import css_parser import xml.dom class CSSUnknownRule(cssrule.CSSRule): """ Represents an at-rule not supported by this user agent, so in effect all other at-rules not defined in css_parser. Format:: @xxx until ';' or block {...} """ def __init__(self, cssText='', parentRule=None, parentStyleSheet=None, readonly=False): """ :param cssText: of type string """ super(CSSUnknownRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = None if cssText: self.cssText = cssText self._readonly = readonly def __repr__(self): return "css_parser.css.%s(cssText=%r)" % ( self.__class__.__name__, self.cssText) def __str__(self): return "" % ( self.__class__.__name__, self.cssText, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSUnknownRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSUnknownRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if not attoken or self._type(attoken) != self._prods.ATKEYWORD: self._log.error('CSSUnknownRule: No CSSUnknownRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: # for closures: must be a mutable new = {'nesting': [], # {} [] or () 'wellformed': True } def CHAR(expected, seq, token, tokenizer=None): type_, val, line, col = token if expected != 'EOF': if val in '{[(': new['nesting'].append(val) elif val in '}])': opening = {'}': '{', ']': '[', ')': '('}[val] try: if new['nesting'][-1] == opening: new['nesting'].pop() else: raise IndexError() except IndexError: new['wellformed'] = False self._log.error('CSSUnknownRule: Wrong nesting of ' '{, [ or (.', token=token) if val in '};' and not new['nesting']: expected = 'EOF' seq.append(val, type_, line=line, col=col) return expected else: new['wellformed'] = False self._log.error('CSSUnknownRule: Expected end of rule.', token=token) return expected def FUNCTION(expected, seq, token, tokenizer=None): # handled as opening ( type_, val, line, col = token val = self._tokenvalue(token) if expected != 'EOF': new['nesting'].append('(') seq.append(val, type_, line=line, col=col) return expected else: new['wellformed'] = False self._log.error('CSSUnknownRule: Expected end of rule.', token=token) return expected def EOF(expected, seq, token, tokenizer=None): "close all blocks and return 'EOF'" for x in reversed(new['nesting']): closing = {'{': '}', '[': ']', '(': ')'}[x] seq.append(closing, closing) new['nesting'] = [] return 'EOF' def INVALID(expected, seq, token, tokenizer=None): # makes rule invalid self._log.error('CSSUnknownRule: Bad syntax.', token=token, error=xml.dom.SyntaxErr) new['wellformed'] = False return expected def STRING(expected, seq, token, tokenizer=None): type_, val, line, col = token val = self._stringtokenvalue(token) if expected != 'EOF': seq.append(val, type_, line=line, col=col) return expected else: new['wellformed'] = False self._log.error('CSSUnknownRule: Expected end of rule.', token=token) return expected def URI(expected, seq, token, tokenizer=None): type_, val, line, col = token val = self._uritokenvalue(token) if expected != 'EOF': seq.append(val, type_, line=line, col=col) return expected else: new['wellformed'] = False self._log.error('CSSUnknownRule: Expected end of rule.', token=token) return expected def default(expected, seq, token, tokenizer=None): type_, val, line, col = token if expected != 'EOF': seq.append(val, type_, line=line, col=col) return expected else: new['wellformed'] = False self._log.error('CSSUnknownRule: Expected end of rule.', token=token) return expected # unknown : ATKEYWORD S* ... ; | } newseq = self._tempSeq() wellformed, expected = self._parse(expected=None, seq=newseq, tokenizer=tokenizer, productions={'CHAR': CHAR, 'EOF': EOF, 'FUNCTION': FUNCTION, 'INVALID': INVALID, 'STRING': STRING, 'URI': URI, 'S': default # overwrite default default! }, default=default, new=new) # wellformed set by parse wellformed = wellformed and new['wellformed'] # post conditions if expected != 'EOF': wellformed = False self._log.error('CSSUnknownRule: No ending ";" or "}" found: ' '%r' % self._valuestr(cssText)) elif new['nesting']: wellformed = False self._log.error('CSSUnknownRule: Unclosed "{", "[" or "(": %r' % self._valuestr(cssText)) # set all if wellformed: self.atkeyword = self._tokenvalue(attoken) self._setSeq(newseq) cssText = property(fget=_getCssText, fset=_setCssText, doc="(DOM) The parsable textual representation.") type = property(lambda self: self.UNKNOWN_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") wellformed = property(lambda self: bool(self.atkeyword)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssvalue.py0000644000175000017500000014667300000000000020557 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import re import math import css_parser.helper import css_parser from css_parser.prodparser import Choice, Sequence, PreDef, Prod, ProdParser """CSSValue related classes - CSSValue implements DOM Level 2 CSS CSSValue - CSSPrimitiveValue implements DOM Level 2 CSS CSSPrimitiveValue - CSSValueList implements DOM Level 2 CSS CSSValueList """ __all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor', 'CSSVariable'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: text_type = str string_type = str else: text_type = unicode string_type = basestring class CSSValue(css_parser.util._NewBase): """The CSSValue interface represents a simple or a complex value. A CSSValue object only occurs in a context of a CSS property. """ # The value is inherited and the cssText contains "inherit". CSS_INHERIT = 0 # The value is a CSSPrimitiveValue. CSS_PRIMITIVE_VALUE = 1 # The value is a CSSValueList. CSS_VALUE_LIST = 2 # The value is a custom value. CSS_CUSTOM = 3 # The value is a CSSVariable. CSS_VARIABLE = 4 _typestrings = {0: 'CSS_INHERIT', 1: 'CSS_PRIMITIVE_VALUE', 2: 'CSS_VALUE_LIST', 3: 'CSS_CUSTOM', 4: 'CSS_VARIABLE'} def __init__(self, cssText=None, parent=None, readonly=False): """ :param cssText: the parsable cssText of the value :param readonly: defaults to False """ super(CSSValue, self).__init__() self._cssValueType = None self.wellformed = False self.parent = parent if cssText is not None: # may be 0 if isinstance(cssText, int): cssText = text_type(cssText) # if it is an integer elif isinstance(cssText, float): cssText = '%f' % cssText # if it is a floating point number self.cssText = cssText self._readonly = readonly def __repr__(self): return "css_parser.css.%s(%r)" % ( self.__class__.__name__, self.cssText) def __str__(self): return "" % (self.__class__.__name__, self.cssValueTypeString, self.cssText, id(self)) def _setCssText(self, cssText): """ Format:: unary_operator : '-' | '+' ; operator : '/' S* | ',' S* | /* empty */ ; expr : term [ operator term ]* ; term : unary_operator? [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* ] | STRING S* | IDENT S* | URI S* | hexcolor | function | UNICODE-RANGE S* ; function : FUNCTION S* expr ')' S* ; /* * There is a constraint on the color that it must * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) * after the "#"; e.g., "#000" is OK, but "#abcd" is not. */ hexcolor : HASH S* ; :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error (according to the attached property) or is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: TODO: Raised if the specified CSS string value represents a different type of values than the values allowed by the CSS property. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this value is readonly. """ self._checkReadonly() # used as operator is , / or S nextSor = ',/' term = Choice(Sequence(PreDef.unary(), Choice(PreDef.number(nextSor=nextSor), PreDef.percentage(nextSor=nextSor), PreDef.dimension(nextSor=nextSor))), PreDef.string(nextSor=nextSor), PreDef.ident(nextSor=nextSor), PreDef.uri(nextSor=nextSor), PreDef.hexcolor(nextSor=nextSor), PreDef.unicode_range(nextSor=nextSor), # special case IE only expression Prod(name='expression', match=lambda t, v: t == self._prods.FUNCTION and ( css_parser.helper.normalize(v) in ( 'expression(', 'alpha(', 'blur(', 'chroma(', 'dropshadow(', 'fliph(', 'flipv(', 'glow(', 'gray(', 'invert(', 'mask(', 'shadow(', 'wave(', 'xray(') or v.startswith('progid:DXImageTransform.Microsoft.') ), nextSor=nextSor, toSeq=lambda t, tokens: (ExpressionValue._functionName, ExpressionValue( css_parser.helper.pushtoken(t, tokens), parent=self) ) ), # CSS Variable var( PreDef.variable(nextSor=nextSor, toSeq=lambda t, tokens: ('CSSVariable', CSSVariable( css_parser.helper.pushtoken(t, tokens), parent=self) ) ), # calc( PreDef.calc(nextSor=nextSor, toSeq=lambda t, tokens: (CalcValue._functionName, CalcValue( css_parser.helper.pushtoken(t, tokens), parent=self) ) ), # TODO: # # rgb/rgba( # Prod(name='RGBColor', # match=lambda t, v: t == self._prods.FUNCTION and ( # css_parser.helper.normalize(v) in (u'rgb(', # u'rgba(' # ) # ), # nextSor=nextSor, # toSeq=lambda t, tokens: (RGBColor._functionName, # RGBColor( # css_parser.helper.pushtoken(t, tokens), # parent=self) # ) # ), # other functions like rgb( etc PreDef.function(nextSor=nextSor, toSeq=lambda t, tokens: ('FUNCTION', CSSFunction( css_parser.helper.pushtoken(t, tokens), parent=self) ) ) ) operator = Choice(PreDef.S(), PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])), PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])), optional=True) # CSSValue PRODUCTIONS valueprods = Sequence(term, Sequence(operator, # mayEnd this Sequence if whitespace # TODO: only when setting via other class # used by variabledeclaration currently PreDef.char('END', ';', stopAndKeep=True, optional=True), term, minmax=lambda: (0, None))) # parse wellformed, seq, store, notused = ProdParser().parse(cssText, 'CSSValue', valueprods, keepS=True) if wellformed: # - count actual values and set firstvalue which is used later on # - combine comma separated list, e.g. font-family to a single item # - remove S which should be an operator but is no needed count, firstvalue = 0, () newseq = self._tempSeq() i, end = 0, len(seq) while i < end: item = seq[i] if item.type == self._prods.S: pass elif (item.value, item.type) == (',', 'operator'): # , separared counts as a single STRING for now # URI or STRING value might be a single CHAR too! newseq.appendItem(item) count -= 1 if firstvalue: # list of IDENTs is handled as STRING! if firstvalue[1] == self._prods.IDENT: firstvalue = firstvalue[0], 'STRING' elif item.value == '/': # / separated items count as one newseq.appendItem(item) elif item.value == '-' or item.value == '+': # combine +- and following number or other i += 1 try: next = seq[i] except IndexError: firstvalue = () # raised later break newval = item.value + next.value newseq.append(newval, next.type, item.line, item.col) if not firstvalue: firstvalue = (newval, next.type) count += 1 elif item.type != css_parser.css.CSSComment: newseq.appendItem(item) if not firstvalue: firstvalue = (item.value, item.type) count += 1 else: newseq.appendItem(item) i += 1 if not firstvalue: self._log.error( 'CSSValue: Unknown syntax or no value: %r.' % self._valuestr(cssText)) else: # ok and set self._setSeq(newseq) self.wellformed = wellformed if hasattr(self, '_value'): # only in case of CSSPrimitiveValue, else remove! del self._value if count == 1: # inherit, primitive or variable # Under Pythn 2.x this was basestring but ... if isinstance(firstvalue[0], string_type) and\ 'inherit' == css_parser.helper.normalize(firstvalue[0]): self.__class__ = CSSValue self._cssValueType = CSSValue.CSS_INHERIT elif 'CSSVariable' == firstvalue[1]: self.__class__ = CSSVariable self._value = firstvalue # TODO: remove major hack! self._name = firstvalue[0]._name else: self.__class__ = CSSPrimitiveValue self._value = firstvalue elif count > 1: # valuelist self.__class__ = CSSValueList # change items in list to specific type (primitive etc) newseq = self._tempSeq() commalist = [] nexttocommalist = False def itemValue(item): "Reserialized simple item.value" if self._prods.STRING == item.type: return css_parser.helper.string(item.value) elif self._prods.URI == item.type: return css_parser.helper.uri(item.value) elif self._prods.FUNCTION == item.type or\ 'CSSVariable' == item.type: return item.value.cssText else: return item.value def saveifcommalist(commalist, newseq): """ saves items in commalist to seq and items if anything in there """ if commalist: newseq.replace(-1, CSSPrimitiveValue(cssText=''.join( commalist)), CSSPrimitiveValue, newseq[-1].line, newseq[-1].col) del commalist[:] for i, item in enumerate(self._seq): if issubclass(type(item.value), CSSValue): # set parent of CSSValueList items to the lists # parent item.value.parent = self.parent if item.type in (self._prods.DIMENSION, self._prods.FUNCTION, self._prods.HASH, self._prods.IDENT, self._prods.NUMBER, self._prods.PERCENTAGE, self._prods.STRING, self._prods.URI, self._prods.UNICODE_RANGE, 'CSSVariable'): if nexttocommalist: # wait until complete commalist.append(itemValue(item)) else: saveifcommalist(commalist, newseq) # append new item if hasattr(item.value, 'cssText'): newseq.append(item.value, item.value.__class__, item.line, item.col) else: newseq.append(CSSPrimitiveValue( itemValue(item)), CSSPrimitiveValue, item.line, item.col) nexttocommalist = False elif ',' == item.value: if not commalist: # save last item to commalist commalist.append(itemValue(self._seq[i - 1])) commalist.append(',') nexttocommalist = True else: if nexttocommalist: commalist.append(item.value.cssText) else: newseq.appendItem(item) saveifcommalist(commalist, newseq) self._setSeq(newseq) else: # should not happen... self.__class__ = CSSValue self._cssValueType = CSSValue.CSS_CUSTOM cssText = property(lambda self: css_parser.ser.do_css_CSSValue(self), _setCssText, doc="A string representation of the current value.") cssValueType = property(lambda self: self._cssValueType, doc="A (readonly) code defining the type of the value.") cssValueTypeString = property( lambda self: CSSValue._typestrings.get(self.cssValueType, None), doc="(readonly) Name of cssValueType.") class CSSPrimitiveValue(CSSValue): """Represents a single CSS Value. May be used to determine the value of a specific style property currently set in a block or to set a specific style property explicitly within the block. Might be obtained from the getPropertyCSSValue method of CSSStyleDeclaration. Conversions are allowed between absolute values (from millimeters to centimeters, from degrees to radians, and so on) but not between relative values. (For example, a pixel value cannot be converted to a centimeter value.) Percentage values can't be converted since they are relative to the parent value (or another property value). There is one exception for color percentage values: since a color percentage value is relative to the range 0-255, a color percentage value can be converted to a number; (see also the RGBColor interface). """ # constant: type of this CSSValue class cssValueType = CSSValue.CSS_PRIMITIVE_VALUE __types = css_parser.cssproductions.CSSProductions # An integer indicating which type of unit applies to the value. CSS_UNKNOWN = 0 # only obtainable via cssText CSS_NUMBER = 1 CSS_PERCENTAGE = 2 CSS_EMS = 3 CSS_EXS = 4 CSS_PX = 5 CSS_CM = 6 CSS_MM = 7 CSS_IN = 8 CSS_PT = 9 CSS_PC = 10 CSS_DEG = 11 CSS_RAD = 12 CSS_GRAD = 13 CSS_MS = 14 CSS_S = 15 CSS_HZ = 16 CSS_KHZ = 17 CSS_DIMENSION = 18 CSS_STRING = 19 CSS_URI = 20 CSS_IDENT = 21 CSS_ATTR = 22 CSS_COUNTER = 23 CSS_RECT = 24 CSS_RGBCOLOR = 25 # NOT OFFICIAL: CSS_RGBACOLOR = 26 CSS_UNICODE_RANGE = 27 _floattypes = (CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION) _stringtypes = (CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI) _countertypes = (CSS_COUNTER,) _recttypes = (CSS_RECT,) _rbgtypes = (CSS_RGBCOLOR, CSS_RGBACOLOR) _lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC) # oldtype: newType: converterfunc _converter = { # cm <-> mm <-> in, 1 inch is equal to 2.54 centimeters. # pt <-> pc, the points used by CSS 2.1 are equal to 1/72nd of an inch. # pc: picas - 1 pica is equal to 12 points (CSS_CM, CSS_MM): lambda x: x * 10, (CSS_MM, CSS_CM): lambda x: x / 10, (CSS_PT, CSS_PC): lambda x: x * 12, (CSS_PC, CSS_PT): lambda x: x / 12, (CSS_CM, CSS_IN): lambda x: x / 2.54, (CSS_IN, CSS_CM): lambda x: x * 2.54, (CSS_MM, CSS_IN): lambda x: x / 25.4, (CSS_IN, CSS_MM): lambda x: x * 25.4, (CSS_IN, CSS_PT): lambda x: x / 72, (CSS_PT, CSS_IN): lambda x: x * 72, (CSS_CM, CSS_PT): lambda x: x / 2.54 / 72, (CSS_PT, CSS_CM): lambda x: x * 72 * 2.54, (CSS_MM, CSS_PT): lambda x: x / 25.4 / 72, (CSS_PT, CSS_MM): lambda x: x * 72 * 25.4, (CSS_IN, CSS_PC): lambda x: x / 72 / 12, (CSS_PC, CSS_IN): lambda x: x * 12 * 72, (CSS_CM, CSS_PC): lambda x: x / 2.54 / 72 / 12, (CSS_PC, CSS_CM): lambda x: x * 12 * 72 * 2.54, (CSS_MM, CSS_PC): lambda x: x / 25.4 / 72 / 12, (CSS_PC, CSS_MM): lambda x: x * 12 * 72 * 25.4, # hz <-> khz (CSS_KHZ, CSS_HZ): lambda x: x * 1000, (CSS_HZ, CSS_KHZ): lambda x: x / 1000, # s <-> ms (CSS_S, CSS_MS): lambda x: x * 1000, (CSS_MS, CSS_S): lambda x: x / 1000, (CSS_RAD, CSS_DEG): lambda x: math.degrees(x), (CSS_DEG, CSS_RAD): lambda x: math.radians(x), # TODO: convert grad <-> deg or rad # (CSS_RAD, CSS_GRAD): lambda x: math.degrees(x), # (CSS_DEG, CSS_GRAD): lambda x: math.radians(x), # (CSS_GRAD, CSS_RAD): lambda x: math.radians(x), # (CSS_GRAD, CSS_DEG): lambda x: math.radians(x) } def __init__(self, cssText=None, parent=None, readonly=False): """See CSSPrimitiveValue.__init__()""" super(CSSPrimitiveValue, self).__init__(cssText=cssText, parent=parent, readonly=readonly) def __str__(self): return ""\ % (self.__class__.__name__, self.primitiveTypeString, self.cssText, id(self)) _unitnames = ['CSS_UNKNOWN', 'CSS_NUMBER', 'CSS_PERCENTAGE', 'CSS_EMS', 'CSS_EXS', 'CSS_PX', 'CSS_CM', 'CSS_MM', 'CSS_IN', 'CSS_PT', 'CSS_PC', 'CSS_DEG', 'CSS_RAD', 'CSS_GRAD', 'CSS_MS', 'CSS_S', 'CSS_HZ', 'CSS_KHZ', 'CSS_DIMENSION', 'CSS_STRING', 'CSS_URI', 'CSS_IDENT', 'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT', 'CSS_RGBCOLOR', 'CSS_RGBACOLOR', 'CSS_UNICODE_RANGE' ] _reNumDim = re.compile(r'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I | re.U | re.X) def _unitDIMENSION(value): """Check val for dimension name.""" units = {'em': 'CSS_EMS', 'ex': 'CSS_EXS', 'px': 'CSS_PX', 'cm': 'CSS_CM', 'mm': 'CSS_MM', 'in': 'CSS_IN', 'pt': 'CSS_PT', 'pc': 'CSS_PC', 'deg': 'CSS_DEG', 'rad': 'CSS_RAD', 'grad': 'CSS_GRAD', 'ms': 'CSS_MS', 's': 'CSS_S', 'hz': 'CSS_HZ', 'khz': 'CSS_KHZ' } val, dim = CSSPrimitiveValue._reNumDim.findall(css_parser.helper.normalize(value))[0] return units.get(dim, 'CSS_DIMENSION') def _unitFUNCTION(value): """Check val for function name.""" units = {'attr(': 'CSS_ATTR', 'counter(': 'CSS_COUNTER', 'rect(': 'CSS_RECT', 'rgb(': 'CSS_RGBCOLOR', 'rgba(': 'CSS_RGBACOLOR', } return units.get(re.findall(r'^(.*?\()', css_parser.helper.normalize(value.cssText), re.U)[0], 'CSS_UNKNOWN') __unitbytype = { __types.NUMBER: 'CSS_NUMBER', __types.PERCENTAGE: 'CSS_PERCENTAGE', __types.STRING: 'CSS_STRING', __types.UNICODE_RANGE: 'CSS_UNICODE_RANGE', __types.URI: 'CSS_URI', __types.IDENT: 'CSS_IDENT', __types.HASH: 'CSS_RGBCOLOR', __types.DIMENSION: _unitDIMENSION, __types.FUNCTION: _unitFUNCTION } def __set_primitiveType(self): """primitiveType is readonly but is set lazy if accessed""" # TODO: check unary and font-family STRING a, b, "c" val, type_ = self._value # try get by type_ pt = self.__unitbytype.get(type_, 'CSS_UNKNOWN') if callable(pt): # multiple options, check value too pt = pt(val) self._primitiveType = getattr(self, pt) def _getPrimitiveType(self): if not hasattr(self, '_primitivetype'): self.__set_primitiveType() return self._primitiveType primitiveType = property(_getPrimitiveType, doc="(readonly) The type of the value as defined " "by the constants in this class.") def _getPrimitiveTypeString(self): return self._unitnames[self.primitiveType] primitiveTypeString = property(_getPrimitiveTypeString, doc="Name of primitive type of this value.") def _getCSSPrimitiveTypeString(self, type): "get TypeString by given type which may be unknown, used by setters" try: return self._unitnames[type] except (IndexError, TypeError): return '%r (UNKNOWN TYPE)' % type def _getNumDim(self, value=None): "Split self._value in numerical and dimension part." if value is None: value = css_parser.helper.normalize(self._value[0]) try: val, dim = CSSPrimitiveValue._reNumDim.findall(value)[0] except IndexError: val, dim = value, '' try: val = float(val) if val == int(val): val = int(val) except ValueError: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: No float value %r' % self._value[0]) return val, dim def getFloatValue(self, unitType=None): """(DOM) This method is used to get a float value in a specified unit. If this CSS value doesn't contain a float value or can't be converted into the specified unit, a DOMException is raised. :param unitType: to get the float value. The unit code can only be a float unit type (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION) or None in which case the current dimension is used. :returns: not necessarily a float but some cases just an integer e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0`` Conversions might return strange values like 1.000000000001 """ if unitType is not None and unitType not in self._floattypes: raise xml.dom.InvalidAccessErr( 'unitType Parameter is not a float type') val, dim = self._getNumDim() if unitType is not None and self.primitiveType != unitType: # convert if needed try: val = self._converter[self.primitiveType, unitType](val) except KeyError: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r' % (self.primitiveTypeString, self._getCSSPrimitiveTypeString(unitType))) if val == int(val): val = int(val) return val def setFloatValue(self, unitType, floatValue): """(DOM) A method to set the float value with a specified unit. If the property attached with this value can not accept the specified unit or the float value, the value will be unchanged and a DOMException will be raised. :param unitType: a unit code as defined above. The unit code can only be a float unit type :param floatValue: the new float value which does not have to be a float value but may simple be an int e.g. if setting:: setFloatValue(CSS_PX, 1) :exceptions: - :exc:`~xml.dom.InvalidAccessErr`: Raised if the attached property doesn't support the float value or the unit type. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this property is readonly. """ self._checkReadonly() if unitType not in self._floattypes: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: unitType %r is not a float type' % self._getCSSPrimitiveTypeString(unitType)) try: val = float(floatValue) except ValueError: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: floatValue %r is not a float' % floatValue) oldval, dim = self._getNumDim() if self.primitiveType != unitType: # convert if possible try: val = self._converter[unitType, self.primitiveType](val) except KeyError: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r' % (self.primitiveTypeString, self._getCSSPrimitiveTypeString(unitType))) if val == int(val): val = int(val) self.cssText = '%s%s' % (val, dim) def getStringValue(self): """(DOM) This method is used to get the string value. If the CSS value doesn't contain a string value, a DOMException is raised. Some properties (like 'font-family' or 'voice-family') convert a whitespace separated list of idents to a string. Only the actual value is returned so e.g. all the following return the actual value ``a``: url(a), attr(a), "a", 'a' """ if self.primitiveType not in self._stringtypes: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue %r is not a string type' % self.primitiveTypeString) if CSSPrimitiveValue.CSS_ATTR == self.primitiveType: return self._value[0].cssText[5:-1] else: return self._value[0] def setStringValue(self, stringType, stringValue): """(DOM) A method to set the string value with the specified unit. If the property attached to this value can't accept the specified unit or the string value, the value will be unchanged and a DOMException will be raised. :param stringType: a string code as defined above. The string code can only be a string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and CSS_ATTR). :param stringValue: the new string value Only the actual value is expected so for (CSS_URI, "a") the new value will be ``url(a)``. For (CSS_STRING, "'a'") the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are not part of the string value :exceptions: - :exc:`~xml.dom.InvalidAccessErr`: Raised if the CSS value doesn't contain a string value or if the string value can't be converted into the specified unit. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this property is readonly. """ self._checkReadonly() # self not stringType if self.primitiveType not in self._stringtypes: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue %r is not a string type' % self.primitiveTypeString) # given stringType is no StringType if stringType not in self._stringtypes: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: stringType %s is not a string type' % self._getCSSPrimitiveTypeString(stringType)) if self._primitiveType != stringType: raise xml.dom.InvalidAccessErr( 'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r' % (self.primitiveTypeString, self._getCSSPrimitiveTypeString(stringType))) if CSSPrimitiveValue.CSS_STRING == self._primitiveType: self.cssText = css_parser.helper.string(stringValue) elif CSSPrimitiveValue.CSS_URI == self._primitiveType: self.cssText = css_parser.helper.uri(stringValue) elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType: self.cssText = 'attr(%s)' % stringValue else: self.cssText = stringValue self._primitiveType = stringType def getCounterValue(self): """(DOM) This method is used to get the Counter value. If this CSS value doesn't contain a counter value, a DOMException is raised. Modification to the corresponding style property can be achieved using the Counter interface. **Not implemented.** """ if not self.CSS_COUNTER == self.primitiveType: raise xml.dom.InvalidAccessErr('Value is not a counter type') # TODO: use Counter class raise NotImplementedError() def getRGBColorValue(self): """(DOM) This method is used to get the RGB color. If this CSS value doesn't contain a RGB color value, a DOMException is raised. Modification to the corresponding style property can be achieved using the RGBColor interface. """ if self.primitiveType not in self._rbgtypes: raise xml.dom.InvalidAccessErr('Value is not a RGBColor value') return RGBColor(self._value[0]) def getRectValue(self): """(DOM) This method is used to get the Rect value. If this CSS value doesn't contain a rect value, a DOMException is raised. Modification to the corresponding style property can be achieved using the Rect interface. **Not implemented.** """ if self.primitiveType not in self._recttypes: raise xml.dom.InvalidAccessErr('value is not a Rect value') # TODO: use Rect class raise NotImplementedError() def _getCssText(self): """Overwrites CSSValue.""" return css_parser.ser.do_css_CSSPrimitiveValue(self) def _setCssText(self, cssText): """Use CSSValue.""" return super(CSSPrimitiveValue, self)._setCssText(cssText) cssText = property(_getCssText, _setCssText, doc="A string representation of the current value.") class CSSValueList(CSSValue): """The CSSValueList interface provides the abstraction of an ordered collection of CSS values. Some properties allow an empty list into their syntax. In that case, these properties take the none identifier. So, an empty list means that the property has the value none. The items in the CSSValueList are accessible via an integral index, starting from 0. """ cssValueType = CSSValue.CSS_VALUE_LIST def __init__(self, cssText=None, parent=None, readonly=False): """Init a new CSSValueList""" super(CSSValueList, self).__init__(cssText=cssText, parent=parent, readonly=readonly) self._items = [] def __iter__(self): "CSSValueList is iterable." for item in self.__items(): yield item.value def __str__(self): return "" % (self.__class__.__name__, self.cssValueTypeString, self.cssText, self.length, id(self)) def __items(self): return [item for item in self._seq if isinstance(item.value, CSSValue)] def item(self, index): """(DOM) Retrieve a CSSValue by ordinal `index`. The order in this collection represents the order of the values in the CSS style property. If `index` is greater than or equal to the number of values in the list, this returns ``None``. """ try: return self.__items()[index].value except IndexError: return None length = property(lambda self: len(self.__items()), doc="(DOM attribute) The number of CSSValues in the " "list.") class CSSFunction(CSSPrimitiveValue): """A CSS function value like rect() etc.""" _functionName = 'CSSFunction' primitiveType = CSSPrimitiveValue.CSS_UNKNOWN def __init__(self, cssText=None, parent=None, readonly=False): """ Init a new CSSFunction :param cssText: the parsable cssText of the value :param readonly: defaults to False """ super(CSSFunction, self).__init__(parent=parent) self._funcType = None self.valid = False self.wellformed = False if cssText is not None: self.cssText = cssText self._readonly = readonly def _productiondefinition(self): """Return definition used for parsing.""" types = self._prods # rename! value = Sequence(PreDef.unary(), Prod(name='PrimitiveValue', match=lambda t, v: t in (types.DIMENSION, types.HASH, types.IDENT, types.NUMBER, types.PERCENTAGE, types.STRING), toSeq=lambda t, tokens: (t[0], CSSPrimitiveValue(t[1])) ) ) valueOrFunc = Choice(value, # FUNC is actually not in spec but used in e.g. Prince PreDef.function(toSeq=lambda t, tokens: ('FUNCTION', CSSFunction( css_parser.helper.pushtoken(t, tokens)) ) ) ) funcProds = Sequence(Prod(name='FUNC', match=lambda t, v: t == types.FUNCTION, toSeq=lambda t, tokens: (t[0], css_parser.helper.normalize(t[1]))), Choice(Sequence(valueOrFunc, # more values starting with Comma # should use store where colorType is saved to # define min and may, closure? Sequence(PreDef.comma(), valueOrFunc, minmax=lambda: (0, None)), PreDef.funcEnd(stop=True)), PreDef.funcEnd(stop=True)) ) return funcProds def _setCssText(self, cssText): self._checkReadonly() # store: colorType, parts wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, self._functionName, self._productiondefinition(), keepS=True) if wellformed: # combine +/- and following CSSPrimitiveValue, remove S newseq = self._tempSeq() i, end = 0, len(seq) while i < end: item = seq[i] if item.type == self._prods.S: pass elif item.value == '+' or item.value == '-': i += 1 next = seq[i] newval = next.value if isinstance(newval, CSSPrimitiveValue): newval.setFloatValue(newval.primitiveType, float(item.value + str(newval.getFloatValue()))) newseq.append(newval, next.type, item.line, item.col) else: # expressions only? newseq.appendItem(item) newseq.appendItem(next) else: newseq.appendItem(item) i += 1 self.wellformed = True self._setSeq(newseq) self._funcType = newseq[0].value cssText = property(lambda self: css_parser.ser.do_css_FunctionValue(self), _setCssText) funcType = property(lambda self: self._funcType) class RGBColor(CSSFunction): """A CSS color like RGB, RGBA or a simple value like `#000` or `red`.""" _functionName = 'Function rgb()' def __init__(self, cssText=None, parent=None, readonly=False): """ Init a new RGBColor :param cssText: the parsable cssText of the value :param readonly: defaults to False """ super(CSSFunction, self).__init__(parent=parent) self._colorType = None self.valid = False self.wellformed = False if cssText is not None: try: # if it is a Function object cssText = cssText.cssText except AttributeError: pass self.cssText = cssText self._readonly = readonly def __repr__(self): return "css_parser.css.%s(%r)" % ( self.__class__.__name__, self.cssText) def __str__(self): return "" % ( self.__class__.__name__, self.colorType, self.cssText, id(self)) def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! valueProd = Prod(name='value', match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE), toSeq=lambda t, v: (CSSPrimitiveValue, CSSPrimitiveValue(v)), toStore='parts' ) # COLOR PRODUCTION funccolor = Sequence(Prod(name='FUNC', match=lambda t, v: t == types.FUNCTION and css_parser.helper.normalize( v) in ('rgb(', 'rgba(', 'hsl(', 'hsla('), toSeq=lambda t, v: (t, v), # css_parser.helper.normalize(v)), toStore='colorType'), PreDef.unary(), valueProd, # 2 or 3 more values starting with Comma Sequence(PreDef.comma(), PreDef.unary(), valueProd, minmax=lambda: (2, 3)), PreDef.funcEnd() ) colorprods = Choice(funccolor, PreDef.hexcolor('colorType'), Prod(name='named color', match=lambda t, v: t == types.IDENT, toStore='colorType' ) ) # store: colorType, parts wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, 'RGBColor', colorprods, keepS=True, store={'parts': []}) if wellformed: self.wellformed = True if store['colorType'].type == self._prods.HASH: self._colorType = 'HEX' elif store['colorType'].type == self._prods.IDENT: self._colorType = 'Named Color' else: self._colorType = store['colorType'].value[:-1] # self._colorType = css_parser.helper.normalize(store['colorType'].value)[:-1] self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_RGBColor(self), _setCssText) colorType = property(lambda self: self._colorType) class CalcValue(CSSFunction): """Calc Function""" _functionName = 'Function calc()' def _productiondefinition(self): """Return defintion used for parsing.""" types = self._prods # rename! def toSeq(t, tokens): "Do not normalize function name!" return t[0], t[1] funcProds = Sequence(Prod(name='calc', match=lambda t, v: t == types.FUNCTION, toSeq=toSeq ), Sequence(Choice(Prod(name='nested function', match=lambda t, v: t == self._prods.FUNCTION, toSeq=lambda t, tokens: ( CSSFunction._functionName, CSSFunction( css_parser.helper.pushtoken(t, tokens))) ), Prod(name='part', match=lambda t, v: v != ')', toSeq=lambda t, tokens: (t[0], t[1])), ), minmax=lambda: (0, None)), PreDef.funcEnd(stop=True)) return funcProds def _getCssText(self): return css_parser.ser.do_css_CalcValue(self) def _setCssText(self, cssText): return super(CalcValue, self)._setCssText(cssText) cssText = property(_getCssText, _setCssText, doc="A string representation of the current value.") class ExpressionValue(CSSFunction): """Special IE only CSSFunction which may contain *anything*. Used for expressions and ``alpha(opacity=100)`` currently.""" _functionName = 'Expression (IE only)' def _productiondefinition(self): """Return defintion used for parsing.""" types = self._prods # rename! def toSeq(t, tokens): "Do not normalize function name!" return t[0], t[1] funcProds = Sequence(Prod(name='expression', match=lambda t, v: t == types.FUNCTION, toSeq=toSeq ), Sequence(Choice(Prod(name='nested function', match=lambda t, v: t == self._prods.FUNCTION, toSeq=lambda t, tokens: ( ExpressionValue._functionName, ExpressionValue(css_parser.helper.pushtoken(t, tokens))) ), Prod(name='part', match=lambda t, v: v != ')', toSeq=lambda t, tokens: (t[0], t[1])), ), minmax=lambda: (0, None)), PreDef.funcEnd(stop=True)) return funcProds def _getCssText(self): return css_parser.ser.do_css_ExpressionValue(self) def _setCssText(self, cssText): # self._log.warn(u'CSSValue: Unoffial and probably invalid MS value used!') return super(ExpressionValue, self)._setCssText(cssText) cssText = property(_getCssText, _setCssText, doc="A string representation of the current value.") class CSSVariable(CSSValue): """The CSSVariable represents a call to CSS Variable.""" def __init__(self, cssText=None, parent=None, readonly=False): """Init a new CSSVariable. :param cssText: the parsable cssText of the value, e.g. ``var(x)`` :param readonly: defaults to False """ self._name = None super(CSSVariable, self).__init__(cssText=cssText, parent=parent, readonly=readonly) def __repr__(self): return "css_parser.css.%s(%r)" % (self.__class__.__name__, self.cssText) def __str__(self): return "" % ( self.__class__.__name__, self.name, self.value, id(self)) def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! funcProds = Sequence(Prod(name='var', match=lambda t, v: t == types.FUNCTION ), PreDef.ident(toStore='ident'), PreDef.funcEnd(stop=True)) # store: name of variable store = {'ident': None} wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, 'CSSVariable', funcProds, keepS=True) if wellformed: self._name = store['ident'].value self._setSeq(seq) self.wellformed = True cssText = property(lambda self: css_parser.ser.do_css_CSSVariable(self), _setCssText, doc="A string representation of the current variable.") cssValueType = CSSValue.CSS_VARIABLE # TODO: writable? check if var (value) available? name = property(lambda self: self._name) def _getValue(self): "Find contained sheet and @variables there" try: variables = self.parent.parent.parentRule.parentStyleSheet.variables except AttributeError: return None else: try: return variables[self.name] except KeyError: return None value = property(_getValue) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssvariablesdeclaration.py0000644000175000017500000002773000000000000023611 0ustar00kovidkovid"""CSSVariablesDeclaration http://disruptive-innovations.com/zoo/cssvariables/#mozTocId496530 """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSVariablesDeclaration'] __docformat__ = 'restructuredtext' __version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $' from css_parser.prodparser import Prod, Sequence, PreDef, ProdParser from css_parser.helper import normalize from .value import PropertyValue import css_parser import itertools def as_list(p): if isinstance(p, list): return p return list(p) class CSSVariablesDeclaration(css_parser.util._NewBase): """The CSSVariablesDeclaration interface represents a single block of variable declarations. """ def __init__(self, cssText='', parentRule=None, readonly=False): """ :param cssText: Shortcut, sets CSSVariablesDeclaration.cssText :param parentRule: The CSS rule that contains this declaration block or None if this CSSVariablesDeclaration is not attached to a CSSRule. :param readonly: defaults to False Format:: variableset : vardeclaration [ ';' S* vardeclaration ]* S* ; vardeclaration : varname ':' S* term ; varname : IDENT S* ; """ super(CSSVariablesDeclaration, self).__init__() self._parentRule = parentRule self._vars = {} if cssText: self.cssText = cssText self._readonly = readonly def __repr__(self): return "css_parser.css.%s(cssText=%r)" % (self.__class__.__name__, self.cssText) def __str__(self): return "" % ( self.__class__.__name__, self.length, id(self)) def __contains__(self, variableName): """Check if a variable is in variable declaration block. :param variableName: a string """ return normalize(variableName) in as_list(self.keys()) def __getitem__(self, variableName): """Retrieve the value of variable ``variableName`` from this declaration. """ return self.getVariableValue(variableName) def __setitem__(self, variableName, value): self.setVariable(variableName, value) def __delitem__(self, variableName): return self.removeVariable(variableName) def __iter__(self): """Iterator of names of set variables.""" for name in as_list(self.keys()): yield name def keys(self): """Analoguous to standard dict returns variable names which are set in this declaration.""" return as_list(self._vars.keys()) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_css_CSSVariablesDeclaration(self) def _setCssText(self, cssText): """Setting this attribute will result in the parsing of the new value and resetting of all the properties in the declaration block including the removal or addition of properties. :exceptions: - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this declaration is readonly or a property is readonly. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. Format:: variableset : vardeclaration [ ';' S* vardeclaration ]* ; vardeclaration : varname ':' S* term ; varname : IDENT S* ; expr : [ VARCALL | term ] [ operator [ VARCALL | term ] ]* ; """ self._checkReadonly() vardeclaration = Sequence( PreDef.ident(), PreDef.char(':', ':', toSeq=False, optional=True), # PreDef.S(toSeq=False, optional=True), Prod(name='term', match=lambda t, v: True, toSeq=lambda t, tokens: ('value', PropertyValue(itertools.chain([t], tokens), parent=self) ) ) ) prods = Sequence(vardeclaration, Sequence(PreDef.S(optional=True), PreDef.char(';', ';', toSeq=False, optional=True), PreDef.S(optional=True), vardeclaration, minmax=lambda: (0, None)), PreDef.S(optional=True), PreDef.char(';', ';', toSeq=False, optional=True) ) # parse wellformed, seq, store, notused = \ ProdParser().parse(cssText, 'CSSVariableDeclaration', prods, emptyOk=True) if wellformed: newseq = self._tempSeq() newvars = {} # seq contains only name: value pairs plus comments etc nameitem = None for item in seq: if 'IDENT' == item.type: nameitem = item elif 'value' == item.type: nname = normalize(nameitem.value) if nname in newvars: # replace var with same name for i, it in enumerate(newseq): if normalize(it.value[0]) == nname: newseq.replace(i, (nameitem.value, item.value), 'var', nameitem.line, nameitem.col) else: # saved non normalized name for reserialization newseq.append((nameitem.value, item.value), 'var', nameitem.line, nameitem.col) # newseq.append((nameitem.value, item.value), # 'var', # nameitem.line, nameitem.col) newvars[nname] = item.value else: newseq.appendItem(item) self._setSeq(newseq) self._vars = newvars self.wellformed = True cssText = property(_getCssText, _setCssText, doc="(DOM) A parsable textual representation of the declaration " "block excluding the surrounding curly braces.") def _setParentRule(self, parentRule): self._parentRule = parentRule parentRule = property(lambda self: self._parentRule, _setParentRule, doc="(DOM) The CSS rule that contains this" " declaration block or None if this block" " is not attached to a CSSRule.") def getVariableValue(self, variableName): """Used to retrieve the value of a variable if it has been explicitly set within this variable declaration block. :param variableName: The name of the variable. :returns: the value of the variable if it has been explicitly set in this variable declaration block. Returns the empty string if the variable has not been set. """ try: return self._vars[normalize(variableName)].cssText except KeyError: return '' def removeVariable(self, variableName): """Used to remove a variable if it has been explicitly set within this variable declaration block. :param variableName: The name of the variable. :returns: the value of the variable if it has been explicitly set for this variable declaration block. Returns the empty string if the variable has not been set. :exceptions: - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this declaration is readonly is readonly. """ normalname = variableName try: r = self._vars[normalname] except KeyError: return '' else: self.seq._readonly = False if normalname in self._vars: for i, x in enumerate(self.seq): if x.value[0] == variableName: del self.seq[i] self.seq._readonly = True del self._vars[normalname] return r.cssText def setVariable(self, variableName, value): """Used to set a variable value within this variable declaration block. :param variableName: The name of the CSS variable. :param value: The new value of the variable, may also be a PropertyValue object. :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this declaration is readonly or the property is readonly. """ self._checkReadonly() # check name wellformed, seq, store, unused = \ ProdParser().parse(normalize(variableName), 'variableName', Sequence(PreDef.ident())) if not wellformed: self._log.error('Invalid variableName: %r: %r' % (variableName, value)) else: # check value if isinstance(value, PropertyValue): v = value else: v = PropertyValue(cssText=value, parent=self) if not v.wellformed: self._log.error('Invalid variable value: %r: %r' % (variableName, value)) else: # update seq self.seq._readonly = False variableName = normalize(variableName) if variableName in self._vars: for i, x in enumerate(self.seq): if x.value[0] == variableName: self.seq.replace(i, [variableName, v], x.type, x.line, x.col) break else: self.seq.append([variableName, v], 'var') self.seq._readonly = True self._vars[variableName] = v def item(self, index): """Used to retrieve the variables that have been explicitly set in this variable declaration block. The order of the variables retrieved using this method does not have to be the order in which they were set. This method can be used to iterate over all variables in this variable declaration block. :param index: of the variable name to retrieve, negative values behave like negative indexes on Python lists, so -1 is the last element :returns: The name of the variable at this ordinal position. The empty string if no variable exists at this position. """ try: return as_list(self.keys())[index] except IndexError: return '' length = property(lambda self: len(self._vars), doc="The number of variables that have been explicitly set in this" " variable declaration block. The range of valid indices is 0" " to length-1 inclusive.") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/cssvariablesrule.py0000644000175000017500000001726500000000000022275 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser from . import cssrule from .cssvariablesdeclaration import CSSVariablesDeclaration """CSSVariables implements (and only partly) experimental `CSS Variables `_ """ __all__ = ['CSSVariablesRule'] __docformat__ = 'restructuredtext' __version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class CSSVariablesRule(cssrule.CSSRule): """ The CSSVariablesRule interface represents a @variables rule within a CSS style sheet. The @variables rule is used to specify variables. css_parser uses a :class:`~css_parser.css.CSSVariablesDeclaration` to represent the variables. Format:: variables VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S* variableset* '}' S* ; for variableset see :class:`css_parser.css.CSSVariablesDeclaration` **Media are not implemented. Reason is that css_parser is using CSS variables in a kind of preprocessing and therefor no media information is available at this stage. For now do not use media!** Example:: @variables { CorporateLogoBGColor: #fe8d12; } div.logoContainer { background-color: var(CorporateLogoBGColor); } """ def __init__(self, mediaText=None, variables=None, parentRule=None, parentStyleSheet=None, readonly=False): """ If readonly allows setting of properties in constructor only. """ super(CSSVariablesRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = '@variables' # dummy self._media = css_parser.stylesheets.MediaList(mediaText, readonly=readonly) if variables: self.variables = variables else: self.variables = CSSVariablesDeclaration(parentRule=self) self._readonly = readonly def __repr__(self): return "css_parser.css.%s(mediaText=%r, variables=%r)" % ( self.__class__.__name__, self._media.mediaText, self.variables.cssText) def __str__(self): return "" % (self.__class__.__name__, self._media.mediaText, self.variables.cssText, self.valid, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_CSSVariablesRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. Format:: variables : VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S* variableset* '}' S* ; variableset : LBRACE S* vardeclaration [ ';' S* vardeclaration ]* '}' S* ; """ super(CSSVariablesRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if self._type(attoken) != self._prods.VARIABLES_SYM: self._log.error('CSSVariablesRule: No CSSVariablesRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: newVariables = CSSVariablesDeclaration(parentRule=self) ok = True beforetokens, brace = self._tokensupto2(tokenizer, blockstartonly=True, separateEnd=True) if self._tokenvalue(brace) != '{': ok = False self._log.error('CSSVariablesRule: No start { of variable ' 'declaration found: %r' % self._valuestr(cssText), brace) # parse stuff before { which should be comments and S only new = {'wellformed': True} newseq = self._tempSeq() # [] beforewellformed, expected = self._parse(expected=':', seq=newseq, tokenizer=self._tokenize2(beforetokens), productions={}) ok = ok and beforewellformed and new['wellformed'] variablestokens, braceorEOFtoken = self._tokensupto2(tokenizer, blockendonly=True, separateEnd=True) val, type_ = self._tokenvalue(braceorEOFtoken), \ self._type(braceorEOFtoken) if val != '}' and type_ != 'EOF': ok = False self._log.error('CSSVariablesRule: No "}" after variables ' 'declaration found: %r' % self._valuestr(cssText)) nonetoken = self._nexttoken(tokenizer) if nonetoken: ok = False self._log.error('CSSVariablesRule: Trailing content found.', token=nonetoken) if 'EOF' == type_: # add again as variables needs it variablestokens.append(braceorEOFtoken) # SET but may raise: newVariables.cssText = variablestokens if ok: # contains probably comments only upto { self._setSeq(newseq) self.variables = newVariables cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this " "rule.") media = property(doc="NOT IMPLEMENTED! As css_parser resolves variables " "during serializing media information is lost.") def _setVariables(self, variables): """ :param variables: a CSSVariablesDeclaration or string """ self._checkReadonly() # Under Pythoin 2.x this was basestring but ... if isinstance(variables, string_type): self._variables = CSSVariablesDeclaration(cssText=variables, parentRule=self) else: variables._parentRule = self self._variables = variables variables = property(lambda self: self._variables, _setVariables, doc="(DOM) The variables of this rule set, a " ":class:`css_parser.css.CSSVariablesDeclaration`.") type = property(lambda self: self.VARIABLES_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") valid = property(lambda self: True, doc='NOT IMPLEMTED REALLY (TODO)') # constant but needed: wellformed = property(lambda self: True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/marginrule.py0000644000175000017500000001675500000000000021074 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import css_parser from . import cssrule from .cssstyledeclaration import CSSStyleDeclaration from css_parser.prodparser import Prod, PreDef, Sequence, Choice, ProdParser """MarginRule implements DOM Level 2 CSS MarginRule.""" __all__ = ['MarginRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class MarginRule(cssrule.CSSRule): """ A margin at-rule consists of an ATKEYWORD that identifies the margin box (e.g. '@top-left') and a block of declarations (said to be in the margin context). Format:: margin : margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* ; margin_sym : TOPLEFTCORNER_SYM | TOPLEFT_SYM | TOPCENTER_SYM | TOPRIGHT_SYM | TOPRIGHTCORNER_SYM | BOTTOMLEFTCORNER_SYM | BOTTOMLEFT_SYM | BOTTOMCENTER_SYM | BOTTOMRIGHT_SYM | BOTTOMRIGHTCORNER_SYM | LEFTTOP_SYM | LEFTMIDDLE_SYM | LEFTBOTTOM_SYM | RIGHTTOP_SYM | RIGHTMIDDLE_SYM | RIGHTBOTTOM_SYM ; e.g.:: @top-left { content: "123"; } """ margins = ['@top-left-corner', '@top-left', '@top-center', '@top-right', '@top-right-corner', '@bottom-left-corner', '@bottom-left', '@bottom-center', '@bottom-right', '@bottom-right-corner', '@left-top', '@left-middle', '@left-bottom', '@right-top', '@right-middle', '@right-bottom' ] def __init__(self, margin=None, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ :param atkeyword: The margin area, e.g. '@top-left' for this rule :param style: CSSStyleDeclaration for this MarginRule """ super(MarginRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = self._keyword = None if margin: self.margin = margin if style: self.style = style else: self.style = CSSStyleDeclaration(parentRule=self) self._readonly = readonly def _setMargin(self, margin): """Check if new keyword fits the rule it is used for.""" n = self._normalize(margin) if n not in MarginRule.margins: self._log.error('Invalid margin @keyword for this %s rule: %r' % (self.margin, margin), error=xml.dom.InvalidModificationErr) else: self._atkeyword = n self._keyword = margin margin = property(lambda self: self._atkeyword, _setMargin, doc="Margin area of parent CSSPageRule. " "`margin` and `atkeyword` are both normalized " "@keyword of the @rule.") atkeyword = margin def __repr__(self): return "css_parser.css.%s(margin=%r, style=%r)" % ( self.__class__.__name__, self.margin, self.style.cssText) def __str__(self): return "" % (self.__class__.__name__, self.margin, self.style.cssText, id(self)) def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_MarginRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(MarginRule, self)._setCssText(cssText) # TEMP: all style tokens are saved in store to fill styledeclaration # TODO: resolve when all generators styletokens = Prod(name='styletokens', match=lambda t, v: v != '}', # toSeq=False, toStore='styletokens', storeToken=True ) prods = Sequence(Prod(name='@ margin', match=lambda t, v: t == 'ATKEYWORD' and self._normalize(v) in MarginRule.margins, toStore='margin' # TODO? # , exception=xml.dom.InvalidModificationErr ), PreDef.char('OPEN', '{'), Sequence(Choice(PreDef.unknownrule(toStore='@'), styletokens), minmax=lambda: (0, None) ), PreDef.char('CLOSE', '}', stopAndKeep=True) ) # parse ok, seq, store, unused = ProdParser().parse(cssText, 'MarginRule', prods) if ok: # TODO: use seq for serializing instead of fixed stuff? self._setSeq(seq) if 'margin' in store: # may raise: self.margin = store['margin'].value else: self._log.error('No margin @keyword for this %s rule' % self.margin, error=xml.dom.InvalidModificationErr) # new empty style self.style = CSSStyleDeclaration(parentRule=self) if 'styletokens' in store: # may raise: self.style.cssText = store['styletokens'] cssText = property(fget=_getCssText, fset=_setCssText, doc="(DOM) The parsable textual representation.") def _setStyle(self, style): """ :param style: A string or CSSStyleDeclaration which replaces the current style object. """ self._checkReadonly() if isinstance(style, string_type): self._style = CSSStyleDeclaration(cssText=style, parentRule=self) else: style._parentRule = self self._style = style style = property(lambda self: self._style, _setStyle, doc="(DOM) The declaration-block of this rule set.") type = property(lambda self: self.MARGIN_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") wellformed = property(lambda self: bool(self.atkeyword)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/property.py0000644000175000017500000004475300000000000020612 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import css_parser from .value import PropertyValue from css_parser.helper import Deprecated """Property is a single CSS property in a CSSStyleDeclaration.""" __all__ = ['Property'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: string_type = str else: string_type = basestring class Property(css_parser.util.Base): """A CSS property in a StyleDeclaration of a CSSStyleRule (css_parser). Format:: property = name : IDENT S* ; expr = value : term [ operator term ]* ; term : unary_operator? [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* | function ] | STRING S* | IDENT S* | URI S* | hexcolor ; function : FUNCTION S* expr ')' S* ; /* * There is a constraint on the color that it must * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) * after the "#"; e.g., "#000" is OK, but "#abcd" is not. */ hexcolor : HASH S* ; prio : IMPORTANT_SYM S* ; """ def __init__(self, name=None, value=None, priority='', _mediaQuery=False, parent=None): """ :param name: a property name string (will be normalized) :param value: a property value string :param priority: an optional priority string which currently must be u'', u'!important' or u'important' :param _mediaQuery: if ``True`` value is optional (used by MediaQuery) :param parent: the parent object, normally a :class:`css_parser.css.CSSStyleDeclaration` """ super(Property, self).__init__() self.seqs = [[], None, []] self.wellformed = False self._mediaQuery = _mediaQuery self.parent = parent self.__nametoken = None self._name = '' self._literalname = '' self.seqs[1] = PropertyValue(parent=self) if name: self.name = name self.propertyValue = value self._priority = '' self._literalpriority = '' if priority: self.priority = priority def __repr__(self): return "css_parser.css.%s(name=%r, value=%r, priority=%r)" % ( self.__class__.__name__, self.literalname, self.propertyValue.cssText, self.priority) def __str__(self): return "<%s.%s object name=%r value=%r priority=%r valid=%r at 0x%x>" \ % (self.__class__.__module__, self.__class__.__name__, self.name, self.propertyValue.cssText, self.priority, self.valid, id(self)) def _isValidating(self): """Return True if validation is enabled.""" try: return self.parent.validating except AttributeError: # default (no parent) return True def _getCssText(self): """Return serialized property cssText.""" return css_parser.ser.do_Property(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ # check and prepare tokenlists for setting tokenizer = self._tokenize2(cssText) nametokens = self._tokensupto2(tokenizer, propertynameendonly=True) if nametokens: wellformed = True valuetokens = self._tokensupto2(tokenizer, propertyvalueendonly=True) prioritytokens = self._tokensupto2(tokenizer, propertypriorityendonly=True) if self._mediaQuery and not valuetokens: # MediaQuery may consist of name only self.name = nametokens self.propertyValue = None self.priority = None return # remove colon from nametokens colontoken = nametokens.pop() if self._tokenvalue(colontoken) != ':': wellformed = False self._log.error('Property: No ":" after name found: %s' % self._valuestr(cssText), colontoken) elif not nametokens: wellformed = False self._log.error('Property: No property name found: %s' % self._valuestr(cssText), colontoken) if valuetokens: if self._tokenvalue(valuetokens[-1]) == '!': # priority given, move "!" to prioritytokens prioritytokens.insert(0, valuetokens.pop(-1)) else: wellformed = False self._log.error('Property: No property value found: %s' % self._valuestr(cssText), colontoken) if wellformed: self.wellformed = True self.name = nametokens self.propertyValue = valuetokens self.priority = prioritytokens # also invalid values are set! if self._isValidating(): self.validate() else: self._log.error('Property: No property name found: %s' % self._valuestr(cssText)) cssText = property(fget=_getCssText, fset=_setCssText, doc="A parsable textual representation.") def _setName(self, name): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified name has a syntax error and is unparsable. """ # for closures: must be a mutable new = {'literalname': None, 'wellformed': True} def _ident(expected, seq, token, tokenizer=None): # name if 'name' == expected: new['literalname'] = self._tokenvalue(token).lower() seq.append(new['literalname']) return 'EOF' else: new['wellformed'] = False self._log.error('Property: Unexpected ident.', token) return expected newseq = [] wellformed, expected = self._parse(expected='name', seq=newseq, tokenizer=self._tokenize2(name), productions={'IDENT': _ident}) wellformed = wellformed and new['wellformed'] # post conditions # define a token for error logging if isinstance(name, list): token = name[0] self.__nametoken = token else: token = None if not new['literalname']: wellformed = False self._log.error('Property: No name found: %s' % self._valuestr(name), token=token) if wellformed: self.wellformed = True self._literalname = new['literalname'] self._name = self._normalize(self._literalname) self.seqs[0] = newseq # validate if self._isValidating() and self._name not in css_parser.profile.knownNames: # self.valid = False self._log.warn('Property: Unknown Property name.', token=token, neverraise=True) else: pass # self.valid = True # if self.propertyValue: # self.propertyValue._propertyName = self._name # #self.valid = self.propertyValue.valid else: self.wellformed = False name = property(lambda self: self._name, _setName, doc="Name of this property.") literalname = property(lambda self: self._literalname, doc="Readonly literal (not normalized) name " "of this property") def _setPropertyValue(self, cssText): """ See css.PropertyValue :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error (according to the attached property) or is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: TODO: Raised if the specified CSS string value represents a different type of values than the values allowed by the CSS property. """ if self._mediaQuery and not cssText: self.seqs[1] = PropertyValue(parent=self) else: self.seqs[1].cssText = cssText self.wellformed = self.wellformed and self.seqs[1].wellformed propertyValue = property(lambda self: self.seqs[1], _setPropertyValue, doc="(css_parser) PropertyValue object of property") def _getValue(self): if self.propertyValue: # value without comments return self.propertyValue.value else: return '' def _setValue(self, value): self._setPropertyValue(value) value = property(_getValue, _setValue, doc="The textual value of this Properties propertyValue.") def _setPriority(self, priority): """ priority a string, currently either u'', u'!important' or u'important' Format:: prio : IMPORTANT_SYM S* ; "!"{w}"important" {return IMPORTANT_SYM;} :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified priority has a syntax error and is unparsable. In this case a priority not equal to None, "" or "!{w}important". As CSSOM defines CSSStyleDeclaration.getPropertyPriority resulting in u'important' this value is also allowed to set a Properties priority """ if self._mediaQuery: self._priority = '' self._literalpriority = '' if priority: self._log.error('Property: No priority in a MediaQuery - ' 'ignored.') return if isinstance(priority, string_type) and 'important' == self._normalize(priority): priority = '!%s' % priority # for closures: must be a mutable new = {'literalpriority': '', 'wellformed': True} def _char(expected, seq, token, tokenizer=None): # "!" val = self._tokenvalue(token) if '!' == expected == val: seq.append(val) return 'important' else: new['wellformed'] = False self._log.error('Property: Unexpected char.', token) return expected def _ident(expected, seq, token, tokenizer=None): # "important" val = self._tokenvalue(token) if 'important' == expected: new['literalpriority'] = val seq.append(val) return 'EOF' else: new['wellformed'] = False self._log.error('Property: Unexpected ident.', token) return expected newseq = [] wellformed, expected = self._parse(expected='!', seq=newseq, tokenizer=self._tokenize2(priority), productions={'CHAR': _char, 'IDENT': _ident}) wellformed = wellformed and new['wellformed'] # post conditions if priority and not new['literalpriority']: wellformed = False self._log.info('Property: Invalid priority: %s' % self._valuestr(priority)) if wellformed: self.wellformed = self.wellformed and wellformed self._literalpriority = new['literalpriority'] self._priority = self._normalize(self.literalpriority) self.seqs[2] = newseq # validate priority if self._priority not in ('', 'important'): self._log.error('Property: No CSS priority value: %s' % self._priority) priority = property(lambda self: self._priority, _setPriority, doc="Priority of this property.") literalpriority = property(lambda self: self._literalpriority, doc="Readonly literal (not normalized) priority of this property") def _setParent(self, parent): self._parent = parent parent = property(lambda self: self._parent, _setParent, doc="The Parent Node (normally a CSSStyledeclaration) of this " "Property") def validate(self): """Validate value against `profiles` which are checked dynamically. properties in e.g. @font-face rules are checked against ``css_parser.profile.CSS3_FONT_FACE`` only. For each of the following cases a message is reported: - INVALID (so the property is known but not valid) ``ERROR Property: Invalid value for "{PROFILE-1[/PROFILE-2...]" property: ...`` - VALID but not in given profiles or defaultProfiles ``WARNING Property: Not valid for profile "{PROFILE-X}" but valid "{PROFILE-Y}" property: ...`` - VALID in current profile ``DEBUG Found valid "{PROFILE-1[/PROFILE-2...]" property...`` - UNKNOWN property ``WARNING Unknown Property name...`` is issued so for example:: css_parser.log.setLevel(logging.DEBUG) parser = css_parser.CSSParser() s = parser.parseString('''body { unknown-property: x; color: 4; color: rgba(1,2,3,4); color: red }''') # Log output: WARNING Property: Unknown Property name. [2:9: unknown-property] ERROR Property: Invalid value for "CSS Color Module Level 3/CSS Level 2.1" property: 4 [3:9: color] DEBUG Property: Found valid "CSS Color Module Level 3" value: rgba(1, 2, 3, 4) [4:9: color] DEBUG Property: Found valid "CSS Level 2.1" value: red [5:9: color] and when setting an explicit default profile:: css_parser.profile.defaultProfiles = css_parser.profile.CSS_LEVEL_2 s = parser.parseString('''body { unknown-property: x; color: 4; color: rgba(1,2,3,4); color: red }''') # Log output: WARNING Property: Unknown Property name. [2:9: unknown-property] ERROR Property: Invalid value for "CSS Color Module Level 3/CSS Level 2.1" property: 4 [3:9: color] WARNING Property: Not valid for profile "CSS Level 2.1" but valid "CSS Color Module Level 3" value: rgba(1, 2, 3, 4) [4:9: color] DEBUG Property: Found valid "CSS Level 2.1" value: red [5:9: color] """ valid = False profiles = None try: # if @font-face use that profile rule = self.parent.parentRule except AttributeError: pass else: if rule is not None: if rule.type == rule.FONT_FACE_RULE: profiles = [css_parser.profile.CSS3_FONT_FACE] # TODO: same for @page if self.name and self.value: pass # TODO # cv = self.propertyValue # if cv.cssValueType == cv.CSS_VARIABLE and not cv.value: # # TODO: false alarms too! # css_parser.log.warn(u'No value for variable "%s" found, keeping ' # u'variable.' % cv.name, neverraise=True) if self.name in css_parser.profile.knownNames: # add valid, matching, validprofiles... valid, matching, validprofiles = \ css_parser.profile.validateWithProfile(self.name, self.value, profiles) if not valid: self._log.error('Property: Invalid value for ' '"%s" property: %s' % ('/'.join(validprofiles), self.value), token=self.__nametoken, neverraise=True) # TODO: remove logic to profiles! elif valid and not matching: # (profiles and profiles not in validprofiles): if not profiles: notvalidprofiles = '/'.join(css_parser.profile.defaultProfiles) else: notvalidprofiles = profiles self._log.warn('Property: Not valid for profile "%s" ' 'but valid "%s" value: %s ' % (notvalidprofiles, '/'.join(validprofiles), self.value), token=self.__nametoken, neverraise=True) valid = False elif valid: self._log.debug('Property: Found valid "%s" value: %s' % ('/'.join(validprofiles), self.value), token=self.__nametoken, neverraise=True) if self._priority not in ('', 'important'): valid = False return valid valid = property(validate, doc="Check if value of this property is valid " "in the properties context.") @Deprecated('Use ``property.propertyValue`` instead.') def _getCSSValue(self): return self.propertyValue @Deprecated('Use ``property.propertyValue`` instead.') def _setCSSValue(self, cssText): self._setPropertyValue(cssText) cssValue = property(_getCSSValue, _setCSSValue, doc="(DEPRECATED) Use ``property.propertyValue`` instead.") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/selector.py0000644000175000017500000010003100000000000020524 0ustar00kovidkovid"""Selector is a single Selector of a CSSStyleRule SelectorList. Partly implements http://www.w3.org/TR/css3-selectors/. TODO - .contains(selector) - .isSubselector(selector) """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['Selector'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from css_parser.helper import Deprecated from css_parser.util import _SimpleNamespaces import css_parser import xml.dom def as_list(p): if isinstance(p, list): return p class Selector(css_parser.util.Base2): """ (css_parser) a single selector in a :class:`~css_parser.css.SelectorList` of a :class:`~css_parser.css.CSSStyleRule`. Format:: # implemented in SelectorList selectors_group : selector [ COMMA S* selector ]* ; selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; combinator /* combinators can be surrounded by white space */ : PLUS S* | GREATER S* | TILDE S* | S+ ; simple_selector_sequence : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* | [ HASH | class | attrib | pseudo | negation ]+ ; type_selector : [ namespace_prefix ]? element_name ; namespace_prefix : [ IDENT | '*' ]? '|' ; element_name : IDENT ; universal : [ namespace_prefix ]? '*' ; class : '.' IDENT ; attrib : '[' S* [ namespace_prefix ]? IDENT S* [ [ PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | '=' | INCLUDES | DASHMATCH ] S* [ IDENT | STRING ] S* ]? ']' ; pseudo /* '::' starts a pseudo-element, ':' a pseudo-class */ /* Exceptions: :first-line, :first-letter, :before and :after. */ /* Note that pseudo-elements are restricted to one per selector and */ /* occur only in the last simple_selector_sequence. */ : ':' ':'? [ IDENT | functional_pseudo ] ; functional_pseudo : FUNCTION S* expression ')' ; expression /* In CSS3, the expressions are identifiers, strings, */ /* or of the form "an+b" */ : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ ; negation : NOT S* negation_arg S* ')' ; negation_arg : type_selector | universal | HASH | class | attrib | pseudo ; """ def __init__(self, selectorText=None, parent=None, readonly=False): """ :Parameters: selectorText initial value of this selector parent a SelectorList readonly default to False """ super(Selector, self).__init__() self.__namespaces = _SimpleNamespaces(log=self._log) self._element = None self._parent = parent self._specificity = (0, 0, 0, 0) if selectorText: self.selectorText = selectorText self._readonly = readonly def __repr__(self): if self.__getNamespaces(): st = (self.selectorText, self._getUsedNamespaces()) else: st = self.selectorText return "css_parser.css.%s(selectorText=%r)" % (self.__class__.__name__, st) def __str__(self): return "" % (self.__class__.__name__, self.selectorText, self.specificity, self._getUsedNamespaces(), id(self)) def _getUsedUris(self): "Return list of actually used URIs in this Selector." uris = set() for item in self.seq: type_, val = item.type, item.value if type_.endswith('-selector') or type_ == 'universal' and \ isinstance(val, tuple) and val[0] not in (None, '*'): uris.add(val[0]) return uris def _getUsedNamespaces(self): "Return actually used namespaces only." useduris = self._getUsedUris() namespaces = _SimpleNamespaces(log=self._log) for p, uri in as_list(self._namespaces.items()): if uri in useduris: namespaces[p] = uri return namespaces def __getNamespaces(self): "Use own namespaces if not attached to a sheet, else the sheet's ones." try: return self._parent.parentRule.parentStyleSheet.namespaces except AttributeError: return self.__namespaces _namespaces = property(__getNamespaces, doc="If this Selector is attached to a " "CSSStyleSheet the namespaces of that sheet " "are mirrored here. While the Selector (or " "parent SelectorList or parentRule(s) of that " "are not attached a own dict of {prefix: " "namespaceURI} is used.") element = property(lambda self: self._element, doc="Effective element target of this selector.") parent = property(lambda self: self._parent, doc="(DOM) The SelectorList that contains this Selector " "or None if this Selector is not attached to a " "SelectorList.") def _getSelectorText(self): """Return serialized format.""" return css_parser.ser.do_css_Selector(self) def _setSelectorText(self, selectorText): """ :param selectorText: parsable string or a tuple of (selectorText, dict-of-namespaces). Given namespaces are ignored if this object is attached to a CSSStyleSheet! :exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() # might be (selectorText, namespaces) selectorText, namespaces = self._splitNamespacesOff(selectorText) try: # uses parent stylesheets namespaces if available, # otherwise given ones namespaces = self.parent.parentRule.parentStyleSheet.namespaces except AttributeError: pass tokenizer = self._tokenize2(selectorText) if not tokenizer: self._log.error('Selector: No selectorText given.') else: # prepare tokenlist: # "*" -> type "universal" # "*"|IDENT + "|" -> combined to "namespace_prefix" # "|" -> type "namespace_prefix" # "." + IDENT -> combined to "class" # ":" + IDENT, ":" + FUNCTION -> pseudo-class # FUNCTION "not(" -> negation # "::" + IDENT, "::" + FUNCTION -> pseudo-element tokens = [] for t in tokenizer: typ, val, lin, col = t if val == ':' and tokens and\ self._tokenvalue(tokens[-1]) == ':': # combine ":" and ":" tokens[-1] = (typ, '::', lin, col) elif typ == 'IDENT' and tokens\ and self._tokenvalue(tokens[-1]) == '.': # class: combine to .IDENT tokens[-1] = ('class', '.'+val, lin, col) elif typ == 'IDENT' and tokens and \ self._tokenvalue(tokens[-1]).startswith(':') and\ not self._tokenvalue(tokens[-1]).endswith('('): # pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b" if self._tokenvalue(tokens[-1]).startswith('::'): t = 'pseudo-element' else: t = 'pseudo-class' tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col) elif typ == 'FUNCTION' and val == 'not(' and tokens and \ ':' == self._tokenvalue(tokens[-1]): tokens[-1] = ('negation', ':' + val, lin, tokens[-1][3]) elif typ == 'FUNCTION' and tokens\ and self._tokenvalue(tokens[-1]).startswith(':'): # pseudo-X: combine to :FUNCTION( or ::FUNCTION( if self._tokenvalue(tokens[-1]).startswith('::'): t = 'pseudo-element' else: t = 'pseudo-class' tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col) elif val == '*' and tokens and\ self._type(tokens[-1]) == 'namespace_prefix' and\ self._tokenvalue(tokens[-1]).endswith('|'): # combine prefix|* tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val, lin, col) elif val == '*': # universal: "*" tokens.append(('universal', val, lin, col)) elif val == '|' and tokens and\ self._type(tokens[-1]) in (self._prods.IDENT, 'universal')\ and self._tokenvalue(tokens[-1]).find('|') == -1: # namespace_prefix: "IDENT|" or "*|" tokens[-1] = ('namespace_prefix', self._tokenvalue(tokens[-1])+'|', lin, col) elif val == '|': # namespace_prefix: "|" tokens.append(('namespace_prefix', val, lin, col)) else: tokens.append(t) tokenizer = iter(tokens) # for closures: must be a mutable new = {'context': [''], # stack of: 'attrib', 'negation', 'pseudo' 'element': None, '_PREFIX': None, 'specificity': [0, 0, 0, 0], # mutable, finally a tuple! 'wellformed': True } # used for equality checks and setting of a space combinator S = ' ' def append(seq, val, typ=None, token=None): """ appends to seq namespace_prefix, IDENT will be combined to a tuple (prefix, name) where prefix might be None, the empty string or a prefix. Saved are also: - specificity definition: style, id, class/att, type - element: the element this Selector is for """ context = new['context'][-1] if token: line, col = token[2], token[3] else: line, col = None, None if typ == '_PREFIX': # SPECIAL TYPE: save prefix for combination with next new['_PREFIX'] = val[:-1] # handle next time return if new['_PREFIX'] is not None: # as saved from before and reset to None prefix, new['_PREFIX'] = new['_PREFIX'], None elif typ == 'universal' and '|' in val: # val == *|* or prefix|* prefix, val = val.split('|') else: prefix = None # namespace if (typ.endswith('-selector') or typ == 'universal') and not ( 'attribute-selector' == typ and not prefix): # att **IS NOT** in default ns if prefix == '*': # *|name: in ANY_NS namespaceURI = css_parser._ANYNS elif prefix is None: # e or *: default namespace with prefix u'' # or local-name() namespaceURI = namespaces.get('', None) elif prefix == '': # |name or |*: in no (or the empty) namespace namespaceURI = '' else: # explicit namespace prefix # does not raise KeyError, see _SimpleNamespaces namespaceURI = namespaces[prefix] if namespaceURI is None: new['wellformed'] = False self._log.error('Selector: No namespaceURI found ' 'for prefix %r' % prefix, token=token, error=xml.dom.NamespaceErr) return # val is now (namespaceprefix, name) tuple val = (namespaceURI, val) # specificity if not context or context == 'negation': if 'id' == typ: new['specificity'][1] += 1 elif 'class' == typ or '[' == val: new['specificity'][2] += 1 elif typ in ('type-selector', 'negation-type-selector', 'pseudo-element'): new['specificity'][3] += 1 if not context and typ in ('type-selector', 'universal'): # define element new['element'] = val seq.append(val, typ, line=line, col=col) # expected constants simple_selector_sequence = 'type_selector universal HASH class ' \ 'attrib pseudo negation ' simple_selector_sequence2 = 'HASH class attrib pseudo negation ' element_name = 'element_name' negation_arg = 'type_selector universal HASH class attrib pseudo' negationend = ')' attname = 'prefix attribute' attname2 = 'attribute' attcombinator = 'combinator ]' # optional attvalue = 'value' # optional attend = ']' expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT' expression = expressionstart + ' )' combinator = ' combinator' def _COMMENT(expected, seq, token, tokenizer=None): "special implementation for comment token" append(seq, css_parser.css.CSSComment([token]), 'COMMENT', token=token) return expected def _S(expected, seq, token, tokenizer=None): # S context = new['context'][-1] if context.startswith('pseudo-'): if seq and seq[-1].value not in '+-': # e.g. x:func(a + b) append(seq, S, 'S', token=token) return expected elif context != 'attrib' and 'combinator' in expected: append(seq, S, 'descendant', token=token) return simple_selector_sequence + combinator else: return expected def _universal(expected, seq, token, tokenizer=None): # *|* or prefix|* context = new['context'][-1] val = self._tokenvalue(token) if 'universal' in expected: append(seq, val, 'universal', token=token) if 'negation' == context: return negationend else: return simple_selector_sequence2 + combinator else: new['wellformed'] = False self._log.error( 'Selector: Unexpected universal.', token=token) return expected def _namespace_prefix(expected, seq, token, tokenizer=None): # prefix| => element_name # or prefix| => attribute_name if attrib context = new['context'][-1] val = self._tokenvalue(token) if 'attrib' == context and 'prefix' in expected: # [PREFIX|att] append(seq, val, '_PREFIX', token=token) return attname2 elif 'type_selector' in expected: # PREFIX|* append(seq, val, '_PREFIX', token=token) return element_name else: new['wellformed'] = False self._log.error( 'Selector: Unexpected namespace prefix.', token=token) return expected def _pseudo(expected, seq, token, tokenizer=None): # pseudo-class or pseudo-element :a ::a :a( ::a( """ /* '::' starts a pseudo-element, ':' a pseudo-class */ /* Exceptions: :first-line, :first-letter, :before and :after. */ /* Note that pseudo-elements are restricted to one per selector and */ /* occur only in the last simple_selector_sequence. */ """ context = new['context'][-1] val, typ = self._tokenvalue(token, normalize=True),\ self._type(token) if 'pseudo' in expected: if val in (':first-line', ':first-letter', ':before', ':after'): # always pseudo-element ??? typ = 'pseudo-element' append(seq, val, typ, token=token) if val.endswith('('): # function # "pseudo-" "class" or "element" new['context'].append(typ) return expressionstart elif 'negation' == context: return negationend elif 'pseudo-element' == typ: # only one per element, check at ) also! return combinator else: return simple_selector_sequence2 + combinator else: new['wellformed'] = False self._log.error( 'Selector: Unexpected start of pseudo.', token=token) return expected def _expression(expected, seq, token, tokenizer=None): # [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ context = new['context'][-1] val, typ = self._tokenvalue(token), self._type(token) if context.startswith('pseudo-'): append(seq, val, typ, token=token) return expression else: new['wellformed'] = False self._log.error( 'Selector: Unexpected %s.' % typ, token=token) return expected def _attcombinator(expected, seq, token, tokenizer=None): # context: attrib # PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES | # DASHMATCH context = new['context'][-1] val, typ = self._tokenvalue(token), self._type(token) if 'attrib' == context and 'combinator' in expected: # combinator in attrib append(seq, val, typ.lower(), token=token) return attvalue else: new['wellformed'] = False self._log.error( 'Selector: Unexpected %s.' % typ, token=token) return expected def _string(expected, seq, token, tokenizer=None): # identifier context = new['context'][-1] typ, val = self._type(token), self._stringtokenvalue(token) # context: attrib if 'attrib' == context and 'value' in expected: # attrib: [...=VALUE] append(seq, val, typ, token=token) return attend # context: pseudo elif context.startswith('pseudo-'): # :func(...) append(seq, val, typ, token=token) return expression else: new['wellformed'] = False self._log.error( 'Selector: Unexpected STRING.', token=token) return expected def _ident(expected, seq, token, tokenizer=None): # identifier context = new['context'][-1] val, typ = self._tokenvalue(token), self._type(token) # context: attrib if 'attrib' == context and 'attribute' in expected: # attrib: [...|ATT...] append(seq, val, 'attribute-selector', token=token) return attcombinator elif 'attrib' == context and 'value' in expected: # attrib: [...=VALUE] append(seq, val, 'attribute-value', token=token) return attend # context: negation elif 'negation' == context: # negation: (prefix|IDENT) append(seq, val, 'negation-type-selector', token=token) return negationend # context: pseudo elif context.startswith('pseudo-'): # :func(...) append(seq, val, typ, token=token) return expression elif 'type_selector' in expected or element_name == expected: # element name after ns or complete type_selector append(seq, val, 'type-selector', token=token) return simple_selector_sequence2 + combinator else: new['wellformed'] = False self._log.error('Selector: Unexpected IDENT.', token=token) return expected def _class(expected, seq, token, tokenizer=None): # .IDENT context = new['context'][-1] val = self._tokenvalue(token) if 'class' in expected: append(seq, val, 'class', token=token) if 'negation' == context: return negationend else: return simple_selector_sequence2 + combinator else: new['wellformed'] = False self._log.error('Selector: Unexpected class.', token=token) return expected def _hash(expected, seq, token, tokenizer=None): # #IDENT context = new['context'][-1] val = self._tokenvalue(token) if 'HASH' in expected: append(seq, val, 'id', token=token) if 'negation' == context: return negationend else: return simple_selector_sequence2 + combinator else: new['wellformed'] = False self._log.error('Selector: Unexpected HASH.', token=token) return expected def _char(expected, seq, token, tokenizer=None): # + > ~ ) [ ] + - context = new['context'][-1] val = self._tokenvalue(token) # context: attrib if ']' == val and 'attrib' == context and ']' in expected: # end of attrib append(seq, val, 'attribute-end', token=token) context = new['context'].pop() # attrib is done context = new['context'][-1] if 'negation' == context: return negationend else: return simple_selector_sequence2 + combinator elif '=' == val and 'attrib' == context\ and 'combinator' in expected: # combinator in attrib append(seq, val, 'equals', token=token) return attvalue # context: negation elif ')' == val and 'negation' == context and ')' in expected: # not(negation_arg)" append(seq, val, 'negation-end', token=token) new['context'].pop() # negation is done context = new['context'][-1] return simple_selector_sequence + combinator # context: pseudo (at least one expression) elif val in '+-' and context.startswith('pseudo-'): # :func(+ -)" _names = {'+': 'plus', '-': 'minus'} if val == '+' and seq and seq[-1].value == S: seq.replace(-1, val, _names[val]) else: append(seq, val, _names[val], token=token) return expression elif ')' == val and context.startswith('pseudo-') and\ expression == expected: # :func(expression)" append(seq, val, 'function-end', token=token) new['context'].pop() # pseudo is done if 'pseudo-element' == context: return combinator else: return simple_selector_sequence + combinator # context: ROOT elif '[' == val and 'attrib' in expected: # start of [attrib] append(seq, val, 'attribute-start', token=token) new['context'].append('attrib') return attname elif val in '+>~' and 'combinator' in expected: # no other combinator except S may be following _names = { '>': 'child', '+': 'adjacent-sibling', '~': 'following-sibling'} if seq and seq[-1].value == S: seq.replace(-1, val, _names[val]) else: append(seq, val, _names[val], token=token) return simple_selector_sequence elif ',' == val: # not a selectorlist new['wellformed'] = False self._log.error( 'Selector: Single selector only.', error=xml.dom.InvalidModificationErr, token=token) return expected else: new['wellformed'] = False self._log.error( 'Selector: Unexpected CHAR.', token=token) return expected def _negation(expected, seq, token, tokenizer=None): # not( val = self._tokenvalue(token, normalize=True) if 'negation' in expected: new['context'].append('negation') append(seq, val, 'negation-start', token=token) return negation_arg else: new['wellformed'] = False self._log.error( 'Selector: Unexpected negation.', token=token) return expected def _atkeyword(expected, seq, token, tokenizer=None): "invalidates selector" new['wellformed'] = False self._log.error( 'Selector: Unexpected ATKEYWORD.', token=token) return expected # expected: only|not or mediatype, mediatype, feature, and newseq = self._tempSeq() wellformed, expected = self._parse( expected=simple_selector_sequence, seq=newseq, tokenizer=tokenizer, productions={'CHAR': _char, 'class': _class, 'HASH': _hash, 'STRING': _string, 'IDENT': _ident, 'namespace_prefix': _namespace_prefix, 'negation': _negation, 'pseudo-class': _pseudo, 'pseudo-element': _pseudo, 'universal': _universal, # pseudo 'NUMBER': _expression, 'DIMENSION': _expression, # attribute 'PREFIXMATCH': _attcombinator, 'SUFFIXMATCH': _attcombinator, 'SUBSTRINGMATCH': _attcombinator, 'DASHMATCH': _attcombinator, 'INCLUDES': _attcombinator, 'S': _S, 'COMMENT': _COMMENT, 'ATKEYWORD': _atkeyword}) wellformed = wellformed and new['wellformed'] # post condition if len(new['context']) > 1 or not newseq: wellformed = False self._log.error('Selector: Invalid or incomplete selector: %s' % self._valuestr(selectorText)) if expected == 'element_name': wellformed = False self._log.error('Selector: No element name found: %s' % self._valuestr(selectorText)) if expected == simple_selector_sequence and newseq: wellformed = False self._log.error('Selector: Cannot end with combinator: %s' % self._valuestr(selectorText)) if newseq and hasattr(newseq[-1].value, 'strip') \ and newseq[-1].value.strip() == '': del newseq[-1] # set if wellformed: self.__namespaces = namespaces self._element = new['element'] self._specificity = tuple(new['specificity']) self._setSeq(newseq) # filter that only used ones are kept self.__namespaces = self._getUsedNamespaces() selectorText = property(_getSelectorText, _setSelectorText, doc="(DOM) The parsable textual representation of " "the selector.") specificity = property(lambda self: self._specificity, doc="""Specificity of this selector (READONLY). Tuple of (a, b, c, d) where: a presence of style in document, always 0 if not used on a document b number of ID selectors c number of .class selectors d number of Element (type) selectors""") wellformed = property(lambda self: bool(len(self.seq))) @Deprecated('Use property parent instead') def _getParentList(self): return self.parent parentList = property(_getParentList, doc="DEPRECATED, see property parent instead") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/css/selectorlist.py0000644000175000017500000002101400000000000021423 0ustar00kovidkovid"""SelectorList is a list of CSS Selector objects. TODO - remove duplicate Selectors. -> CSSOM canonicalize - ??? CSS2 gives a special meaning to the comma (,) in selectors. However, since it is not known if the comma may acquire other meanings in future versions of CSS, the whole statement should be ignored if there is an error anywhere in the selector, even though the rest of the selector may look reasonable in CSS2. Illegal example(s): For example, since the "&" is not a valid token in a CSS2 selector, a CSS2 user agent must ignore the whole second line, and not set the color of H3 to red: """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['SelectorList'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from .selector import Selector import css_parser class SelectorList(css_parser.util.Base, css_parser.util.ListSeq): """A list of :class:`~css_parser.css.Selector` objects of a :class:`~css_parser.css.CSSStyleRule`.""" def __init__(self, selectorText=None, parentRule=None, readonly=False): """ :Parameters: selectorText parsable list of Selectors parentRule the parent CSSRule if available """ super(SelectorList, self).__init__() self._parentRule = parentRule if selectorText: self.selectorText = selectorText self._readonly = readonly def __repr__(self): if self._namespaces: st = (self.selectorText, self._namespaces) else: st = self.selectorText return "css_parser.css.%s(selectorText=%r)" % (self.__class__.__name__, st) def __str__(self): return "" % (self.__class__.__name__, self.selectorText, self._namespaces, id(self)) def __setitem__(self, index, newSelector): """Overwrite ListSeq.__setitem__ Any duplicate Selectors are **not** removed. """ newSelector = self.__prepareset(newSelector) if newSelector: self.seq[index] = newSelector def __prepareset(self, newSelector, namespaces=None): "Used by appendSelector and __setitem__" if not namespaces: namespaces = {} self._checkReadonly() if not isinstance(newSelector, Selector): newSelector = Selector((newSelector, namespaces), parent=self) if newSelector.wellformed: newSelector._parent = self # maybe set twice but must be! return newSelector def __getNamespaces(self): """Use children namespaces if not attached to a sheet, else the sheet's ones. """ try: return self.parentRule.parentStyleSheet.namespaces except AttributeError: namespaces = {} for selector in self.seq: namespaces.update(selector._namespaces) return namespaces def _getUsedUris(self): "Used by CSSStyleSheet to check if @namespace rules are needed" uris = set() for s in self: uris.update(s._getUsedUris()) return uris _namespaces = property(__getNamespaces, doc="""If this SelectorList is attached to a CSSStyleSheet the namespaces of that sheet are mirrored here. While the SelectorList (or parentRule(s) are not attached the namespaces of all children Selectors are used.""") def append(self, newSelector): "Same as :meth:`appendSelector`." self.appendSelector(newSelector) def appendSelector(self, newSelector): """ Append `newSelector` to this list (a string will be converted to a :class:`~css_parser.css.Selector`). :param newSelector: comma-separated list of selectors (as a single string) or a tuple of `(newSelector, dict-of-namespaces)` :returns: New :class:`~css_parser.css.Selector` or ``None`` if `newSelector` is not wellformed. :exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() # might be (selectorText, namespaces) newSelector, namespaces = self._splitNamespacesOff(newSelector) try: # use parent's only if available namespaces = self.parentRule.parentStyleSheet.namespaces except AttributeError: # use already present namespaces plus new given ones _namespaces = self._namespaces _namespaces.update(namespaces) namespaces = _namespaces newSelector = self.__prepareset(newSelector, namespaces) if newSelector: seq = self.seq[:] del self.seq[:] for s in seq: if s.selectorText != newSelector.selectorText: self.seq.append(s) self.seq.append(newSelector) return newSelector def _getSelectorText(self): "Return serialized format." return css_parser.ser.do_css_SelectorList(self) def _setSelectorText(self, selectorText): """ :param selectorText: comma-separated list of selectors or a tuple of (selectorText, dict-of-namespaces) :exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() # might be (selectorText, namespaces) selectorText, namespaces = self._splitNamespacesOff(selectorText) try: # use parent's only if available namespaces = self.parentRule.parentStyleSheet.namespaces except AttributeError: pass wellformed = True tokenizer = self._tokenize2(selectorText) newseq = [] expected = True while True: # find all upto and including next ",", EOF or nothing selectortokens = self._tokensupto2(tokenizer, listseponly=True) if selectortokens: if self._tokenvalue(selectortokens[-1]) == ',': expected = selectortokens.pop() else: expected = None selector = Selector((selectortokens, namespaces), parent=self) if selector.wellformed: newseq.append(selector) else: wellformed = False self._log.error('SelectorList: Invalid Selector: %s' % self._valuestr(selectortokens)) else: break # post condition if ',' == expected: wellformed = False self._log.error('SelectorList: Cannot end with ",": %r' % self._valuestr(selectorText)) elif expected: wellformed = False self._log.error('SelectorList: Unknown Syntax: %r' % self._valuestr(selectorText)) if wellformed: self.seq = newseq selectorText = property(_getSelectorText, _setSelectorText, doc="(css_parser) The textual representation of the " "selector for a rule set.") length = property(lambda self: len(self), doc="The number of :class:`~css_parser.css.Selector` " "objects in the list.") parentRule = property(lambda self: self._parentRule, doc="(DOM) The CSS rule that contains this " "SelectorList or ``None`` if this SelectorList " "is not attached to a CSSRule.") wellformed = property(lambda self: bool(len(self.seq))) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1603522484.0 css-parser-1.0.7/src/css_parser/css/value.py0000644000175000017500000011323400000000000020031 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function import re import colorsys from css_parser.helper import normalize, pushtoken import css_parser from css_parser.prodparser import Choice, PreDef, Sequence, ProdParser, Prod """Value related classes. DOM Level 2 CSS CSSValue, CSSPrimitiveValue and CSSValueList are **no longer** supported and are replaced by these new classes. """ __all__ = ['PropertyValue', 'Value', 'ColorValue', 'DimensionValue', 'URIValue', 'CSSFunction', 'CSSCalc', 'CSSVariable', 'MSValue' ] __docformat__ = 'restructuredtext' __version__ = '$Id$' from ..util import text_type, urljoin def as_list(p): if isinstance(p, list): return p return list(p) class PropertyValue(css_parser.util._NewBase): """ An unstructured list like holder for all values defined for a :class:`~css_parser.css.Property`. Contains :class:`~css_parser.css.Value` or subclass objects. Currently there is no access to the combinators of the defined values which might simply be space or comma or slash. You may: - iterate over all contained Value objects (not the separators like ``,``, ``/`` or `` `` though!) - get a Value item by index or use ``PropertyValue[index]`` - find out the number of values defined (unstructured) """ def __init__(self, cssText=None, parent=None, readonly=False): """ :param cssText: the parsable cssText of the value :param readonly: defaults to False """ super(PropertyValue, self).__init__() self.parent = parent self.wellformed = False if cssText is not None: # may be 0 if isinstance(cssText, (int, float)): cssText = text_type(cssText) # if it is a number self.cssText = cssText self._readonly = readonly def __len__(self): return len(as_list(self.__items())) def __getitem__(self, index): try: return as_list(self.__items())[index] except IndexError: return None def __iter__(self): "Generator which iterates over values." for item in self.__items(): yield item def __repr__(self): return "css_parser.css.%s(%r)" % (self.__class__.__name__, self.cssText) def __str__(self): return "" % (self.__class__.__name__, self.length, self.cssText, id(self)) def __items(self, seq=None): "a generator of Value obects only, no , / or ' '" if seq is None: seq = self.seq return (x.value for x in seq if isinstance(x.value, Value)) def _setCssText(self, cssText): if isinstance(cssText, (int, float)): cssText = text_type(cssText) # if it is a number """ Format:: unary_operator : '-' | '+' ; operator : '/' S* | ',' S* | /* empty */ ; expr : term [ operator term ]* ; term : unary_operator? [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* ] | STRING S* | IDENT S* | URI S* | hexcolor | function | UNICODE-RANGE S* ; function : FUNCTION S* expr ')' S* ; /* * There is a constraint on the color that it must * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) * after the "#"; e.g., "#000" is OK, but "#abcd" is not. */ hexcolor : HASH S* ; :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error (according to the attached property) or is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: TODO: Raised if the specified CSS string value represents a different type of values than the values allowed by the CSS property. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this value is readonly. """ self._checkReadonly() # used as operator is , / or S nextSor = ',/' term = Choice(_ColorProd(self, nextSor), _DimensionProd(self, nextSor), _URIProd(self, nextSor), _ValueProd(self, nextSor), # _Rect(self, nextSor), # all other functions _CSSVariableProd(self, nextSor), _MSValueProd(self, nextSor), _CalcValueProd(self, nextSor), _CSSFunctionProd(self, nextSor) ) operator = Choice(PreDef.S(toSeq=False), PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1]), optional=True ), PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1]), optional=True), optional=True) prods = Sequence(term, Sequence( # mayEnd this Sequence if whitespace operator, # TODO: only when setting via other class # used by variabledeclaration currently PreDef.char('END', ';', stopAndKeep=True, optional=True), # TODO: } and !important ends too! term, minmax=lambda: (0, None))) # parse ok, seq, store, unused = ProdParser().parse(cssText, 'PropertyValue', prods) # must be at least one value! ok = ok and len(as_list(self.__items(seq))) > 0 for item in seq: if hasattr(item.value, 'wellformed') and not item.value.wellformed: ok = False break self.wellformed = ok if ok: self._setSeq(seq) else: self._log.error('PropertyValue: Unknown syntax or no value: %s' % self._valuestr(cssText)) cssText = property(lambda self: css_parser.ser.do_css_PropertyValue(self), _setCssText, doc="A string representation of the current value.") def item(self, index): """ The value at position `index`. Alternatively simple use ``PropertyValue[index]``. :param index: the parsable cssText of the value :exceptions: - :exc:`~IndexError`: Raised if index if out of bounds """ return self[index] length = property(lambda self: len(self), doc="Number of values set.") value = property(lambda self: css_parser.ser.do_css_PropertyValue(self, valuesOnly=True), doc="A string representation of the current value " "without any comments used for validation.") class Value(css_parser.util._NewBase): """ Represents a single CSS value. For now simple values of IDENT, STRING, or UNICODE-RANGE values are represented directly as Value objects. Other values like e.g. FUNCTIONs are represented by subclasses with an extended API. """ IDENT = 'IDENT' STRING = 'STRING' UNICODE_RANGE = 'UNICODE-RANGE' URI = 'URI' DIMENSION = 'DIMENSION' NUMBER = 'NUMBER' PERCENTAGE = 'PERCENTAGE' COLOR_VALUE = 'COLOR_VALUE' HASH = 'HASH' FUNCTION = 'FUNCTION' CALC = 'CALC' VARIABLE = 'VARIABLE' _type = None _value = '' def __init__(self, cssText=None, parent=None, readonly=False): super(Value, self).__init__() self.parent = parent self.wellformed = False if cssText: self.cssText = cssText def __repr__(self): return "css_parser.css.%s(%r)" % (self.__class__.__name__, self.cssText) def __str__(self): return ""\ % (self.__class__.__name__, self.type, self.value, self.cssText, id(self)) def _setCssText(self, cssText): self._checkReadonly() prods = Choice(PreDef.hexcolor(stop=True), PreDef.ident(stop=True), PreDef.string(stop=True), PreDef.unicode_range(stop=True), ) ok, seq, store, unused = ProdParser().parse(cssText, 'Value', prods) self.wellformed = ok if ok: # only 1 value anyway! self._type = seq[0].type self._value = seq[0].value self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_Value(self), _setCssText, doc='String value of this value.') type = property(lambda self: self._type, # _setType, doc="Type of this value, for now the production type " "like e.g. `DIMENSION` or `STRING`. All types are " "defined as constants in :class:`~css_parser.css.Value`.") def _setValue(self, value): # TODO: check! self._value = value value = property(lambda self: self._value, _setValue, doc="Actual value if possible: An int or float or else " " a string") class ColorValue(Value): """ A color value like rgb(), rgba(), hsl(), hsla() or #rgb, #rrggbb TODO: Color Keywords """ from .colors import COLORS type = Value.COLOR_VALUE # hexcolor, FUNCTION? _colorType = None _red = 0 _green = 0 _blue = 0 _alpha = 0 def __str__(self): return ""\ % (self.__class__.__name__, self.type, self.value, self.colorType, self.red, self.green, self.blue, self.alpha, id(self)) def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! component = Choice(PreDef.unary(toSeq=lambda t, tokens: (t[0], DimensionValue(pushtoken(t, tokens), parent=self) )), PreDef.number(toSeq=lambda t, tokens: (t[0], DimensionValue(pushtoken(t, tokens), parent=self) )), PreDef.percentage(toSeq=lambda t, tokens: (t[0], DimensionValue(pushtoken(t, tokens), parent=self) )) ) noalp = Sequence(Prod(name='FUNCTION', match=lambda t, v: t == types.FUNCTION and v.lower() in ('rgb(', 'hsl('), toSeq=lambda t, tokens: (t[0], normalize(t[1]))), component, Sequence(PreDef.comma(optional=True), component, minmax=lambda: (2, 2) ), PreDef.funcEnd(stop=True) ) witha = Sequence(Prod(name='FUNCTION', match=lambda t, v: t == types.FUNCTION and v.lower() in ('rgba(', 'hsla('), toSeq=lambda t, tokens: (t[0], normalize(t[1])) ), component, Sequence(PreDef.comma(optional=True), component, minmax=lambda: (3, 3) ), PreDef.funcEnd(stop=True) ) namedcolor = Prod(name='Named Color', match=lambda t, v: t == 'IDENT' and ( normalize(v) in as_list(self.COLORS.keys()) ), stop=True) prods = Choice(PreDef.hexcolor(stop=True), namedcolor, noalp, witha) ok, seq, store, unused = ProdParser().parse(cssText, self.type, prods) self.wellformed = ok if ok: t, v = seq[0].type, seq[0].value if 'IDENT' == t: rgba = self.COLORS[normalize(v)] if 'HASH' == t: if len(v) == 4: # HASH #rgb rgba = (int(2*v[1], 16), int(2*v[2], 16), int(2*v[3], 16), 1.0) else: # HASH #rrggbb rgba = (int(v[1:3], 16), int(v[3:5], 16), int(v[5:7], 16), 1.0) elif 'FUNCTION' == t: functiontype, raw, check = None, [], '' HSL = False for item in seq: try: type_ = item.value.type except AttributeError: # type of function, e.g. rgb( if item.type == 'FUNCTION': functiontype = item.value HSL = functiontype in ('hsl(', 'hsla(') continue # save components if type_ == Value.NUMBER: raw.append(item.value.value) check += 'N' elif type_ == Value.PERCENTAGE: if HSL: # save as percentage fraction raw.append(item.value.value / 100.0) else: # save as real value of percentage of 255 raw.append(int(255 * item.value.value / 100)) check += 'P' if HSL: # convert to rgb # h is 360 based (circle) h, s, l_ = raw[0] / 360.0, raw[1], raw[2] # ORDER h l_ s !!! r, g, b = colorsys.hls_to_rgb(h, l_, s) # back to 255 based rgba = [int(round(r*255)), int(round(g*255)), int(round(b*255))] if len(raw) > 3: rgba.append(raw[3]) else: # rgb, rgba rgba = raw if len(rgba) < 4: rgba.append(1.0) # validate checks = {'rgb(': ('NNN', 'PPP'), 'rgba(': ('NNNN', 'PPPN'), 'hsl(': ('NPP',), 'hsla(': ('NPPN',) } if check not in checks[functiontype]: self._log.error('ColorValue has invalid %s) parameters: ' '%s (N=Number, P=Percentage)' % (functiontype, check)) self._colorType = t self._red, self._green, self._blue, self._alpha = tuple(rgba) self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_ColorValue(self), _setCssText, doc="String value of this value.") value = property(lambda self: css_parser.ser.do_css_CSSFunction(self, True), doc='Same as cssText but without comments.') type = property(lambda self: Value.COLOR_VALUE, doc="Type is fixed to Value.COLOR_VALUE.") def _getName(self): for n, v in as_list(self.COLORS.items()): if v == (self.red, self.green, self.blue, self.alpha): return n colorType = property(lambda self: self._colorType, doc="IDENT (red), HASH (#f00) or FUNCTION (rgb(255, 0, 0).") name = property(_getName, doc='Name of the color if known (in ColorValue.COLORS) ' 'else None') red = property(lambda self: self._red, doc='red part as integer between 0 and 255') green = property(lambda self: self._green, doc='green part as integer between 0 and 255') blue = property(lambda self: self._blue, doc='blue part as integer between 0 and 255') alpha = property(lambda self: self._alpha, doc='alpha part as float between 0.0 and 1.0') class DimensionValue(Value): """ A numerical value with an optional dimension like e.g. "px" or "%". Covers DIMENSION, PERCENTAGE or NUMBER values. """ __reUnNumDim = re.compile(r'^([+-]?)(\d*\.\d+|\d+)(.*)$', re.I | re.U | re.X) _dimension = None _sign = None def __str__(self): return ""\ % (self.__class__.__name__, self.type, self.value, self.dimension, self.cssText, id(self)) def _setCssText(self, cssText): self._checkReadonly() prods = Sequence( # PreDef.unary(), Choice(PreDef.dimension(stop=True), PreDef.number(stop=True), PreDef.percentage(stop=True) ) ) ok, seq, store, unused = ProdParser().parse(cssText, 'DimensionValue', prods) self.wellformed = ok if ok: item = seq[0] sign, v, d = self.__reUnNumDim.findall( normalize(item.value))[0] if '.' in v: val = float(sign + v) else: val = int(sign + v) dim = None if d: dim = d self._sign = sign self._value = val self._dimension = dim self._type = item.type self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_Value(self), _setCssText, doc="String value of this value including dimension.") dimension = property(lambda self: self._dimension, # _setValue, doc="Dimension if a DIMENSION or PERCENTAGE value, " "else None") class URIValue(Value): """ An URI value like ``url(example.png)``. """ _type = Value.URI _uri = Value._value def __str__(self): return ""\ % (self.__class__.__name__, self.type, self.value, self.uri, self.cssText, id(self)) def _setCssText(self, cssText): self._checkReadonly() prods = Sequence(PreDef.uri(stop=True)) ok, seq, store, unused = ProdParser().parse(cssText, 'URIValue', prods) self.wellformed = ok if ok: # only 1 value only anyway self._type = seq[0].type self._value = seq[0].value self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_Value(self), _setCssText, doc='String value of this value.') def _setUri(self, uri): # TODO: check? self._value = uri uri = property(lambda self: self._value, _setUri, doc="Actual URL without delimiters or the empty string") def absoluteUri(self): """Actual URL, made absolute if possible, else same as `uri`.""" # Ancestry: PropertyValue, Property, CSSStyleDeclaration, CSSStyleRule, # CSSStyleSheet try: # TODO: better way? styleSheet = self.parent.parent.parent.parentRule.parentStyleSheet except AttributeError: return self.uri else: return urljoin(styleSheet.href, self.uri) absoluteUri = property(absoluteUri, doc=absoluteUri.__doc__) class CSSFunction(Value): """ A function value. """ _functionName = 'Function' def _productions(self): """Return definition used for parsing.""" types = self._prods # rename! itemProd = Choice(_ColorProd(self), _DimensionProd(self), _URIProd(self), _ValueProd(self), _CalcValueProd(self), _CSSVariableProd(self), _CSSFunctionProd(self) ) funcProds = Sequence(Prod(name='FUNCTION', match=lambda t, v: t == types.FUNCTION, toSeq=lambda t, tokens: (t[0], normalize(t[1]))), Choice(Sequence(itemProd, Sequence(PreDef.comma(optional=True), itemProd, minmax=lambda: (0, None)), PreDef.funcEnd(stop=True)), PreDef.funcEnd(stop=True)) ) return funcProds def _setCssText(self, cssText): self._checkReadonly() ok, seq, store, unused = ProdParser().parse(cssText, self.type, self._productions()) self.wellformed = ok if ok: self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_CSSFunction(self), _setCssText, doc="String value of this value.") value = property(lambda self: css_parser.ser.do_css_CSSFunction(self, True), doc='Same as cssText but without comments.') type = property(lambda self: Value.FUNCTION, doc="Type is fixed to Value.FUNCTION.") class MSValue(CSSFunction): """An IE specific Microsoft only function value which is much looser in what is syntactically allowed.""" _functionName = 'MSValue' def _productions(self): """Return definition used for parsing.""" types = self._prods # rename! func = Prod(name='MSValue-Sub', match=lambda t, v: t == self._prods.FUNCTION, toSeq=lambda t, tokens: (MSValue._functionName, MSValue(pushtoken(t, tokens ), parent=self ) ) ) funcProds = Sequence(Prod(name='FUNCTION', match=lambda t, v: t == types.FUNCTION, toSeq=lambda t, tokens: (t[0], t[1]) ), Sequence(Choice(_ColorProd(self), _DimensionProd(self), _URIProd(self), _ValueProd(self), _MSValueProd(self), # _CalcValueProd(self), _CSSVariableProd(self), func, # _CSSFunctionProd(self), Prod(name='MSValuePart', match=lambda t, v: v != ')', toSeq=lambda t, tokens: (t[0], t[1]) ) ), minmax=lambda: (0, None) ), PreDef.funcEnd(stop=True) ) return funcProds def _setCssText(self, cssText): super(MSValue, self)._setCssText(cssText) cssText = property(lambda self: css_parser.ser.do_css_MSValue(self), _setCssText, doc="String value of this value.") class CSSCalc(CSSFunction): """The CSSCalc function represents a CSS calc() function. No further API is provided. For multiplication and division no check if one operand is a NUMBER is made. """ _functionName = 'CSSCalc' def __str__(self): return "" % ( self.__class__.__name__, id(self)) def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! _operator = Choice(Prod(name='Operator */', match=lambda t, v: v in '*/', toSeq=lambda t, tokens: (t[0], t[1]) ), Sequence( PreDef.S(), Choice( Sequence( Prod(name='Operator */', match=lambda t, v: v in '*/', toSeq=lambda t, tokens: (t[0], t[1]) ), PreDef.S(optional=True) ), Sequence( Prod(name='Operator +-', match=lambda t, v: v in '+-', toSeq=lambda t, tokens: (t[0], t[1]) ), PreDef.S() ), PreDef.funcEnd(stop=True, mayEnd=True) ) ) ) def _operant(): return Choice(_DimensionProd(self), _CSSVariableProd(self)) prods = Sequence(Prod(name='CALC', match=lambda t, v: t == types.FUNCTION and normalize(v) == 'calc(' ), PreDef.S(optional=True), _operant(), Sequence(_operator, _operant(), minmax=lambda: (0, None) ), PreDef.funcEnd(stop=True) ) # store: name of variable ok, seq, store, unused = ProdParser().parse(cssText, 'CSSCalc', prods, checkS=True) self.wellformed = ok if ok: self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_CSSCalc(self), _setCssText, doc="String representation of calc function.") type = property(lambda self: Value.CALC, doc="Type is fixed to Value.CALC.") class CSSVariable(CSSFunction): """The CSSVariable represents a CSS variables like ``var(varname)``. A variable has a (nonnormalized!) `name` and a `value` which is tried to be resolved from any available CSSVariablesRule definition. """ _functionName = 'CSSVariable' _name = None _fallback = None def __str__(self): return "" % ( self.__class__.__name__, self.name, self.value, id(self)) def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! prods = Sequence(Prod(name='var', match=lambda t, v: t == types.FUNCTION and normalize(v) == 'var(' ), PreDef.ident(toStore='ident'), Sequence(PreDef.comma(), Choice(_ColorProd(self, toStore='fallback'), _DimensionProd(self, toStore='fallback'), _URIProd(self, toStore='fallback'), _ValueProd(self, toStore='fallback'), _CalcValueProd(self, toStore='fallback'), _CSSVariableProd(self, toStore='fallback'), _CSSFunctionProd(self, toStore='fallback') ), minmax=lambda: (0, 1) ), PreDef.funcEnd(stop=True)) # store: name of variable store = {'ident': None, 'fallback': None} ok, seq, store, unused = ProdParser().parse(cssText, 'CSSVariable', prods) self.wellformed = ok if ok: self._name = store['ident'].value try: self._fallback = store['fallback'].value except KeyError: self._fallback = None self._setSeq(seq) cssText = property(lambda self: css_parser.ser.do_css_CSSVariable(self), _setCssText, doc="String representation of variable.") # TODO: writable? check if var (value) available? name = property(lambda self: self._name, doc="The name identifier of this variable referring to " "a value in a " ":class:`css_parser.css.CSSVariablesDeclaration`.") fallback = property(lambda self: self._fallback, doc="The fallback Value of this variable") type = property(lambda self: Value.VARIABLE, doc="Type is fixed to Value.VARIABLE.") def _getValue(self): "Find contained sheet and @variables there" rel = self while True: # find node which has parentRule to get to StyleSheet if hasattr(rel, 'parent'): rel = rel.parent else: break try: variables = rel.parentRule.parentStyleSheet.variables except AttributeError: return None else: try: return variables[self.name] except KeyError: return None value = property(_getValue, doc='The resolved actual value or None.') # helper for productions def _ValueProd(parent, nextSor=False, toStore=None): return Prod(name='Value', match=lambda t, v: t in ('IDENT', 'STRING', 'UNICODE-RANGE'), nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: ('Value', Value(pushtoken(t, tokens), parent=parent) ) ) def _DimensionProd(parent, nextSor=False, toStore=None): return Prod(name='Dimension', match=lambda t, v: t in ('DIMENSION', 'NUMBER', 'PERCENTAGE'), nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: ('DIMENSION', DimensionValue( pushtoken(t, tokens), parent=parent) ) ) def _URIProd(parent, nextSor=False, toStore=None): return Prod(name='URIValue', match=lambda t, v: t == 'URI', toStore=toStore, nextSor=nextSor, toSeq=lambda t, tokens: ('URIValue', URIValue( pushtoken(t, tokens), parent=parent) ) ) reHexcolor = re.compile(r'^\#(?:[0-9abcdefABCDEF]{3}|[0-9abcdefABCDEF]{6})$') def _ColorProd(parent, nextSor=False, toStore=None): return Prod(name='ColorValue', match=lambda t, v: (t == 'HASH' and reHexcolor.match(v) ) or (t == 'FUNCTION' and normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') ) or (t == 'IDENT' and normalize(v) in as_list(ColorValue.COLORS.keys()) ), nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: ('ColorValue', ColorValue( pushtoken(t, tokens), parent=parent) ) ) def _CSSFunctionProd(parent, nextSor=False, toStore=None): return PreDef.function(nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: (CSSFunction._functionName, CSSFunction( pushtoken(t, tokens), parent=parent) ) ) def _CalcValueProd(parent, nextSor=False, toStore=None): return Prod(name=CSSCalc._functionName, match=lambda t, v: t == PreDef.types.FUNCTION and normalize(v) == 'calc(', toStore=toStore, toSeq=lambda t, tokens: (CSSCalc._functionName, CSSCalc( pushtoken(t, tokens), parent=parent) ), nextSor=nextSor) def _CSSVariableProd(parent, nextSor=False, toStore=None): return PreDef.variable(nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: (CSSVariable._functionName, CSSVariable( pushtoken(t, tokens), parent=parent) ) ) def _MSValueProd(parent, nextSor=False): return Prod(name=MSValue._functionName, match=lambda t, v: ( # t == self._prods.FUNCTION and ( normalize(v) in ('expression(', 'alpha(', 'blur(', 'chroma(', 'dropshadow(', 'fliph(', 'flipv(', 'glow(', 'gray(', 'invert(', 'mask(', 'shadow(', 'wave(', 'xray(') or v.startswith('progid:DXImageTransform.Microsoft.') ), nextSor=nextSor, toSeq=lambda t, tokens: (MSValue._functionName, MSValue(pushtoken(t, tokens ), parent=parent ) ) ) def MediaQueryValueProd(parent): return Choice(_ColorProd(parent), _DimensionProd(parent), _ValueProd(parent), PreDef.ratio(), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/cssproductions.py0000644000175000017500000001150100000000000021201 0ustar00kovidkovid"""productions for css_parser based on a mix of CSS 2.1 and CSS 3 Syntax productions - http://www.w3.org/TR/css3-syntax - http://www.w3.org/TR/css3-syntax/#grammar0 open issues - numbers contain "-" if present - HASH: #aaa is, #000 is not anymore, CSS2.1: 'nmchar': r'[_a-z0-9-]|{nonascii}|{escape}', CSS3: 'nmchar': r'[_a-z-]|{nonascii}|{escape}', """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS'] __docformat__ = 'restructuredtext' __version__ = '$Id$' # a complete list of css3 macros MACROS = { 'nonascii': r'[^\0-\177]', 'unicode': r'\\[0-9A-Fa-f]{1,6}(?:{nl}|{s})?', # 'escape': r'{unicode}|\\[ -~\200-\777]', 'escape': r'{unicode}|\\[^\n\r\f0-9a-f]', 'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}', 'nmchar': r'[-_a-zA-Z0-9]|{nonascii}|{escape}', 'string1': r'"([^\n\r\f\\"]|\\{nl}|{escape})*"', 'string2': r"'([^\n\r\f\\']|\\{nl}|{escape})*'", 'invalid1': r'\"([^\n\r\f\\"]|\\{nl}|{escape})*', 'invalid2': r"\'([^\n\r\f\\']|\\{nl}|{escape})*", 'comment': r'\/\*[^*]*\*+([^/][^*]*\*+)*\/', 'ident': r'[-]?{nmstart}{nmchar}*', 'name': r'{nmchar}+', # TODO??? 'num': r'[+-]?[0-9]*\.[0-9]+|[+-]?[0-9]+', # r'[-]?\d+|[-]?\d*\.\d+', 'string': r'{string1}|{string2}', # from CSS2.1 'invalid': r'{invalid1}|{invalid2}', 'url': r'[\x09\x21\x23-\x26\x28\x2a-\x7E]|{nonascii}|{escape}', 's': r'\t|\r|\n|\f|\x20', 'w': r'{s}*', 'nl': r'\n|\r\n|\r|\f', 'A': r'A|a|\\0{0,4}(?:41|61)(?:\r\n|[ \t\r\n\f])?', 'B': r'B|b|\\0{0,4}(?:42|62)(?:\r\n|[ \t\r\n\f])?', 'C': r'C|c|\\0{0,4}(?:43|63)(?:\r\n|[ \t\r\n\f])?', 'D': r'D|d|\\0{0,4}(?:44|64)(?:\r\n|[ \t\r\n\f])?', 'E': r'E|e|\\0{0,4}(?:45|65)(?:\r\n|[ \t\r\n\f])?', 'F': r'F|f|\\0{0,4}(?:46|66)(?:\r\n|[ \t\r\n\f])?', 'G': r'G|g|\\0{0,4}(?:47|67)(?:\r\n|[ \t\r\n\f])?|\\G|\\g', 'H': r'H|h|\\0{0,4}(?:48|68)(?:\r\n|[ \t\r\n\f])?|\\H|\\h', 'I': r'I|i|\\0{0,4}(?:49|69)(?:\r\n|[ \t\r\n\f])?|\\I|\\i', 'K': r'K|k|\\0{0,4}(?:4b|6b)(?:\r\n|[ \t\r\n\f])?|\\K|\\k', 'L': r'L|l|\\0{0,4}(?:4c|6c)(?:\r\n|[ \t\r\n\f])?|\\L|\\l', 'M': r'M|m|\\0{0,4}(?:4d|6d)(?:\r\n|[ \t\r\n\f])?|\\M|\\m', 'N': r'N|n|\\0{0,4}(?:4e|6e)(?:\r\n|[ \t\r\n\f])?|\\N|\\n', 'O': r'O|o|\\0{0,4}(?:4f|6f)(?:\r\n|[ \t\r\n\f])?|\\O|\\o', 'P': r'P|p|\\0{0,4}(?:50|70)(?:\r\n|[ \t\r\n\f])?|\\P|\\p', 'R': r'R|r|\\0{0,4}(?:52|72)(?:\r\n|[ \t\r\n\f])?|\\R|\\r', 'S': r'S|s|\\0{0,4}(?:53|73)(?:\r\n|[ \t\r\n\f])?|\\S|\\s', 'T': r'T|t|\\0{0,4}(?:54|74)(?:\r\n|[ \t\r\n\f])?|\\T|\\t', 'U': r'U|u|\\0{0,4}(?:55|75)(?:\r\n|[ \t\r\n\f])?|\\U|\\u', 'V': r'V|v|\\0{0,4}(?:56|76)(?:\r\n|[ \t\r\n\f])?|\\V|\\v', 'X': r'X|x|\\0{0,4}(?:58|78)(?:\r\n|[ \t\r\n\f])?|\\X|\\x', 'Z': r'Z|z|\\0{0,4}(?:5a|7a)(?:\r\n|[ \t\r\n\f])?|\\Z|\\z', } # The following productions are the complete list of tokens # used by css_parser, a mix of CSS3 and some CSS2.1 productions. # The productions are **ordered**: PRODUCTIONS = [ # UTF8_BOM or UTF8_BOM_SIG will only be checked at beginning of CSS ('BOM', '\xfe\xff|\xef\xbb\xbf'), ('S', r'{s}+'), # 1st in list of general productions ('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'), ('RATIO', r'(?'), ('CHAR', r'[^"\']') # MUST always be last # valid ony at start so not checked everytime # ('CHARSET_SYM', r'@charset '), # from Errata includes ending space! # checked specially if fullsheet is parsed ] class CSSProductions(object): """ most attributes are set later """ EOF = True # removed from productions as they simply are ATKEYWORD until # tokenizing CHARSET_SYM = 'CHARSET_SYM' FONT_FACE_SYM = 'FONT_FACE_SYM' MEDIA_SYM = 'MEDIA_SYM' IMPORT_SYM = 'IMPORT_SYM' NAMESPACE_SYM = 'NAMESPACE_SYM' PAGE_SYM = 'PAGE_SYM' VARIABLES_SYM = 'VARIABLES_SYM' for i, t in enumerate(PRODUCTIONS): setattr(CSSProductions, t[0].replace('-', '_'), t[0]) # may be enabled by settings.set _DXImageTransform = ('FUNCTION', r'progid\:DXImageTransform\.Microsoft\..+\(' ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639191348.1373482 css-parser-1.0.7/src/css_parser/encutils/0000755000175000017500000000000000000000000017375 5ustar00kovidkovid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563228.0 css-parser-1.0.7/src/css_parser/encutils/__init__.py0000644000175000017500000005472400000000000021522 0ustar00kovidkovid#!/usr/bin/env python # -*- coding: utf-8 -*- """encutils - encoding detection collection for Python :Version: 0.9.8 :Author: Christof Hoeke, see http://cthedot.de/encutils/ :Contributor: Robert Siemer, Fredrik Hedman ported to python3 :Copyright: 2005-2012: Christof Hoeke :License: encutils has a dual-license, please choose whatever you prefer: * encutils is published under the `LGPL 3 or later `__ * encutils is published under the `Creative Commons License `__. encutils is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. encutils 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with encutils. If not, see . A collection of helper functions to detect encodings of text files (like HTML, XHTML, XML, CSS, etc.) retrieved via HTTP, file or string. :func:`getEncodingInfo` is probably the main function of interest which uses other supplied functions itself and gathers all information together and supplies an :class:`EncodingInfo` object. example:: >>> import encutils >>> info = encutils.getEncodingInfo(url='http://cthedot.de/encutils/') >>> str(info) utf-8 >>> repr(info) # doctest:+ELLIPSIS >>> info.logtext HTTP media_type: text/html HTTP encoding: utf-8 Encoding (probably): utf-8 (Mismatch: False) references XML RFC 3023 (http://www.ietf.org/rfc/rfc3023.txt) easier explained in - http://feedparser.org/docs/advanced.html - http://www.xml.com/pub/a/2004/07/21/dive.html HTML http://www.w3.org/TR/REC-html40/charset.html#h-5.2.2 TODO - parse @charset of HTML elements? - check for more texttypes if only text given """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = [ 'buildlog', 'encodingByMediaType', 'getHTTPInfo', 'getMetaInfo', 'detectXMLEncoding', 'getEncodingInfo', 'tryEncodings', 'EncodingInfo' ] __docformat__ = 'restructuredtext' __author__ = 'Christof Hoeke, Robert Siemer, Fredrik Hedman' __version__ = '$Id$' import sys import cgi import re import types PY2x = sys.version_info < (3, 0) if PY2x: from StringIO import StringIO as io_StringIO from HTMLParser import HTMLParser as htmlparser_HTMLParser from HTMLParser import HTMLParseError as htmlparser_HTMLParseError from urllib2 import urlopen as urllib_urlopen else: from io import StringIO as io_StringIO from html.parser import HTMLParser as htmlparser_HTMLParser from urllib.request import urlopen as urllib_urlopen VERSION = '0.9.8' class _MetaHTMLParser(htmlparser_HTMLParser): """Parse given data for .""" content_type = None def handle_starttag(self, tag, attrs): if tag == 'meta' and not self.content_type: atts = dict([(a.lower(), v.lower()) for a, v in attrs]) if atts.get('http-equiv', '').strip() == 'content-type': self.content_type = atts.get('content') # application/xml, application/xml-dtd, application/xml-external-parsed-entity, or a subtype like application/rss+xml. _XML_APPLICATION_TYPE = 0 # text/xml, text/xml-external-parsed-entity, or a subtype like text/AnythingAtAll+xml _XML_TEXT_TYPE = 1 # text/html _HTML_TEXT_TYPE = 2 # any other of text/* like text/plain, ... _TEXT_TYPE = 3 # any text/* like which defaults to UTF-8 encoding, for now only text/css _TEXT_UTF8 = 5 # types not fitting in above types _OTHER_TYPE = 4 class EncodingInfo(object): """ All encoding related information, returned by :func:`getEncodingInfo`. Attributes filled: - ``encoding``: The guessed encoding Encoding is the explicit or implicit encoding or None and always lowercase. - from HTTP response * ``http_encoding`` * ``http_media_type`` - from HTML element * ``meta_encoding`` * ``meta_media_type`` - from XML declaration * ``xml_encoding`` - ``mismatch``: True if mismatch between XML declaration and HTTP header. Mismatch is True if any mismatches between HTTP header, XML declaration or textcontent (meta) are found. More detailed mismatch reports are written to the optional log or ``logtext`` Mismatches are not necessarily errors as preferences are defined. For details see the specifications. - ``logtext``: if no log was given log reports are given here """ def __init__(self): """Initialize all possible properties to ``None``, see class description """ self.encoding = self.mismatch = self.logtext =\ self.http_encoding = self.http_media_type =\ self.meta_encoding = self.meta_media_type =\ self.xml_encoding = None def __str__(self): """Output the guessed encoding itself or the empty string.""" if self.encoding: return self.encoding else: return '' def __repr__(self): return "<%s.%s object encoding=%r mismatch=%s at 0x%x>" % ( self.__class__.__module__, self.__class__.__name__, self.encoding, self.mismatch, id(self)) def buildlog(logname='encutils', level='INFO', stream=sys.stderr, filename=None, filemode="w", format='%(levelname)s\t%(message)s'): """Helper to build a basic log - if `filename` is given returns a log logging to `filename` with mode `filemode` - else uses a log streaming to `stream` which defaults to `sys.stderr` - `level` defines the level of the log - `format` defines the formatter format of the log :returns: a log with the name `logname` """ import logging log = logging.getLogger(logname) if filename: hdlr = logging.FileHandler(filename, filemode) else: hdlr = logging.StreamHandler(stream) formatter = logging.Formatter(format) hdlr.setFormatter(formatter) log.addHandler(hdlr) log.setLevel(logging.__dict__.get(level, logging.INFO)) return log def _getTextTypeByMediaType(media_type, log=None): """ :returns: type as defined by constants in this class """ if not media_type: return _OTHER_TYPE xml_application_types = [ r'application/.*?\+xml', 'application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity' ] xml_text_types = [ r'text\/.*?\+xml', 'text/xml', 'text/xml-external-parsed-entity' ] media_type = media_type.strip().lower() if media_type in xml_application_types or\ re.match(xml_application_types[0], media_type, re.I | re.S | re.X): return _XML_APPLICATION_TYPE elif media_type in xml_text_types or\ re.match(xml_text_types[0], media_type, re.I | re.S | re.X): return _XML_TEXT_TYPE elif media_type == 'text/html': return _HTML_TEXT_TYPE elif media_type == 'text/css': return _TEXT_UTF8 elif media_type.startswith('text/'): return _TEXT_TYPE else: return _OTHER_TYPE def _getTextType(text, log=None): """Check if given text is XML (**naive test!**) used if no content-type given """ if text[:30].find('`` element if available in `text`. XHTML format:: """ p = _MetaHTMLParser() try: p.feed(text) except htmlparser_HTMLParseError: pass if p.content_type: media_type, params = cgi.parse_header(p.content_type) encoding = params.get('charset') # defaults to None if encoding: encoding = encoding.lower() if log: log.info('HTML META media_type: %s', media_type) log.info('HTML META encoding: %s', encoding) else: media_type = encoding = None return media_type, encoding def detectXMLEncoding(fp, log=None, includeDefault=True): """Attempt to detect the character encoding of the xml file given by a file object `fp`. `fp` must not be a codec wrapped file object! `fp` may be a string or unicode string though. Based on a recipe by Lars Tiede: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/363841 which itself is based on Paul Prescotts recipe: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52257 :returns: - if detection of the BOM succeeds, the codec name of the corresponding unicode charset is returned - if BOM detection fails, the xml declaration is searched for the encoding attribute and its value returned. the "<" character has to be the very first in the file then (it's xml standard after all). - if BOM and xml declaration fail, utf-8 is returned according to XML 1.0. """ if PY2x and isinstance(fp, types.StringTypes): fp = io_StringIO(fp) elif isinstance(fp, str): fp = io_StringIO(fp) # detection using BOM # the BOMs we know, by their pattern bomDict = { # bytepattern: name (0x00, 0x00, 0xFE, 0xFF): "utf_32_be", (0xFF, 0xFE, 0x00, 0x00): "utf_32_le", (0xFE, 0xFF, None, None): "utf_16_be", (0xFF, 0xFE, None, None): "utf_16_le", (0xEF, 0xBB, 0xBF, None): "utf-8", } # go to beginning of file and get the first 4 bytes oldFP = fp.tell() fp.seek(0) (byte1, byte2, byte3, byte4) = tuple(map(ord, fp.read(4))) # try bom detection using 4 bytes, 3 bytes, or 2 bytes bomDetection = bomDict.get((byte1, byte2, byte3, byte4)) if not bomDetection: bomDetection = bomDict.get((byte1, byte2, byte3, None)) if not bomDetection: bomDetection = bomDict.get((byte1, byte2, None, None)) # if BOM detected, we're done :-) if bomDetection: if log: log.info('XML BOM encoding: %s' % bomDetection) fp.seek(oldFP) return bomDetection # still here? BOM detection failed. # now that BOM detection has failed we assume one byte character # encoding behaving ASCII # search xml declaration for encoding attribute # assume xml declaration fits into the first 2 KB (*cough*) fp.seek(0) buffer = fp.read(2048) # set up regular expression xmlDeclPattern = r""" ^<\?xml # w/o BOM, xmldecl starts with # what's matched in the brackets will be named encstr [^"']+ # every character not delimiter (not overly exact!) ) # closes the brackets pair for the named group ["'] # attribute end delimiter .*? # some chars optionally (standalone decl or whitespace) \?> # xmldecl end """ xmlDeclRE = re.compile(xmlDeclPattern, re.VERBOSE) # search and extract encoding string match = xmlDeclRE.search(buffer) fp.seek(oldFP) if match: enc = match.group("encstr").lower() if log: log.info('XML encoding="%s"' % enc) return enc else: if includeDefault: if log: log.info('XML encoding default utf-8') return 'utf-8' else: return None def tryEncodings(text, log=None): """If installed uses chardet http://chardet.feedparser.org/ to detect encoding, else tries different encodings on `text` and returns the one that does not raise an exception which is not very advanced or may be totally wrong. The tried encoding are in order 'ascii', 'iso-8859-1', 'windows-1252' (which probably will never happen as 'iso-8859-1' can decode these strings too) and lastly 'utf-8'. :param text: a byte string :returns: Working encoding or ``None`` if no encoding does work at all. The returned encoding might nevertheless be not the one intended by the author as it is only checked if the text might be encoded in that encoding. Some texts might be working in "iso-8859-1" *and* "windows-1252" *and* "ascii" *and* "utf-8" and ... """ try: import chardet encoding = chardet.detect(text)["encoding"] except ImportError: msg = 'Using simplified encoding detection, you might want to install chardet.' if log: log.warning(msg) else: print(msg) encodings = ( 'ascii', 'iso-8859-1', # 'windows-1252', # test later 'utf-8') encoding = None for e in encodings: try: text.decode(e) except UnicodeDecodeError: pass else: if 'iso-8859-1' == e: try: if '€' in text.decode('windows-1252'): return 'windows-1252' except UnicodeDecodeError: pass return e return encoding def getEncodingInfo(response=None, text='', log=None, url=None): """Find all encoding related information in given `text`. Information in headers of supplied HTTPResponse, possible XML declaration and X/HTML ```` elements are used. :param response: HTTP response object, e.g. via ``urllib.urlopen('url')`` :param text: a byte string to guess encoding for. XML prolog with encoding pseudo attribute or HTML meta element will be used to detect the encoding :param url: When given fetches document at `url` and all needed information. No `reponse` or `text` parameters are needed in this case. :param log: an optional logging logger to which messages may go, if no log given all log messages are available from resulting ``EncodingInfo`` :returns: instance of :class:`EncodingInfo`. How the resulting encoding is retrieved: XML RFC 3023 states if media type given in the Content-Type HTTP header is application/xml, application/xml-dtd, application/xml-external-parsed-entity, or any one of the subtypes of application/xml such as application/atom+xml or application/rss+xml etc then the character encoding is determined in this order: 1. the encoding given in the charset parameter of the Content-Type HTTP header, or 2. the encoding given in the encoding attribute of the XML declaration within the document, or 3. utf-8. Mismatch possibilities: - HTTP + XMLdecla - HTTP + HTMLmeta application/xhtml+xml ? XMLdecla + HTMLmeta If the media type given in the Content-Type HTTP header is text/xml, text/xml-external-parsed-entity, or a subtype like text/Anything+xml, the encoding attribute of the XML declaration is ignored completely and the character encoding is determined in the order: 1. the encoding given in the charset parameter of the Content-Type HTTP header, or 2. ascii. No mismatch possible. If no media type is given the XML encoding pseuso attribute is used if present. No mismatch possible. HTML For HTML served as text/html: http://www.w3.org/TR/REC-html40/charset.html#h-5.2.2 1. An HTTP "charset" parameter in a "Content-Type" field. (maybe defaults to ISO-8859-1, but should not assume this) 2. A META declaration with "http-equiv" set to "Content-Type" and a value set for "charset". 3. The charset attribute set on an element that designates an external resource. (NOT IMPLEMENTED HERE YET) Mismatch possibilities: - HTTP + HTMLmeta TEXT For most text/* types the encoding will be reported as iso-8859-1. Exceptions are XML formats send as text/* mime type (see above) and text/css which has a default encoding of UTF-8. """ if url: # may cause IOError which is raised response = urllib_urlopen(url) if text is None: # read text from response only if not explicitly given try: text = response.read() except IOError: pass if text is None: # text must be a string (not None) text = '' encinfo = EncodingInfo() logstream = io_StringIO() if not log: log = buildlog(stream=logstream, format='%(message)s') # HTTP if response: encinfo.http_media_type, encinfo.http_encoding = getHTTPInfo( response, log) texttype = _getTextTypeByMediaType(encinfo.http_media_type, log) else: # check if maybe XML or (TODO:) HTML texttype = _getTextType(text, log) # XML only served as application/xml ! #(also XHTML served as text/html) if texttype == _XML_APPLICATION_TYPE: # or texttype == _XML_TEXT_TYPE: try: encinfo.xml_encoding = detectXMLEncoding(text, log) except (AttributeError, ValueError): encinfo.xml_encoding = None # XML (also XHTML served as text/html) if texttype == _HTML_TEXT_TYPE: try: encinfo.xml_encoding = detectXMLEncoding( text, log, includeDefault=False) except (AttributeError, ValueError): encinfo.xml_encoding = None # HTML if texttype == _HTML_TEXT_TYPE or texttype == _TEXT_TYPE: encinfo.meta_media_type, encinfo.meta_encoding = getMetaInfo(text, log) # guess # 1. HTTP charset? encinfo.encoding = encinfo.http_encoding encinfo.mismatch = False # 2. media_type? # XML application/... if texttype == _XML_APPLICATION_TYPE: if not encinfo.encoding: encinfo.encoding = encinfo.xml_encoding # xml_encoding has default of utf-8 # text/html elif texttype == _HTML_TEXT_TYPE: if not encinfo.encoding: encinfo.encoding = encinfo.meta_encoding if not encinfo.encoding: encinfo.encoding = encodingByMediaType(encinfo.http_media_type) if not encinfo.encoding: encinfo.encoding = tryEncodings(text) # text/... + xml or text/* elif texttype == _XML_TEXT_TYPE or texttype == _TEXT_TYPE: if not encinfo.encoding: encinfo.encoding = encodingByMediaType(encinfo.http_media_type) elif texttype == _TEXT_UTF8: if not encinfo.encoding: encinfo.encoding = encodingByMediaType(encinfo.http_media_type) # possible mismatches, checks if present at all and then if equal # HTTP + XML if encinfo.http_encoding and encinfo.xml_encoding and\ encinfo.http_encoding != encinfo.xml_encoding: encinfo.mismatch = True log.warning('"%s" (HTTP) != "%s" (XML) encoding mismatch' % (encinfo.http_encoding, encinfo.xml_encoding)) # HTTP + Meta if encinfo.http_encoding and encinfo.meta_encoding and\ encinfo.http_encoding != encinfo.meta_encoding: encinfo.mismatch = True log.warning('"%s" (HTTP) != "%s" (HTML ) encoding mismatch' % (encinfo.http_encoding, encinfo.meta_encoding)) # XML + Meta if encinfo.xml_encoding and encinfo.meta_encoding and\ encinfo.xml_encoding != encinfo.meta_encoding: encinfo.mismatch = True log.warning('"%s" (XML) != "%s" (HTML ) encoding mismatch' % (encinfo.xml_encoding, encinfo.meta_encoding)) log.info('Encoding (probably): %s (Mismatch: %s)', encinfo.encoding, encinfo.mismatch) encinfo.logtext = logstream.getvalue() return encinfo if __name__ == '__main__': import pydoc pydoc.help(__name__) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/errorhandler.py0000644000175000017500000001017200000000000020611 0ustar00kovidkovid#!/usr/bin/env python from __future__ import unicode_literals, division, absolute_import, print_function import xml.dom import logging """css_parser ErrorHandler ErrorHandler used as log with usual levels (debug, info, warn, error) if instanciated with ``raiseExceptions=True`` raises exeptions instead of logging log defaults to instance of ErrorHandler for any kind of log message from lexerm, parser etc. - raiseExceptions = [False, True] - setloglevel(loglevel) """ __all__ = ['ErrorHandler'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import sys if sys.version_info[0] >= 3: from urllib.error import HTTPError as urllib_HTTPError from urllib.error import URLError as urllib_URLError else: from urllib2 import HTTPError as urllib_HTTPError from urllib2 import URLError as urllib_URLError class _ErrorHandler(object): """ handles all errors and log messages """ def __init__(self, log, defaultloglevel=logging.INFO, raiseExceptions=True): """ inits log if none given log for parse messages, default logs to sys.stderr defaultloglevel if none give this is logging.DEBUG raiseExceptions - True: Errors will be raised e.g. during building - False: Errors will be written to the log, this is the default behaviour when parsing """ # may be disabled during setting of known valid items self.enabled = True if log: self._log = log else: import sys self._log = logging.getLogger('CSSUTILS') hdlr = logging.StreamHandler(sys.stderr) formatter = logging.Formatter('%(levelname)s\t%(message)s') hdlr.setFormatter(formatter) self._log.addHandler(hdlr) self._log.setLevel(defaultloglevel) self.raiseExceptions = raiseExceptions def __getattr__(self, name): "use self._log items" calls = ('debug', 'info', 'warn', 'error', 'critical', 'fatal') other = ('setLevel', 'getEffectiveLevel', 'addHandler', 'removeHandler') if name in calls: if name == 'warn': name = 'warning' self._logcall = getattr(self._log, name) return self.__handle elif name in other: return getattr(self._log, name) else: raise AttributeError( '(errorhandler) No Attribute %r found' % name) def __handle(self, msg='', token=None, error=xml.dom.SyntaxErr, neverraise=False, args=None): """ handles all calls logs or raises exception """ if self.enabled: if error is None: error = xml.dom.SyntaxErr line, col = None, None if token: if isinstance(token, tuple): value, line, col = token[1], token[2], token[3] else: value, line, col = token.value, token.line, token.col msg = '%s [%s:%s: %s]' % ( msg, line, col, value) if error and self.raiseExceptions and not neverraise: if isinstance(error, urllib_HTTPError) or isinstance(error, urllib_URLError): raise elif issubclass(error, xml.dom.DOMException): error.line = line error.col = col raise error(msg) else: self._logcall(msg) def setLog(self, log): """set log of errorhandler's log""" self._log = log class ErrorHandler(_ErrorHandler): "Singleton, see _ErrorHandler" instance = None def __init__(self, log=None, defaultloglevel=logging.INFO, raiseExceptions=True): if ErrorHandler.instance is None: ErrorHandler.instance = _ErrorHandler(log=log, defaultloglevel=defaultloglevel, raiseExceptions=raiseExceptions) self.__dict__ = ErrorHandler.instance.__dict__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/helper.py0000644000175000017500000000766200000000000017413 0ustar00kovidkovid"""css_parser helper TEST """ from __future__ import unicode_literals, division, absolute_import, print_function __docformat__ = 'restructuredtext' __version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $' import os import re import sys if sys.version_info[0] >= 3: from urllib.request import pathname2url as urllib_pathname2url else: from urllib import pathname2url as urllib_pathname2url class Deprecated(object): """This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used. It accepts a single paramter ``msg`` which is shown with the warning. It should contain information which function or method to use instead. """ def __init__(self, msg): self.msg = msg def __call__(self, func): def newFunc(*args, **kwargs): import warnings warnings.warn("Call to deprecated method %r. %s" % (func.__name__, self.msg), category=DeprecationWarning, stacklevel=2) return func(*args, **kwargs) newFunc.__name__ = func.__name__ newFunc.__doc__ = func.__doc__ newFunc.__dict__.update(func.__dict__) return newFunc # simple escapes, all non unicodes _simpleescapes = re.compile(r'(\\[^0-9a-fA-F])').sub def normalize(x): r""" normalizes x, namely: - remove any \ before non unicode sequences (0-9a-zA-Z) so for x=="c\olor\" return "color" (unicode escape sequences should have been resolved by the tokenizer already) - lowercase """ if x: def removeescape(matchobj): return matchobj.group(0)[1:] x = _simpleescapes(removeescape, x) return x.lower() else: return x def path2url(path): """Return file URL of `path`""" return 'file:' + urllib_pathname2url(os.path.abspath(path)) def pushtoken(token, tokens): """Return new generator starting with token followed by all tokens in ``tokens``""" # TODO: may use itertools.chain? yield token for t in tokens: yield t def string(value): """ Serialize value with quotes e.g.:: ``a \'string`` => ``'a \'string'`` """ # \n = 0xa, \r = 0xd, \f = 0xc value = value.replace('\n', '\\a ').replace( '\r', '\\d ').replace( '\f', '\\c ').replace( '"', '\\"') if value.endswith('\\'): value = value[:-1] + '\\\\' return '"%s"' % value def stringvalue(string): """ Retrieve actual value of string without quotes. Escaped quotes inside the value are resolved, e.g.:: ``'a \'string'`` => ``a 'string`` """ return string.replace('\\'+string[0], string[0])[1:-1] _match_forbidden_in_uri = re.compile(r'''.*?[\(\)\s\;,'"]''', re.U).match def uri(value): """ Serialize value by adding ``url()`` and with quotes if needed e.g.:: ``"`` => ``url("\"")`` """ if _match_forbidden_in_uri(value): value = string(value) return 'url(%s)' % value def urivalue(uri): """ Return actual content without surrounding "url(" and ")" and removed surrounding quotes too including contained escapes of quotes, e.g.:: ``url("\"")`` => ``"`` """ uri = uri[uri.find('(')+1:-1].strip() if uri and (uri[0] in '\'"') and (uri[0] == uri[-1]): return stringvalue(uri) else: return uri # def normalnumber(num): # """ # Return normalized number as string. # """ # sign = '' # if num.startswith('-'): # sign = '-' # num = num[1:] # elif num.startswith('+'): # num = num[1:] # # if float(num) == 0.0: # return '0' # else: # if num.find('.') == -1: # return sign + str(int(num)) # else: # a, b = num.split('.') # if not a: # a = '0' # return '%s%s.%s' % (sign, int(a), b) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/parse.py0000644000175000017500000002114300000000000017234 0ustar00kovidkovid#!/usr/bin/env python """A validating CSSParser""" from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['CSSParser'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from .helper import path2url import codecs import css_parser import sys from . import tokenize2 from css_parser import css if sys.version_info < (2, 6): bytes = str class CSSParser(object): """Parse a CSS StyleSheet from URL, string or file and return a DOM Level 2 CSS StyleSheet object. Usage:: parser = CSSParser() # optionally parser.setFetcher(fetcher) sheet = parser.parseFile('test1.css', 'ascii') print sheet.cssText """ def __init__(self, log=None, loglevel=None, raiseExceptions=None, fetcher=None, parseComments=True, validate=True): """ :param log: logging object :param loglevel: logging loglevel :param raiseExceptions: if log should simply log (default) or raise errors during parsing. Later while working with the resulting sheets the setting used in css_parser.log.raiseExeptions is used :param fetcher: see ``setFetcher(fetcher)`` :param parseComments: if comments should be added to CSS DOM or simply omitted :param validate: if parsing should validate, may be overwritten in parse methods """ if log is not None: css_parser.log.setLog(log) if loglevel is not None: css_parser.log.setLevel(loglevel) # remember global setting self.__globalRaising = css_parser.log.raiseExceptions if raiseExceptions: self.__parseRaising = raiseExceptions else: # DEFAULT during parse self.__parseRaising = False self.__tokenizer = tokenize2.Tokenizer(doComments=parseComments) self.setFetcher(fetcher) self._validate = validate def __parseSetting(self, parse): """during parse exceptions may be handled differently depending on init parameter ``raiseExceptions`` """ if parse: css_parser.log.raiseExceptions = self.__parseRaising else: css_parser.log.raiseExceptions = self.__globalRaising def parseStyle(self, cssText, encoding='utf-8', validate=None): """Parse given `cssText` which is assumed to be the content of a HTML style attribute. :param cssText: CSS string to parse :param encoding: It will be used to decode `cssText` if given as a (byte) string. :param validate: If given defines if validation is used. Uses CSSParser settings as fallback :returns: :class:`~css_parser.css.CSSStyleDeclaration` """ self.__parseSetting(True) if isinstance(cssText, bytes): # TODO: use codecs.getdecoder('css') here? cssText = cssText.decode(encoding) if validate is None: validate = self._validate style = css.CSSStyleDeclaration(cssText, validating=validate) self.__parseSetting(False) return style def parseString(self, cssText, encoding=None, href=None, media=None, title=None, validate=None): """Parse `cssText` as :class:`~css_parser.css.CSSStyleSheet`. Errors may be raised (e.g. UnicodeDecodeError). :param cssText: CSS string to parse :param encoding: If ``None`` the encoding will be read from BOM or an @charset rule or defaults to UTF-8. If given overrides any found encoding including the ones for imported sheets. It also will be used to decode `cssText` if given as a (byte) string. :param href: The ``href`` attribute to assign to the parsed style sheet. Used to resolve other urls in the parsed sheet like @import hrefs. :param media: The ``media`` attribute to assign to the parsed style sheet (may be a MediaList, list or a string). :param title: The ``title`` attribute to assign to the parsed style sheet. :param validate: If given defines if validation is used. Uses CSSParser settings as fallback :returns: :class:`~css_parser.css.CSSStyleSheet`. """ self.__parseSetting(True) # TODO: py3 needs bytes here! if isinstance(cssText, bytes): cssText = codecs.getdecoder('css')(cssText, encoding=encoding)[0] if validate is None: validate = self._validate sheet = css_parser.css.CSSStyleSheet( href=href, media=css_parser.stylesheets.MediaList(media), title=title, validating=validate) sheet._setFetcher(self.__fetcher) # tokenizing this ways closes open constructs and adds EOF sheet._setCssTextWithEncodingOverride(self.__tokenizer.tokenize(cssText, fullsheet=True), encodingOverride=encoding) self.__parseSetting(False) return sheet def parseFile(self, filename, encoding=None, href=None, media=None, title=None, validate=None): """Retrieve content from `filename` and parse it. Errors may be raised (e.g. IOError). :param filename: of the CSS file to parse, if no `href` is given filename is converted to a (file:) URL and set as ``href`` of resulting stylesheet. If `href` is given it is set as ``sheet.href``. Either way ``sheet.href`` is used to resolve e.g. stylesheet imports via @import rules. :param encoding: Value ``None`` defaults to encoding detection via BOM or an @charset rule. Other values override detected encoding for the sheet at `filename` including any imported sheets. :returns: :class:`~css_parser.css.CSSStyleSheet`. """ if not href: href = path2url(filename) f = open(filename, 'rb') css = f.read() f.close() return self.parseString(css, encoding=encoding, # read returns a str href=href, media=media, title=title, validate=validate) def parseUrl(self, href, encoding=None, media=None, title=None, validate=None): """Retrieve content from URL `href` and parse it. Errors may be raised (e.g. URLError). :param href: URL of the CSS file to parse, will also be set as ``href`` of resulting stylesheet :param encoding: Value ``None`` defaults to encoding detection via HTTP, BOM or an @charset rule. A value overrides detected encoding for the sheet at ``href`` including any imported sheets. :returns: :class:`~css_parser.css.CSSStyleSheet`. """ encoding, enctype, text = css_parser.util._readUrl( href, fetcher=self.__fetcher, overrideEncoding=encoding) if enctype == 5: # do not use if defaulting to UTF-8 encoding = None if text is not None: return self.parseString(text, encoding=encoding, href=href, media=media, title=title, validate=validate) def setFetcher(self, fetcher=None): """Replace the default URL fetch function with a custom one. :param fetcher: A function which gets a single parameter ``url`` the URL to read and must return ``(encoding, content)`` where ``encoding`` is the HTTP charset normally given via the Content-Type header (which may simply omit the charset in which case ``encoding`` would be ``None``) and ``content`` being the string (or unicode) content. The Mimetype should be 'text/css' but this has to be checked by the fetcher itself (the default fetcher emits a warning if encountering a different mimetype). Calling ``setFetcher`` with ``fetcher=None`` resets css_parser to use its default function. """ self.__fetcher = fetcher ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/prodparser.py0000644000175000017500000006725400000000000020320 0ustar00kovidkovid# -*- coding: utf-8 -*- """Productions parser used by css and stylesheets classes to parse test into a css_parser.util.Seq and at the same time retrieving additional specific css_parser.util.Item objects for later use. TODO: - ProdsParser - handle EOF or STOP? - handle unknown @rules - handle S: maybe save to Seq? parameterized? - store['_raw']: always? - Sequence: - opt first(), naive impl for now """ from __future__ import unicode_literals, division, absolute_import, print_function __all__ = ['ProdParser', 'Sequence', 'Choice', 'Prod', 'PreDef'] __docformat__ = 'restructuredtext' __version__ = '$Id: parse.py 1418 2008-08-09 19:27:50Z cthedot $' from .helper import pushtoken import css_parser import itertools import re import sys import types if sys.version_info[0] >= 3: text_type = str string_type = str else: text_type = unicode string_type = basestring class ParseError(Exception): """Base Exception class for ProdParser (used internally).""" pass class Done(ParseError): """Raised if Sequence or Choice is finished and no more Prods left.""" pass class Exhausted(ParseError): """Raised if Sequence or Choice is finished but token is given.""" pass class Missing(ParseError): """Raised if Sequence or Choice is not finished but no matching token given.""" pass class NoMatch(ParseError): """Raised if nothing in Sequence or Choice does match.""" pass class Choice(object): """A Choice of productions (Sequence or single Prod).""" def __init__(self, *prods, **options): """ *prods Prod or Sequence objects options: optional=False """ self._prods = prods try: self.optional = options['optional'] except KeyError: for p in self._prods: if p.optional: self.optional = True break else: self.optional = False self.reset() def reset(self): """Start Choice from zero""" self._exhausted = False def matches(self, token): """Check if token matches""" for prod in self._prods: if prod.matches(token): return True return False def nextProd(self, token): """ Return: - next matching Prod or Sequence - ``None`` if any Prod or Sequence is optional and no token matched - raise ParseError if nothing matches and all are mandatory - raise Exhausted if choice already done ``token`` may be None but this occurs when no tokens left.""" # print u'TEST for %s in %s' % (token, self) if not self._exhausted: optional = False for p in self._prods: if p.matches(token): self._exhausted = True p.reset() # print u'FOUND for %s: %s' % (token, p);#print return p elif p.optional: optional = True else: if not optional: # None matched but also None is optional raise NoMatch('No match for %s in %s' % (token, self)) # raise ParseError(u'No match in %s for %s' % (self, token)) elif token: raise Exhausted('Extra token') def __repr__(self): return "" % ( self.__class__.__name__, self.__str__(), self.optional, id(self)) def __str__(self): return 'Choice(%s)' % ', '.join([str(x) for x in self._prods]) class Sequence(object): """A Sequence of productions (Choice or single Prod).""" def __init__(self, *prods, **options): """ *prods Prod or Choice or Sequence objects **options: minmax = lambda: (1, 1) callback returning number of times this sequence may run """ self._prods = prods try: minmax = options['minmax'] except KeyError: def minmax(): return (1, 1) self._min, self._max = minmax() if self._max is None: # unlimited try: # py2.6/3 self._max = sys.maxsize except AttributeError: # py<2.6 self._max = sys.maxint self._prodcount = len(self._prods) self.reset() def matches(self, token): """Called by Choice to try to find if Sequence matches.""" for prod in self._prods: if prod.matches(token): return True try: if not prod.optional: break except AttributeError: pass return False def reset(self): """Reset this Sequence if it is nested.""" self._roundstarted = False self._i = 0 self._round = 0 def _currentName(self): """Return current element of Sequence, used by name""" # TODO: current impl first only if 1st if an prod! for prod in self._prods[self._i:]: if not prod.optional: return str(prod) else: return 'Sequence' optional = property(lambda self: self._min == 0) def nextProd(self, token): """Return - next matching Prod or Choice - raises ParseError if nothing matches - raises Exhausted if sequence already done """ # print u'TEST for %s in %s' % (token, self) while self._round < self._max: # for this round i = self._i round = self._round p = self._prods[i] if i == 0: self._roundstarted = False # for next round self._i += 1 if self._i == self._prodcount: self._round += 1 self._i = 0 if p.matches(token): self._roundstarted = True # reset nested Choice or Prod to use from start p.reset() # print u'FOUND for %s: %s' % (token, p);#print return p elif p.optional: continue elif round < self._min or self._roundstarted: # or (round == 0 and self._min == 0): raise Missing('Missing token for production %s' % p) elif not token: if self._roundstarted: raise Missing('Missing token for production %s' % p) else: raise Done() else: raise NoMatch('No match for %s in %s' % (token, self)) if token: raise Exhausted('Extra token') def __repr__(self): return "" % ( self.__class__.__name__, self.__str__(), self.optional, id(self)) def __str__(self): return 'Sequence(%s)' % ', '.join([str(x) for x in self._prods]) class Prod(object): """Single Prod in Sequence or Choice.""" def __init__(self, name, match, optional=False, toSeq=None, toStore=None, stop=False, stopAndKeep=False, stopIfNoMoreMatch=False, nextSor=False, mayEnd=False, storeToken=None, exception=None): """ name name used for error reporting match callback function called with parameters tokentype and tokenvalue returning True, False or raising ParseError toSeq callback (optional) or False calling toSeq(token, tokens) returns (type_, val) == (token[0], token[1]) to be appended to seq else simply unaltered (type_, val) if False nothing is added toStore (optional) key to save util.Item to store or callback(store, util.Item) optional = False whether Prod is optional or not stop = False if True stop parsing of tokens here stopAndKeep if True stop parsing of tokens here but return stopping token in unused tokens stopIfNoMoreMatch = False stop even if more tokens available, similar to stop and keep but with condition no more matches nextSor=False next is S or other like , or / (CSSValue) mayEnd = False no token must follow even defined by Sequence. Used for operator ',/ ' currently only storeToken = None if True toStore saves simple token tuple and not and Item object to store. Old style processing, TODO: resolve exception = None exception to be raised in case of error, normaly SyntaxErr """ self._name = name self.match = match self.optional = optional self.stop = stop self.stopAndKeep = stopAndKeep self.stopIfNoMoreMatch = stopIfNoMoreMatch self.nextSor = nextSor self.mayEnd = mayEnd self.storeToken = storeToken self.exception = exception def makeToStore(key): "Return a function used by toStore." def toStore(store, item): "Set or append store item." if key in store: _v = store[key] if not isinstance(_v, list): store[key] = [_v] store[key].append(item) else: store[key] = item return toStore if toSeq or toSeq is False: # called: seq.append(toSeq(value)) self.toSeq = toSeq else: self.toSeq = lambda t, tokens: (t[0], t[1]) if hasattr(toStore, '__call__'): self.toStore = toStore elif toStore: self.toStore = makeToStore(toStore) else: # always set! self.toStore = None def matches(self, token): """Return if token matches.""" if not token: return False type_, val, line, col = token return self.match(type_, val) def reset(self): pass def __str__(self): return self._name def __repr__(self): return "" % ( self.__class__.__name__, self._name, id(self)) # global tokenizer as there is only one! tokenizer = css_parser.tokenize2.Tokenizer() # global: saved from subProds savedTokens = [] class ProdParser(object): """Productions parser.""" def __init__(self, clear=True): self.types = css_parser.cssproductions.CSSProductions self._log = css_parser.log if clear: tokenizer.clear() def _texttotokens(self, text): """Build a generator which is the only thing that is parsed! old classes may use lists etc """ # under python 2.x this was basestring, but ... if isinstance(text, string_type): # DEFAULT, to tokenize strip space return tokenizer.tokenize(text.strip()) elif type(text) is types.GeneratorType: # noqa # DEFAULT, already tokenized, should be generator return text elif isinstance(text, tuple): # single token return iter([text]) elif isinstance(text, list): # OLD: generator from list return iter(text) else: # ? return text def _SorTokens(self, tokens, until=',/'): """New tokens generator which has S tokens removed, if followed by anything in ``until``, normally a ``,``.""" for token in tokens: if token[0] == self.types.S: try: next_ = next(tokens) except StopIteration: yield token else: if next_[1] in until: # omit S as e.g. ``,`` has been found yield next_ elif next_[0] == self.types.COMMENT: # pass COMMENT yield next_ else: yield token yield next_ elif token[0] == self.types.COMMENT: # pass COMMENT yield token else: yield token break # normal mode again for token in tokens: yield token def parse(self, text, name, productions, keepS=False, checkS=False, store=None, emptyOk=False, debug=False): """ text (or token generator) to parse, will be tokenized if not a generator yet may be: - a string to be tokenized - a single token, a tuple - a tuple of (token, tokensGenerator) - already tokenized so a tokens generator name used for logging productions used to parse tokens keepS if WS should be added to Seq or just be ignored store UPDATED If a Prod defines ``toStore`` the key defined there is a key in store to be set or if store[key] is a list the next Item is appended here. TODO: NEEDED? : Key ``raw`` is always added and holds all unprocessed values found emptyOk if True text may be empty, hard to test before as may be generator returns :wellformed: True or False :seq: a filled css_parser.util.Seq object which is NOT readonly yet :store: filled keys defined by Prod.toStore :unusedtokens: token generator containing tokens not used yet """ tokens = self._texttotokens(text) if not tokens: self._log.error('No content to parse.') return False, [], None, None seq = css_parser.util.Seq(readonly=False) if not store: # store for specific values store = {} prods = [productions] # stack of productions wellformed = True # while no real token is found any S are ignored started = False stopall = False prod = None # flag if default S handling should be done defaultS = True stopIfNoMoreMatch = False while True: # get from savedTokens or normal tokens try: # print debug, "SAVED", savedTokens token = savedTokens.pop() except IndexError: try: token = next(tokens) except StopIteration: break # print debug, token, stopIfNoMoreMatch type_, val, line, col = token # default productions if type_ == self.types.COMMENT: # always append COMMENT seq.append(css_parser.css.CSSComment(val), css_parser.css.CSSComment, line, col) elif defaultS and type_ == self.types.S and not checkS: # append S (but ignore starting ones) if not keepS or not started: continue else: seq.append(val, type_, line, col) # elif type_ == self.types.ATKEYWORD: # # @rule # r = css_parser.css.CSSUnknownRule(cssText=val) # seq.append(r, type(r), line, col) elif type_ == self.types.INVALID: # invalidate parse wellformed = False self._log.error('Invalid token: %r' % (token,)) break elif type_ == 'EOF': # do nothing? (self.types.EOF == True!) stopall = True else: started = True # check S now try: while True: # find next matching production try: prod = prods[-1].nextProd(token) except (Exhausted, NoMatch): # try next prod = None if isinstance(prod, Prod): # found actual Prod, not a Choice or Sequence break elif prod: # nested Sequence, Choice prods.append(prod) else: # nested exhausted, try in parent if len(prods) > 1: prods.pop() else: raise NoMatch('No match') except NoMatch as e: if stopIfNoMoreMatch: # and token: # print "\t1stopIfNoMoreMatch", e, token, prod, 'PUSHING' # tokenizer.push(token) savedTokens.append(token) stopall = True else: wellformed = False self._log.error('%s: %s: %r' % (name, e, token)) break except ParseError as e: # needed??? if stopIfNoMoreMatch: # and token: # print "\t2stopIfNoMoreMatch", e, token, prod tokenizer.push(token) stopall = True else: wellformed = False self._log.error('%s: %s: %r' % (name, e, token)) break else: # print '\t1', debug, 'PROD', prod # may stop next time, once set stays stopIfNoMoreMatch = prod.stopIfNoMoreMatch or stopIfNoMoreMatch # process prod if prod.toSeq and not prod.stopAndKeep: type_, val = prod.toSeq(token, tokens) if val is not None: seq.append(val, type_, line, col) if prod.toStore: if not prod.storeToken: prod.toStore(store, seq[-1]) else: # workaround for now for old style token # parsing! # TODO: remove when all new style prod.toStore(store, token) if prod.stop: # stop here and ignore following tokens # EOF? or end of e.g. func ")" break if prod.stopAndKeep: # e.g. ; # stop here and ignore following tokens # but keep this token for next run # TODO: CHECK!!!! tokenizer.push(token) tokens = itertools.chain(token, tokens) stopall = True break if prod.nextSor: # following is S or other token (e.g. ",")? # remove S if tokens = self._SorTokens(tokens, ',/') defaultS = False else: defaultS = True lastprod = prod # print debug, 'parse done', token, stopall, '\n' if not stopall: # stop immediately while True: # all productions exhausted? try: prod = prods[-1].nextProd(token=None) except Done: # ok prod = None except Missing as e: prod = None # last was a S operator which may End a Sequence, then ok if hasattr(lastprod, 'mayEnd') and not lastprod.mayEnd: wellformed = False self._log.error('%s: %s' % (name, e)) except ParseError as e: prod = None wellformed = False self._log.error('%s: %s' % (name, e)) else: if prods[-1].optional: prod = None elif prod and prod.optional: # ignore optional continue if prod and not prod.optional: wellformed = False self._log.error('%s: Missing token for production %r' % (name, text_type(prod))) break elif len(prods) > 1: # nested exhausted, next in parent prods.pop() else: break if not emptyOk and not len(seq): self._log.error('No content to parse.') return False, [], None, None # trim S from end seq.rstrip() return wellformed, seq, store, tokens class PreDef(object): """Predefined Prod definition for use in productions definition for ProdParser instances. """ types = css_parser.cssproductions.CSSProductions reHexcolor = re.compile(r'^\#(?:[0-9abcdefABCDEF]{3}|[0-9abcdefABCDEF]{6})$') @staticmethod def calc(toSeq=None, nextSor=False): return Prod(name='calcfunction', match=lambda t, v: 'calc(' == css_parser.helper.normalize(v), toSeq=toSeq, nextSor=nextSor) @staticmethod def char(name='char', char=',', toSeq=None, stop=False, stopAndKeep=False, mayEnd=False, stopIfNoMoreMatch=False, optional=False, # WAS: optional=True, nextSor=False): "any CHAR" return Prod(name=name, match=lambda t, v: v == char, toSeq=toSeq, stop=stop, stopAndKeep=stopAndKeep, mayEnd=mayEnd, stopIfNoMoreMatch=stopIfNoMoreMatch, optional=optional, nextSor=nextSor) @staticmethod def comma(optional=False, toSeq=None): return PreDef.char('comma', ',', optional=optional, toSeq=toSeq) @staticmethod def comment(parent=None): return Prod(name='comment', match=lambda t, v: t == 'COMMENT', toSeq=lambda t, tokens: (t[0], css_parser.css.CSSComment([1], parentRule=parent)), optional=True ) @staticmethod def dimension(nextSor=False, stop=False): return Prod(name='dimension', match=lambda t, v: t == PreDef.types.DIMENSION, toSeq=lambda t, tokens: (t[0], css_parser.helper.normalize(t[1])), stop=stop, nextSor=nextSor) @staticmethod def function(toSeq=None, nextSor=False, toStore=None): return Prod(name='function', match=lambda t, v: t == PreDef.types.FUNCTION, toStore=toStore, toSeq=toSeq, nextSor=nextSor) @staticmethod def funcEnd(stop=False, mayEnd=False): ")" return PreDef.char('end FUNC ")"', ')', stop=stop, mayEnd=mayEnd) @staticmethod def hexcolor(stop=False, nextSor=False): "#123 or #123456" return Prod(name='HEX color', match=lambda t, v: ( t == PreDef.types.HASH and PreDef.reHexcolor.match(v) ), stop=stop, nextSor=nextSor) @staticmethod def ident(stop=False, toStore=None, nextSor=False): return Prod(name='ident', match=lambda t, v: t == PreDef.types.IDENT, stop=stop, toStore=toStore, nextSor=nextSor) @staticmethod def number(stop=False, toSeq=None, nextSor=False): return Prod(name='number', match=lambda t, v: t == PreDef.types.NUMBER, stop=stop, toSeq=toSeq, nextSor=nextSor) @staticmethod def percentage(stop=False, toSeq=None, nextSor=False): return Prod(name='percentage', match=lambda t, v: t == PreDef.types.PERCENTAGE, stop=stop, toSeq=toSeq, nextSor=nextSor) @staticmethod def string(stop=False, nextSor=False): "string delimiters are removed by default" return Prod(name='string', match=lambda t, v: t == PreDef.types.STRING, toSeq=lambda t, tokens: (t[0], css_parser.helper.stringvalue(t[1])), stop=stop, nextSor=nextSor) @staticmethod def S(name='whitespace', toSeq=None, optional=False): return Prod(name=name, match=lambda t, v: t == PreDef.types.S, toSeq=toSeq, optional=optional, mayEnd=True) @staticmethod def unary(stop=False, toSeq=None, nextSor=False): "+ or -" return Prod(name='unary +-', match=lambda t, v: v in ('+', '-'), optional=True, stop=stop, toSeq=toSeq, nextSor=nextSor) @staticmethod def uri(stop=False, nextSor=False): "'url(' and ')' are removed and URI is stripped" return Prod(name='URI', match=lambda t, v: t == PreDef.types.URI, toSeq=lambda t, tokens: (t[0], css_parser.helper.urivalue(t[1])), stop=stop, nextSor=nextSor) @staticmethod def unicode_range(stop=False, nextSor=False): "u+123456-abc normalized to lower `u`" return Prod(name='unicode-range', match=lambda t, v: t == PreDef.types.UNICODE_RANGE, toSeq=lambda t, tokens: (t[0], t[1].lower()), stop=stop, nextSor=nextSor ) @staticmethod def ratio(stop=False, nextSor=False): "positive integer / positive integer" return Prod(name='ratio', match=lambda t, v: t == PreDef.types.RATIO, toSeq=lambda t, tokens: (t[0], t[1].lower()), stop=stop, nextSor=nextSor ) @staticmethod def variable(toSeq=None, stop=False, nextSor=False, toStore=None): return Prod(name='variable', match=lambda t, v: 'var(' == css_parser.helper.normalize(v), toSeq=toSeq, toStore=toStore, stop=stop, nextSor=nextSor) # used for MarginRule for now: @staticmethod def unknownrule(name='@', toStore=None): """@rule dummy (matches ATKEYWORD to remove unknown rule tokens from stream:: @x; @x {...} no nested yet! """ def rule(tokens): saved = [] for t in tokens: saved.append(t) if (t[1] == '}' or t[1] == ';'): return css_parser.css.CSSUnknownRule(saved) return Prod(name=name, match=lambda t, v: t == 'ATKEYWORD', toSeq=lambda t, tokens: ('CSSUnknownRule', rule(pushtoken(t, tokens)) ), toStore=toStore ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578454756.0 css-parser-1.0.7/src/css_parser/profiles.py0000644000175000017500000010542500000000000017753 0ustar00kovidkovidfrom __future__ import unicode_literals, division, absolute_import, print_function from css_parser import util import sys import re """CSS profiles. Profiles is based on code by Kevin D. Smith, orginally used as cssvalues, thanks! """ __all__ = ['Profiles'] __docformat__ = 'restructuredtext' __version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $' if sys.version_info[0] >= 3: string_type = str else: string_type = basestring def as_list(p): if isinstance(p, list): return p return list(p) class NoSuchProfileException(Exception): """Raised if no profile with given name is found""" pass class Profiles(object): """ All profiles used for validation. ``css_parser.profile`` is a preset object of this class and used by all properties for validation. Predefined profiles are (use :meth:`~css_parser.profiles.Profiles.propertiesByProfile` to get a list of defined properties): :attr:`~css_parser.profiles.Profiles.CSS_LEVEL_2` Properties defined by CSS2.1 :attr:`~css_parser.profiles.Profiles.CSS3_BASIC_USER_INTERFACE` Currently resize and outline properties only :attr:`~css_parser.profiles.Profiles.CSS3_BOX` Currently overflow related properties only :attr:`~css_parser.profiles.Profiles.CSS3_COLOR` CSS 3 color properties :attr:`~css_parser.profiles.Profiles.CSS3_PAGED_MEDIA` As defined at http://www.w3.org/TR/css3-page/ (at 090307) Predefined macros are: :attr:`~css_parser.profiles.Profiles._TOKEN_MACROS` Macros containing the token values as defined to CSS2 :attr:`~css_parser.profiles.Profiles._MACROS` Additional general macros. If you want to redefine any of these macros do this in your custom macros. """ CSS_LEVEL_2 = 'CSS Level 2.1' CSS3_BACKGROUNDS_AND_BORDERS = 'CSS Backgrounds and Borders Module Level 3' CSS3_BASIC_USER_INTERFACE = 'CSS3 Basic User Interface Module' CSS3_BOX = CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3' CSS3_COLOR = CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3' CSS3_FONTS = 'CSS Fonts Module Level 3' CSS3_FONT_FACE = 'CSS Fonts Module Level 3 @font-face properties' CSS3_PAGED_MEDIA = 'CSS3 Paged Media Module' CSS3_TEXT = 'CSS Text Level 3' _TOKEN_MACROS = { 'ident': r'[-]?{nmstart}{nmchar}*', 'name': r'{nmchar}+', 'nmstart': r'[_a-z]|{nonascii}|{escape}', 'nonascii': r'[^\0-\177]', 'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?', 'escape': r'{unicode}|\\[ -~\u0080-\u01ff]', # 'escape': r'{unicode}|\\[ -~\200-\4177777]', 'int': r'[-]?\d+', 'nmchar': r'[\w-]|{nonascii}|{escape}', 'num': r'[-]?\d+|[-]?\d*\.\d+', 'positivenum': r'\d+|\d*\.\d+', 'number': r'{num}', 'string': r'{string1}|{string2}', 'string1': r'"(\\\"|[^\"])*"', 'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)', 'string2': r"'(\\\'|[^\'])*'", 'nl': r'\n|\r\n|\r|\f', 'w': r'\s*', } _MACROS = { 'hexcolor': r'#[0-9a-f]{3}|#[0-9a-f]{6}', 'rgbcolor': r'rgb\({w}{int}{w}\,{w}{int}{w}\,{w}{int}{w}\)|rgb\({w}{num}%{w}\,{w}{num}%{w}\,{w}{num}%{w}\)', 'namedcolor': r'(transparent|orange|maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray)', # noqa 'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)', # noqa 'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{uicolor}', 'integer': r'{int}', 'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)', 'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc)', 'angle': r'0|{num}(deg|grad|rad)', 'time': r'0|{num}m?s', 'frequency': r'0|{num}k?Hz', 'percentage': r'{num}%', 'shadow': '(inset)?{w}{length}{w}{length}{w}{length}?{w}{length}?{w}{color}?' } def __init__(self, log=None): """A few profiles are predefined.""" self._log = log # macro cache self._usedMacros = Profiles._TOKEN_MACROS.copy() self._usedMacros.update(Profiles._MACROS.copy()) # to keep order, REFACTOR! self._profileNames = [] # for reset if macro changes self._rawProfiles = {} # already compiled profiles: {profile: {property: checkfunc, ...}, ...} self._profilesProperties = {} self._defaultProfiles = None self.addProfiles([(self.CSS_LEVEL_2, properties[self.CSS_LEVEL_2], macros[self.CSS_LEVEL_2] ), (self.CSS3_BACKGROUNDS_AND_BORDERS, properties[self.CSS3_BACKGROUNDS_AND_BORDERS], macros[self.CSS3_BACKGROUNDS_AND_BORDERS] ), (self.CSS3_BASIC_USER_INTERFACE, properties[self.CSS3_BASIC_USER_INTERFACE], macros[self.CSS3_BASIC_USER_INTERFACE] ), (self.CSS3_BOX, properties[self.CSS3_BOX], macros[self.CSS3_BOX] ), (self.CSS3_COLOR, properties[self.CSS3_COLOR], macros[self.CSS3_COLOR] ), (self.CSS3_FONTS, properties[self.CSS3_FONTS], macros[self.CSS3_FONTS] ), # new object for font-face only? (self.CSS3_FONT_FACE, properties[self.CSS3_FONT_FACE], macros[self.CSS3_FONTS] ), (self.CSS3_PAGED_MEDIA, properties[self.CSS3_PAGED_MEDIA], macros[self.CSS3_PAGED_MEDIA] ), (self.CSS3_TEXT, properties[self.CSS3_TEXT], macros[self.CSS3_TEXT] ) ]) self.__update_knownNames() def _expand_macros(self, dictionary, macros): """Expand macros in token dictionary""" def macro_value(m): return '(?:%s)' % macros[m.groupdict()['macro']] for key, value in as_list(dictionary.items()): if not hasattr(value, '__call__'): while re.search(r'{[a-z][a-z0-9-]*}', value): value = re.sub(r'{(?P[a-z][a-z0-9-]*)}', macro_value, value) dictionary[key] = value return dictionary def _compile_regexes(self, dictionary): """Compile all regular expressions into callable objects""" for key, value in as_list(dictionary.items()): if not hasattr(value, '__call__'): # Compiling them now will slow down the css_parser import time, # even if css_parser is not needed. We lazily compile them the # first time they're needed. # https://bitbucket.org/cthedot/css_parser/issues/72 value = util.LazyRegex('^(?:%s)$' % value, re.I) dictionary[key] = value return dictionary def __update_knownNames(self): self._knownNames = [] for properties in as_list(self._profilesProperties.values()): self._knownNames.extend(as_list(properties.keys())) def _getDefaultProfiles(self): "If not explicitly set same as Profiles.profiles but in reverse order." if not self._defaultProfiles: return self.profiles else: return self._defaultProfiles def _setDefaultProfiles(self, profiles): "profiles may be a single or a list of profile names" # under python 2.X this was originally basestring but given unicode literals ... if isinstance(profiles, string_type): self._defaultProfiles = (profiles,) else: self._defaultProfiles = profiles defaultProfiles = property(_getDefaultProfiles, _setDefaultProfiles, doc="Names of profiles to use for validation." "To use e.g. the CSS2 profile set " "``css_parser.profile.defaultProfiles = " "css_parser.profile.CSS_LEVEL_2``") profiles = property(lambda self: self._profileNames, doc='Names of all profiles in order as defined.') knownNames = property(lambda self: self._knownNames, doc="All known property names of all profiles.") def _resetProperties(self, newMacros=None): "reset all props from raw values as changes in macros happened" # base macros = Profiles._TOKEN_MACROS.copy() macros.update(Profiles._MACROS.copy()) # former for profile in self._profileNames: macros.update(self._rawProfiles[profile]['macros']) # new if newMacros: macros.update(newMacros) # reset properties self._profilesProperties.clear() for profile in self._profileNames: properties = self._expand_macros( # keep raw self._rawProfiles[profile]['properties'].copy(), macros) self._profilesProperties[profile] = self._compile_regexes(properties) # save self._usedMacros = macros def addProfiles(self, profiles): """Add a list of profiles at once. Useful as if profiles define custom macros these are used in one go. Using `addProfile` instead my be **very** slow instead. """ # add macros for profile, properties, macros in profiles: if macros: self._usedMacros.update(macros) self._rawProfiles[profile] = {'macros': macros.copy()} # only add new properties for profile, properties, macros in profiles: self.addProfile(profile, properties.copy(), None) def addProfile(self, profile, properties, macros=None): """Add a new profile with name `profile` (e.g. 'CSS level 2') and the given `properties`. :param profile: the new `profile`'s name :param properties: a dictionary of ``{ property-name: propery-value }`` items where property-value is a regex which may use macros defined in given ``macros`` or the standard macros Profiles.tokens and Profiles.generalvalues. ``propery-value`` may also be a function which takes a single argument which is the value to validate and which should return True or False. Any exceptions which may be raised during this custom validation are reported or raised as all other css_parser exceptions depending on css_parser.log.raiseExceptions which e.g during parsing normally is False so the exceptions would be logged only. :param macros: may be used in the given properties definitions. There are some predefined basic macros which may always be used in :attr:`Profiles._TOKEN_MACROS` and :attr:`Profiles._MACROS`. """ if macros: # check if known macros would change and if yes reset properties if len(set(macros.keys()).intersection(as_list(self._usedMacros.keys()))): self._resetProperties(newMacros=macros) else: # no replacement, simply continue self._usedMacros.update(macros) else: # might have been set by addProfiles before try: macros = self._rawProfiles[profile]['macros'] except KeyError: macros = {} # save name and raw props/macros if macros change to completely reset self._profileNames.append(profile) self._rawProfiles[profile] = {'properties': properties.copy(), 'macros': macros.copy()} # prepare and save properties properties = self._expand_macros(properties, self._usedMacros) self._profilesProperties[profile] = self._compile_regexes(properties) self.__update_knownNames() def removeProfile(self, profile=None, all=False): """Remove `profile` or remove `all` profiles. If the removed profile used custom macros all remaining profiles are reset to reflect the macro changes. This may be quite an expensive operation! :param profile: profile name to remove :param all: if ``True`` removes all profiles to start with a clean state :exceptions: - :exc:`css_parser.profiles.NoSuchProfileException`: If given `profile` cannot be found. """ if all: self._profilesProperties.clear() self._rawProfiles.clear() del self._profileNames[:] else: reset = False try: if (self._rawProfiles[profile]['macros']): reset = True del self._profilesProperties[profile] del self._rawProfiles[profile] del self._profileNames[self._profileNames.index(profile)] except KeyError: raise NoSuchProfileException('No profile %r.' % profile) else: if reset: # reset properties as macros were removed self._resetProperties() self.__update_knownNames() def propertiesByProfile(self, profiles=None): """Generator: Yield property names, if no `profiles` is given all profile's properties are used. :param profiles: a single profile name or a list of names. """ if not profiles: profiles = self.profiles elif isinstance(profiles, string_type): profiles = (profiles, ) try: for profile in sorted(profiles): for name in sorted(self._profilesProperties[profile].keys()): yield name except KeyError as e: raise NoSuchProfileException(e) def validate(self, name, value): """Check if `value` is valid for given property `name` using **any** profile. :param name: a property name :param value: a CSS value (string) :returns: if the `value` is valid for the given property `name` in any profile """ for profile in self.profiles: if name in self._profilesProperties[profile]: try: # custom validation errors are caught r = bool(self._profilesProperties[profile][name](value)) except Exception as e: # TODO: more specific exception? # Validate should not be fatal though! self._log.error(e, error=Exception) r = False if r: return r return False def validateWithProfile(self, name, value, profiles=None): """Check if `value` is valid for given property `name` returning ``(valid, profile)``. :param name: a property name :param value: a CSS value (string) :param profiles: internal parameter used by Property.validate only :returns: ``valid, matching, profiles`` where ``valid`` is if the `value` is valid for the given property `name` in any profile, ``matching==True`` if it is valid in the given `profiles` and ``profiles`` the profile names for which the value is valid (or ``[]`` if not valid at all) Example:: >>> css_parser.profile.defaultProfiles = css_parser.profile.CSS_LEVEL_2 >>> print css_parser.profile.validateWithProfile('color', 'rgba(1,1,1,1)') (True, False, Profiles.CSS3_COLOR) """ if name not in self.knownNames: return False, False, [] else: if not profiles: profiles = self.defaultProfiles elif isinstance(profiles, string_type): profiles = (profiles, ) for profilename in reversed(profiles): # check given profiles if name in self._profilesProperties[profilename]: validate = self._profilesProperties[profilename][name] try: if validate(value): return True, True, [profilename] except Exception as e: self._log.error(e, error=Exception) for profilename in (p for p in self._profileNames if p not in profiles): # check remaining profiles as well if name in self._profilesProperties[profilename]: validate = self._profilesProperties[profilename][name] try: if validate(value): return True, False, [profilename] except Exception as e: self._log.error(e, error=Exception) names = [] for profilename, properties in as_list(self._profilesProperties.items()): # return profile to which name belongs if name in as_list(properties.keys()): names.append(profilename) names.sort() return False, False, names properties = {} macros = {} """ Define some regular expression fragments that will be used as macros within the CSS property value regular expressions. """ macros[Profiles.CSS_LEVEL_2] = { 'background-color': r'{color}|transparent|inherit', 'background-image': r'{uri}|none|inherit', 'background-position': r'({percentage}|{length}|left|center|right)(\s*({percentage}|{length}|top|center|bottom))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?)|inherit', # noqa 'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit', 'background-attachment': r'scroll|fixed|inherit', 'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)', 'counter': r'counter\({w}{ident}{w}(?:,{w}{list-style-type}{w})?\)', 'identifier': r'{ident}', 'family-name': r'{string}|({ident}(\s+{ident})*)', 'generic-family': r'serif|sans-serif|cursive|fantasy|monospace', 'absolute-size': r'(x?x-)?(small|large)|medium', 'relative-size': r'smaller|larger', 'font-family': r'({family-name}({w},{w}{family-name})*)|inherit', 'font-size': r'{absolute-size}|{relative-size}|{positivelength}|{percentage}|inherit', 'font-style': r'normal|italic|oblique|inherit', 'font-variant': r'normal|small-caps|inherit', 'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit', 'line-height': r'normal|{number}|{length}|{percentage}|inherit', 'list-style-image': r'{uri}|none|inherit', 'list-style-position': r'inside|outside|inherit', 'list-style-type': r'disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-(latin|alpha)|upper-(latin|alpha)|armenian|georgian|none|inherit', # noqa 'margin-width': r'{length}|{percentage}|auto', 'padding-width': r'{length}|{percentage}', 'specific-voice': r'{ident}', 'generic-voice': r'male|female|child', 'content': r'{string}|{uri}|{counter}|attr\({w}{ident}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote', 'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}', # noqa 'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}', 'font-attrs': r'{font-style}|{font-variant}|{font-weight}', 'text-attrs': r'underline|overline|line-through|blink', 'overflow': r'visible|hidden|scroll|auto|inherit', } """ Define the regular expressions for validation all CSS values """ properties[Profiles.CSS_LEVEL_2] = { 'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit', # noqa 'background-attachment': r'{background-attachment}', 'background-color': r'{background-color}', 'background-image': r'{background-image}', 'background-position': r'{background-position}', 'background-repeat': r'{background-repeat}', # Each piece should only be allowed one time 'background': r'{background-attrs}(\s+{background-attrs})*|inherit', 'border-collapse': r'collapse|separate|inherit', 'border-spacing': r'{length}(\s+{length})?|inherit', 'bottom': r'{length}|{percentage}|auto|inherit', 'caption-side': r'top|bottom|inherit', 'clear': r'none|left|right|both|inherit', 'clip': r'{shape}|auto|inherit', 'color': r'{color}|inherit', 'content': r'none|normal|{content}(\s+{content})*|inherit', 'counter-increment': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit', 'counter-reset': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit', 'cue-after': r'{uri}|none|inherit', 'cue-before': r'{uri}|none|inherit', 'cue': r'({uri}|none|inherit){1,2}|inherit', 'direction': r'ltr|rtl|inherit', 'display': r'inline|block|list-item|run-in|inline-block|table|inline-table|table-row-group|table-header-group|table-barter-group|table-row|table-column-group|table-column|table-cell|table-caption|none|inherit', # noqa 'elevation': r'{angle}|below|level|above|higher|lower|inherit', 'empty-cells': r'show|hide|inherit', 'float': r'left|right|none|inherit', 'font-family': r'{font-family}', 'font-size': r'{font-size}', 'font-style': r'{font-style}', 'font-variant': r'{font-variant}', 'font-weight': r'{font-weight}', 'font': r'(({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family})|caption|icon|menu|message-box|small-caption|status-bar|inherit', # noqa 'height': r'{length}|{percentage}|auto|inherit', 'left': r'{length}|{percentage}|auto|inherit', 'letter-spacing': r'normal|{length}|inherit', 'line-height': r'{line-height}', 'list-style-image': r'{list-style-image}', 'list-style-position': r'{list-style-position}', 'list-style-type': r'{list-style-type}', 'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit', 'margin-right': r'{margin-width}|inherit', 'margin-left': r'{margin-width}|inherit', 'margin-top': r'{margin-width}|inherit', 'margin-bottom': r'{margin-width}|inherit', 'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit', 'max-height': r'{length}|{percentage}|none|inherit', 'max-width': r'{length}|{percentage}|none|inherit', 'min-height': r'{length}|{percentage}|none|inherit', 'min-width': r'{length}|{percentage}|none|inherit', 'orphans': r'{integer}|inherit', 'overflow': r'{overflow}', 'padding-top': r'{padding-width}|inherit', 'padding-right': r'{padding-width}|inherit', 'padding-bottom': r'{padding-width}|inherit', 'padding-left': r'{padding-width}|inherit', 'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit', 'page-break-after': r'auto|always|avoid|left|right|inherit', 'page-break-before': r'auto|always|avoid|left|right|inherit', 'page-break-inside': r'avoid|auto|inherit', 'pause-after': r'{time}|{percentage}|inherit', 'pause-before': r'{time}|{percentage}|inherit', 'pause': r'({time}|{percentage}){1,2}|inherit', 'pitch-range': r'{number}|inherit', 'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit', 'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit', 'position': r'static|relative|absolute|fixed|inherit', 'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit', 'richness': r'{number}|inherit', 'right': r'{length}|{percentage}|auto|inherit', 'speak-header': r'once|always|inherit', 'speak-numeral': r'digits|continuous|inherit', 'speak-punctuation': r'code|none|inherit', 'speak': r'normal|none|spell-out|inherit', 'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit', 'stress': r'{number}|inherit', 'table-layout': r'auto|fixed|inherit', 'text-align': r'left|right|center|justify|inherit', 'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit', 'text-indent': r'{length}|{percentage}|inherit', 'text-transform': r'capitalize|uppercase|lowercase|none|inherit', 'top': r'{length}|{percentage}|auto|inherit', 'unicode-bidi': r'normal|embed|bidi-override|inherit', 'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit', 'visibility': r'visible|hidden|collapse|inherit', 'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit', 'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit', 'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit', 'widows': r'{integer}|inherit', 'width': r'{length}|{percentage}|auto|inherit', 'word-spacing': r'normal|{length}|inherit', 'z-index': r'auto|{integer}|inherit', } macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = { 'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset', 'border-width': '{length}|thin|medium|thick', 'b1': r'{border-width}?({w}{border-style})?({w}{color})?', 'b2': r'{border-width}?({w}{color})?({w}{border-style})?', 'b3': r'{border-style}?({w}{border-width})?({w}{color})?', 'b4': r'{border-style}?({w}{color})?({w}{border-width})?', 'b5': r'{color}?({w}{border-style})?({w}{border-width})?', 'b6': r'{color}?({w}{border-width})?({w}{border-style})?', 'border-attrs': r'{b1}|{b2}|{b3}|{b4}|{b5}|{b6}', 'border-radius-part': r'({length}|{percentage})(\s+({length}|{percentage}))?' } properties[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = { 'border-color': r'({color}|transparent)(\s+({color}|transparent)){0,3}|inherit', 'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit', 'border-top': r'{border-attrs}|inherit', 'border-right': r'{border-attrs}|inherit', 'border-bottom': r'{border-attrs}|inherit', 'border-left': r'{border-attrs}|inherit', 'border-top-color': r'{color}|transparent|inherit', 'border-right-color': r'{color}|transparent|inherit', 'border-bottom-color': r'{color}|transparent|inherit', 'border-left-color': r'{color}|transparent|inherit', 'border-top-style': r'{border-style}|inherit', 'border-right-style': r'{border-style}|inherit', 'border-bottom-style': r'{border-style}|inherit', 'border-left-style': r'{border-style}|inherit', 'border-top-width': r'{border-width}|inherit', 'border-right-width': r'{border-width}|inherit', 'border-bottom-width': r'{border-width}|inherit', 'border-left-width': r'{border-width}|inherit', 'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit', 'border': r'{border-attrs}|inherit', 'border-top-right-radius': '{border-radius-part}', 'border-bottom-right-radius': '{border-radius-part}', 'border-bottom-left-radius': '{border-radius-part}', 'border-top-left-radius': '{border-radius-part}', 'border-radius': '({length}{w}|{percentage}{w}){1,4}(/{w}({length}{w}|{percentage}{w}){1,4})?', 'box-shadow': 'none|{shadow}({w},{w}{shadow})*', } # CSS3 Basic User Interface Module macros[Profiles.CSS3_BASIC_USER_INTERFACE] = { 'border-style': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-style'], 'border-width': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-width'], 'outline-1': r'{outline-color}(\s+{outline-style})?(\s+{outline-width})?', 'outline-2': r'{outline-color}(\s+{outline-width})?(\s+{outline-style})?', 'outline-3': r'{outline-style}(\s+{outline-color})?(\s+{outline-width})?', 'outline-4': r'{outline-style}(\s+{outline-width})?(\s+{outline-color})?', 'outline-5': r'{outline-width}(\s+{outline-color})?(\s+{outline-style})?', 'outline-6': r'{outline-width}(\s+{outline-style})?(\s+{outline-color})?', 'outline-color': r'{color}|invert|inherit', 'outline-style': r'auto|{border-style}|inherit', 'outline-width': r'{border-width}|inherit', } properties[Profiles.CSS3_BASIC_USER_INTERFACE] = { 'box-sizing': r'content-box|border-box', 'cursor': r'((({uri}{w}({number}{w}{number}{w})?,{w})*)?(auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|(e|n|ne|nw|s|se|sw|w|ew|ns|nesw|nwse|col|row)-resize|all-scroll))|inherit', # noqa 'nav-index': r'auto|{number}|inherit', 'outline-color': r'{outline-color}', 'outline-style': r'{outline-style}', 'outline-width': r'{outline-width}', 'outline-offset': r'{length}|inherit', # 'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit', 'outline': r'{outline-1}|{outline-2}|{outline-3}|{outline-4}|{outline-5}|{outline-6}|inherit', 'resize': 'none|both|horizontal|vertical|inherit', } # CSS Box Module Level 3 macros[Profiles.CSS3_BOX] = { 'overflow': macros[Profiles.CSS_LEVEL_2]['overflow'] } properties[Profiles.CSS3_BOX] = { 'overflow': '{overflow}{w}{overflow}?|inherit', 'overflow-x': '{overflow}|inherit', 'overflow-y': '{overflow}|inherit' } # CSS Color Module Level 3 macros[Profiles.CSS3_COLOR] = { # orange and transparent in CSS 2.1 'namedcolor': r'(currentcolor|transparent|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)', # noqa # orange? 'rgbacolor': r'rgba\({w}{int}{w}\,{w}{int}{w}\,{w}{int}{w}\,{w}{num}{w}\)|rgba\({w}{num}%{w}\,{w}{num}%{w}\,{w}{num}%{w}\,{w}{num}{w}\)', # noqa 'hslcolor': r'hsl\({w}{int}{w}\,{w}{num}%{w}\,{w}{num}%{w}\)|hsla\({w}{int}{w}\,{w}{num}%{w}\,{w}{num}%{w}\,{w}{num}{w}\)', # noqa 'x11color': r'aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen', # noqa 'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)', # noqa 'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|{x11color}|inherit', } properties[Profiles.CSS3_COLOR] = { 'opacity': r'{num}|inherit', } # CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/ macros[Profiles.CSS3_FONTS] = { # 'family-name': r'{string}|{ident}', 'family-name': r'{string}|({ident}(\s+{ident})*)', 'font-face-name': r'local\({w}{family-name}{w}\)', 'font-stretch-names': r'(ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)', # noqa 'unicode-range': r'[uU]\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?' } properties[Profiles.CSS3_FONTS] = { 'font-size-adjust': r'{number}|none|inherit', 'font-stretch': r'normal|wider|narrower|{font-stretch-names}|inherit' } properties[Profiles.CSS3_FONT_FACE] = { 'font-family': '{family-name}', 'font-stretch': r'{font-stretch-names}', 'font-style': r'normal|italic|oblique', 'font-weight': r'normal|bold|[1-9]00', 'src': r'({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name})({w},{w}({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name}))*', # noqa 'unicode-range': '{unicode-range}({w},{w}{unicode-range})*' } # CSS3 Paged Media macros[Profiles.CSS3_PAGED_MEDIA] = { 'page-size': 'a5|a4|a3|b5|b4|letter|legal|ledger', 'page-orientation': 'portrait|landscape', 'page-1': '{page-size}(?:{w}{page-orientation})?', 'page-2': '{page-orientation}(?:{w}{page-size})?', 'page-size-orientation': '{page-1}|{page-2}', 'pagebreak': 'auto|always|avoid|left|right' } break_properties = ( 'auto|avoid|always|all|avoid-page|page|left|right|recto|verso|avoid-column|column|avoid-region|region') global_properties = 'inherit|initial|unset' properties[Profiles.CSS3_PAGED_MEDIA] = { 'fit': 'fill|hidden|meet|slice', 'fit-position': r'auto|(({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?))', # noqa 'image-orientation': 'auto|{angle}', 'orphans': r'{integer}|inherit', 'page': 'auto|{ident}', 'page-break-before': '{pagebreak}|inherit', 'page-break-after': '{pagebreak}|inherit', 'page-break-inside': 'auto|avoid|inherit', 'break-before': break_properties + '|' + global_properties, 'break-after': break_properties + '|' + global_properties, 'break-inside': 'auto|avoid|avoid-page|avoid-column|avoid-region|' + global_properties, 'size': '({length}{w}){1,2}|auto|{page-size-orientation}', 'widows': r'{integer}|inherit' } macros[Profiles.CSS3_TEXT] = { } properties[Profiles.CSS3_TEXT] = { 'text-shadow': 'none|{shadow}({w},{w}{shadow})*', } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/sac.py0000644000175000017500000004172600000000000016701 0ustar00kovidkovid#!/usr/bin/env python from __future__ import unicode_literals, division, absolute_import, print_function from . import tokenize2 from . import errorhandler import codecs from . import helper """A validating CSSParser""" __docformat__ = 'restructuredtext' __version__ = '$Id: parse.py 1754 2009-05-30 14:50:13Z cthedot $' import sys PY2 = sys.version_info[0] == 2 class ErrorHandler(object): """Basic class for CSS error handlers. This class class provides a default implementation ignoring warnings and recoverable errors and throwing a SAXParseException for fatal errors. If a CSS application needs to implement customized error handling, it must extend this class and then register an instance with the CSS parser using the parser's setErrorHandler method. The parser will then report all errors and warnings through this interface. The parser shall use this class instead of throwing an exception: it is up to the application whether to throw an exception for different types of errors and warnings. Note, however, that there is no requirement that the parser continue to provide useful information after a call to fatalError (in other words, a CSS driver class could catch an exception and report a fatalError). """ def __init__(self): self._log = errorhandler.ErrorHandler() def error(self, exception, token=None): self._log.error(exception, token, neverraise=True) def fatal(self, exception, token=None): self._log.fatal(exception, token) def warn(self, exception, token=None): self._log.warn(exception, token, neverraise=True) class DocumentHandler(object): """ void endFontFace() Receive notification of the end of a font face statement. void endMedia(SACMediaList media) Receive notification of the end of a media statement. void endPage(java.lang.String name, java.lang.String pseudo_page) Receive notification of the end of a media statement. void importStyle(java.lang.String uri, SACMediaList media, java.lang.String defaultNamespaceURI) Receive notification of a import statement in the style sheet. void startFontFace() Receive notification of the beginning of a font face statement. void startMedia(SACMediaList media) Receive notification of the beginning of a media statement. void startPage(java.lang.String name, java.lang.String pseudo_page) Receive notification of the beginning of a page statement. """ def __init__(self): def log(msg): sys.stderr.write('INFO\t%s\n' % msg) self._log = log def comment(self, text, line=None, col=None): "Receive notification of a comment." self._log("comment %r at [%s, %s]" % (text, line, col)) def startDocument(self, encoding): "Receive notification of the beginning of a style sheet." # source self._log("startDocument encoding=%s" % encoding) def endDocument(self, source=None, line=None, col=None): "Receive notification of the end of a document." self._log("endDocument EOF") def importStyle(self, uri, media, name, line=None, col=None): "Receive notification of a import statement in the style sheet." # defaultNamespaceURI??? self._log("importStyle at [%s, %s]" % (line, col)) def namespaceDeclaration(self, prefix, uri, line=None, col=None): "Receive notification of an unknown rule t-rule not supported by this parser." # prefix might be None! self._log("namespaceDeclaration at [%s, %s]" % (line, col)) def startSelector(self, selectors=None, line=None, col=None): "Receive notification of the beginning of a rule statement." # TODO selectorList! self._log("startSelector at [%s, %s]" % (line, col)) def endSelector(self, selectors=None, line=None, col=None): "Receive notification of the end of a rule statement." self._log("endSelector at [%s, %s]" % (line, col)) def property(self, name, value='TODO', important=False, line=None, col=None): "Receive notification of a declaration." # TODO: value is LexicalValue? self._log("property %r at [%s, %s]" % (name, line, col)) def ignorableAtRule(self, atRule, line=None, col=None): "Receive notification of an unknown rule t-rule not supported by this parser." self._log("ignorableAtRule %r at [%s, %s]" % (atRule, line, col)) class EchoHandler(DocumentHandler): "Echos all input to property `out`" def __init__(self): super(EchoHandler, self).__init__() self._out = [] out = property(lambda self: ''.join(self._out)) def startDocument(self, encoding): super(EchoHandler, self).startDocument(encoding) if 'utf-8' != encoding: self._out.append('@charset "%s";\n' % encoding) # def comment(self, text, line=None, col=None): # self._out.append(u'/*%s*/' % text) def importStyle(self, uri, media, name, line=None, col=None): "Receive notification of a import statement in the style sheet." # defaultNamespaceURI??? super(EchoHandler, self).importStyle(uri, media, name, line, col) self._out.append('@import %s%s%s;\n' % (helper.string(uri), '%s ' % media if media else '', '%s ' % name if name else '') ) def namespaceDeclaration(self, prefix, uri, line=None, col=None): super(EchoHandler, self).namespaceDeclaration(prefix, uri, line, col) self._out.append('@namespace %s%s;\n' % ('%s ' % prefix if prefix else '', helper.string(uri))) def startSelector(self, selectors=None, line=None, col=None): super(EchoHandler, self).startSelector(selectors, line, col) if selectors: self._out.append(', '.join(selectors)) self._out.append(' {\n') def endSelector(self, selectors=None, line=None, col=None): self._out.append(' }') def property(self, name, value, important=False, line=None, col=None): super(EchoHandler, self).property(name, value, line, col) self._out.append(' %s: %s%s;\n' % (name, value, ' !important' if important else '')) class Parser(object): """ java.lang.String getParserVersion() Returns a string about which CSS language is supported by this parser. boolean parsePriority(InputSource source) Parse a CSS priority value (e.g. LexicalUnit parsePropertyValue(InputSource source) Parse a CSS property value. void parseRule(InputSource source) Parse a CSS rule. SelectorList parseSelectors(InputSource source) Parse a comma separated list of selectors. void parseStyleDeclaration(InputSource source) Parse a CSS style declaration (without '{' and '}'). void parseStyleSheet(InputSource source) Parse a CSS document. void parseStyleSheet(java.lang.String uri) Parse a CSS document from a URI. void setConditionFactory(ConditionFactory conditionFactory) void setDocumentHandler(DocumentHandler handler) Allow an application to register a document event handler. void setErrorHandler(ErrorHandler handler) Allow an application to register an error event handler. void setLocale(java.util.Locale locale) Allow an application to request a locale for errors and warnings. void setSelectorFactory(SelectorFactory selectorFactory) """ def __init__(self, documentHandler=None, errorHandler=None): self._tokenizer = tokenize2.Tokenizer() if documentHandler: self.setDocumentHandler(documentHandler) else: self.setDocumentHandler(DocumentHandler()) if errorHandler: self.setErrorHandler(errorHandler) else: self.setErrorHandler(ErrorHandler()) def parseString(self, cssText, encoding=None): if isinstance(cssText, str): cssText = codecs.getdecoder('css')(cssText, encoding=encoding)[0] tokens = self._tokenizer.tokenize(cssText, fullsheet=True) def COMMENT(val, line, col): self._handler.comment(val[2:-2], line, col) def EOF(val, line, col): self._handler.endDocument(val, line, col) def simple(t): map = {'COMMENT': COMMENT, 'S': lambda val, line, col: None, 'EOF': EOF} type_, val, line, col = t if type_ in map: map[type_](val, line, col) return True else: return False # START PARSING t = next(tokens) type_, val, line, col = t encoding = 'utf-8' if 'CHARSET_SYM' == type_: # @charset "encoding"; # S encodingtoken = next(tokens) semicolontoken = next(tokens) if 'STRING' == type_: encoding = helper.stringvalue(val) # ; if 'STRING' == encodingtoken[0] and semicolontoken: encoding = helper.stringvalue(encodingtoken[1]) else: self._errorHandler.fatal('Invalid @charset') t = next(tokens) type_, val, line, col = t self._handler.startDocument(encoding) while True: start = (line, col) try: if simple(t): pass elif 'ATKEYWORD' == type_ or type_ in ('PAGE_SYM', 'MEDIA_SYM', 'FONT_FACE_SYM'): atRule = [val] braces = 0 while True: # read till end ; # TODO: or {} t = next(tokens) type_, val, line, col = t atRule.append(val) if ';' == val and not braces: break elif '{' == val: braces += 1 elif '}' == val: braces -= 1 if braces == 0: break self._handler.ignorableAtRule(''.join(atRule), *start) elif 'IMPORT_SYM' == type_: # import URI or STRING media? name? uri, media, name = None, None, None while True: t = next(tokens) type_, val, line, col = t if 'STRING' == type_: uri = helper.stringvalue(val) elif 'URI' == type_: uri = helper.urivalue(val) elif ';' == val: break if uri: self._handler.importStyle(uri, media, name) else: self._errorHandler.error('Invalid @import' ' declaration at %r' % (start,)) elif 'NAMESPACE_SYM' == type_: prefix, uri = None, None while True: t = next(tokens) type_, val, line, col = t if 'IDENT' == type_: prefix = val elif 'STRING' == type_: uri = helper.stringvalue(val) elif 'URI' == type_: uri = helper.urivalue(val) elif ';' == val: break if uri: self._handler.namespaceDeclaration(prefix, uri, *start) else: self._errorHandler.error('Invalid @namespace' ' declaration at %r' % (start,)) else: # CSSSTYLERULE selector = [] selectors = [] while True: # selectors[, selector]* { if 'S' == type_: selector.append(' ') elif simple(t): pass elif ',' == val: selectors.append(''.join(selector).strip()) selector = [] elif '{' == val: selectors.append(''.join(selector).strip()) self._handler.startSelector(selectors, *start) break else: selector.append(val) t = next(tokens) type_, val, line, col = t end = None while True: # name: value [!important][;name: value [!important]]*;? name, value, important = None, [], False while True: # name: t = next(tokens) type_, val, line, col = t if 'S' == type_: pass elif simple(t): pass elif 'IDENT' == type_: if name: self._errorHandler.error('more than one property name', t) else: name = val elif ':' == val: if not name: self._errorHandler.error('no property name', t) break elif ';' == val: self._errorHandler.error('premature end of property', t) end = val break elif '}' == val: if name: self._errorHandler.error('premature end of property', t) end = val break else: self._errorHandler.error('unexpected property name token %r' % val, t) while not ';' == end and not '}' == end: # value !;} t = next(tokens) type_, val, line, col = t if 'S' == type_: value.append(' ') elif simple(t): pass elif '!' == val or ';' == val or '}' == val: value = ''.join(value).strip() if not value: self._errorHandler.error('premature end of property (no value)', t) end = val break else: value.append(val) while '!' == end: # !important t = next(tokens) type_, val, line, col = t if simple(t): pass elif 'IDENT' == type_ and not important: important = True elif ';' == val or '}' == val: end = val break else: self._errorHandler.error('unexpected priority token %r' % val) if name and value: self._handler.property(name, value, important) if '}' == end: self._handler.endSelector(selectors, line=line, col=col) break else: # reset end = None else: self._handler.endSelector(selectors, line=line, col=col) t = next(tokens) type_, val, line, col = t except StopIteration: break def setDocumentHandler(self, handler): "Allow an application to register a document event `handler`." self._handler = handler def setErrorHandler(self, handler): "TODO" self._errorHandler = handler ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1559563160.0 css-parser-1.0.7/src/css_parser/script.py0000644000175000017500000003153200000000000017431 0ustar00kovidkovidfrom __future__ import (absolute_import, division, print_function, unicode_literals) import codecs import errno import logging import os import sys import css_parser import css_parser.encutils as encutils """classes and functions used by css_parser scripts """ __all__ = ['CSSCapture', 'csscombine'] __docformat__ = 'restructuredtext' __version__ = '$Id: parse.py 1323 2008-07-06 18:13:57Z cthedot $' if sys.version_info[0] >= 3: from html.parser import HTMLParser as htmlparser_HTMLParser from urllib.parse import urlsplit as urllib_urlsplit from urllib.parse import urljoin as urllib_urljoin from urllib.request import urlopen as urllib_urlopen from urllib.request import Request as urllib_Request from urllib.error import HTTPError as urllib_HTTPError else: from HTMLParser import HTMLParser as htmlparser_HTMLParser from urlparse import urlsplit as urllib_urlsplit from urlparse import urljoin as urllib_urljoin from urllib2 import urlopen as urllib_urlopen from urllib2 import HTTPError as urllib_HTTPError from urllib2 import Request as urllib_Request # types of sheets in HTML LINK = 0 # STYLE = 1 # class CSSCaptureHTMLParser(htmlparser_HTMLParser): """CSSCapture helper: Parse given data for link and style elements""" curtag = '' sheets = [] # (type, [atts, cssText]) def _loweratts(self, atts): return dict([(a.lower(), v.lower()) for a, v in atts]) def handle_starttag(self, tag, atts): if tag == 'link': atts = self._loweratts(atts) if 'text/css' == atts.get('type', ''): self.sheets.append((LINK, atts)) elif tag == 'style': # also get content of style atts = self._loweratts(atts) if 'text/css' == atts.get('type', ''): self.sheets.append((STYLE, [atts, ''])) self.curtag = tag else: # close as only intersting