diff options
author | Karl Berry <karl@freefriends.org> | 2012-12-18 00:16:57 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2012-12-18 00:16:57 +0000 |
commit | 7ce0811299d8127ecd4eff9d16c73e4972cd2c32 (patch) | |
tree | 9fd64f068ccc5b095a1467b6a5ef3efeda66ba30 /Master/texmf-dist/doc/support | |
parent | 140b20f73ca9e01da13e96a751ce89703fc87872 (diff) |
latexdiff (17dec12)
git-svn-id: svn://tug.org/texlive/trunk@28564 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc/support')
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/COPYING | 623 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/README | 107 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/contrib/README.latexchanges | 13 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/contrib/latexchanges.py | 67 | ||||
-rwxr-xr-x | Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff-wrap | 192 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff.spec | 58 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/doc/example-diff.tex | 89 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.pdf | bin | 0 -> 193735 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.tex | 355 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/example/example-draft.tex | 59 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/latexdiff/example/example-rev.tex | 60 | ||||
-rwxr-xr-x | Master/texmf-dist/doc/support/latexdiff/latexdiff | 3400 | ||||
-rwxr-xr-x | Master/texmf-dist/doc/support/latexdiff/latexdiff-fast | 3961 |
13 files changed, 8984 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/support/latexdiff/COPYING b/Master/texmf-dist/doc/support/latexdiff/COPYING new file mode 100644 index 00000000000..d6fa9152e8d --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/COPYING @@ -0,0 +1,623 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + 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 + + diff --git a/Master/texmf-dist/doc/support/latexdiff/README b/Master/texmf-dist/doc/support/latexdiff/README new file mode 100644 index 00000000000..7c7ff86766b --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/README @@ -0,0 +1,107 @@ +INTRODUCTION + +latexdiff is a Perl script, which compares two latex files and marks +up significant differences between them (i.e. a diff for latex files). + Various options are available for visual markup using standard latex +packages such as "color.sty". Changes not directly affecting visible +text, for example in formatting commands, are still marked in +the latex source. + +A rudimentary revision facilility is provided by another Perl script, +latexrevise, which accepts or rejects all changes. Manual +editing of the difference file can be used to override this default +behaviour and accept or reject selected changes only. + +The author is F Tilmann (ftilmann@users.berlios.de). + +Project webpage: http://latexdiff.berlios.de/ +CTAN page: http://www.ctan.org/tex-archive/support/latexdiff + + +REQUIREMENTS + +Perl 5.8 or higher must be installed. + The latexdiff script makes use of the Perl package Algorithm::Diff (available +from www.cpan.org, current version 1.19). You can either install this package, or +use the standalone version of latexdiff, latexdiff-so, which has version 1.15 of +this package inlined and does not require external installation of +the package. Because latexdiff uses internal functions of Algorithm:Diff whose +calling format or availability can change without notice, the preferred method is +now to use the standalone version. + +As an alternative, latexdiff-fast has a modified version of Algorithm::Diff inlined, +which internally uses the UNIX diff command. This version is much faster but is dependent +on an external "diff" command. Subtle differences in the algorithm of Algorithm::Diff and +UNIX-diff mean that the resulting set of differences will generally not be the same as +for the standard latexdiff. In most practical cases, these differences are minor, though. + +INSTALLATION UNIX/LINUX + +The basic installation procedure is almost trivial: + +1. Copy latexdiff, latexrevise and latexdiff-vc into a directory which + is in the search path and make them executable. If the Algorithm::Diff + package is not installed, use latexdiff-so instead of latexdiff. + +2. Copy latexdiff.1 and latexrevise.1 into the correct man directory + +3. Optionally create soft links latexdiff-cvs latexdiff-rcs, and + latexdiff-svn for latexdiff-vc. + +The attached Makefile contains example commands to carry out above +steps as root for a typical UNIX installation. Type + + make install (for the stand alone version) +or + make install-ext (for the version using the external Algorithm::Diff) +or + make install-fast (for the version using the UNIX 'diff' function for fast differencing) + +to get it rolling. You can type + + make test +or + make test-ext +or + make test-fast + +to test the respective versions on a brief example before installation + + +DOCUMENTATION: + +Usage instructions are in the manual latexdiff-man.pdf as well as the +man pages. + +CHANGELOGS: + +Check out the comment lines at the beginning of the perl scripts (latexdiff, latexdiff-vc, latexrevise) + +CONTRIBUTIONS + +The directory contrib contains code written by others relating to latexdiff. +Currently this directory contains: + +latexdiff-wrap (Author: V. Kuhlmann) An alternative wrapper script which can be used + instead of latexdiff-vc. Its main use is as a template for customised wrapper scripts. + +latexdiff.spec (Author: T. Doerges) spec file for RPM generation + +latexchanges (Author: Jan-Ake Larsson) Wrapper script for applying latexdiff with numbered documen version +(see contrib/README.latexchanges for a more detailed description) + +Cntributions by the following authors were incorporated into the latexdiff code, or inspired me to +extend latexdiff in a similar way: J. Paisley, N. Becker, K. Huebner + +LICENSE (also see file COPYING) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 as published by +the Free Software Foundation. + +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 (file LICENSE in the +distribution). + diff --git a/Master/texmf-dist/doc/support/latexdiff/contrib/README.latexchanges b/Master/texmf-dist/doc/support/latexdiff/contrib/README.latexchanges new file mode 100644 index 00000000000..4a027658444 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/contrib/README.latexchanges @@ -0,0 +1,13 @@ +latexchanges.py (Jan-Ake Larsson): +Here's a wrapper I wrote for latexdiff, intended as a drop-in +replacement for latex, when you have several numbered (or dated) +versions of a manuscript. My coauthors don't as a rule know what CVS or +SVN is, they simply use a number or date for the different versions. + +latexchanges replaces the current DVI with one that includes a +latexdiff to the last version. The last version is selected as the +TEX file in the same directory with the same prefix (up to a number +or a dot), that has an mtime immediately preceding the given TEX +file. + + diff --git a/Master/texmf-dist/doc/support/latexdiff/contrib/latexchanges.py b/Master/texmf-dist/doc/support/latexdiff/contrib/latexchanges.py new file mode 100644 index 00000000000..de7acbe8258 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/contrib/latexchanges.py @@ -0,0 +1,67 @@ +#! /bin/env python +# latexchanges +# +# Wrapper for latexdiff, intended as a drop-in replacement for latex, +# when you have several numbered (or dated) versions of a manuscript. +# My coauthors don't as a rule know what CVS or SVN is, they simply +# use a number or date for the different versions. +# +# latexchanges replaces the current DVI with one that includes a +# latexdiff to the last version. The last version is selected as the +# TEX file in the same directory with the same prefix (up to a number +# or a dot), that has an mtime immediately preceding the given TEX +# file. +# +# (I should probably add CVS version numbering too, at some point.) +# +# Copyright (C) 2009 by Jan-\AA{}ke Larsson <jan-ake.larsson@liu.se> +# Released under the terms of the GNU General Public License (GPL) +# Version 2. See http://www.gnu.org/ for details. +# +# Please do provide patches and bug reports, but remember: if it +# breaks, you get to keep the pieces. +# +# Jan-\AA{}ke Larsson +# Sept 16 2009 + +from os import listdir,system,stat +from sys import argv +from re import split + +name="" +newarg=[] + +# Find filename argument +for i in range(1,len(argv)): + if argv[i][-4:]==".tex": + basename=split('[0-9.]',argv[i])[0] + name=argv[i][:-4] + newarg.append(name+".changes.tex") + else: + newarg.append(argv[i]) + +if name: + print "Filename",name+".tex" + print "Prefix is",basename + # Find last archived version + mtime=stat(name+".tex").st_mtime + old_mtime=0 + ls=listdir(".") + for j in ls: + if j.startswith(basename) and j.endswith(".tex")\ + and not j.endswith(".changes.tex"): + tmptime=stat(j).st_mtime + if mtime>tmptime and old_mtime<tmptime: + oldname=j + old_mtime=tmptime + + # Archived version found? + if old_mtime>0: + print "Comparing with",oldname + system ("/bin/cp "+name+".aux "+name+".changes.aux") + system ("/bin/cp "+name+".bbl "+name+".changes.bbl") + system ("latexdiff "+oldname+" "+name+".tex > "+name+".changes.tex") + system ("latex "+" ".join(newarg)) + system ("cp "+name+".changes.dvi "+name+".dvi") + else: + system ("latex "+" ".join(argv[1:])) diff --git a/Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff-wrap b/Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff-wrap new file mode 100755 index 00000000000..894b424de36 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff-wrap @@ -0,0 +1,192 @@ +#!/bin/bash +# +# latexdiff-wrap +# +# Wrapper for latexdiff, to +# * provide support for documents consiting of more than 1 latex file +# * provide my common arguments +# +# Copyright (C) by Volker Kuhlmann <VolkerKuhlmann@gmx.de> +# Released under the terms of the GNU General Public License (GPL) Version 2. +# See http://www.gnu.org/ for details. +# +# Volker Kuhlmann +# 5, 6, 7, 12, 16, 17 Oct 2005 +# 31 Jan; 5, 7, 13, 15 Feb 2006 +# + +VERSION="0.6, 15 Feb 2006" +AUTHOR="Volker Kuhlmann <VolkerKuhlmann@gmx.de>" +COPYRIGHT="Copyright (C) 2005-2006" + + +#### +#### Constants and initialised variables +# +diffcmd="latexdiff" +diffrc="$HOME/texmf/latexdiff" +#diffargs="-e latin1 --ignore-warnings -p latexdiff-preamble.sty" +diffargs="-e latin1 --ignore-warnings" +diffargs="$diffargs --append-safecmd $diffrc/safe-cmds" +diffargs="$diffargs --append-textcmd $diffrc/text-cmds" +# Note: Can't use multiple --append-safecmd +# show current command lists: +#diffcmd="$diffcmd --show-safecmd --show-textcmd --show-config" + + +#### +#### Version, Usage, Help +# +show_version() { + echo "${0##*/} version $VERSION +$COPYRIGHT by $AUTHOR" +} + +show_usage() { + echo " +Usage: ${0##*/} OLDDIR NEWDIR DIFFDIR [DIFFARGS --] FILE.tex [...] + ${0##*/} --show [DIFFARGS] +Version $VERSION +$COPYRIGHT by $AUTHOR +" +} + +show_help() { + show_usage + echo "\ +For each FILE.tex, build a new file DIFFDIR/FILE.tex with markup of the changes +which were made from OLDDIR/FILE.tex to NEWDIR/FILE.tex. +Any path given with FILE.tex is stripped off. +Any DIFFARGS are added to the latexdiff call, if present (remember to follow +them with a double-hyphen on its own before the FILE arguments). + +With --show, shows the settings latexdiff would be running with, including the +changes applied by the user. +" +} + +# For scripts not using function library only: +Version() { show_version; exitwith ErrVersion; } +Usage() { show_help; exitwith ErrUsage; } +Help() { test "$1" && exitwith ErrHelp show_help; show_help; exitwith ErrOK; } + + +#### +#### Error/Exit codes +# +exitwith() { + exec 1>&2 # write stdout on stderr instead + case "$1" in + ErrOK) + exit 0;; + ErrVersion|ErrUsage|ErrHelp) + # Output generated by function (program) $2, if given + test -n "$2" && "$2" + exit 1;; + # more codes in here + # more codes in here + ErrBadoption) + echo "Bad option '$2'." + echo "Call with -h for help." + exit 9;; + ErrMissingParameter) + echo "A required parameter for option $2 is missing." + echo "Call with -h for help." + exit 9;; + *) + echo "Internal error: exitwith() called with illegal error code '$1'." + exit 19;; + esac +} + + +#### +#### Parse command line parameters +# + +# If the next arg starts with a "-", collect additional argument for latexdiff +# until "--". +scanextraargs() { + addargs=() + case "$1" in -*) + while [ $# -gt 0 -a "$1" != "--" ]; do + addargs=( "${addargs[@]}" "$1" ) + shift + done + test "$1" == "--" && shift + ;; esac + fileargs=( "$@" ) +} + +case "$1" in + --version) Version;; + --usage) Usage;; + --help|-h|-help) Help;; + --show) + shift + scanextraargs "$@" + (set -x + $diffcmd $diffargs "${addargs[@]}" \ + --show-safecmd --show-textcmd --show-config + ) | fmt + exit $? ;; +esac + +olddir="${1%/}" +newdir="${2%/}" +diffdir="${3%/}" + +if ! [ -d "$olddir" -a -d "$newdir" -a -d "$diffdir" ]; then + Help 1>&2 err +fi + +shift 3 + +scanextraargs "$@" +set -- "${fileargs[@]}" + + + +#### +#### Functions +# +#set -x +Log() { echo 1>&2 "+ $@"; "$@"; } + + +#### +#### Main +# + +# Create output directory, just in case. +(set -x +mkdir -p "$diffdir" +) +while [ $# -gt 0 ]; do + file="${1##*/}" + echo Examining: "$file" + # No point running latexdiff if both files are identical, + # but run latexdiff on top-level LaTeX file in any case. + if cmp --quiet "$olddir/$file" "$newdir/$file" \ + && ! grep -lq '\\begin.*{document}' "$newdir/$file"; then + (set -x + cp -p "$olddir/$file" "$diffdir" + ) + else + # Delete file, to make sure it's not clobbered by redirecting stdout + # in case it's a symlink to te original. + test -f "$diffdir/$file" && (set -x + rm "$diffdir/$file" + ) + # Run latexdiff if both input files are present. + run=1 + test -f "$olddir/$file" || { echo 1>&2 "No file: $olddir/$file"; run=; } + test -f "$newdir/$file" || { echo 1>&2 "No file: $newdir/$file"; run=; } + test -n "$run" && \ + (set -x + $diffcmd $diffargs "${addargs[@]}" \ + "$olddir/$file" "$newdir/$file" > "$diffdir/$file" + ) + fi + shift +done diff --git a/Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff.spec b/Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff.spec new file mode 100644 index 00000000000..9255a69fea9 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/contrib/latexdiff.spec @@ -0,0 +1,58 @@ +Summary: Diff for LaTeX files +Name: latexdiff +Version: 0.5 +Release: 1 +License: GPL +Group: Productivity/Publishing/TeX/Utilities +URL: http://www.tug.org/tex-archive/help/Catalogue/entries/latexdiff.html +Source0: %{name}.zip +BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +# only required for 'make install-ext' +# Requires: perl-Algorithm-Diff + + +%description +latexdiff is a Perl script, which compares two latex files and marks +up significant differences between them (i.e. a diff for latex files). + Various options are available for visual markup using standard latex +packages such as "color.sty". Changes not directly affecting visible +text, for example in formatting commands, are still marked in +the latex source. + +(C) 2004 Frederik Tilmann <tilmann@esc.cam.ac.uk> + + +%prep +%setup -n %{name} + + +%build +# quick had to adapt the Makefile +%{__mv} Makefile Makefile.old +%{__sed} \ + -e "s;INSTALLPATH = /usr/local;INSTALLPATH = \${DESTDIR}%{_prefix};" \ + -e "s;INSTALLMANPATH = \$(INSTALLPATH)/man;INSTALLMANPATH = \${DESTDIR}%{_mandir};" \ + Makefile.old > Makefile + + +%install +%{__mkdir_p} $RPM_BUILD_ROOT%{_bindir} +%{__mkdir_p} $RPM_BUILD_ROOT%{_mandir}/man1 + +%makeinstall + + +%clean +[ "${RPM_BUILD_ROOT}" != "/" ] && [ -d "${RPM_BUILD_ROOT}" ] && %{__rm} -rf "${RPM_BUILD_ROOT}" + + +%files +%defattr(-,root,root) +%doc example CHANGES LICENSE README +%{_bindir}/* +%{_mandir}/man*/* + +%changelog +* Thu Jan 4 2007 Till Dörges <till@doerges.net> - 0.5-1 +- Initial build. diff --git a/Master/texmf-dist/doc/support/latexdiff/doc/example-diff.tex b/Master/texmf-dist/doc/support/latexdiff/doc/example-diff.tex new file mode 100644 index 00000000000..149588d2110 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/doc/example-diff.tex @@ -0,0 +1,89 @@ +\documentclass[12pt,a4paper]{article} +%DIF LATEXDIFF DIFFERENCE FILE +%DIF DEL example-draft.tex Sat Nov 17 00:45:22 2012 +%DIF ADD example-rev.tex Sat Nov 17 00:45:22 2012 + +\setlength{\topmargin}{-0.2in} +\setlength{\textheight}{9.5in} +\setlength{\oddsidemargin}{0.0in} + +%DIF 7c7 +%DIF < \setlength{\textwidth}{6.5in} +%DIF ------- +\setlength{\textwidth}{6in} %DIF > +%DIF ------- + +\title{latexdiff Example - \DIFdelbegin \DIFdel{Draft }\DIFdelend \DIFaddbegin \DIFadd{Revised }\DIFaddend version} +\author{F Tilmann} +% Note how in the preamble visual markup is never used (even %DIF > +% if some preamble might eventually end up as visible text.) %DIF > +%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF +%DIF UNDERLINE PREAMBLE %DIF PREAMBLE +\RequirePackage[normalem]{ulem} %DIF PREAMBLE +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} %DIF PREAMBLE +\providecommand{\DIFadd}[1]{{\protect\color{blue}\uwave{#1}}} %DIF PREAMBLE +\providecommand{\DIFdel}[1]{{\protect\color{red}\sout{#1}}} %DIF PREAMBLE +%DIF SAFE PREAMBLE %DIF PREAMBLE +\providecommand{\DIFaddbegin}{} %DIF PREAMBLE +\providecommand{\DIFaddend}{} %DIF PREAMBLE +\providecommand{\DIFdelbegin}{} %DIF PREAMBLE +\providecommand{\DIFdelend}{} %DIF PREAMBLE +%DIF FLOATSAFE PREAMBLE %DIF PREAMBLE +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} %DIF PREAMBLE +\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} %DIF PREAMBLE +\providecommand{\DIFaddbeginFL}{} %DIF PREAMBLE +\providecommand{\DIFaddendFL}{} %DIF PREAMBLE +\providecommand{\DIFdelbeginFL}{} %DIF PREAMBLE +\providecommand{\DIFdelendFL}{} %DIF PREAMBLE +%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF + +\begin{document} +\maketitle + +\section*{Introduction} + +This is an extremely simple document that showcases some of \DIFaddbegin \DIFadd{the }\DIFaddend latexdiff features. +Type +\begin{verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{verbatim} +to create the difference file. You can inspect this file directly. Then run either +\begin{verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{verbatim} +or +\begin{verbatim} +latex example-diff.tex +dvips -o example-diff.ps example-diff.dvi +gv example-diff.ps +\end{verbatim} +to display the markup. + +\section*{\DIFdelbegin \DIFdel{Another }\DIFdelend \DIFaddbegin \DIFadd{Yet another }\DIFaddend section title} + + \DIFdelbegin \DIFdel{A paragraph with a line only in the draft document. }\DIFdelend More things could be said were it not for the constraints of time and space. + +\DIFaddbegin \DIFadd{A paragraph with a line only in the revised document. }\DIFaddend More things could be +said were it not for the constraints of time and space. + +And here is a \DIFdelbegin \DIFdel{tipo}\DIFdelend \DIFaddbegin \DIFadd{typo}\DIFaddend . + +Here is a table: + +\begin{tabular}{ll} +Name & Description \\ +\hline +Gandalf & \DIFdelbegin \DIFdel{Grey }\DIFdelend \DIFaddbegin \DIFadd{White }\DIFaddend \\ +Saruman & \DIFdelbegin \DIFdel{White +}\DIFdelend \DIFaddbegin \DIFadd{Evil +}\DIFaddend \end{tabular} + +And \DIFdelbegin \DIFdel{sometimes a whole paragraph gets completely rewritten. In this +case latexdiff marks up the whole paragraph even if some words in it +are identical}\DIFdelend \DIFaddbegin \DIFadd{now for something completely different, with not a paragraph in sight}\DIFaddend . +No change, +no markup! +\end{document} + + diff --git a/Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.pdf b/Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.pdf Binary files differnew file mode 100644 index 00000000000..a6b5a33aacd --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.pdf diff --git a/Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.tex b/Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.tex new file mode 100644 index 00000000000..b6bb37effe6 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/doc/latexdiff-man.tex @@ -0,0 +1,355 @@ +\documentclass[a4]{article} +\usepackage{graphicx} +%\def\C++{{\rm C\kern-.05em\raise.3ex\hbox{\footnotesize ++}}} +%\def\underscore{\leavevmode\kern.04em\vbox{\hrule width 0.4em height 0.3pt}} +\setlength{\parindent}{0pt} +%\setlength{\textwidth}{6.5in} +%\setlength{\oddsidemargin}{0.0in} +\title{Marking up differences between latex files with {\em latexdiff}} +\author{F.J. Tilmann\thanks{tilmann@gfz-potsdam.de,ftilmann@users.berlios.de}} +\date{\today} + +\begin{document} +\maketitle + +\section*{Preamble} + +{\em latexdiff} is a Perl script, which compares two +latex files and marks up significant differences between them. Various options are available for visual markup using standard +latex packages such as {\em color.sty}. Changes not directly affecting visible +text, for example in formatting commands, are still marked in the +latex source. + +A rudimentary revision facilility is provided by another Perl script, +{\em latexrevise}, which accepts or rejects all changes. Manual editing +of the difference file can be used to override this default behaviour +and accept or reject selected changes only. + +There is no explicit support for annotations as these are trivial to implement. +For example, I include the following command definition in the preamble +\begin{verbatim} +\newcommand{\remark}[1]{{ \bf [ \footnotesize #1 ]}} +\end{verbatim} +and mark up annotations as follows +\begin{verbatim} +... The roadrunner is the fastest running bird \remark{Check this +again with a zoologist!}. The most famous roadrunner ... +\end{verbatim} +Alternatively, instead of a command like \verb#\remark# in the example just given, an +equivalent annotation environment could be defined. +{\em latexrevise} can remove such comments or +environments from the text body. + +%It is planned that the revision capabilities of this system will be +%further expanded, dependent on the amount of feedback received. + +On the following pages you find the {\em man} pages for {\em + latexdiff} and {\em latexrevise} and a simple example. + +\include{latexdiff} +\setcounter{section}{0} + +\include{latexrevise} +\setcounter{section}{0} + +\include{latexdiff-vc} +\setcounter{section}{0} + +\section*{A simple example} + +We start with a draft text, \verb|example-draft.tex|, listed here in +full but also included in the distribution (except that the ``verbatim'' environment had +to be renamed to ``Verbatim'' for the listing). + +{\scriptsize +\begin{verbatim} +\documentclass[12pt,a4paper]{article} + +\setlength{\topmargin}{-0.2in} +\setlength{\textheight}{9.5in} +\setlength{\oddsidemargin}{0.0in} + +\setlength{\textwidth}{6.5in} + +\title{latexdiff Example - Draft version} +\author{F Tilmann} + +\begin{document} +\maketitle + +\section*{Introduction} + +This is an extremely simple document that showcases some of latexdiff features. +Type +\begin{Verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{Verbatim} +to create the difference file. You can inspect this file directly. Then run either +\begin{Verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{Verbatim} +or +\begin{Verbatim} +latex example-diff.tex +dvips -o example-diff.ps example-diff.dvi +gv example-diff.ps +\end{Verbatim} +to display the markup. Of course, instead of \verb|xpdf| you can use +\verb|okular, evince, acroread| or any other pdf or postscript viewer. + +\section*{Another section title} + +A paragraph with a line only in the draft document. More things +could be said were it not for the constraints of time and space. + +More things could be said were it not for the constraints of time and space. + +And here is a tipo. + +Here is a table: + +\begin{tabular}{ll} +Name & Description \\ +\hline +Gandalf & Grey \\ +Saruman & White +\end{tabular} + +And sometimes a whole paragraph gets completely rewritten. In this +case latexdiff marks up the whole paragraph even if some words in it +are identical. +No change, no markup! +\end{document} +\end{verbatim} +} + +We can now edit +this text as we would do with any other latex file to create +a new revision of the text, \verb|example-rev.tex|. We should run +\begin{verbatim} +latex example-rev.tex +\end{verbatim} +and look at the resulting \verb|.dvi| file to make sure that all +changes are valid. An example revision is listed here: + +{\scriptsize +\begin{verbatim} +\documentclass[12pt,a4paper]{article} + +\setlength{\topmargin}{-0.2in} +\setlength{\textheight}{9.5in} +\setlength{\oddsidemargin}{0.0in} + +\setlength{\textwidth}{6in} + +\title{latexdiff Example - Revised version} +\author{F Tilmann} +% Note how in the preamble visual markup is never used (even +% if some preamble might eventually end up as visible text.) + +\begin{document} +\maketitle + +\section*{Introduction} + +This is an extremely simple document that showcases some of the latexdiff features. +Type +\begin{Verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{Verbatim} +to create the difference file. You can inspect this file directly. Then run either +\begin{Verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{Verbatim} +or +\begin{Verbatim} +latex example-diff.tex +dvips -o example-diff.ps example-diff.dvi +gv example-diff.ps +\end{Verbatim} +to display the markup. + +\section*{Yet another section title} + + More things could be said were it not for the constraints of time and space. + +A paragraph with a line only in the revised document. +More things could be said were it not for the constraints of time and space. + +And here is a typo. + +Here is a table: + +\begin{tabular}{ll} +Name & Description \\ +\hline +Gandalf & White \\ +Saruman & Evil +\end{tabular} + +And now for something completely different, with not a paragraph in sight. +No change, +no markup! +\end{document} +\end{verbatim} +} + +To compare both revisions, type +\begin{verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{verbatim} +This results in the following difference file (a few newlines have been +added in this listing for legibility reasosn): +{\scriptsize +\begin{verbatim} +\documentclass[12pt,a4paper]{article} + +\setlength{\topmargin}{-0.2in} +\setlength{\textheight}{9.5in} +\setlength{\oddsidemargin}{0.0in} + +%DIF 7c7 +%DIF < \setlength{\textwidth}{6.5in} +%DIF ------- +\setlength{\textwidth}{6in} %DIF > +%DIF ------- + +%DIF 9c9 +%DIF < \title{latexdiff Example - Draft version} +%DIF ------- +\title{latexdiff Example - Revised version} %DIF > +%DIF ------- +\author{F Tilmann} +% Note how in the preamble visual markup is never used (even %DIF > +% if some preamble might eventually end up as visible text.) %DIF > +%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF +%DIF UNDERLINE PREAMBLE %DIF PREAMBLE +\RequirePackage[normalem]{ulem} %DIF PREAMBLE +\RequirePackage{color} %DIF PREAMBLE +\providecommand{\DIFadd}[1]{{\color{blue}\uline{#1}}} %DIF PREAMBLE +\providecommand{\DIFdel}[1]{{\color{red}\sout{#1}}} %DIF PREAMBLE +%DIF SAFE PREAMBLE %DIF PREAMBLE +\providecommand{\DIFaddbegin}{} %DIF PREAMBLE +\providecommand{\DIFaddend}{} %DIF PREAMBLE +\providecommand{\DIFdelbegin}{} %DIF PREAMBLE +\providecommand{\DIFdelend}{} %DIF PREAMBLE +%DIF FLOATSAFE PREAMBLE %DIF PREAMBLE +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} %DIF PREAMBLE +\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} %DIF PREAMBLE +\providecommand{\DIFaddbeginFL}{} %DIF PREAMBLE +\providecommand{\DIFaddendFL}{} %DIF PREAMBLE +\providecommand{\DIFdelbeginFL}{} %DIF PREAMBLE +\providecommand{\DIFdelendFL}{} %DIF PREAMBLE +%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF + +\begin{document} +\maketitle + +\section*{Introduction} + +This is an extremely simple document that showcases some of latexdiff features. +Type +\begin{Verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{Verbatim} +to create the difference file. You can inspect this file directly. Then run either +\begin{Verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{Verbatim} +or +\begin{Verbatim} +latex example-diff.tex +dvips -o example-diff.ps example-diff.dvi +gv example-diff.ps +\end{Verbatim} +to display the markup. + +\section*{\DIFaddbegin \DIFadd{Yet another }\DIFaddend \DIFdelbegin +\DIFdel{Another }\DIFdelend section title} + + \DIFdelbegin \DIFdel{A paragraph with a line only in the draft + document. }\DIFdelend More things could + be said were it not for the constraints of time and space. + +\DIFaddbegin \DIFadd{A paragraph with a line only in the revised + document. }\DIFaddend More things could be said +were it not for the constraints of time and space. + +And here is a \DIFaddbegin \DIFadd{typo}\DIFaddend \DIFdelbegin +\DIFdel{tipo}\DIFdelend . + +Here is a table: + +\begin{tabular}{ll} +Name & Description \\ +\hline +Gandalf & \DIFaddbegin \DIFadd{White }\DIFaddend \DIFdelbegin +\DIFdel{Grey }\DIFdelend \\ +Saruman & \DIFaddbegin \DIFadd{Evil +}\DIFaddend \DIFdelbegin \DIFdel{White +}\DIFdelend \end{tabular} + +And \DIFaddbegin \DIFadd{now for something completely different, with not + a paragraph in sight}\DIFaddend \DIFdelbegin \DIFdel{sometimes a whole + paragraph gets completely rewritten. In this +case latexdiff marks up the whole paragraph even if some words in it +are identical}\DIFdelend . +No change, +no markup! +\end{document} +\end{verbatim} +} +Type +\begin{verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{verbatim} +to make the markup visible. This is what it looks like: + +\vspace{1cm} +\framebox[\textwidth]{\includegraphics[width=\textwidth]{example-diff}} +\vspace{1cm} + +If you approve of all the changes in the revision, just continue with +\verb|example-rev.tex| for the next revision. If you like to adopt +most but not all changes you can use \verb|latexrevise| in the +following manner. Simply remove the \verb|\DIFdelbegin| and +\verb|\DIFdelend| tags around the text you would like to keep and +simply remove the text between \verb|\DIFaddbegin| and +\verb|\DIFaddend| tags, if you do not wish to keep them. Say you are happy with all proposed changes for the +example above except in +the last paragraph where you prefer the original draft. You have +to change + +{\scriptsize +\begin{verbatim} +... +And \DIFaddbegin \DIFadd{now for something completely different, with not + a paragraph in sight}\DIFaddend \DIFdelbegin \DIFdel{sometimes a whole + paragraph gets completely rewritten. In this +case latexdiff marks up the whole paragraph even if some words in it +are identical}\DIFdelend . +... +\end{verbatim} +} +into +{\scriptsize +\begin{verbatim} +... +And \DIFdel{sometimes a whole + paragraph gets completely rewritten. In this +case latexdiff marks up the whole paragraph even if some words in it +are identical}. +... +\end{verbatim} +} +and run +\begin{verbatim} +latexrevise -a example-rev.tex > example-final.tex +\end{verbatim} +\verb|example-final.tex| is then almost identical to +\verb|example-rev.tex| except for the last paragraph. +\end{document} diff --git a/Master/texmf-dist/doc/support/latexdiff/example/example-draft.tex b/Master/texmf-dist/doc/support/latexdiff/example/example-draft.tex new file mode 100644 index 00000000000..593a1707121 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/example/example-draft.tex @@ -0,0 +1,59 @@ +\documentclass[12pt,a4paper]{article} + +\setlength{\topmargin}{-0.2in} +\setlength{\textheight}{9.5in} +\setlength{\oddsidemargin}{0.0in} + +\setlength{\textwidth}{6.5in} + +\title{latexdiff Example - Draft version} +\author{F Tilmann} + +\begin{document} +\maketitle + +\section*{Introduction} + +This is an extremely simple document that showcases some of latexdiff features. +Type +\begin{verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{verbatim} +to create the difference file. You can inspect this file directly. Then run either +\begin{verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{verbatim} +or +\begin{verbatim} +latex example-diff.tex +dvips -o example-diff.ps example-diff.dvi +gv example-diff.ps +\end{verbatim} +to display the markup. + +\section*{Another section title} + +A paragraph with a line only in the draft document. More things could be said +were it not for the constraints of time and space. + +More things could be said were it not for the constraints of time and space. + +And here is a tipo. + +Here is a table: + +\begin{tabular}{ll} +Name & Description \\ +\hline +Gandalf & Grey \\ +Saruman & White +\end{tabular} + +And sometimes a whole paragraph gets completely rewritten. In this +case latexdiff marks up the whole paragraph even if some words in it +are identical. +No change, no markup! +\end{document} + + diff --git a/Master/texmf-dist/doc/support/latexdiff/example/example-rev.tex b/Master/texmf-dist/doc/support/latexdiff/example/example-rev.tex new file mode 100644 index 00000000000..4bcaf156272 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/example/example-rev.tex @@ -0,0 +1,60 @@ +\documentclass[12pt,a4paper]{article} + +\setlength{\topmargin}{-0.2in} +\setlength{\textheight}{9.5in} +\setlength{\oddsidemargin}{0.0in} + +\setlength{\textwidth}{6in} + +\title{latexdiff Example - Revised version} +\author{F Tilmann} +% Note how in the preamble visual markup is never used (even +% if some preamble might eventually end up as visible text.) + +\begin{document} +\maketitle + +\section*{Introduction} + +This is an extremely simple document that showcases some of the latexdiff features. +Type +\begin{verbatim} +latexdiff -t UNDERLINE example-draft.tex example-rev.tex > example-diff.tex +\end{verbatim} +to create the difference file. You can inspect this file directly. Then run either +\begin{verbatim} +pdflatex example-diff.tex +xpdf example-diff.pdf +\end{verbatim} +or +\begin{verbatim} +latex example-diff.tex +dvips -o example-diff.ps example-diff.dvi +gv example-diff.ps +\end{verbatim} +to display the markup. + +\section*{Yet another section title} + + More things could be said were it not for the constraints of time and space. + +A paragraph with a line only in the revised document. More things could be +said were it not for the constraints of time and space. + +And here is a typo. + +Here is a table: + +\begin{tabular}{ll} +Name & Description \\ +\hline +Gandalf & White \\ +Saruman & Evil +\end{tabular} + +And now for something completely different, with not a paragraph in sight. +No change, +no markup! +\end{document} + + diff --git a/Master/texmf-dist/doc/support/latexdiff/latexdiff b/Master/texmf-dist/doc/support/latexdiff/latexdiff new file mode 100755 index 00000000000..7075561e119 --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/latexdiff @@ -0,0 +1,3400 @@ +#!/usr/bin/env perl +##!/usr/bin/perl -w +# latexdiff - differences two latex files on the word level +# and produces a latex file with the differences marked up. +# +# Copyright (C) 2004-12 F J Tilmann (tilmann@gfz-potsdam.de, ftilmann@users.berlios.de) +# +# Project webpages: http://latexdiff.berlios.de/ +# CTAN page: http://www.ctan.org/tex-archive/support/latexdiff +# +# 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 <http://www.gnu.org/licenses/>. +# +# Detailed usage information at the end of the file +# +# Version 1.0.1 - treat \big,\bigg etc. equivalently to \left and +# \right - include starred version in MATHENV - apply +# - flatten recursively and --flatten expansion is now +# aware of comments (thanks to Tim Connors for patch) +# - Change to post-processing for more reliability for +# deleted math environments +# - On linux systems, recognise and remove DOS style newlines +# - Provide markup for some special preamble commands (\title, +# \author,\date, +# - configurable by setting context2cmd +# - for styles using ulem package, remove \emph and \text.. from list of +# safe commands in order to allow linebreaks within the +# highlighted sections. +# - for ulem style, now show citations by enclosing them in \mbox commands. +# This unfortunately implies linebreaks within citations no longer function, +# so this functionality can be turned off (Option --disable-citation-markup). +# With --enable-citation-markup, the mbox markup is forced for other styles) +# - new substyle COLOR. This is particularly useful for marking up citations +# and some special post-processing is implemented to retain cite +# commands in deleted blocks. +# - four different levels of math-markup +# - Option --driver for choosing driver for modes employing changebar package +# - accept \\* as valid command (and other commands of form \.*). Also accept +# \<nl> (backslashed newline) +# - some typo fixes, include commands defined in preamble as safe commands +# (Sebastian Gouezel) +# - include compared filenames as comments as line 2 and 3 of +# the preamble (can be modified with option --label, and suppressed with +# --no-label), option --visible-label to show files in generated pdf or dvi +# at the beginning of main document +# +# Version 0.5 A number of minor improvements based on feedback +# Deleted blocks are now shown before added blocks +# Package specific processing +# +# Version 0.43 unreleased typo in list of styles at the end +# Add protect to all \cbstart, \cbend commands +# More robust substitution of deleted math commands +# +# Version 0.42 November 06 Bug fixes only +# +# Version 0.4 March 06 option for fast differencing using UNIX diff command, several minor bug fixes (\par bug, improved highlighting of textcmds) +# +# Version 0.3 August 05 improved parsing of displayed math, --allow-spaces +# option, several minor bug fixes +# +# Version 0.25 October 04 Fix bug with deleted equations, add math mode commands to safecmd, add | to allowed interpunctuation signs +# Version 0.2 September 04 extension to utf-8 and variable encodings +# Version 0.1 August 04 First public release + +use Algorithm::Diff qw(traverse_sequences); + +use Getopt::Long ; +use strict ; +use warnings; +use utf8 ; + +my ($algodiffversion)=split(/ /,$Algorithm::Diff::VERSION); + + +my ($versionstring)=<<EOF ; +This is LATEXDIFF 1.0.2 (Algorithm::Diff $Algorithm::Diff::VERSION, Perl $^V) + (c) 2004-2012 F J Tilmann +EOF + +# Configuration variables: these have to be visible from the subroutines +my $MINWORDSBLOCK=3; # minimum number of tokens to form an independent block + # shorter identical blocks will be merged to the previous word +my $FLOATENV='(?:figure|table|plate)[\w\d*@]*' ; # Environments in which FL variants of defined commands are used +my $PICTUREENV='(?:picture|DIFnomarkup)[\w\d*@]*' ; # Environments in which all change markup is removed +my $MATHENV='(?:equation[*]?|displaymath|DOLLARDOLLAR)[*]?' ; # Environments turning on display math mode (code also knows about \[ and \]) +my $MATHREPL='displaymath'; # Environment introducing deleted maths blocks +my $MATHARRENV='(?:eqnarray|align|alignat|gather|multline|flalign)[*]?' ; # Environments turning on eqnarray math mode +my $MATHARRREPL='eqnarray*'; # Environment introducing deleted maths blocks +my $ARRENV='(?:array|[pbvBV]matrix)'; # Environments making arrays in math mode. The underlining style does not cope well with those - as a result in-text math environments are surrounded by \mbox{ } if any of these commands is used in an inline math block +my $COUNTERCMD='(?:footnote|part|chapter|section|subsection|subsubsection|paragraph|subparagraph)'; # textcmds which are associated with a counter + # If any of these commands occur in a deleted block + # they will be succeeded by an \addtocounter{...}{-1} + # for the associated counter such that the overall numbers + # should be the same as in the new file +my $CITECMD=0 ; # \cite-type commands which need to be protected within an mbox in UNDERLINE and other modes using ulem; pattern simply designed to never match; will be overwritten later for selected styles +my $CITE2CMD=0; # \cite-type commands which should be reinstated in deleted blocks + + + +# Markup strings +# If at all possible, do not change these as parts of the program +# depend on the actual name (particularly post-processing) +# At the very least adapt subroutine postprocess to new tokens. +my $ADDMARKOPEN='\DIFaddbegin '; # Token to mark begin of appended text +my $ADDMARKCLOSE='\DIFaddend '; # Token to mark end of appended text +my $ADDOPEN='\DIFadd{'; # To mark begin of added text passage +my $ADDCLOSE='}'; # To mark end of added text passage +my $ADDCOMMENT='DIF > '; # To mark added comment line +my $DELMARKOPEN='\DIFdelbegin '; # Token to mark begin of deleted text +my $DELMARKCLOSE='\DIFdelend '; # Token to mark end of deleted text +my $DELOPEN='\DIFdel{'; # To mark begin of deleted text passage +my $DELCLOSE='}'; # To mark end of deleted text passage +my $DELCMDOPEN='%DIFDELCMD < '; # To mark begin of deleted commands (must begin with %, i.e., be a comment +my $DELCMDCLOSE="%%%\n"; # To mark end of deleted commands (must end with a new line) +my $AUXCMD='%DIFAUXCMD' ; # follows auxiliary commands put in by latexdiff to make difference file legal + # auxiliary commands must be on a line of their own +my $DELCOMMENT='DIF < '; # To mark deleted comment line + + +# main local variables: +my @TEXTCMDLIST=(); # array containing patterns of commands with text arguments +my @TEXTCMDEXCL=(); # array containing patterns of commands without text arguments (if a pattern + # matches both TEXTCMDLIST and TEXTCMDEXCL it is excluded) +my @CONTEXT1CMDLIST=(); # array containing patterns of commands with text arguments (subset of text commands), + # but which cause confusion if used out of context (e.g. \caption). + # In deleted passages, the command will be disabled but its argument is marked up + # Otherwise they behave exactly like TEXTCMD's +my @CONTEXT1CMDEXCL=(); # exclude list for above, but always empty +my @CONTEXT2CMDLIST=(); # array containing patterns of commands with text arguments, but which fail or cause confusion + # if used out of context (e.g. \title). They and their arguments will be disabled in deleted + # passages +my @CONTEXT2CMDEXCL=(); # exclude list for above, but always empty +my @MATHTEXTCMDLIST=(); # treat like textcmd. If a textcmd is in deleted or added block, just wrap the + # whole content with \DIFadd or \DIFdel irrespective of content. This functionality + # is useful for pseudo commands \MATHBLOCK.. into which math environments are being + # transformed +my @MATHTEXTCMDEXCL=(); # + +# Note I need to declare this with "our" instead of "my" because later in the code I have to "local"ise these +our @SAFECMDLIST=(); # array containing patterns of safe commands (which do not break when in the argument of DIFadd or DIFDEL) +our @SAFECMDEXCL=(); + +my ($i,$j,$l); +my ($old,$new); +my ($line); +my ($newpreamble,$oldpreamble); +my (@newpreamble,@oldpreamble,@diffpreamble,@diffbody); +my ($latexdiffpreamble); +my ($oldbody, $newbody, $diffbo); +my ($oldpost, $newpost); +my ($diffall); +# Option names +my ($type,$subtype,$floattype,$config,$preamblefile,$encoding,$nolabel,$visiblelabel, + $showpreamble,$showsafe,$showtext,$showconfig,$showall, + $replacesafe,$appendsafe,$excludesafe, + $replacetext,$appendtext,$excludetext, + $replacecontext1,$appendcontext1, + $replacecontext2,$appendcontext2, + $help,$verbose,$driver,$version,$ignorewarnings, + $enablecitmark,$disablecitmark,$allowspaces,$flatten,$debug); ###$disablemathmark, +# MNEMNONICS for mathmarkup +my $mathmarkup; +use constant { + OFF => 0, + WHOLE => 1, + COARSE => 2, + FINE => 3 +}; + + +my (@configlist,@labels, + @appendsafelist,@excludesafelist, + @appendtextlist,@excludetextlist, + @appendcontext1list,@appendcontext2list, + @packagelist); +my ($assign,@config); +# Hash where keys corresponds to the names of all included packages (including the documentclass as another package +# the optional arguments to the package are the values of the hash elements +my ($pkg,%packages); +# Defaults +$type='UNDERLINE'; +$subtype='SAFE'; +$floattype='FLOATSAFE'; +$mathmarkup=COARSE; + +$verbose=0; +# output debug and intermediate files, set to 0 in final distribution +$debug=0; +# define character properties +sub IsNonAsciiPunct { return <<'END' # Unicode punctuation but excluding ASCII punctuation ++utf8::IsPunct +-utf8::IsASCII +END +} +sub IsNonAsciiS { return <<'END' # Unicode symbol but excluding ASCII ++utf8::IsS +-utf8::IsASCII +END +} + + +my %verbhash; + +Getopt::Long::Configure('bundling'); +GetOptions('type|t=s' => \$type, + 'subtype|s=s' => \$subtype, + 'floattype|f=s' => \$floattype, + 'config|c=s' => \@configlist, + 'preamble|p=s' => \$preamblefile, + 'encoding|e=s' => \$encoding, + 'label|L=s' => \@labels, + 'no-label' => \$nolabel, + 'visible-label' => \$visiblelabel, + 'exclude-safecmd|A=s' => \@excludesafelist, + 'replace-safecmd=s' => \$replacesafe, + 'append-safecmd|a=s' => \@appendsafelist, + 'exclude-textcmd|X=s' => \@excludetextlist, + 'replace-textcmd=s' => \$replacetext, + 'append-textcmd|x=s' => \@appendtextlist, + 'replace-context1cmd=s' => \$replacecontext1, + 'append-context1cmd=s' => \@appendcontext1list, + 'replace-context2cmd=s' => \$replacecontext2, + 'append-context2cmd=s' => \@appendcontext2list, + 'show-preamble' => \$showpreamble, + 'show-safecmd' => \$showsafe, + 'show-textcmd' => \$showtext, + 'show-config' => \$showconfig, + 'show-all' => \$showall, + 'packages=s' => \@packagelist, + 'allow-spaces' => \$allowspaces, + 'math-markup=s' => \$mathmarkup, + 'enable-citation-markup' => \$enablecitmark, + 'disable-citation-markup' => \$disablecitmark, + 'verbose|V' => \$verbose, + 'ignore-warnings' => \$ignorewarnings, + 'driver=s'=> \$driver, + 'flatten' => \$flatten, + 'version' => \$version, + 'help|h|H' => \$help); + +if ( $help ) { + usage() ; +} + + +if ( $version ) { + die $versionstring ; +} + +print STDERR $versionstring if $verbose; + +if (defined($showall)){ + $showpreamble=$showsafe=$showtext=$showconfig=1; +} + +if (defined($mathmarkup)) { + $mathmarkup=~tr/a-z/A-Z/; + if ( $mathmarkup eq 'OFF' ){ + $mathmarkup=OFF; + } elsif ( $mathmarkup eq 'WHOLE' ){ + $mathmarkup=WHOLE; + } elsif ( $mathmarkup eq 'COARSE' ){ + $mathmarkup=COARSE; + } elsif ( $mathmarkup eq 'FINE' ){ + $mathmarkup=FINE; + } elsif ( $mathmarkup !~ m/^[0123]$/ ) { + die "Illegal value: ($mathmarkup) for option--math-markup. Possible values: OFF,WHOLE,COARSE,FINE,0- "; + } + # else use numerical value +} + +# setting extra preamble commands +if (defined($preamblefile)) { + $latexdiffpreamble=join "\n",(extrapream($preamblefile),""); +} else { + $latexdiffpreamble=join "\n",(extrapream($type,$subtype,$floattype),""); +} + +if ( defined($driver) ) { + # for changebar only + $latexdiffpreamble=~s/\[dvips\]/[$driver]/sg; +} +# setting up @SAFECMDLIST and @SAFECMDEXCL +if (defined($replacesafe)) { + init_regex_arr_ext(\@SAFECMDLIST,$replacesafe); +} else { + init_regex_arr_data(\@SAFECMDLIST, "SAFE COMMANDS"); +} +foreach $appendsafe ( @appendsafelist ) { + init_regex_arr_ext(\@SAFECMDLIST, $appendsafe); +} +foreach $excludesafe ( @excludesafelist ) { + init_regex_arr_ext(\@SAFECMDEXCL, $excludesafe); +} + +# Special: treat all cite commands as safe except in UNDERLINE and FONTSTRIKE mode +# (there is a conflict between citation and ulem package, see +# package documentation) +# Use post-processing + +if ( uc($type) ne "UNDERLINE" && uc($type) ne "FONTSTRIKE" && uc($type) ne "CULINECHBAR" ) { + push (@SAFECMDLIST, qr/^cite.*$/); +} else { + ### Experimental: disable text and emph commands + push (@SAFECMDLIST, qr/^cite.*$/) unless $disablecitmark; + push(@SAFECMDEXCL, qr/^emph$/, qr/^text..$/); + # replace \cite{..} by \mbox{\cite{..}} in added or deleted blocks in post-processing + if ( uc($subtype) eq "COLOR" or uc($subtype) eq "DVIPSCOL" ) { + # remove \cite command again from list of safe commands + pop @SAFECMDLIST; + # deleted cite commands + $CITE2CMD='(?:cite\w*|nocite)' unless $disablecitmark ; # \cite-type commands which should be reinstated in deleted blocks + } else { + $CITECMD='(?:cite\w*|nocite)' unless $disablecitmark ; # \cite commands which need to be protected within an mbox in UNDERLINE and other modes using ulem + } +} +$CITECMD='(?:cite\w*|nocite)' if $enablecitmark ; # as above for explicit selection + +# setting up @TEXTCMDLIST and @TEXTCMDEXCL +if (defined($replacetext)) { + init_regex_arr_ext(\@TEXTCMDLIST,$replacetext); +} else { + init_regex_arr_data(\@TEXTCMDLIST, "TEXT COMMANDS"); +} +foreach $appendtext ( @appendtextlist ) { + init_regex_arr_ext(\@TEXTCMDLIST, $appendtext); +} +foreach $excludetext ( @excludetextlist ) { + init_regex_arr_ext(\@TEXTCMDEXCL, $excludetext); +} + + +# setting up @CONTEXT1CMDLIST ( @CONTEXT1CMDEXCL exist but is always empty ) +if (defined($replacecontext1)) { + init_regex_arr_ext(\@CONTEXT1CMDLIST,$replacecontext1); +} else { + init_regex_arr_data(\@CONTEXT1CMDLIST, "CONTEXT1 COMMANDS"); +} +foreach $appendcontext1 ( @appendcontext1list ) { + init_regex_arr_ext(\@CONTEXT1CMDLIST, $appendcontext1); +} + + +# setting up @CONTEXT2CMDLIST ( @CONTEXT2CMDEXCL exist but is always empty ) +if (defined($replacecontext2)) { + init_regex_arr_ext(\@CONTEXT2CMDLIST,$replacecontext2); +} else { + init_regex_arr_data(\@CONTEXT2CMDLIST, "CONTEXT2 COMMANDS"); +} +foreach $appendcontext2 ( @appendcontext2list ) { + init_regex_arr_ext(\@CONTEXT2CMDLIST, $appendcontext2); +} + +# setting configuration variables +@config=(); +foreach $config ( @configlist ) { + if (-f $config ) { + open(FILE,$config) or die ("Couldn't open configuration file $config: $!"); + while (<FILE>) { + chomp; + next if /^\s*#/ || /^\s*%/ || /^\s*$/ ; + push (@config,$_); + } + close(FILE); + } + else { +# foreach ( split(",",$config) ) { +# push @config,$_; +# } + push @config,split(",",$config) + } +} +foreach $assign ( @config ) { + $assign=~ m/\s*(\w*)\s*=\s*(\S*)\s*$/ or die "Illegal assignment $assign in configuration list (must be variable=value)"; + if ( $1 eq "MINWORDSBLOCK" ) { $MINWORDSBLOCK = $2; } + elsif ( $1 eq "FLOATENV" ) { $FLOATENV = $2 ; } + elsif ( $1 eq "PICTUREENV" ) { $PICTUREENV = $2 ; } + elsif ( $1 eq "MATHENV" ) { $MATHENV = $2 ; } + elsif ( $1 eq "MATHREPL" ) { $MATHREPL = $2 ; } + elsif ( $1 eq "MATHARRENV" ) { $MATHARRENV = $2 ; } + elsif ( $1 eq "MATHARRREPL" ) { $MATHARRREPL = $2 ; } + elsif ( $1 eq "ARRENV" ) { $ARRENV = $2 ; } + elsif ( $1 eq "COUNTERCMD" ) { $COUNTERCMD = $2 ; } + else { die "Unknown variable $1 in assignment.";} +} + +if ( $mathmarkup == COARSE || $mathmarkup == WHOLE ) { + push(@MATHTEXTCMDLIST,qr/^MATHBLOCK(?:$MATHENV|$MATHARRENV|SQUAREBRACKET)$/); +} + + + +foreach $pkg ( @packagelist ) { + map { $packages{$_}="" } split(/,/,$pkg) ; +} + +if ($showpreamble) { + print "\nPreamble commands:\n"; + print $latexdiffpreamble ; +} + +if ($showsafe) { + print "\nCommands safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE (unless excluded):\n"; + print_regex_arr(@SAFECMDLIST); + print "\nCommands not safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE :\n"; + print_regex_arr(@SAFECMDEXCL); +} + +if ($showtext) { + print "\nCommands with last argument textual (unless excluded) and safe in every context:\n"; + print_regex_arr(@TEXTCMDLIST); + print "\nContext1 commands (last argument textual, command will be disabled in deleted passages, last argument will be shown as plain text):\n"; + print_regex_arr(@CONTEXT1CMDLIST); + print "\nContext2 commands (last argument textual, command and its argument will be disabled in deleted passages):\n"; + print_regex_arr(@CONTEXT2CMDLIST); + print "\nExclude list of Commands with last argument not textual (overrides patterns above):\n"; + print_regex_arr(@TEXTCMDEXCL); +} + + +if ($showconfig) { + print "Configuration variables:\n"; + print "MINWORDSBLOCK=$MINWORDSBLOCK\n"; + print "FLOATENV=$FLOATENV\n"; + print "PICTUREENV=$PICTUREENV\n"; + print "MATHENV=$MATHENV\n"; + print "MATHREPL=$MATHREPL\n"; + print "MATHARRENV=$MATHARRENV\n"; + print "MATHARRREPL=$MATHARRREPL\n"; + print "ARRENV=$ARRENV\n"; + print "COUNTERCMD=$COUNTERCMD\n"; +} +if ($showconfig || $showtext || $showsafe || $showpreamble) { + exit 0; } +if ( @ARGV != 2 ) { + print STDERR "2 and only 2 non-option arguments required. Write latexdiff -h to get help\n"; + exit(2); +} + +# Are extra spaces between command arguments permissible? +my $extraspace; +if ($allowspaces) { + $extraspace='\s*'; +} else { + $extraspace=''; +} + +# append context lists to text lists (as text property is implied) +push @TEXTCMDLIST, @CONTEXT1CMDLIST; +push @TEXTCMDLIST, @CONTEXT2CMDLIST; + +push @TEXTCMDLIST, @MATHTEXTCMDLIST if $mathmarkup==COARSE; + +# internal additions to SAFECMDLIST +push(@SAFECMDLIST, qr/^QLEFTBRACE$/, qr/^QRIGHTBRACE$/); + + +# Patterns. These are used by some of the subroutines, too +# I can only define them down here because value of extraspace depends on an option + my $pat0 = '(?:[^{}])*'; + my $pat1 = '(?:[^{}]|\{'.$pat0.'\})*'; + my $pat2 = '(?:[^{}]|\{'.$pat1.'\})*'; + my $pat3 = '(?:[^{}]|\{'.$pat2.'\})*'; + my $pat4 = '(?:[^{}]|\{'.$pat3.'\})*'; + my $pat5 = '(?:[^{}]|\{'.$pat4.'\})*'; + my $pat6 = '(?:[^{}]|\{'.$pat5.'\})*'; + my $brat0 = '(?:[^\[\]]|\\\[|\\\])*'; + + my $quotemarks = '(?:\'\')|(?:\`\`)'; + my $punct='[0.,\/\'\`:;\"\?\(\)\[\]!~\p{IsNonAsciiPunct}\p{IsNonAsciiS}]'; + my $number='-?\d*\.\d*'; + my $mathpunct='[+=<>\-\|]'; + my $and = '&'; + my $coords= '[\-.,\s\d]*'; +# word: sequence of letters or accents followed by letter + my $word='(?:[-\w\d*]|\\\\[\"\'\`~^][A-Za-z\*])+'; + my $cmdleftright='\\\\(?:left|right|[Bb]igg?[lrm]?|middle)\s*(?:[()\[\]|]|\\\\(?:[|{}]|\w+))'; + + my $cmdoptseq='\\\\[\w\d\*]+'.$extraspace.'(?:(?:\['.$brat0.'\]|\{'. $pat6 . '\}|\(' . $coords .'\))'.$extraspace.')*'; + my $backslashnl='\\\\\n'; + my $oneletcmd='\\\\.\*?(?:\['.$brat0.'\]|\{'. $pat6 . '\})*'; + my $math='\$(?:[^$]|\\\$)*?\$|\\\\[(].*?\\\\[)]'; +## the current maths command cannot cope with newline within the math expression + + my $comment='%.*?\n'; + my $pat=qr/(?:\A\s*)?(?:${and}|${quotemarks}|${number}|${word}|$cmdleftright|${cmdoptseq}|${math}|${backslashnl}|${oneletcmd}|${comment}|${punct}|${mathpunct}|\{|\})\s*/ ; + + + +# now we are done setting up and can start working +my ($oldfile, $newfile) = @ARGV; +# check for existence of input files +if ( ! -e $oldfile ) { + die "Input file $oldfile does not exist."; +} +if ( ! -e $newfile ) { + die "Input file $newfile does not exist."; +} + + +# set the labels to be included into the file +my ($oldtime,$newtime,$oldlabel,$newlabel); +if (defined($labels[0])) { + $oldlabel=$labels[0] ; +} else { + $oldtime=localtime((stat($oldfile))[9]); + $oldlabel="$oldfile " . " "x(length($newfile)-length($oldfile)) . $oldtime; +} +if (defined($labels[1])) { + $newlabel=$labels[1] ; +} else { + $newtime=localtime((stat($newfile))[9]); + $newlabel="$newfile " . " "x(length($oldfile)-length($newfile)) . $newtime; +} + +$encoding=guess_encoding($newfile) unless defined($encoding); + +$encoding = "utf8" if $encoding =~ m/^utf8/i ; +if (lc($encoding) eq "utf8" ) { + binmode(STDOUT, ":utf8"); + binmode(STDERR, ":utf8"); +} + +$old=read_file_with_encoding($oldfile,$encoding); +$new=read_file_with_encoding($newfile,$encoding); + + + + +# reset time +exetime(1); +($oldpreamble,$oldbody,$oldpost)=splitdoc($old,'\\\\begin\{document\}','\\\\end\{document\}'); + + +($newpreamble,$newbody,$newpost)=splitdoc($new,'\\\\begin\{document\}','\\\\end\{document\}'); + + +if ($flatten) { + $oldbody=flatten($oldbody,$oldpreamble,$oldfile,$encoding); + $newbody=flatten($newbody,$newpreamble,$newfile,$encoding); +} + +my @auxlines; +if ( length $oldpreamble && length $newpreamble ) { + # pre-process preamble by looking for commands used in \maketitle (title, author, date etc commands) + # and marking up content with latexdiff markup + @auxlines=preprocess_preamble($oldpreamble,$newpreamble); + + @oldpreamble = split /\n/, $oldpreamble; + @newpreamble = split /\n/, $newpreamble; + + # If a command is defined in the preamble of the new file, and only uses safe commands, then it can be considered to be safe) (contribution S. Gouezel) + # Base this assessment on the new preamble + add_safe_commands($newpreamble); + + %packages=list_packages(@newpreamble) unless %packages; + if (defined $packages{"hyperref"} ) { + print STDERR "hyperref package detected.\n" if $verbose ; + $latexdiffpreamble =~ s/\{\\DIFadd\}/{\\DIFaddtex}/g; + $latexdiffpreamble =~ s/\{\\DIFdel\}/{\\DIFdeltex}/g; + $latexdiffpreamble .= join "\n",(extrapream("HYPERREF"),""); + } + print STDERR "Differencing preamble.\n" if $verbose; + + # insert dummy first line such that line count begins with line 1 (rather than perl's line 0) - just so that line numbers inserted by linediff are correct + unshift @newpreamble,''; + unshift @oldpreamble,''; + @diffpreamble = linediff(\@oldpreamble, \@newpreamble); + # remove dummy line again + shift @diffpreamble; + # add filenames, modification time and latexdiff mark + defined($nolabel) or splice @diffpreamble,1,0, + "%DIF LATEXDIFF DIFFERENCE FILE", + ,"%DIF DEL $oldlabel", + "%DIF ADD $newlabel"; + if ( @auxlines ) { + push @diffpreamble,"%DIF DELETED TITLE COMMANDS FOR MARKUP"; + push @diffpreamble,join("\n",@auxlines); + } + push @diffpreamble,$latexdiffpreamble; + push @diffpreamble,'\begin{document}'; +} +elsif ( !length $oldpreamble && !length $newpreamble ) { + @diffpreamble=(); +} else { + print STDERR "Either both texts must have preamble or neither text must have the preamble.\n"; + exit(2); +} + +if (defined $packages{"amsmath"} or defined $packages{"amsart"} or defined $packages{"amsbook"} ) { + print STDERR "amsmath package detected.\n" if $verbose ; + $MATHARRREPL='align*'; +} + +print STDERR "Preprocessing body. " if $verbose; +my ($oldleadin,$newleadin)=preprocess($oldbody,$newbody); + + +# run difference algorithm +@diffbody=bodydiff($oldbody, $newbody); +$diffbo=join("",@diffbody); +if ( $debug ) { + open(RAWDIFF,">","latexdiff.debug.bodydiff"); + print RAWDIFF $diffbo; + close(RAWDIFF); +} +print STDERR "(",exetime()," s)\n","Postprocessing body. \n " if $verbose; +postprocess($diffbo); +$diffall =join("\n",@diffpreamble) ; +# add visible labels +if (defined($visiblelabel)) { + # Give information right after \begin{document} (or at the beginning of the text for files without preamble + ### if \date command is used, add information to \date argument, otherwise give right after \begin{document} + ### $diffall=~s/(\\date$extraspace(?:\[$brat0\])?$extraspace)\{($pat6)\}/$1\{$2 \\ LATEXDIFF comparison \\ Old: $oldlabel \\ New: $newlabel \}/ or + $diffbo = "\\begin{verbatim}LATEXDIFF comparison\nOld: $oldlabel\nNew: $newlabel\\end{verbatim}\n$diffbo" ; +} + +$diffall .= "$newleadin$diffbo" ; +$diffall .= "\\end{document}$newpost" if length $newpreamble ; +if ( lc($encoding) ne "utf8" && lc($encoding) ne "ascii" ) { + print STDERR "Encoding output file to $encoding\n" if $verbose; + $diffall=Encode::encode($encoding,$diffall); + binmode STDOUT; +} +print $diffall; + + +print STDERR "(",exetime()," s)\n","Done.\n" if $verbose; + + + +## guess_encoding(filename) +## reads the first 20 lines of filename and looks for call of inputenc package +## if found, return the option of this package (encoding), otherwise return ascii +sub guess_encoding { + my ($filename)=@_; + my ($i,$enc); + open (FH, $filename) or die("Couldn't open $filename: $!"); + $i=0; + while (<FH>) { + next if /^\s*%/; # skip comment lines + if (m/\\usepackage\[(\w*?)\]\{inputenc\}/) { + close(FH); + return($1); + } + last if (++$i > 20 ); # scan at most 20 non-comment lines + } + close(FH); + return("ascii"); +} + + +sub read_file_with_encoding { + my ($output); + my ($filename, $encoding) = @_; + + if (lc($encoding) eq "utf8" ) { + open (FILE, "<:utf8",$filename) or die("Couldn't open $filename: $!"); + local $/ ; # locally set record operator to undefined, ie. enable whole-file mode + $output=<FILE>; + } elsif ( lc($encoding) eq "ascii") { + open (FILE, $filename) or die("Couldn't open $filename: $!"); + local $/ ; # locally set record operator to undefined, ie. enable whole-file mode + $output=<FILE>; + } else { + require Encode; + open (FILE, "<",$filename) or die("Couldn't open $filename: $!"); + local $/ ; # locally set record operator to undefined, ie. enable whole-file mode + $output=<FILE>; + print STDERR "Converting $filename from $encoding to utf8\n" if $verbose; + $output=Encode::decode($encoding,$output); + } + close FILE; + if ($^O eq "linux" ) { + $output =~ s/\r\n/\n/g ; + } + return $output; +} + +# %packages=list_packages(@preamble) +# scans the arguments for \documentclass and \usepackage statements and constructs a hash +# whose keys are the included packages, and whose values are the associated optional arguments +sub list_packages { + my (@preamble)=@_; + my %packages=(); + foreach $line ( @preamble ) { + # get rid of comments + $line=~s/(?<!\\)%.*$// ; + if ( $line =~ m/\\(?:documentclass|usepackage)(?:\[(.+?)\])?\{(.*?)\}/ ) { +# print STDERR "Found something: |$line|\n"; + if (defined($1)) { + $packages{$2}=$1; + } else { + $packages{$2}=""; + } + } + } + return (%packages); +} + +# Subroutine add_safe_commands modified from version provided by S. Gouezel +# add_safe_commands($preamble) +# scans the argument for \newcommand and \DeclareMathOperator, +# and adds the created commands which are clearly safe to @SAFECMDLIST +sub add_safe_commands { + my ($preamble)=@_; + my $added_command = 1; + + $preamble=~s/(?<!\\)%.*$//mg ; + + while ($added_command) { + $added_command = 0; + # get rid of comments + my $to_test = ""; + # test for \DeclareMathOperator{\foo}{myoperator} + while ( $preamble =~ m/\DeclareMathOperator\s*\{\\(\w*?)\}/s) { + $to_test=$1; + if ($to_test ne "" and not iscmd($to_test,\@SAFECMDLIST,\@SAFECMDEXCL) and not iscmd($to_test, \@SAFECMDEXCL, [])) { + # one should add $to_test to the list of safe commands. + init_regex_arr_ext(\@SAFECMDLIST, $to_test); + $added_command = 1; + print STDERR "Adding $to_test to the list of safe commands\n" if $verbose; + } + } + + while ( $preamble =~ m/\\(?:|re|provide)newcommand\s*{\\(\w*)\}(?:|\[\d*\])\s*\{(${pat6})\}/osg ) { + my $maybe_to_test = $1; + my $should_be_safe = $2; + my $success = 0; + # test if all latex commands inside it are safe + $success = 1; + if ($should_be_safe =~ m/\\\\/) { + $success = 0; + } else { + while ($should_be_safe =~ m/\\(\w+)/g) { + $success = 0 unless iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL); ### or $1 eq ""; + } + } + ### } + $to_test = $maybe_to_test if $success; + + if ($to_test ne "" and not iscmd($to_test,\@SAFECMDLIST,\@SAFECMDEXCL) and not iscmd($to_test, \@SAFECMDEXCL, [])) { +# # one should add $to_test to the list of safe commands. + init_regex_arr_ext(\@SAFECMDLIST, $to_test); + $added_command = 1; + print STDERR "Adding $to_test to the list of safe commands\n" if $verbose; + } + } + } +} + + + +# flatten($text,$preamble,$filename,$encoding) +# expands \input and \include commands within text +# preamble is scanned for includeonly commands +# encoding is the encoding +sub flatten { + my ($text,$preamble,$filename,$encoding)=@_; + my ($includeonly,$dirname,$fname,$newpage,$replacement,$begline); + require File::Basename ; + require File::Spec ; + $dirname = File::Basename::dirname($filename); + + if ( ($includeonly) = ($preamble =~ m/\\includeonly{(.*?)}/ ) ) { + $includeonly =~ s/,/|/g; + } else { + $includeonly = '.*?'; + } + + print STDERR "DEBUG: includeonly $includeonly\n" if $debug; + + $text=~s/(^(?:[^%\n]|\\%)*)\\input{(.*?)}|\\include{(${includeonly}(?:\.tex)?)}/{ + $fname = $2 if defined($2) ; + $fname = $3 if defined($3) ; + # # add tex extension unless there is a three letter extension already + $fname .= ".tex" unless $fname =~ m|\.\w{3}|; + print STDERR "DEBUG Beg of line match |$1|\n" if defined($1) && $debug ; + print STDERR "Include file $fname\n" if $verbose; + print STDERR "DEBUG looking for file ",File::Spec->catfile($dirname,$fname), "\n" if $debug; + # content of file becomes replacement value (use recursion), add \newpage if the command was include + ###$replacement=read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; + $replacement=flatten(read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding), $preamble,$filename,$encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; + # \include always starts a new page; use explicit \newpage command to simulate this + $begline=(defined($1)? $1 : "") ; + $newpage=(defined($3)? " \\newpage " : "") ; + "$begline$newpage$replacement$newpage"; + }/exgm; + + return($text); +} + + +# print_regex_arr(@arr) +# prints regex array without x-ism expansion put in by pearl to stdout +sub print_regex_arr { + my $dumstring; + $dumstring = join(" ",@_); # PERL generates string (?-xism:^ref$) for quoted refex ^ref$ + $dumstring =~ s/\(\?-xism:\^(.*?)\$\)/$1/g; # remove string and ^,$ marks before output + print $dumstring,"\n"; +} + + +# @lines=extrapream($type) +# reads line from appendix (end of file after __END__ token) +sub extrapream { + my $type; + my @retval=("%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; + my ($copy); + + while (@_) { + $copy=0; + $type=shift ; + if ( -f $type ) { + open (FILE,$type) or die "Cannot open preamble file $type: $!"; + print STDERR "Reading preamble file $type\n" if $verbose ; + while (<FILE>) { + chomp ; + if ( $_ =~ m/%DIF PREAMBLE/ ) { + push (@retval,"$_"); + } else { + push (@retval,"$_ %DIF PREAMBLE"); + } + } + } + else { # not (-f $type) + $type=uc($type); # upcase argument + print STDERR "Preamble Internal Type $type\n" if $verbose; + while (<DATA>) { + if ( m/^%DIF $type/ ) { + $copy=1; } + elsif ( m/^%DIF END $type/ ) { + last; } + chomp; + push (@retval,"$_ %DIF PREAMBLE") if $copy; + } + if ( $copy == 0 ) { + print STDERR "\nPreamble style $type not implemented.\n"; + print STDERR "Write latexdiff -h to get help with available styles\n"; + exit(2); + } + seek DATA,0,0; # rewind DATA handle to file begin + } + } + push (@retval,"%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; + return @retval; +} + + +# ($part1,$part2,$part3)=splitdoc($text,$word1,$word2) +# splits $text into 3 parts at $word1 and $word2. +# if neither $word1 nor $word2 exist, $part1 and $part3 are empty, $part2 is $text +# If only $word1 or $word2 exist but not the other, output an error message. + +# NB this version avoids $` and $' for performance reason although it only makes a tiny difference +# (in one test gain a tenth of a second for a 30s run) +sub splitdoc { + my ($text,$word1,$word2)=@_; + my ($part1,$part2,$part3)=("","",""); + my ($rest,$pos); + + if ( $text =~ m/(^[^%]*)($word1)/mg ) { + $pos=pos $text; + $part1=substr($text,0,$pos-length($2)); + $rest=substr($text,$pos); + if ( $rest =~ m/(^[^%]*)($word2)/mg ) { + $pos=pos $rest; + $part2=substr($rest,0,$pos-length($2)); + $part3=substr($rest,$pos); + } + else { + die "$word1 and $word2 not in the correct order or not present as a pair." ; + } + } else { + $part2=$text; + die "$word2 present but not $word1." if ( $text =~ m/(^[^%]*)$word2/ms ); + } + return ($part1,$part2,$part3); +} + + + + + +# bodydiff($old,$new) +sub bodydiff { + my ($oldwords, $newwords) = @_; + my @retwords; + + print STDERR "(",exetime()," s)\n","Splitting into latex tokens \n" if $verbose; + print STDERR "Parsing $oldfile \n" if $verbose; + my @oldwords = splitlatex($oldwords); + print STDERR "Parsing $newfile \n" if $verbose; + my @newwords = splitlatex($newwords); + + if ( $debug ) { + open(TOKENOLD,">","latexdiff.debug.tokenold"); + print TOKENOLD join("***\n",@oldwords); + close(TOKENOLD); + open(TOKENNEW,">","latexdiff.debug.tokennew"); + print TOKENNEW join("***\n",@newwords); + close(TOKENNEW); + } + + print STDERR "(",exetime()," s)\n","Pass 1: Expanding text commands and merging isolated identities with changed blocks " if $verbose; + pass1(\@oldwords, \@newwords); + + + print STDERR "(",exetime()," s)\n","Pass 2: inserting DIF tokens and mark up. " if $verbose; + if ( $debug ) { + open(TOKENOLD,">","latexdiff.debug.tokenold2.tex"); + print TOKENOLD join("***\n",@oldwords); + close(TOKENOLD); + open(TOKENNEW,">","latexdiff.debug.tokennew2.tex"); + print TOKENNEW join("***\n",@newwords); + close(TOKENNEW); + } + + @retwords=pass2(\@oldwords, \@newwords); + + return(@retwords); +} + + + + +# @words=splitlatex($string) +# split string according to latex rules +# Each element of words is either +# a word (including trailing spaces and punctuation) +# a latex command +sub splitlatex { + my ($string) = @_ ; + # if input is empty, return empty list + length($string)>0 or return (); + + my @retval=($string =~ m/$pat/osg); + + if (length($string) != length(join("",@retval))) { + print STDERR "\nWARNING: Inconsistency in length of input string and parsed string:\n This often indicates faulty or non-standard latex code.\n In many cases you can ignore this and the following warning messages.\n Note that character numbers in the following are counted beginning after \\begin{document} and are only approximate." unless $ignorewarnings; + print STDERR "DEBUG Original length ",length($string)," Parsed length ",length(join("",@retval)),"\n" if $debug; + print STDERR "DEBUG Input string: |$string|\n" if (length($string)<500) && $debug; + print STDERR "DEBUG Token parsing: |",join("+",@retval),"|\n" if (length($string)<500) && $debug ; + @retval=(); + # slow way only do this if other m//sg method fails + my $last = 0; + while ( $string =~ m/$pat/osg ) { + my $match=$&; + if ($last + length $& != pos $string ) { + my $pos=pos($string); + my $offset=30<$last ? 30 : $last; + my $dum=substr($string,$last-$offset,$pos-$last+2*$offset); + my $dum1=$dum; + my $cnt=$#retval; + my $i; + $dum1 =~ s/\n/ /g; + unless ($ignorewarnings) { + print STDERR "\n$dum1\n"; + print STDERR " " x 30,"^" x ($pos-$last)," " x 30,"\n"; + print STDERR "Missing characters near word " . (scalar @retval) . " character index: " . $last . "-" . pos($string) . " Length: " . length($match) . " Match: |$match| (expected match marked above).\n"; + } + # put in missing characters `by hand' + push (@retval, substr($dum,$offset,$pos-$last-length($match))); +# Note: there seems to be a bug in substr with utf8 that made the following line output substr which were too long, +# using dum instead appears to work +# push (@retval, substr($string,$last, pos($string)-$last-length($match))); + } + push (@retval, $match); + $last=pos $string; + } + + } + return @retval; +} + + +# pass1( \@seq1,\@seq2) +# Look for differences between seq1 and seq2. +# Where an common-subsequence block is flanked by deleted or appended blocks, +# and is shorter than $MINWORDSBLOCK words it is appended +# to the last deleted or appended word. If the block contains tokens other than words +# or punctuation it is not merged. +# Deleted or appended block consisting of words and safe commands only are +# also merged, to prevent break-up in pass2 (after previous isolated words have been removed) +# If there are commands with textual arguments (e.g. \caption) both in corresponding +# appended and deleted blocks split them such that the command and opening bracket +# are one token, then the rest is split up following standard rules, and the closing +# bracket is a separate token, ie. turn +# "\caption{This is a textual argument}" into +# ("\caption{","This ","is ","a ","textual ","argument","}") +# No return value. Destructively changes sequences +sub pass1 { + my $seq1 = shift ; + my $seq2 = shift ; + + my $len1 = scalar @$seq1; + my $len2 = scalar @$seq2; + my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #' + + my ($last1,$last2)=(-1,-1) ; + my $cnt=0; + my $block=[]; + my $addblock=[]; + my $delblock=[]; + my $todo=[]; + my $instruction=[]; + my $i; + my (@delmid,@addmid,@dummy); + + my ($addcmds,$delcmds,$matchindex); + my ($addtextblocks,$deltextblocks); + my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0); + my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0); + + my $adddiscard = sub { + if ($cnt > 0 ) { + $matblkcnt++; + # just after an unchanged block +# print STDERR "Unchanged block $cnt, $last1,$last2 \n"; + if ($cnt < $MINWORDSBLOCK + && $cnt==scalar ( + grep { /^$wpat/ || ( /^\\([\w\d\*]+)((?:\[$brat0\]|\{$pat6\})*)/o + && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) + && scalar(@dummy=split(" ",$2))<3 ) } + @$block) ) { + # merge identical blocks shorter than $MINWORDSBLOCK + # and only containing ordinary words + # with preceding different word + # We cannot carry out this merging immediately as this + # would change the index numbers of seq1 and seq2 and confuse + # the algorithm, instead we store in @$todo where we have to merge + push(@$todo, [ $last1,$last2,$cnt,@$block ]); + } + $block = []; + $cnt=0; $last1=-1; $last2=-1; + } + }; + my $discard=sub { $deltokcnt++; + &$adddiscard; #($_[0],$_[1]); + push(@$delblock,[ $seq1->[$_[0]],$_[0] ]); + $last1=$_[0] }; + + my $add = sub { $addtokcnt++; + &$adddiscard; #($_[0],$_[1]); + push(@$addblock,[ $seq2->[$_[1]],$_[1] ]); + $last2=$_[1] }; + + my $match = sub { $mattokcnt++; + if ($cnt==0) { # first word of matching sequence after changed sequence or at beginning of word sequence + $deltextblocks = extracttextblocks($delblock); + $delblkcnt++ if scalar @$delblock; + $addtextblocks = extracttextblocks($addblock); + $addblkcnt++ if scalar @$addblock; + + $delcmds = extractcommands($delblock); + $addcmds = extractcommands($addblock); + # keygen(third argument of _longestCommonSubsequence) implies to sort on command (0th elements of $addcmd elements) + # the calling format for longestCommonSubsequence has changed between versions of + # Algorithm::Diff so we need to check which one we are using + if ( $algodiffversion > 1.15 ) { + ### Algorithm::Diff 1.19 + $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, 0, sub { $_[0]->[0] } ); + } else { + ### Algorithm::Diff 1.15 + $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, sub { $_[0]->[0] } ); + } + + for ($i=0 ; $i<=$#$matchindex ; $i++) { + if (defined($matchindex->[$i])){ + $j=$matchindex->[$i]; + @delmid=splitlatex($delcmds->[$i][3]); + @addmid=splitlatex($addcmds->[$j][3]); + while (scalar(@$deltextblocks) && $deltextblocks->[0][0]<$delcmds->[$i][1]) { + my ($index,$block,$cnt)=@{ shift(@$deltextblocks) }; + push(@$todo, [$index,-1,$cnt,@$block]); + } + push(@$todo, [ $delcmds->[$i][1],-1,-1,$delcmds->[$i][2],@delmid,$delcmds->[$i][4]]); + + while (scalar(@$addtextblocks) && $addtextblocks->[0][0]<$addcmds->[$j][1]) { + my ($index,$block,$cnt)=@{ shift(@$addtextblocks) }; + push(@$todo, [-1,$index,$cnt,@$block]); + } + push(@$todo, [ -1,$addcmds->[$j][1],-1,$addcmds->[$j][2],@addmid,$addcmds->[$j][4]]); + } + } + # mop up remaining textblocks + while (scalar(@$deltextblocks)) { + my ($index,$block,$cnt)=@{ shift(@$deltextblocks) } ; + push(@$todo, [$index,-1,$cnt,@$block]); + } + while (scalar(@$addtextblocks)) { + my ($index,$block,$cnt)=@{ shift(@$addtextblocks) }; + push(@$todo, [-1,$index,$cnt,@$block]); + } + + $addblock=[]; + $delblock=[]; + } + push(@$block,$seq2->[$_[1]]); + $cnt++ }; + + my $keyfunc = sub { join(" ",split(" ",shift())) }; + + traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); + + + # now carry out the merging/splitting. Refer to elements relative from + # the end (with negative indices) as these offsets don't change before the instruction is executed + # cnt>0: merged small unchanged groups with previous changed blocks + # cnt==-1: split textual commands into components + foreach $instruction ( @$todo) { + ($last1,$last2,$cnt,@$block)=@$instruction ; + if ($cnt>=0) { + splice(@$seq1,$last1-$len1,1+$cnt,join("",$seq1->[$last1-$len1],@$block)) if $last1>=0; + splice(@$seq2,$last2-$len2,1+$cnt,join("",$seq2->[$last2-$len2],@$block)) if $last2>=0; + } else { + splice(@$seq1,$last1-$len1,1,@$block) if $last1>=0; + splice(@$seq2,$last2-$len2,1,@$block) if $last2>=0; + } + } + + if ($verbose) { + print STDERR "\n"; + print STDERR " $mattokcnt matching tokens in $matblkcnt blocks.\n"; + print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n"; + print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n"; + } +} + + +# extracttextblocks(\@blockindex) +# $blockindex has the following format +# [ [ token1, index1 ], [token2, index2],.. ] +# where index refers to the index in the original old or new word sequence +# Returns: reference to an array of the form +# [[ $index, $textblock, $cnt ], .. +# where $index index of block to be merged +# $textblock contains all the words to be merged with the word at $index (but does not contain this word) +# $cnt is length of block +# +# requires: iscmd +# +sub extracttextblocks { + my $block=shift; + my ($i,$token,$index); + my $textblock=[]; + my $last=-1; + my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #' + my $retval=[]; + + for ($i=0;$i< scalar @$block;$i++) { + ($token,$index)=@{ $block->[$i] }; + # store pure text blocks + if ($token =~ /$wpat/ || ( $token =~/^\\([\w\d\*]+)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat6\})*)/o + && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) + && !iscmd($1,\@TEXTCMDLIST,\@TEXTCMDEXCL))) { + # we have text or a command which can be treated as text + if ($last<0) { + # new pure-text block + $last=$index; + } else { + # add to pure-text block + push(@$textblock, $token); + } + } else { + # it is not text + if (scalar(@$textblock)) { + push(@$retval,[ $last, $textblock, scalar(@$textblock) ]); + } + $textblock=[]; + $last=-1; + } + } + # finish processing a possibly unfinished block before returning + if (scalar(@$textblock)) { + push(@$retval,[ $last, $textblock, scalar(@$textblock) ]); + } + return($retval) +} + + + +# extractcommands( \@blockindex ) +# $blockindex has the following format +# [ [ token1, index1 ], [token2, index2],.. ] +# where index refers to the index in the original old or new word sequence +# Returns: reference to an array of the form +# [ [ "\cmd1", index, "\cmd1[optarg]{arg1}{", "arg2" ,"} " ],.. +# where index is just taken from input array +# command must have a textual argument as last argument +# +# requires: iscmd +# +sub extractcommands { + my $block=shift; + my ($i,$token,$index,$cmd,$open,$mid,$closing); + my $retval=[]; + + for ($i=0;$i< scalar @$block;$i++) { + ($token,$index)=@{ $block->[$i] }; + # check if token is an alphanumeric command sequence with at least one non-optional argument + # \cmd[...]{...}{last argument} + # Capturing in the following results in these associations + # $1: \cmd[...]{...}{ + # $2: \cmd + # $3: last argument + # $4: } + trailing spaces + if ( ( $token =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat6\})*${extraspace}\{)($pat6)(\}\s*)$/so ) + && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) ) { + # push(@$retval,[ $2,$index,$1,$3,$4 ]); + ($cmd,$open,$mid,$closing) = ($2,$1,$3,$4) ; + $closing =~ s/\}/\\RIGHTBRACE/ ; + push(@$retval,[ $cmd,$index,$open,$mid,$closing ]); + } + } + return $retval; +} + +# iscmd($cmd,\@regexarray,\@regexexcl) checks +# return 1 if $cmd matches any of the patterns in the +# array $@regexarray, and none of the patterns in \@regexexcl, otherwise return 0 +sub iscmd { + my ($cmd,$regexar,$regexexcl)=@_; + my ($ret)=0; + foreach $pat ( @$regexar ) { + if ( $cmd =~ m/^${pat}$/ ) { + $ret=1 ; + last; + } + } + return 0 unless $ret; + foreach $pat ( @$regexexcl ) { + return 0 if ( $cmd =~ m/^${pat}$/ ); + } + return 1; +} + + +# pass2( \@seq1,\@seq2) +# Look for differences between seq1 and seq2. +# Mark begin and end of deleted and appended sequences with tags $DELOPEN and $DELCLOSE +# and $ADDOPEN and $ADDCLOSE, respectively, however exclude { } & and all comands, unless +# they match an element of the whitelist (SAFECMD) +# For words in TEXTCMD but not in SAFECMD, enclose interior with $ADDOPEN and $ADDCLOSE brackets +# Deleted comment lines are marked with %DIF < +# Added comment lines are marked with %DIF > +sub pass2 { + my $seq1 = shift ; + my $seq2 = shift ; + + my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0); + my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0); + + my $retval = []; + my $delhunk = []; + my $addhunk = []; + + my $discard = sub { $deltokcnt++; + push ( @$delhunk, $seq1->[$_[0]]) }; + + my $add = sub { $addtokcnt++; + push ( @$addhunk, $seq2->[$_[1]]) }; + + my $match = sub { $mattokcnt++; + if ( scalar @$delhunk ) { + $delblkcnt++; + # mark up changes, but comment out commands + push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk); + $delhunk = []; + } + if ( scalar @$addhunk ) { + $addblkcnt++; + # we mark up changes, but simply quote commands + push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk); + $addhunk = []; + } + push(@$retval,$seq2->[$_[1]]) }; + + my $keyfunc = sub { join(" ",split(" ",shift())) }; + + traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); + # clear up unprocessed hunks + push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk) if scalar @$delhunk; + push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk) if scalar @$addhunk; + + + if ($verbose) { + print STDERR "\n"; + print STDERR " $mattokcnt matching tokens. \n"; + print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n"; + print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n"; + } + + return(@$retval); +} + +# marktags($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,\@block) +# returns ($openmark,$open,$block,$close,$closemark) if @block only contains no commands (except white-listed ones), +# braces, ampersands, or comments +# mark comments with $comment +# exclude all other exceptions from scope of open, close like this +# ($openmark, $open,...,$close, $opencmd,command, command,$closecmd, $open, ..., $close, $closemark) +# If $opencmd begins with "%" marktags assumes it is operating on a deleted block, otherwise on an added block +sub marktags { + my ($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,$block)=@_; + my $word; + my (@argtext); + my $retval=[]; + my $noncomment=0; + my $cmd=-1; # -1 at beginning 0: last token written is a ordinary word + # 1: last token written is a command + # for keeping track whether we are just in a command sequence or in a word sequence + my $cmdcomment= ($opencmd =~ m/^%/); # Flag to indicate whether opencmd is a comment (i.e. if we intend to simply comment out changed commands) + my ($command,$commandword,$closingbracket) ; # temporary variables needed below to remember sub-pattern matches + +# split this block to flatten out sequences joined in pass1 + @$block=splitlatex(join "",@$block); + foreach (@$block) { + $word=$_; + if ( $word =~ s/^%/%$comment/ ) { + # a comment + if ($cmd==1) { + push (@$retval,$closecmd) ; + $cmd=-1; + } + push (@$retval,$word); + next; + } + if (! $noncomment) { + push (@$retval,$openmark); + $noncomment=1; + } + # negative lookahead pattern (?!) in second clause is put in to avoid mathcing \( .. \) patterns + # also note that second pattern will match \\ + # Note: the second pattern should really be $word =~ /^\\(?!\()(\\|[\w*@]+)/, ie * replaced by + + # and then all commands \" \' etc declared safe. But as I don't have a complete list of one letter + # commands, and nobody has complained so far, I will eave this as is + if ( $word =~ /^[&{}\[\]]/ || ( $word =~ /^\\(?!\()(\\|[\w*@]*)/ && !iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)) ) { + # word is a command or other significant token (not in SAFECMDLIST) + ## same conditions as in subroutine extractcommand: + # check if token is an alphanumeric command sequence with at least one non-optional argument + # \cmd[...]{...}{last argument} + # Capturing in the following results in these associations + # $1: \cmd[...]{...}{ + # $2: cmd + # $3: last argument + # $4: } + trailing spaces + ### pre-0.3 if ( ( $token =~ m/^(\\([\w\d\*]+)(?:\[$brat0\]|\{$pat6\})*\{)($pat6)(\}\s*)$/so ) + if ( ( $word =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat6\})*${extraspace}\{)($pat6)(\}\s*)$/so ) + && (iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL)|| iscmd($2,\@MATHTEXTCMDLIST,\@MATHTEXTCMDEXCL)) + && ( !$cmdcomment || !iscmd($2,\@CONTEXT2CMDLIST, \@CONTEXT2CMDEXCL) ) ) { + # Condition 1: word is a command? - if yes, $1,$2,.. will be set as above + # Condition 2: word is a text command - we mark up the interior of the word. There is a separate check for MATHTEXTCMDLIST + # because for $mathmarkup=WHOLE, the commands should not be split in pass1 (ie. math mode commands are not in + # TEXTCMDLIST, but the interior of MATHTEXT commands should be highlighted in both deleted and added blocks + # Condition 3: But if we are in a deleted block ($cmdcomment=1) and + # $2 (the command) is in context2, just treat it as an ordinary command (i.e. comment it open with $opencmd) + # Because we do not want to disable this command + # here we do not use $opencmd and $closecmd($opencmd is empty) + if ($cmd==1) { + push (@$retval,$closecmd) ; + } elsif ($cmd==0) { + push (@$retval,$close) ; + } + $command=$1; $commandword=$2; $closingbracket=$4; + @argtext=splitlatex($3); # split textual argument into tokens + # and mark it up (but we do not need openmark and closemark) + # insert command with initial arguments, marked-up final argument, and closing bracket + if ( $cmdcomment && iscmd($commandword,\@CONTEXT1CMDLIST, \@CONTEXT1CMDEXCL) ) { + # context1cmd in a deleted environment; delete command itself but keep last argument, marked up + push (@$retval,$opencmd); + $command =~ s/\n/\n${opencmd}/sg ; # repeat opencmd at the beginning of each line + # argument, note that the additional comment character is included + # to suppress linebreak after opening parentheses, which is important + # for latexrevise + push (@$retval,$command,"%\n{$AUXCMD\n",marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket); + } elsif ( iscmd($commandword,,\@MATHTEXTCMDLIST, \@MATHTEXTCMDEXCL) ) { + # MATHBLOCK pseudo command: consider all commands safe, except & and \\ + # Keep these commands even in deleted blocks, hence set $opencmd and $closecmd (5th and 6th argument of marktags) to + # "" + local @SAFECMDLIST=(".*"); + local @SAFECMDEXCL=('\\','\\\\'); + push(@$retval,$command,marktags("","",$open,$close,"","",$comment,\@argtext)#@argtext + ,$closingbracket); + } else { + # normal textcmd or context1cmd in an added block + push (@$retval,$command,marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket); + } + push (@$retval,$AUXCMD,"\n") if $cmdcomment ; + $cmd=-1 ; + } else { + # ordinary command + push (@$retval,$opencmd) if $cmd==-1 ; + push (@$retval,$close,$opencmd) if $cmd==0 ; + $word =~ s/\n/\n${opencmd}/sg if $cmdcomment ; # if opencmd is a comment, repeat this at the beginning of every line + push (@$retval,$word); + $cmd=1; + } + } else { + # just an ordinary word or word in SAFECMD + push (@$retval,$open) if $cmd==-1 ; + push (@$retval,$closecmd,$open) if $cmd==1 ; + push (@$retval,$word); + $cmd=0; + } + } + push (@$retval,$close) if $cmd==0; + push (@$retval,$closecmd) if $cmd==1; + + push (@$retval,$closemark) if ($noncomment); + return @$retval; +} + +# preprocess($string, ..) +# carry out the following pre-processing steps for all arguments: +# 1. Remove leading white-space +# Change \{ to \LEFTBRACE and \} to \RIGHTBRACE +# #. change begin and end commands within comments to BEGINDIF, ENDDIF +# so they don't disturb the pattern matching (if there are several \begin or \end in one line +# 2. mark all first empty line (in block of several) with \PAR tokens +# 3. Convert all '\%' into '\PERCENTAGE ' to make parsing regular expressions easier +# 4. Convert all \verb|some verbatim text| commands (where | can be an arbitrary character) +# into \verb{hash} +# 5. Convert \begin{verbatim} some verbatim text \end{verbatim} into \verbatim{hash} +# 6. Convert _n into \SUBSCRIPTNB{n} and _{nnn} into \SUBSCRIPT{nn} +# 7. Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn} +# 8. a. Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} +# b. Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} +# 9. Convert all picture environmentent (\begin{PICTUREENV} .. \end{PICTUREENV} \PICTUREBLOCKenv +# For --block-math-markup option -convert all \begin{MATH} .. \end{MATH} +# into \MATHBLOCKmath{...} commands, where MATH/math is any valid math environment + +# 10. Add final token STOP to the very end. This is put in because the algorithm works better if the last token is identical. This is removed again in postprocessing. +# +# NB: step 6 and 7 is likely to convert some "_" inappropriately, e.g. in file +# names or labels but it does not matter because they are converted back in the postprocessing step +# Returns: leading white space removed in step 1 +sub preprocess { + my @leadin=() ; + for (@_) { + s/^(\s*)//s; + push(@leadin,$1); + # Change \{ to \QLEFTBRACE and \} to \QRIGHTBRACE + s/(?<!\\)\\{/\\QLEFTBRACE /sg; + s/(?<!\\)\\}/\\QRIGHTBRACE /sg; + # change begin and end commands within comments such that they + # don't disturb the pattern matching (if there are several \begin or \end in one line + # this substitution is insufficient but that appears unlikely) + s/(%.*)\\begin\{(.*)$/$1\\BEGINDIF\{$2/mg ; + s/(%.*)\\end\{(.*)$/$1\\ENDDIF\{$2/mg ; + + s/\n(\s*?)\n((?:\s*\n)*)/\n$1\\PAR\n$2/g ; + s/(?<!\\)\\%/\\PERCENTAGE /g ; # (?<! is negative lookbehind assertion to prevent \\% from being converted + s/(\\verb\*?)(\S)(.*?)\2/"${1}{". tohash(\%verbhash,"${2}${3}${2}") ."}"/esg; + s/\\begin\{(verbatim\*?)\}(.*?)\\end\{\1\}/"\\${1}{". tohash(\%verbhash,"${2}") . "}"/esg; + # Convert _n or _\cmd into \SUBSCRIPTNB{n} or \SUBSCRIPTNB{\cmd} and _{nnn} into \SUBSCRIPT{nn} + 1 while s/_([^{\\]|\\\w+)/\\SUBSCRIPTNB{$1}/g ; + 1 while s/_{($pat6)}/\\SUBSCRIPT{$1}/g ; + # Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn} + 1 while s/\^([^{\\]|\\\w+)/\\SUPERSCRIPTNB{$1}/g ; + 1 while s/\^{($pat6)}/\\SUPERSCRIPT{$1}/g ; + # Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} + s/\$\$(.*?)\$\$/\\begin{DOLLARDOLLAR}$1\\end{DOLLARDOLLAR}/sg; + # Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} + s/(?<!\\)\\\[/\\begin{SQUAREBRACKET}/sg; + s/\\\]/\\end{SQUAREBRACKET}/sg; + # Convert all picture environmentent (\begin{PICTUREENV} .. \end{PICTUREENV} \PICTUREBLOCKenv + s/\\begin{($PICTUREENV)}(.*?)\\end{\1}/\\PICTUREBLOCK$1\{$2\}/sg; + # For --block-math-markup option -convert all \begin{MATH} .. \end{MATH} + # into \MATHBLOCKMATH{...} commands, where MATH is any valid math environment + # Also convert all array environments into ARRAYBLOCK environments + if ( $mathmarkup != FINE ) { + s/\\begin{($ARRENV)}(.*?)\\end{\1}/\\ARRAYBLOCK$1\{$2\}/sg; + s/\\begin{($MATHENV|$MATHARRENV|SQUAREBRACKET)}(.*?)\\end{\1}/\\MATHBLOCK$1\{$2\}/sg; + } + # add final token " STOP" + $_ .= " STOP" + } + return(@leadin); +} + +#hashstring=tohash(\%hash,$string) +# creates a hash value based on string and stores in %hash +sub tohash { + my ($hash,$string)=@_; + my (@arr,$val); + my ($sum,$i)=(0,1); + my ($hstr); + + @arr=unpack('c*',$string); + + foreach $val (@arr) { + $sum += $i*$val; + $i++; + } + $hstr= "$sum"; + if (defined($hash->{$hstr}) && $string ne $hash->{$hstr}) { + warn "Repeated hash value for verbatim mode in spite of different content."; + $hstr="-$hstr"; + } + $hash->{$hstr}=$string; + return($hstr); +} + +#string=fromhash(\%hash,$fromstring) +# restores string value stored in hash +#string=fromhash(\%hash,$fromstring,$prependstring) +# additionally begins each line with prependstring +sub fromhash { + my ($hash,$hstr)=($_[0],$_[1]); + my $retstr=$hash->{$hstr}; + if ( $#_ >= 2) { + $retstr =~ s/^/$_[2]/mg; + } + return $retstr; +} + + +# postprocess($string, ..) +# carry out the following post-processing steps for all arguments: +# * Remove STOP token from the end +# * Replace \RIGHTBRACE by } +# * change citation commands within comments to protect from processing (using marker CITEDIF) +# 1. Check all deleted blocks: +# a.where a deleted block contains a matching \begin and +# \end environment (these will be disabled by a %DIFDELCMD statements), for selected environments enable +# these commands again (such that for example displayed math in a deleted equation +# is properly within math mode. For math mode environments replace numbered equation +# environments with their display only variety (so that equation numbers in new file and +# diff file are identical). Where the correct type of math environment cannot be determined +# use a place holder MATHMODE +# b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement +# subtracting one from the respective counter to keep numbering consistent with new file +# Replace all MATHMODE environment commands by the correct environment to achieve matching +# pairs +# c. Convert MATHBLOCKmath commands to their uncounted numbers (e.g. convert equation -> displaymath +# (environments defined in $MATHENV will be replaced by $MATHREPL, and environments in $MATHARRENV +# will be replaced by $MATHARRREPL +# d. If in-line math mode contains array environment, enclose the whole environment in \mbox'es +# d. place \cite commands in mbox'es (for UNDERLINE style) +# +# For added blocks: +# c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es +# d. place \cite commands in mbox'es (for UNDERLINE style) +# +# 2. If --block-math-markup option set: Convert \MATHBLOCKmath{..} commands back to environments +# +# Convert all PICTUREblock{..} commands back to the appropriate environments +# 3. Convert DIFadd, DIFdel, DIFFaddbegin , ... into FL varieties +# within floats (currently recognised float environments: plate,table,figure +# plus starred varieties). +# 4. Remove empty %DIFDELCMD < lines +# 4. Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \] +# Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$ +# 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn} +# 6. Convert \SUBSCRIPTNB{n} into _n and \SUBCRIPT{nn} into _{nnn} +# 7. Expand hashes of verb and verbatim environments +# 8. Convert '\PERCENTAGE ' back into '\%' +# 9.. remove all \PAR tokens +# 10. package specific processing: endfloat: make sure \begin{figure} and \end{figure} are always +# on a line by themselves, similarly for table environment +# 4, undo renaming of the \begin and \end in comments +# Change \QLEFTBRACE, \QRIGHTBRACE to \{,\} +# +# Note have to manually synchronize substitution commands below and +# DIF.. command names in the header +sub postprocess { + my ($begin,$len,$cnt,$float,$delblock,$addblock); + # second level blocks + my ($begin2,$cnt2,$len2,$eqarrayblock,$mathblock); + + for (@_) { + + # change $'s in comments to something harmless + 1 while s/(%.*)\$/$1DOLLARDIF/mg ; + + # Remove final STOP token + s/ STOP$//; + # Replace \RIGHTBRACE by } + s/\\RIGHTBRACE/}/g; + + # change citation commands within comments to protect from processing + if ($CITECMD){ + 1 while s/(%.*)\\($CITECMD)/$1\\CITEDIF$2/m ; + } + # Check all deleted blocks: where a deleted block contains a matching \begin and + # \end environment (these will be disabled by a %DIFDELCMD statements), enable + # these commands again (such that for example displayed math in a deleted equation + # is properly within math mode. For math mode environments replace numbered equation + # environments with their display only variety (so that equation numbers in new file and + # diff file are identical + while ( m/\\DIFdelbegin.*?\\DIFdelend/sg ) { + $cnt=0; + $len=length($&); + $begin=pos($_) - $len; + $delblock=$&; + + + ### (.*?[^\n]?)\n? construct is necessary to avoid empty lines in math mode, which result in + ### an error + # displayed math environments + if ($mathmarkup == FINE ) { + $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}.*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHREPL}$AUXCMD\n$1$3\n\\end{$MATHREPL}$AUXCMD\n$4/sg; + # also transform the opposite pair \end{displaymath} .. \begin{displaymath} but we have to be careful not to interfere with the results of the transformation in the line directly above + ### pre-0.42 obsolete version which did not work on eqnarray test $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHENV)\}\s*?\n)(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/$1\\end{$MATHREPL}$AUXCMD\n$3\n\\begin{$MATHREPL}$AUXCMD\n$4/sg; + ###0.5: $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg; + $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}.*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg; + + # now look for unpaired %DIFDELCMD < \begin{MATHENV}; if found add \begin{$MATHREPL} and insert \end{$MATHREPL} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHREPL}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\end{$MATHREPL}$AUXCMD\n$1/s ; + } + # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ; + } + + + ### pre-0.42 # same as above for special case \[.\] (latex abbreviation for displaymath) + ### pre-0.42 $delblock=~ s/(\%DIFDELCMD < \s*\\\[\s*?\n())(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\\])/$1\\\[$AUXCMD\n$3\n\\\]$AUXCMD\n$4/sg; + ### pre-0.42 $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\\]\s*?\n())(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\\[)/$1\\\]$AUXCMD\n$3\n\\\[$AUXCMD\n$4/sg; + # equation array environment + ###pre-0.3 $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?\n)(.*?)(\%DIFDELCMD < \s*\\end\{\2\})/$1\\begin{$MATHARRREPL}$AUXCMD\n$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg; + ###0.5 $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHARRREPL}$AUXCMD\n$1$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg; + $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}.*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHARRREPL}$AUXCMD\n$1$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg; + ### pre-0.42 obsolete version which did not work on eqnarray test $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?\n)(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/$1\\end{$MATHARRREPL}$AUXCMD\n$3\n\\begin{$MATHARRREPL}$AUXCMD\n$4/sg; + $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg; + + # now look for unpaired %DIFDELCMD < \begin{MATHARRENV}; if found add \begin{$MATHARRREPL} and insert \end{$MATHARRREPL} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHARRREPL}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\end{$MATHARRREPL}$AUXCMD\n$1/s ; + } + # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ; + } + + # parse $delblock for deleted and reinstated eqnarray* environments - within those reinstate \\ and & commands + while ( $delblock =~ m/\\begin\Q{$MATHARRREPL}$AUXCMD\E\n.*?\n\\end\Q{$MATHARRREPL}$AUXCMD\E\n/sg ) { + $cnt2=0; + $len2=length($&); + $begin2=pos($delblock) - $len2; + $eqarrayblock=$&; + # reinstate deleted & and \\ commands + $eqarrayblock=~ s/(\%DIFDELCMD < \s*(\&|\\\\)\s*?(?:$DELCMDCLOSE|\n))/$1$2$AUXCMD\n/sg ; + + substr($delblock,$begin2,$len2)=$eqarrayblock; + pos($delblock) = $begin2 + length($eqarrayblock); + } + } elsif ( $mathmarkup == COARSE || $mathmarkup == WHOLE ) { +# Convert MATHBLOCKmath commands to their uncounted numbers (e.g. convert equation -> displaymath +# (environments defined in $MATHENV will be replaced by $MATHREPL, and environments in $MATHARRENV +# will be replaced by $MATHARRREPL + $delblock=~ s/\\MATHBLOCK($MATHENV)\{($pat6)\}/\\MATHBLOCK$MATHREPL\{$2\}/sg; + $delblock=~ s/\\MATHBLOCK($MATHARRENV)\{($pat6)\}/\\MATHBLOCK$MATHARRREPL\{$2\}/sg; + } + + +# b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement +# subtracting one from the respective counter to keep numbering consistent with new file + $delblock=~ s/\\($COUNTERCMD)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat6\})*\s*${AUXCMD}\n)/\\$1$2\\addtocounter{$1}{-1}${AUXCMD}\n/sg ; + + +# c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es + while ( $delblock =~ m/($math)(\s*)/sg ) { + $cnt2=0; + $len2=length($&); + $begin2=pos($delblock) - $len2; + $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n"; + next unless $mathblock =~ m/\{$ARRENV\}/ ; + substr($delblock,$begin2,$len2)=$mathblock; + pos($delblock) = $begin2 + length($mathblock); + } + if ($CITE2CMD) { + $delblock=~s/($DELCMDOPEN\s*\\($CITE2CMD)(.*)$DELCMDCLOSE)/ + # Replacement code + {my ($aux,$all); + $aux=$all=$1; + $aux=~s#\n?($DELCMDOPEN|$DELCMDCLOSE)##g; + $all."$aux$AUXCMD\n";}/sge; + } + # or protect \cite commands with \mbox + if ($CITECMD) { + $delblock=~s/(\\($CITECMD)${extraspace}(?:\[$brat0\]${extraspace}){0,2}\{$pat6\})(\s*)/\\mbox{$AUXCMD\n$1\n}$AUXCMD\n/msg ; + } +# splice in modified delblock + substr($_,$begin,$len)=$delblock; + pos = $begin + length($delblock); + } + # make the array modification in added blocks + while ( m/\\DIFaddbegin.*?\\DIFaddend/sg ) { + $cnt=0; + $len=length($&); + $begin=pos($_) - $len; + $addblock=$&; + while ( $addblock =~ m/($math)(\s*)/sg ) { + $cnt2=0; + $len2=length($&); + $begin2=pos($addblock) - $len2; + $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n"; + next unless $mathblock =~ m/\{$ARRENV\}/ ; + substr($addblock,$begin2,$len2)=$mathblock; + pos($addblock) = $begin2 + length($mathblock); + } + if ($CITECMD) { + my $addblockbefore=$addblock; + $addblock=~ s/(\\($CITECMD)${extraspace}(?:\[$brat0\]${extraspace}){0,2}\{$pat2\})(\s*)/\\mbox{$AUXCMD\n$1\n}$AUXCMD\n/msg ; + } +# splice in modified addblock + substr($_,$begin,$len)=$addblock; + pos = $begin + length($addblock); + } + + + ### old place for BEGINDIF, ENDDIF replacement + # change begin and end commands within comments such that they + # don't disturb the pattern matching (if there are several \begin or \end in one line + # this substitution is insufficient but that appears unlikely) + # This needs to be repeated here to also get rid of DIFdelcmd-protected environments + s/(%.*)\\begin\{(.*)$/$1\\BEGINDIF\{$2/mg ; + s/(%.*)\\end\{(.*)$/$1\\ENDDIF\{$2/mg ; + + # Replace MATHMODE environments from step 1a above by the correct Math environment + + # The next line is complicated. The negative look-ahead insertion makes sure that no \end{$MATHENV} (or other mathematical + # environments) are between the \begin{$MATHENV} and \end{MATHMODE} commands. This is necessary as the minimal matching + # is not globally minimal but only 'locally' (matching is beginning from the left side of the string) + if ( $mathmarkup == FINE ) { + 1 while s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}((?:.(?!(?:\\end{(?:(?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}|\\begin{MATHMODE})))*?)\\end{MATHMODE}/\\begin{$1}$2\\end{$1}/s; + 1 while s/\\begin{MATHMODE}((?:.(?!\\end{MATHMODE}))*?)\\end{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}/\\begin{$2}$1\\end{$2}/s; + # convert remaining \begin{MATHMODE} \end{MATHMODE} (and not containing & or \\ )into MATHREPL environments + s/\\begin{MATHMODE}((?:(.(?!(?<!\\)\&|\\\\))*)?)\\end{MATHMODE}/\\begin{$MATHREPL}$1\\end{$MATHREPL}/sg; + # others into MATHARRREPL + s/\\begin{MATHMODE}(.*?)\\end{MATHMODE}/\\begin{$MATHARRREPL}$1\\end{$MATHARRREPL}/sg; + + # now look for AUXCMD math-mode pairs which have only comments (or empty lines between them), and remove the added commands + s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}$AUXCMD\n((?:\s*%.[^\n]*\n)*)\\end{\1}$AUXCMD\n/$2/sg; + } else { + # math modes OFF,WHOLE,COARSE: Convert \MATHBLOCKmath{..} commands back to environments + s/\\MATHBLOCK($MATHENV|$MATHARRENV|SQUAREBRACKET)\{($pat6)\}/\\begin{$1}$2\\end{$1}/sg; + # convert ARRAYBLOCK.. commands back to environments + s/\\ARRAYBLOCK($ARRENV)\{($pat6)\}/\\begin{$1}$2\\end{$1}/sg; + } + # Convert all PICTUREblock{..} commands back to the appropriate environments + s/\\PICTUREBLOCK($PICTUREENV)\{($pat6)\}/\\begin{$1}$2\\end{$1}/sg; +#0.5: # Remove all mark up within picture environments +# while ( m/\\begin\{($PICTUREENV)\}.*?\\end\{\1\}/sg ) { +# $cnt=0; +# $len=length($&); +# $begin=pos($_) - $len; +# $float=$&; +# $float =~ s/\\DIFaddbegin //g; +# $float =~ s/\\DIFaddend //g; +# $float =~ s/\\DIFadd\{($pat6)\}/$1/g; +# $float =~ s/\\DIFdelbegin //g; +# $float =~ s/\\DIFdelend //g; +# $float =~ s/\\DIFdel\{($pat6)\}//g; +# $float =~ s/$DELCMDOPEN.*//g; +# substr($_,$begin,$len)=$float; +# pos = $begin + length($float); +# } + # Convert DIFadd, DIFdel, DIFFaddbegin , ... into varieties + # within floats (currently recognised float environments: plate,table,figure + # plus starred varieties). + while ( m/\\begin\{($FLOATENV)\}.*?\\end\{\1\}/sg ) { + $cnt=0; + $len=length($&); + $begin=pos($_) - $len; + $float=$&; + $float =~ s/\\DIFaddbegin /\\DIFaddbeginFL /g; + $float =~ s/\\DIFaddend /\\DIFaddendFL /g; + $float =~ s/\\DIFadd\{/\\DIFaddFL{/g; + $float =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; + $float =~ s/\\DIFdelend /\\DIFdelendFL /g; + $float =~ s/\\DIFdel\{/\\DIFdelFL{/g; + substr($_,$begin,$len)=$float; + pos = $begin + length($float); + } + ### former location of undo renaming of \begin and \end in comments + + # remove empty DIFCMD < lines + s/^\Q${DELCMDOPEN}\E\n//msg; + + # Expand hashes of verb and verbatim environments (note negative look behind assertion to not leak out of DIFDELCMD comments + s/(\\verb\*?)\{([-\d]*?)\}/"${1}". fromhash(\%verbhash,$2)/esg; + s/${DELCMDOPEN}\\(verbatim\*?)\{([-\d]*?)\}/"${DELCMDOPEN}\\begin{${1}}".fromhash(\%verbhash,$2,$DELCMDOPEN)."${DELCMDOPEN}\\end{${1}}"/esg; + s/\\(verbatim\*?)\{([-\d]*?)\}/"\\begin{${1}}".fromhash(\%verbhash,$2)."\\end{${1}}"/esg; + # Convert '\PERCENTAGE ' back into '\%' + s/\\PERCENTAGE /\\%/g; + # remove all \PAR tokens (taking care to properly keep commented out PAR's + # from introducing uncommented newlines - next line) + s/(%DIF < )([^\n]*?)\\PAR\n/$1$2\n$1\n/sg; + # convert PAR commands which are on a line by themselves + s/\n(\s*?)\\PAR\n/\n\n/sg; + # convert remaining PAR commands (which are preceded by non-white space characters, usually "}" ($ADDCLOSE) + s/\\PAR\n/\n\n/sg; + + # package specific processing: + if ( defined($packages{"endfloat"})) { + #endfloat: make sure \begin{figure} and \end{figure} are always + # on a line by themselves, similarly for table environment + print STDERR "endfloat package detected.\n" if $verbose ; + # eliminate whitespace before and after + s/^(\s*)(\\(?:begin|end)\{(?:figure|table)\})(\s*)$/$2/mg; + # split lines with remaining characters before float enviroment conmmand + s/^(.+)(\\(?:begin|end)\{(?:figure|table)\})/$1\n$2/mg; + # split lines with remaining characters after float enviroment conmmand + s/(\\(?:begin|end)\{(?:figure|table)\})(.+)$/$1\n$2/mg; + } + # undo renaming of the \begin and \end and dollars in comments + s/(%.*)\\BEGINDIF\{(.*)$/$1\\begin\{$2/mg ; + s/(%.*)\\ENDDIF\{(.*)$/$1\\end\{$2/mg ; + 1 while s/(%.*)DOLLARDIF/$1\$/mg ; + # undo renaming of the \cite.. commands in comments + if ( $CITECMD ) { + 1 while s/(%.*)\\CITEDIF($CITECMD)/$1\\$2/mg ; + } +# Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \] + s/\\end{SQUAREBRACKET}/\\\]/sg; + s/\\begin{SQUAREBRACKET}/\\\[/sg; +# 4. Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$ + s/\\begin\{DOLLARDOLLAR\}(.*?)\\end\{DOLLARDOLLAR\}/\$\$$1\$\$/sg; +# 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn} + 1 while s/\\SUPERSCRIPTNB{($pat0)}/^$1/g ; + 1 while s/\\SUPERSCRIPT{($pat6)}/^{$1}/g ; + # Convert \SUBSCRIPNB{n} into _n and \SUBCRIPT{nn} into _{nnn} + 1 while s/\\SUBSCRIPTNB{($pat0)}/_$1/g ; + 1 while s/\\SUBSCRIPT{($pat6)}/_{$1}/g ; +# Change \QLEFTBRACE, \QRIGHTBRACE to \{,\} + s/\\QLEFTBRACE /\\{/sg; + s/\\QRIGHTBRACE /\\}/sg; + + return; + } +} + + +# @auxlines=preprocess_preamble($oldpreamble,$newpreamble); + # pre-process preamble by looking for commands used in \maketitle (title, author, date etc commands) + # if found then use a bodydiff to mark up content, and replace the corresponding commands + # in both preambles by marked up version to 'fool' the linediff (such that only body is marked + # up. + # A special case are e.g. author commands being added (or removed) + # 1. If commands are added, then the entire content is marked up as new, but also the lines are marked as new in the linediff + # 2. If commands are removed, then the linediff will mark the line as deleted. The program returns + # with $auxlines a text to be appended at the end of the preamble, which shows the respective fields as deleted +sub preprocess_preamble { + my ($oldpreambleref,$newpreambleref)=(\$_[0],\$_[1]) ; + my @auxlines=(); + # Remember to use $$oldpreambleref to refer to oldpreamble + my ($titlecmd,$titlecmdpat); + my (@oldtitlecommands,@newtitlecommands ); + my %oldhash = (); + my %newhash = (); + my ($line,$cmd,$optarg,$arg,$optargnew,$optargold,$optargdiff,$argold,$argnew,$argdiff,$auxline); + + # resuse context2cmdlist to define these commands to look out for in preamble + $titlecmd = "(?:".join("|",@CONTEXT2CMDLIST).")"; + # as context2cmdlist is stored as regex, e.g. ((?-xism:^title$), we need to remove ^- fo + # resue in a more complex regex + $titlecmd =~ s/[\$\^]//g; + # make sure to not match on comment lines: + $titlecmdpat=qr/^(?:[^%\n]|\\%)*(\\($titlecmd)$extraspace(?:\[($brat0)\])?(?:\{($pat6)\}))/ms; + @oldtitlecommands= ( $$oldpreambleref =~ m/$titlecmdpat/g ); + @newtitlecommands= ( $$newpreambleref =~ m/$titlecmdpat/g ); + + + while ( @oldtitlecommands ) { + $line=shift @oldtitlecommands; + $cmd=shift @oldtitlecommands; + $optarg=shift @oldtitlecommands; + $arg=shift @oldtitlecommands; + + if ( defined($oldhash{$cmd})) { + warn "$cmd is used twice in preamble of old file. Reverting to pure line diff mode for preamble.\n"; + return; + } + $oldhash{$cmd}=[ $line, $optarg, $arg ]; + } + while ( @newtitlecommands ) { + $line=shift @newtitlecommands; + $cmd=shift @newtitlecommands; + $optarg=shift @newtitlecommands; + $arg=shift @newtitlecommands; + + if ( defined($newhash{$cmd})) { + warn "$cmd is used twice in preamble of new file. Reverting to pure line diff mode for preamble.\n"; + return; + } + $newhash{$cmd}=[ $line, $optarg, $arg ]; + } + foreach $cmd ( keys %newhash ) { + if ( defined($newhash{$cmd}->[1])) { + $optargnew=$newhash{$cmd}->[1]; + } else { + $optargnew=""; + } + if ( defined($oldhash{$cmd}->[1])) { + $optargold=$oldhash{$cmd}->[1]; + } else { + $optargold=""; + } + + if ( defined($oldhash{$cmd}) ) { + $argold=$oldhash{$cmd}->[2]; + } else { + $argold=""; + } + $argnew=$newhash{$cmd}->[2]; + $argdiff="{" . join("",bodydiff($argold,$argnew)) ."}"; + if ( length $optargnew ) { + $optargdiff="[".join("",bodydiff($optargold,$optargnew))."]" ; + $optargdiff =~ s/\\DIFaddbegin /\\DIFaddbeginFL /g; + $optargdiff =~ s/\\DIFaddend /\\DIFaddendFL /g; + $optargdiff =~ s/\\DIFadd\{/\\DIFaddFL{/g; + $optargdiff =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; + $optargdiff =~ s/\\DIFdelend /\\DIFdelendFL /g; + $optargdiff =~ s/\\DIFdel\{/\\DIFdelFL{/g; + } else { + $optargdiff=""; + } + ### print STDERR "DEBUG s/\\Q$newhash{$cmd}->[0]\\E/\\$cmd$optargdiff$argdiff/s\n"; + # Note: \Q and \E force literal interpretation of what it between them but allow + # variable interpolation, such that e.g. \title matches just that and not TAB-itle + $$newpreambleref=~s/\Q$newhash{$cmd}->[0]\E/\\$cmd$optargdiff$argdiff/s; + # replace this in old preamble if necessary + if ( defined($oldhash{$cmd}->[0])) { + $$oldpreambleref=~s/\Q$oldhash{$cmd}->[0]\E/\\$cmd$optargdiff$argdiff/s ; + } + ### print STDERR "DEBUG NEW PRE ".$$newpreambleref."\n"; + } + + foreach $cmd ( keys %oldhash ) { + # if this has already been dealt with above can just skip + next if defined($newhash{$cmd}) ; + if ( defined($oldhash{$cmd}->[1])) { + $optargold=$oldhash{$cmd}->[1]; + $optargdiff="[".join("",bodydiff($optargold,""))."]" ; + $optargdiff =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; + $optargdiff =~ s/\\DIFdelend /\\DIFdelendFL /g; + $optargdiff =~ s/\\DIFdel\{/\\DIFdelFL{/g; + } else { + $optargdiff=""; + } + $argdiff="{" . join("",bodydiff($argold,"")) ."}"; + $auxline = "\\$cmd$optargdiff$argdiff"; + $auxline =~s/$/$AUXCMD/sg; + push @auxlines,$auxline; + } + # add auxcmd comment to highlight added lines + return(@auxlines); +} + + + +# @diffs=linediff(\@seq1, \@seq2) +# mark up lines like this +#%DIF mm-mmdnn +#%< old deleted line(s) +#%DIF ------- +#%DIF mmann-nn +#new appended line %< +#%DIF ------- +# Future extension: mark change explicitly +# Assumes: traverse_sequence traverses deletions before insertions in changed sequences +# all line numbers relative to line 0 (first line of real file) +sub linediff { + my $seq1 = shift ; + my $seq2 = shift ; + + my $block = []; + my $retseq = []; + my @begin=('','',''); # dummy initialisation + my $instring ; + + my $discard = sub { @begin=('d',$_[0],$_[1]) unless scalar @$block ; + push(@$block, "%DIF < " . $seq1->[$_[0]]) }; + my $add = sub { if (! scalar @$block) { + @begin=('a',$_[0],$_[1]) ;} + elsif ( $begin[0] eq 'd' ) { + $begin[0]='c'; $begin[2]=$_[1]; + push(@$block, "%DIF -------") } + push(@$block, $seq2->[$_[1]] . " %DIF > " ) }; + my $match = sub { if ( scalar @$block ) { + if ( $begin[0] eq 'd' && $begin[1]!=$_[0]-1) { + $instring = sprintf "%%DIF %d-%dd%d",$begin[1],$_[0]-1,$begin[2]; } + elsif ( $begin[0] eq 'a' && $begin[2]!=$_[1]-1) { + $instring = sprintf "%%DIF %da%d-%d",$begin[1],$begin[2],$_[1]-1; } + elsif ( $begin[0] eq 'c' ) { + $instring = sprintf "%%DIF %sc%s", + ($begin[1]==$_[0]-1) ? "$begin[1]" : $begin[1]."-".($_[0]-1) , + ($begin[2]==$_[1]-1) ? "$begin[2]" : $begin[2]."-".($_[1]-1) ; } + else { + $instring = sprintf "%%DIF %d%s%d",$begin[1],$begin[0],$begin[2]; } + push @$retseq, $instring,@$block, "%DIF -------" ; + $block = []; + } + push @$retseq, $seq2->[$_[1]] + }; + # key function: remove multiple spaces (such that insertion or deletion of redundant white space is not reported) + my $keyfunc = sub { join(" ",split(" ",shift())) }; + + traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); + push @$retseq, @$block if scalar @$block; + + return wantarray ? @$retseq : $retseq ; +} + + + +# init_regex_arr_data(\@array,"TOKEN INIT") +# scans DATA file handel for line "%% TOKEN INIT" line +# then appends each line not beginning with % into array (as a quoted regex) +sub init_regex_arr_data { + my ($arr,$token)=@_; + my ($copy); + while (<DATA>) { + if ( m/^%%BEGIN $token\s*$/ ) { + $copy=1; } + elsif ( m/^%%END $token\s*/ ) { + last; } + chomp; + push (@$arr,qr/^$_$/) if ( $copy && !/^%/ ) ; + } + seek DATA,0,0; # rewind DATA handle to file begin +} + + +# init_regex_arr_ext(\@array,$arg) +# fills array with regular expressions. +# if arg is a file name, then read in list of regular expressions from that file +# (one expression per line) +# Otherwise treat arg as a comma separated list of regular expressions +sub init_regex_arr_ext { + my ($arr,$arg)=@_; + my $regex; + if ( -f $ arg ) { + open(FILE,"$arg") or die ("Couldn't open $arg: $!"); + while (<FILE>) { + chomp; + next if /^\s*#/ || /^\s*%/ || /^\s*$/ ; + push (@$arr,qr/^$_$/); + } + close(FILE); + } + else { + # assume it is a comma-separated list of reg-ex + foreach $regex (split(qr/(?<!\\),/,$arg)) { + $regex =~ s/\\,/,/g; + push (@$arr,qr/^$regex$/); + } + } +} + +#exetime() returns time since last execution of this command +#exetime(1) resets this time +my $lasttime=-1; # global variable for persistence +sub exetime { + my $reset=0; + my $retval; + if ((scalar @_) >=1) { + $reset=shift; + } + if ($reset) { + $lasttime=times(); + } + else { + $retval=times()-$lasttime; + $lasttime=$lasttime+$retval; + return($retval); + } +} + + +sub usage { + die <<"EOF"; +Usage: $0 [options] old.tex new.tex > diff.tex + +Compares two latex files and writes tex code to stdout, which has the same +format as new.tex but has all changes relative to old.tex marked up or commented. + +--type=markupstyle +-t markupstyle Add code to preamble for selected markup style + Available styles: UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE + CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR + [ Default: UNDERLINE ] + +--subtype=markstyle +-s markstyle Add code to preamble for selected style for bracketing + commands (e.g. to mark changes in margin) + Available styles: SAFE MARGINAL DVIPSCOL COLOR + [ Default: SAFE ] + +--floattype=markstyle +-f markstyle Add code to preamble for selected style which + replace standard marking and markup commands within floats + (e.g., marginal remarks cause an error within floats + so marginal marking can be disabled thus) + Available styles: FLOATSAFE IDENTICAL + [ Default: FLOATSAFE ] + +--encoding=enc +-e enc Specify encoding of old.tex and new.tex. Typical encodings are + ascii, utf8, latin1, latin9. A list of available encodings can be + obtained by executing + perl -MEncode -e 'print join ("\\n",Encode->encodings( ":all" )) ;' + [Default encoding is utf8 unless the first few lines of the preamble contain + an invocation "\\usepackage[..]{inputenc} in which case the + encoding chosen by this command is asssumed. Note that ASCII (standard + latex) is a subset of utf8] + +--preamble=file +-p file Insert file at end of preamble instead of auto-generating + preamble. The preamble must define the following commands + \\DIFaddbegin,\\DIFaddend,\\DIFadd{..}, + \\DIFdelbegin,\\DIFdelend,\\DIFdel{..}, + and varieties for use within floats + \\DIFaddbeginFL,\\DIFaddendFL,\\DIFaddFL{..}, + \\DIFdelbeginFL,\\DIFdelendFL,\\DIFdelFL{..} + (If this option is set -t, -s, and -f options + are ignored.) + +--exclude-safecmd=exclude-file +--exclude-safecmd="cmd1,cmd2,..." +-A exclude-file +--replace-safecmd=replace-file +--append-safecmd=append-file +--append-safecmd="cmd1,cmd2,..." +-a append-file Exclude from, replace or append to the list of regex + matching commands which are safe to use within the + scope of a \\DIFadd or \\DIFdel command. The file must contain + one Perl-RegEx per line (Comment lines beginning with # or % are + ignored). A literal comma within the comma-separated list must be + escaped thus "\\,", Note that the RegEx needs to match the whole of + the token, i.e., /^regex\$/ is implied and that the initial + "\\" of the command is not included. The --exclude-safecmd + and --append-safecmd options can be combined with the --replace-safecmd + option and can be used repeatedly to add cumulatively to the lists. + +--exclude-textcmd=exclude-file +--exclude-textcmd="cmd1,cmd2,..." +-X exclude-file +--replace-textcmd=replace-file +--append-textcmd=append-file +--append-textcmd="cmd1,cmd2,..." +-x append-file Exclude from, replace or append to the list of regex + matching commands whose last argument is text. See + entry for --exclude-safecmd directly above for further details. + +--replace-context1cmd=replace-file +--append-context1cmd=append-file +--append-context1cmd="cmd1,cmd2,..." + Replace or append to the list of regex matching commands + whose last argument is text but which require a particular + context to work, e.g. \\caption will only work within a figure + or table. These commands behave like text commands, except when + they occur in a deleted section, when they are disabled, but their + argument is shown as deleted text. + +--replace-context2cmd=replace-file +--append-context2cmd=append-file +--append-context2cmd="cmd1,cmd2,..." + As corresponding commands for context1. The only difference is that + context2 commands are completely disabled in deleted sections, including + their arguments. + + +--config var1=val1,var2=val2,... +-c var1=val1,.. Set configuration variables. +-c configfile Available variables: + MINWORDSBLOCK (integer) + FLOATENV (RegEx) + PICTUREENV (RegEx) + MATHENV (RegEx) + MATHREPL (String) + MATHARRENV (RegEx) + MATHARRREPL (String) + ARRENV (RegEx) + COUNTERCMD (RegEx) + This option can be repeated. + + +--packages=pkg1,pkg2,.. + Tell latexdiff that .tex file is processed with the packages in list + loaded. This is normally not necessary if the .tex file includes the + preamble, as the preamble is automatically scanned for \\usepackage commands. + Use of the --packages option disables automatic scanning, so if for any + reason package specific parsing needs to be switched off, use --packages=none. + The following packages trigger special behaviour: + endfloat hyperref amsmath + [ Default: scan the preamble for \\usepackage commands to determine + loaded packages.] + +--show-preamble Print generated or included preamble commands to stdout. + +--show-safecmd Print list of regex matching and excluding safe commands. + +--show-textcmd Print list of regex matching and excluding commands with text argument. + +--show-config Show values of configuration variables + +--show-all Show all of the above + + NB For all --show commands, no old.tex or new.tex file needs to be given, and no + differencing takes place. + +Other configuration options: + +--allow-spaces Allow spaces between bracketed or braced arguments to commands + [Default requires arguments to directly follow each other without + intervening spaces] + +--math-markup=level Determine granularity of markup in displayed math environments: + Possible values for level are (both numerical and text labels are acceptable): + off or 0: suppress markup for math environments. Deleted equations will not + appear in diff file. This mode can be used if all the other modes + cause invalid latex code. + whole or 1: Differencing on the level of whole equations. Even trivial changes + to equations cause the whole equation to be marked changed. This + mode can be used if processing in coarse or fine mode results in + invalid latex code. + coarse or 2: Detect changes within equations marked up with a coarse + granularity; changes in equation type (e.g.displaymath to equation) + appear as a change to the complete equation. This mode is recommended + for situations where the content and order of some equations are still + being changed. [Default] + fine or 3: Detect small change in equations and mark up and fine granularity. + This mode is most suitable, if only minor changes to equations are + expected, e.g. correction of typos. + +--disable-citation-markup Suppress citation markup in styles using ulem (UNDERLINE, + FONTSTRIKE, CULINECHBAR) +--enable-citation-markup Protect citation commands in changed sections with \\mbox command + [i.e. use default behaviour for ulem package for other packages] + +Miscelleneous options + +--label=label +-L label Sets the labels used to describe the old and new files. The first use + of this option sets the label describing the old file and the second + use of the option sets the label for the new file. + [Default: use the filename and modification dates for the label] + +--no-label Suppress inclusion of old and new file names as comment in output file + +--visble-label Include old and new filenames (or labels set with --label option) as + visible output + +--flatten Replace \\input and \\include commands within body by the content + of the files in their argument. If \\includeonly is present in the + preamble, only those files are expanded into the document. However, + no recursion is done, i.e. \\input and \\include commands within + included sections are not expanded. The included files are assumed to + be located in the same directories as the old and new master files, + respectively, making it possible to organise files into old and new directories. + --flatten is applied recursively, so inputted files can contain further + \\input statements. + +--help +-h Show this help text. + +--ignore-warnings Suppress warnings about inconsistencies in length between input + and parsed strings and missing characters. + +--verbose +-V Output various status information to stderr during processing. + Default is to work silently. + +--version Show version number. + +For further information, consult http://latexdiff.berlios.de +EOF +} + +=head1 NAME + +latexdiff - determine and markup differences between two latex files + +=head1 SYNOPSIS + +B<latexdiff> [ B<OPTIONS> ] F<old.tex> F<new.tex> > F<diff.tex> + +=head1 DESCRIPTION + +Briefly, I<latexdiff> is a utility program to aid in the management of +revisions of latex documents. It compares two valid latex files, here +called C<old.tex> and C<new.tex>, finds significant differences +between them (i.e., ignoring the number of white spaces and position +of line breaks), and adds special commands to highlight the +differences. Where visual highlighting is not possible, e.g. for changes +in the formatting, the differences are +nevertheless marked up in the source. + +The program treats the preamble differently from the main document. +Differences between the preambles are found using line-based +differencing (similarly to the Unix diff command, but ignoring white +spaces). A comment, "S<C<%DIF E<gt>>>" is appended to each added line, i.e. a +line present in C<new.tex> but not in C<old.tex>. Discarded lines + are deactivated by prepending "S<C<%DIF E<lt>>>". Changed blocks are preceded by +comment lines giving information about line numbers in the original files. Where there are insignificant +differences, the resulting file C<diff.tex> will be similar to +C<new.tex>. At the end of the preamble, the definitions for I<latexdiff> markup commands are inserted. +In differencing the main body of the text, I<latexdiff> attempts to +satisfy the following guidelines (in order of priority): + +=over 3 + +=item 1 + +If both C<old.tex> and C<new.tex> are valid LaTeX, then the resulting +C<diff.tex> should also be valid LateX. (NB If a few plain TeX commands +are used within C<old.tex> or C<new.tex> then C<diff.tex> is not +guaranteed to work but usually will). + +=item 2 + +Significant differences are determined on the level of +individual words. All significant differences, including differences +between comments should be clearly marked in the resulting source code +C<diff.tex>. + +=item 3 + +If a changed passage contains text or text-producing commands, then +running C<diff.tex> through LateX should produce output where added +and discarded passages are highlighted. + +=item 4 + +Where there are insignificant differences, e.g. in the positioning of +line breaks, C<diff.tex> should follow the formatting of C<new.tex> + +=back + +For differencing the same algorithm as I<diff> is used but words +instead of lines are compared. An attempt is made to recognize +blocks which are completely changed such that they can be marked up as a unit. +Comments are differenced line by line +but the number of spaces within comments is ignored. Commands including +all their arguments are generally compared as one unit, i.e., no mark-up +is inserted into the arguments of commands. However, for a selected +number of commands (for example, C<\caption> and all sectioning +commands) the last argument is known to be text. This text is +split into words and differenced just as ordinary text (use options to +show and change the list of text commands, see below). As the +algorithm has no detailed knowledge of LaTeX, it assumes all pairs of +curly braces immediately following a command (i.e. a sequence of +letters beginning with a backslash) are arguments for that command. +As a restriction to condition 1 above it is thus necessary to surround +all arguments with curly braces, and to not insert +extraneous spaces. For example, write + + \section{\textem{This is an emphasized section title}} + +and not + + \section {\textem{This is an emphasized section title}} + +or + + \section\textem{This is an emphasized section title} + +even though all varieties are the same to LaTeX (but see +B<--allow-spaces> option which allows the second variety). + +For environments whose content does not conform to standard LaTeX or +where graphical markup does not make sense all markup commands can be +removed by setting the PICTUREENV configuration variable, set by +default to C<picture> and C<DIFnomarkup> environments; see B<--config> +option). The latter environment (C<DIFnomarkup>) can be used to +protect parts of the latex file where the markup results in illegal +markup. You have to surround the offending passage in both the old and +new file by C<\begin{DIFnomarkup}> and C<\end{DIFnomarkup}>. You must +define the environment in the preambles of both old and new +documents. I prefer to define it as a null-environment, + +C<\newenvironment{DIFnomarkup}{}{}> + +but the choice is yours. Any markup within the environment will be +removed, and generally everything within the environment will just be +taken from the new file. + +It is also possible to difference files which do not have a preamble. + In this case, the file is processed in the main document +mode, but the definitions of the markup commands are not inserted. + +All markup commands inserted by I<latexdiff> begin with "C<\DIF>". Added +blocks containing words, commands or comments which are in C<new.tex> +but not in C<old.tex> are marked by C<\DIFaddbegin> and C<\DIFaddend>. +Discarded blocks are marked by C<\DIFdelbegin> and C<\DIFdelend>. +Within added blocks all text is highlighted with C<\DIFadd> like this: +C<\DIFadd{Added text block}> +Selected `safe' commands can be contained in these text blocks as well +(use options to show and change the list of safe commands, see below). +All other commands as well as braces "{" and "}" are never put within +the scope of C<\DIFadd>. Added comments are marked by prepending +"S<C<%DIF E<gt> >>". + +Within deleted blocks text is highlighted with C<\DIFdel>. Deleted +comments are marked by prepending "S<C<%DIF E<lt> >>". Non-safe command +and curly braces within deleted blocks are commented out with +"S<C<%DIFDELCMD E<lt> >>". + + + +=head1 OPTIONS + +=head2 Preamble + +The following options determine the visual markup style by adding the appropriate +command definitions to the preamble. See the end of this section for a description of +available styles. + +=over 4 + +=item B<--type=markupstyle> or +B<-t markupstyle> + +Add code to preamble for selected markup style. This option defines +C<\DIFadd> and C<\DIFdel> commands. +Available styles: + +C<UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE +CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR> + +[ Default: C<UNDERLINE> ] + +=item B<--subtype=markstyle> or +B<-s markstyle> + +Add code to preamble for selected style for bracketing +commands (e.g. to mark changes in margin). This option defines +C<\DIFaddbegin>, C<\DIFaddend>, C<\DIFdelbegin> and C<\DIFdelend> commands. +Available styles: C<SAFE MARGINAL COLOR DVIPSCOL> + +[ Default: C<SAFE> ] + +=item B<--floattype=markstyle> or +B<-f markstyle> + +Add code to preamble for selected style which +replace standard marking and markup commands within floats +(e.g., marginal remarks cause an error within floats +so marginal marking can be disabled thus). This option defines all +C<\DIF...FL> commands. +Available styles: C<FLOATSAFE TRADITIONALSAFE IDENTICAL> + +[ Default: C<FLOATSAFE> ] + +=item B<--encoding=enc> or +B<-e enc> + +Specify encoding of old.tex and new.tex. Typical encodings are +C<ascii>, C<utf8>, C<latin1>, C<latin9>. A list of available encodings can be +obtained by executing + +C<perl -MEncode -e 'print join ("\n",Encode->encodings( ":all" )) ;' > + +[Default encoding is utf8 unless the first few lines of the preamble contain +an invocation C<\usepackage[..]{inputenc}> in which case the +encoding chosen by this command is asssumed. Note that ASCII (standard +latex) is a subset of utf8] + +=item B<--preamble=file> or +B<-p file> + +Insert file at end of preamble instead of generating +preamble. The preamble must define the following commands +C<\DIFaddbegin, \DIFaddend, \DIFadd{..}, +\DIFdelbegin,\DIFdelend,\DIFdel{..},> +and varieties for use within floats +C<\DIFaddbeginFL, \DIFaddendFL, \DIFaddFL{..}, +\DIFdelbeginFL, \DIFdelendFL, \DIFdelFL{..}> +(If this option is set B<-t>, B<-s>, and B<-f> options +are ignored.) + +=item B<--packages=pkg1,pkg2,..> + +Tell latexdiff that .tex file is processed with the packages in list +loaded. This is normally not necessary if the .tex file includes the +preamble, as the preamble is automatically scanned for C<\usepackage> commands. +Use of the B<--packages> option disables automatic scanning, so if for any +reason package specific parsing needs to be switched off, use B<--packages=none>. +The following packages trigger special behaviour: + +=over 8 + +=item C<amsmath> + +Configuration variable amsmath is set to C<align*> (Default: C<eqnarray*>) + +=item C<endfloat> + +Ensure that C<\begin{figure}> and C<\end{figure}> always appear by themselves on a line. + +=item C<hyperref> + +Change name of C<\DIFadd> and C<\DIFdel> commands to C<\DIFaddtex> and C<\DIFdeltex> and +define new C<\DIFadd> and C<\DIFdel> commands, which provide a wrapper for these commands, +using them for the text but not for the link defining command (where any markup would cause +errors). + +=back + +[ Default: scan the preamble for C<\\usepackage> commands to determine + loaded packages.] + + + +=item B<--show-preamble> + +Print generated or included preamble commands to stdout. + +=back + +=head2 Configuration + +=over 4 + +=item B<--exclude-safecmd=exclude-file> or +B<-A exclude-file> or B<--exclude-safecmd="cmd1,cmd2,..."> + +=item B<--replace-safecmd=replace-file> + +=item B<--append-safecmd=append-file> or +B<-a append-file> or B<--append-safecmd="cmd1,cmd2,..."> + +Exclude from, replace or append to the list of regular expressions (RegEx) +matching commands which are safe to use within the +scope of a C<\DIFadd> or C<\DIFdel> command. The file must contain +one Perl-RegEx per line (Comment lines beginning with # or % are +ignored). Note that the RegEx needs to match the whole of +the token, i.e., /^regex$/ is implied and that the initial +"\" of the command is not included. +The B<--exclude-safecmd> and B<--append-safecmd> options can be combined with the -B<--replace-safecmd> +option and can be used repeatedly to add cumulatively to the lists. + B<--exclude-safecmd> +and B<--append-safecmd> can also take a comma separated list as input. If a +comma for one of the regex is required, escape it thus "\,". In most cases it +will be necessary to protect the comma-separated list from the shell by putting +it in quotation marks. + +=item B<--exclude-textcmd=exclude-file> or +B<-X exclude-file> or B<--exclude-textcmd="cmd1,cmd2,..."> + +=item B<--replace-textcmd=replace-file> + +=item B<--append-textcmd=append-file> or +B<-x append-file> or B<--append-textcmd="cmd1,cmd2,..."> + +Exclude from, replace or append to the list of regular expressions +matching commands whose last argument is text. See +entry for B<--exclude-safecmd> directly above for further details. + + +=item B<--replace-context1cmd=replace-file> + +=item B<--append-context1cmd=append-file> or +=item B<--append-context1cmd="cmd1,cmd2,..."> + +Replace or append to the list of regex matching commands +whose last argument is text but which require a particular +context to work, e.g. \caption will only work within a figure +or table. These commands behave like text commands, except when +they occur in a deleted section, when they are disabled, but their +argument is shown as deleted text. + +=item B<--replace-context2cmd=replace-file> + +=item B<--append-context2cmd=append-file> or +=item B<--append-context2cmd="cmd1,cmd2,..."> +As corresponding commands for context1. The only difference is that +context2 commands are completely disabled in deleted sections, including +their arguments. + + + +=item B<--config var1=val1,var2=val2,...> or B<-c var1=val1,..> + +=item B<-c configfile> + +Set configuration variables. The option can be repeated to set different +variables (as an alternative to the comma-separated list). +Available variables (see below for further explanations): + +C<MINWORDSBLOCK> (integer) + +C<FLOATENV> (RegEx) + +C<PICTUREENV> (RegEx) + +C<MATHENV> (RegEx) + +C<MATHREPL> (String) + +C<MATHARRENV> (RegEx) + +C<MATHARRREPL> (String) + +C<ARRENV> (RegEx) + +C<COUNTERCMD> (RegEx) + +=item B<--show-safecmd> + +Print list of RegEx matching and excluding safe commands. + +=item B<--show-textcmd> + +Print list of RegEx matching and excluding commands with text argument. + +=item B<--show-config> + +Show values of configuration variables. + +=item B<--show-all> + +Combine all --show commands. + +NB For all --show commands, no C<old.tex> or C<new.tex> file needs to be specified, and no +differencing takes place. + +=back + +=head2 Other configuration options: + +=over 4 + +=item B<--allow-spaces> + +Allow spaces between bracketed or braced arguments to commands. Note +that this option might have undesirable side effects (unrelated scope +might get lumpeded with preceding commands) so should only be used if the +default produces erroneous results. (Default requires arguments to +directly follow each other without intervening spaces). + +=item B<--math-markup=level> + +Determine granularity of markup in displayed math environments: +Possible values for level are (both numerical and text labels are acceptable): + +C<off> or C<0>: suppress markup for math environments. Deleted equations will not +appear in diff file. This mode can be used if all the other modes +cause invalid latex code. + +C<whole> or C<1>: Differencing on the level of whole equations. Even trivial changes +to equations cause the whole equation to be marked changed. This +mode can be used if processing in coarse or fine mode results in +invalid latex code. + +C<coarse> or C<2>: Detect changes within equations marked up with a coarse +granularity; changes in equation type (e.g.displaymath to equation) +appear as a change to the complete equation. This mode is recommended +for situations where the content and order of some equations are still +being changed. [Default] + +C<fine> or C<3>: Detect small change in equations and mark up at fine granularity. +This mode is most suitable, if only minor changes to equations are +expected, e.g. correction of typos. + +=item B<--disable-citation-markup> + +Suppress citation markup in styles using ulem (UNDERLINE, +FONTSTRIKE, CULINECHBAR) + +=item B<--enable-citation-markup> + +Protect citation commands in changed sections with \\mbox command [i.e. use default behaviour for ulem package for other packages] + +=back + +=head2 Miscellaneous + +=over 4 +=item B<--verbose> or B<-V> + +Output various status information to stderr during processing. +Default is to work silently. + +=item B<--driver=type> + +Choose driver for changebar package (only relevant for styles using + changebar: CCHANGEBAR CFONTCHBAR CULINECHBAR CHANGEBAR). Possible +drivers are listed in changebar manual, e.g. pdftex,dvips,dvitops + [Default: dvips] + +=item B<--ignore-warnings> + +Suppress warnings about inconsistencies in length between input and +parsed strings and missing characters. These warning messages are +often related to non-standard latex or latex constructions with a +syntax unknown to C<latexdiff> but the resulting difference argument +is often fully functional anyway, particularly if the non-standard +latex only occurs in parts of the text which have not changed. + +=item B<--label=label> or +B<-L label> + +Sets the labels used to describe the old and new files. The first use +of this option sets the label describing the old file and the second +use of the option sets the label for the new file, i.e. set both +labels like this C<-L labelold -L labelnew>. +[Default: use the filename and modification dates for the label] + +=item B<--no-label> + +Suppress inclusion of old and new file names as comment in output file + +=item B<--visble-label> + +Include old and new filenames (or labels set with --label option) as +visible output. + +=item B<--flatten> + +Replace C<\input> and C<\include> commands within body by the content +of the files in their argument. If C<\includeonly> is present in the +preamble, only those files are expanded into the document. However, +no recursion is done, i.e. C<\input> and C<\include> commands within +included sections are not expanded. The included files are assumed to + be located in the same directories as the old and new master files, +respectively, making it possible to organise files into old and new directories. +--flatten is applied recursively, so inputted files can contain further +C<\input> statements. + +Use of this option might result in prohibitive processing times for +larger documents, and the resulting difference document +no longer reflects the structure of the input documents. + +=item B<--help> or +B<-h> + +Show help text + +=item B<--version> + +Show version number + +=back + + + +=head2 Predefined styles + +=head2 Major types + +The major type determine the markup of plain text and some selected latex commands outside floats by defining the markup commands C<\DIFadd{...}> and C<\DIFdel{...}> . + +=over 10 + +=item C<UNDERLINE> + +Added text is wavy-underlined and blue, discarded text is struck out and red +(Requires color and ulem packages). Overstriking does not work in displayed math equations such that deleted parts of equation are underlined, not struck out (this is a shortcoming inherent to the ulem package). + +=item C<CTRADITIONAL> + +Added text is blue and set in sans-serif, and a red footnote is created for each discarded +piece of text. (Requires color package) + +=item C<TRADITIONAL> + +Like C<CTRADITIONAL> but without the use of color. + +=item C<CFONT> + +Added text is blue and set in sans-serif, and discarded text is red and very small size. + +=item C<FONTSTRIKE> + +Added tex is set in sans-serif, discarded text small and struck out + +=item C<CCHANGEBAR> + +Added text is blue, and discarded text is red. Additionally, the changed text is marked with a bar in the margin (Requires color and changebar packages). + +=item C<CFONTCHBAR> + +Like C<CFONT> but with additional changebars (Requires color and changebar packages). + +=item C<CULINECHBAR> + +Like C<UNDERLINE> but with additional changebars (Requires color, ulem and changebar packages). + +=item C<CHANGEBAR> + +No mark up of text, but mark margins with changebars (Requires changebar package). + +=item C<INVISIBLE> + +No visible markup (but generic markup commands will still be inserted. + +=back + +=head2 Subtypes + +The subtype defines the commands that are inserted at the begin and end of added or discarded blocks, irrespectively of whether these blocks contain text or commands (Defined commands: C<\DIFaddbegin, \DIFaddend, \DIFdelbegin, \DIFdelend>) + +=over 10 + +=item C<SAFE> + +No additional markup (Recommended choice) + +=item C<MARGIN> + +Mark beginning and end of changed blocks with symbols in the margin nearby (using +the standard C<\marginpar> command - note that this sometimes moves somewhat +from the intended position. + +=item C<COLOR> + +An alternative way of marking added passages in blue, and deleted ones in red. +(It is recommeneded to use instead the main types to effect colored markup, +although in some cases coloring with dvipscol can be more complete, for example +with citation commands). + +=item C<DVIPSCOL> + +An alternative way of marking added passages in blue, and deleted ones in red. Note +that C<DVIPSCOL> only works with the dvips converter, e.g. not pdflatex. +(it is recommeneded to use instead the main types to effect colored markup, +although in some cases coloring with dvipscol can be more complete). + +=back + +=head2 Float Types + +Some of the markup used in the main text might cause problems when used within +floats (e.g. figures or tables). For this reason alternative versions of all +markup commands are used within floats. The float type defines these alternative commands. + +=over 10 + +=item C<FLOATSAFE> + +Use identical markup for text as in the main body, but set all commands marking the begin and end of changed blocks to null-commands. You have to choose this float type if your subtype is C<MARGIN> as C<\marginpar> does not work properly within floats. + +=item C<TRADITIONALSAFE> + +Mark additions the same way as in the main text. Deleted environments are marked by angular brackets \[ and \] and the deleted text is set in scriptscript size. This float type should always be used with the C<TRADITIONAL> and C<CTRADITIONAL> markup types as the \footnote command does not work properly in floating environments. + +=item C<IDENTICAL> + +Make no difference between the main text and floats. + +=back + + +=head2 Configuration Variables + +=over 10 + +=item C<MINWORDSBLOCK> + +Minimum number of tokens required to form an independent block. This value is +used in the algorithm to detect changes of complete blocks by merging identical text parts of less than C<MINWORDSBLOCK> to the preceding added and discarded parts. + +[ Default: 3 ] + +=item C<FLOATENV> + +Environments whose name matches the regular expression in C<FLOATENV> are +considered floats. Within these environments, the I<latexdiff> markup commands +are replaced by their FL variaties. + +[ Default: S<C<(?:figure|table|plate)[\w\d*@]*> >] + +=item C<PICTUREENV> + +Within environments whose name matches the regular expression in C<PICTUREENV> +all latexdiff markup is removed (in pathologic cases this might lead to + inconsistent markup but this situation should be rare). + +[ Default: S<C<(?:picture|DIFnomarkup)[\w\d*@]*> >] + +=item C<MATHENV>,C<MATHREPL> + +If both \begin and \end for a math environment (environment name matching C<MATHENV> +or \[ and \]) +are within the same deleted block, they are replaced by a \begin and \end commands for C<MATHREPL> +rather than being commented out. + +[ Default: C<MATHENV>=S<C<(?:displaymath|equation)> >, C<MATHREPL>=S<C<displaymath> >] + +=item C<MATHARRENV>,C<MATHARRREPL> + +as C<MATHENV>,C<MATHREPL> but for equation arrays + +[ Default: C<MATHARRENV>=S<C<eqnarray\*?> >, C<MATHREPL>=S<C<eqnarray> >] + +=item C<ARRENV> + +If a match to C<ARRENV> is found within an inline math environment within a deleted or added block, then the inlined math +is surrounded by C<\mbox{>...C<}>. This is necessary as underlining does not work within inlined array environments. + +[ Default: C<ARRENV>=S<C<(?:array|[pbvBV]matrix)> > + +=item C<COUNTERCMD> + +If a command in a deleted block which is also in the textcmd list matches C<COUNTERCMD> then an +additional command C<\addtocounter{>F<cntcmd>C<}{-1}>, where F<cntcmd> is the matching command, is appended in the diff file such that the numbering in the diff file remains synchronized with the +numbering in the new file. + +[ Default: C<COUNTERCMD>=C<(?:footnote|part|section|subsection> ... + +C<|subsubsection|paragraph|subparagraph)> ] + +=back + +=head1 COMMON PROBLEMS + +=over 10 + +=item Citations result in overfull boxes + +There is an incompatibility between the C<ulem> package, which C<latexdiff> uses for underlining and striking out in the UNDERLINE style, +the default style. In order to be able to mark up citations properly, they are placed with an C<\mbox> command in post-processing. As mboxes +cannot be broken across lines, this procedure frequently results in overfull boxes, possibly obscuring the content as it extends beyond the right margin. If this is a problem, you have two possibilities: + +1. Use C<COLOR> or C<DVIPSCOL> subtype markup (option C<-s COLOR>): If this markup is chosen, then changed citations are no longer marked up +with the wavy line (additions) or struck out (deletions), but are still highlighted in the appropriate color. + +2. Choose option C<--disable-citation-markup> which turns off the marking up of citations: deleted citations are no longer shown, and +added ctations are shown without markup. (This was the default behaviour of latexdiff at versions 0.6 and older) + +=item Changes in complicated mathematical equations result in latex processing errors + +Try options C<--math-markup=whole>. If even that fails, you can turn off mark up for equations with C<--math-markup=off>. + +=back + +=head1 BUGS + +Option allow-spaces not implemented entirely consistently. It breaks +the rules that number and type of white space does not matter, as +different numbers of inter-argument spaces are treated as significant. + +Please submit bug reports on the latexdiff project page I<http://latexdiff.berlios.de>, +send them to user discussion list C<latexdiff-users@lists.berlios,de> (prior subscription +to list required, also on project webpage) +or send them to I<tilmann@gfz-potsdam.de>. Include the serial number of I<latexdiff> +(from comments at the top of the source or use B<--version>). If you come across latex +files that are error-free and conform to the specifications set out +above, and whose differencing still does not result in error-free +latex, please send me those files, ideally edited to only contain the +offending passage as long as that still reproduces the problem. If your +file relies on non-standard class files, you must include those. I will not +look at examples where I have trouble to latex the original files. + +=head1 SEE ALSO + +L<latexrevise>, L<latexdiff-vc> + +=head1 PORTABILITY + +I<latexdiff> does not make use of external commands and thus should run +on any platform supporting Perl 5.6 or higher. If files with encodings +other than ASCII or UTF-8 are processed, Perl 5.8 or higher is required. + +The standard version of I<latexdiff> requires installation of the Perl package +C<Algorithm::Diff> (available from I<www.cpan.org> - +I<http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15>) but a stand-alone +version, I<latexdiff-so>, which has this package inlined, is available, too. +I<latexdiff-fast> requires the I<diff> command to be present. + +=head1 AUTHOR + +Version 1.0.2 +Copyright (C) 2004-2012 Frederik Tilmann + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 + +Contributors of fixes and additions: V. Kuhlmann, J. Paisley, N. Becker, T. Doerges, K. Huebner, +T. Connors, Sebastian Gouezel and many others. +Thanks to the many people who send in bug reports, feature suggestions, and other feedback. + +=cut + +__END__ +%%BEGIN SAFE COMMANDS +% Regex matching commands which can safely be in the +% argument of a \DIFadd or \DIFdel command (leave out the \) +arabic +dashbox +emph +fbox +framebox +hspace +math.* +makebox +mbox +pageref +ref +symbol +raisebox +rule +text.* +shortstack +usebox +dag +ddag +copyright +pounds +S +P +oe +OE +ae +AE +aa +AA +o +O +l +L +frac +ss +sqrt +ldots +cdots +vdots +ddots +alpha +beta +gamma +delta +epsilon +varepsilon +zeta +eta +theta +vartheta +iota +kappa +lambda +mu +nu +xi +pi +varpi +rho +varrho +sigma +varsigma +tau +upsilon +phi +varphi +chi +psi +omega +Gamma +Delta +Theta +Lambda +Xi +Pi +Sigma +Upsilon +Phi +Psi +Omega +ps +mp +times +div +ast +star +circ +bullet +cdot +cap +cup +uplus +sqcap +vee +wedge +setminus +wr +diamond +(?:big)?triangle.* +lhd +rhd +unlhd +unrhd +oplus +ominus +otimes +oslash +odot +bigcirc +d?dagger +amalg +leq +prec +preceq +ll +(?:sq)?su[bp]set(?:eq)? +in +vdash +geq +succ(?:eq)? +gg +ni +dashv +equiv +sim(?:eq)? +asymp +approx +cong +neq +doteq +propto +models +perp +mid +parallel +bowtie +Join +smile +frown +.*arrow +(?:long)?mapsto +.*harpoon.* +leadsto +aleph +hbar +imath +jmath +ell +wp +Re +Im +mho +prime +emptyset +nabla +surd +top +bot +angle +forall +exists +neg +flat +natural +sharp +backslash +partial +infty +Box +Diamond +triangle +clubsuit +diamondsuit +heartsuit +spadesuit +sum +prod +coprod +int +oint +big(?:sq)?c[au]p +bigvee +bigwedge +bigodot +bigotimes +bigoplus +biguplus +(?:arc)?(?:cos|sin|tan|cot)h? +csc +arg +deg +det +dim +exp +gcd +hom +inf +ker +lg +lim +liminf +limsup +ln +log +max +min +Pr +sec +sup +(SUPER|SUB)SCRIPTNB +(SUPER|SUB)SCRIPT +%%END SAFE COMMANDS + +%%BEGIN TEXT COMMANDS +% Regex matching commands with a text argument (leave out the \) +addcontents.* +cc +closing +chapter +dashbox +emph +encl +fbox +framebox +footnote +footnotetext +framebox +part +(sub){0,2}section\*? +(sub)?paragraph\*? +makebox +mbox +opening +parbox +raisebox +savebox +sbox +shortstack +signature +text.* +value +underline +sqrt +(SUPER|SUB)SCRIPT +%%END TEXT COMMANDS + +%%BEGIN CONTEXT1 COMMANDS +% Regex matching commands with a text argument (leave out the \), which will fail out of context, but whose argument should be printed as plain text +caption +%%END CONTEXT1 COMMANDS + +%%BEGIN CONTEXT2 COMMANDS +% Regex matching commands with a text argument (leave out the \), which will fail out of context, but whose argument should be printed as plain text +title +author +date +institute +%%END CONTEXT2 COMMANDS + + +%% TYPES (Commands for highlighting changed blocks) + +%DIF UNDERLINE PREAMBLE +\RequirePackage[normalem]{ulem} +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{{\protect\color{blue}\uwave{#1}}} +\providecommand{\DIFdel}[1]{{\protect\color{red}\sout{#1}}} +%DIF END UNDERLINE PREAMBLE + +%DIF CTRADITIONAL PREAMBLE +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\RequirePackage[stable]{footmisc} +\providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}} +\providecommand{\DIFdel}[1]{{\protect\color{red} [..\footnote{removed: #1} ]}} +%DIF END CTRADITIONAL PREAMBLE + +%DIF TRADITIONAL PREAMBLE +\RequirePackage[stable]{footmisc} +\providecommand{\DIFadd}[1]{{\sf #1}} +\providecommand{\DIFdel}[1]{{[..\footnote{removed: #1} ]}} +%DIF END TRADITIONAL PREAMBLE + +%DIF CFONT PREAMBLE +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}} +\providecommand{\DIFdel}[1]{{\protect\color{red} \scriptsize #1}} +%DIF END CFONT PREAMBLE + +%DIF FONTSTRIKE PREAMBLE +\RequirePackage[normalem]{ulem} +\providecommand{\DIFadd}[1]{{\sf #1}} +\providecommand{\DIFdel}[1]{{\footnotesize \sout{#1}}} +%DIF END FONTSTRIKE PREAMBLE + +%DIF CCHANGEBAR PREAMBLE +\RequirePackage[dvips]{changebar} +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}#1}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}#1}\protect\cbdelete} +%DIF END CCHANGEBAR PREAMBLE + +%DIF CFONTCHBAR PREAMBLE +\RequirePackage[dvips]{changebar} +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\sf #1}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\scriptsize #1}\protect\cbdelete} +%DIF END CFONTCHBAR PREAMBLE + +%DIF CULINECHBAR PREAMBLE +\RequirePackage[normalem]{ulem} +\RequirePackage[dvips]{changebar} +\RequirePackage{color} +\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\uwave{#1}}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\sout{#1}}\protect\cbdelete} +%DIF END CULINECHBAR PREAMBLE + +%DIF CHANGEBAR PREAMBLE +\RequirePackage[dvips]{changebar} +\providecommand{\DIFadd}[1]{\protect\cbstart{#1}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete} +%DIF END CHANGEBAR PREAMBLE + +%DIF INVISIBLE PREAMBLE +\providecommand{\DIFadd}[1]{#1} +\providecommand{\DIFdel}[1]{} +%DIF END INVISIBLE PREAMBLE + + +%% SUBTYPES (Markers for beginning and end of changed blocks) + +%DIF SAFE PREAMBLE +\providecommand{\DIFaddbegin}{} +\providecommand{\DIFaddend}{} +\providecommand{\DIFdelbegin}{} +\providecommand{\DIFdelend}{} +%DIF END SAFE PREAMBLE + +%DIF MARGIN PREAMBLE +\providecommand{\DIFaddbegin}{\protect\marginpar{a[}} +\providecommand{\DIFaddend}{\protect\marginpar{]}} +\providecommand{\DIFdelbegin}{\protect\marginpar{d[}} +\providecommand{\DIFdelend}{\protect\marginpar{]}} +%DIF END BRACKET PREAMBLE + +%DIF DVIPSCOL PREAMBLE +%Note: only works with dvips converter +\RequirePackage{color} +\RequirePackage{dvipscol} +\providecommand{\DIFaddbegin}{\protect\nogroupcolor{blue}} +\providecommand{\DIFaddend}{\protect\nogroupcolor{black}} +\providecommand{\DIFdelbegin}{\protect\nogroupcolor{red}} +\providecommand{\DIFdelend}{\protect\nogroupcolor{black}} +%DIF END DVIPSCOL PREAMBLE + +%DIF COLOR PREAMBLE +\RequirePackage{color} +\providecommand{\DIFaddbegin}{\protect\color{blue}} +\providecommand{\DIFaddend}{\protect\color{black}} +\providecommand{\DIFdelbegin}{\protect\color{red}} +\providecommand{\DIFdelend}{\protect\color{black}} +%DIF END COLOR PREAMBLE + + +%% FLOAT TYPES + +%DIF FLOATSAFE PREAMBLE +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} +\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} +\providecommand{\DIFaddbeginFL}{} +\providecommand{\DIFaddendFL}{} +\providecommand{\DIFdelbeginFL}{} +\providecommand{\DIFdelendFL}{} +%DIF END FLOATSAFE PREAMBLE + +%DIF IDENTICAL PREAMBLE +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} +\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} +\providecommand{\DIFaddbeginFL}{\DIFaddbegin} +\providecommand{\DIFaddendFL}{\DIFaddend} +\providecommand{\DIFdelbeginFL}{\DIFdelbegin} +\providecommand{\DIFdelendFL}{\DIFdelend} +%DIF END IDENTICAL PREAMBLE + +%DIF TRADITIONALSAFE PREAMBLE +% procidecommand color to make this work for TRADITIONAL and CTRADITIONAL +\providecommand{\color}[1]{} +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} +\providecommand{\DIFdel}[1]{{\protect\color{red}[..{\scriptsize {removed: #1}} ]}} +\providecommand{\DIFaddbeginFL}{} +\providecommand{\DIFaddendFL}{} +\providecommand{\DIFdelbeginFL}{} +\providecommand{\DIFdelendFL}{} +%DIF END FLOATSAFE PREAMBLE + +%% SPECIAL PACKAGE PREAMBLE COMMANDS + +% Standard \DIFadd and \DIFdel are redefined as \DIFaddtex and \DIFdeltex +% when hyperref package is included. +%DIF HYPERREF PREAMBLE +\providecommand{\DIFadd}[1]{\texorpdfstring{\DIFaddtex{#1}}{#1}} +\providecommand{\DIFdel}[1]{\texorpdfstring{\DIFdeltex{#1}}{}} +%DIF END HYPERREF PACKAGE diff --git a/Master/texmf-dist/doc/support/latexdiff/latexdiff-fast b/Master/texmf-dist/doc/support/latexdiff/latexdiff-fast new file mode 100755 index 00000000000..310ecc4daef --- /dev/null +++ b/Master/texmf-dist/doc/support/latexdiff/latexdiff-fast @@ -0,0 +1,3961 @@ +#!/usr/bin/env perl +##!/usr/bin/perl -w +# latexdiff - differences two latex files on the word level +# and produces a latex file with the differences marked up. +# +# Copyright (C) 2004-12 F J Tilmann (tilmann@gfz-potsdam.de, ftilmann@users.berlios.de) +# +# Project webpages: http://latexdiff.berlios.de/ +# CTAN page: http://www.ctan.org/tex-archive/support/latexdiff +# +# 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 <http://www.gnu.org/licenses/>. +# +# Detailed usage information at the end of the file +# +# Version 1.0.1 - treat \big,\bigg etc. equivalently to \left and +# \right - include starred version in MATHENV - apply +# - flatten recursively and --flatten expansion is now +# aware of comments (thanks to Tim Connors for patch) +# - Change to post-processing for more reliability for +# deleted math environments +# - On linux systems, recognise and remove DOS style newlines +# - Provide markup for some special preamble commands (\title, +# \author,\date, +# - configurable by setting context2cmd +# - for styles using ulem package, remove \emph and \text.. from list of +# safe commands in order to allow linebreaks within the +# highlighted sections. +# - for ulem style, now show citations by enclosing them in \mbox commands. +# This unfortunately implies linebreaks within citations no longer function, +# so this functionality can be turned off (Option --disable-citation-markup). +# With --enable-citation-markup, the mbox markup is forced for other styles) +# - new substyle COLOR. This is particularly useful for marking up citations +# and some special post-processing is implemented to retain cite +# commands in deleted blocks. +# - four different levels of math-markup +# - Option --driver for choosing driver for modes employing changebar package +# - accept \\* as valid command (and other commands of form \.*). Also accept +# \<nl> (backslashed newline) +# - some typo fixes, include commands defined in preamble as safe commands +# (Sebastian Gouezel) +# - include compared filenames as comments as line 2 and 3 of +# the preamble (can be modified with option --label, and suppressed with +# --no-label), option --visible-label to show files in generated pdf or dvi +# at the beginning of main document +# +# Version 0.5 A number of minor improvements based on feedback +# Deleted blocks are now shown before added blocks +# Package specific processing +# +# Version 0.43 unreleased typo in list of styles at the end +# Add protect to all \cbstart, \cbend commands +# More robust substitution of deleted math commands +# +# Version 0.42 November 06 Bug fixes only +# +# Version 0.4 March 06 option for fast differencing using UNIX diff command, several minor bug fixes (\par bug, improved highlighting of textcmds) +# +# Version 0.3 August 05 improved parsing of displayed math, --allow-spaces +# option, several minor bug fixes +# +# Version 0.25 October 04 Fix bug with deleted equations, add math mode commands to safecmd, add | to allowed interpunctuation signs +# Version 0.2 September 04 extension to utf-8 and variable encodings +# Version 0.1 August 04 First public release + +# Inserted block for differenceing +# use Algorithm::Diff qw(traverse_sequences); +# in standard version +# The following BEGIN block contains a verbatim copy of +# Ned Konz' Algorithm::Diff package version 1.15 except +# that subroutine _longestCommonSubsequence has been replace by +# a routine which internally uses the UNIX diff command for +# the differencing rather than the Perl routines if the +# length of the sequences exceeds some threshold. +# Also, all POD documentation has been stripped out. +# +# (the distribution on which this modification is based is available +# from http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15 +# the most recent version can be found via http://search.cpan.org/search?module=Algorithm::Diff ) +# Please note that the LICENCSE for Algorithm::Diff : +# "Copyright (c) 2000-2002 Ned Konz. All rights reserved. +# This program is free software; +# you can redistribute it and/or modify it under the same terms +# as Perl itself." +# The fast-differencing version of latexdiff is provided as a convenience +# for latex users under Unix-like systems which have a 'diff' command. +# If you believe +# the inlining of Algorithm::Diff violates its license please contact +# me and I will modify the latexdiff distribution accordingly. +# Frederik Tilmann (tilmann@esc.cam.ac.uk) +# Jonathan Paisley is acknowledged for the idea of using the system diff +# command to achieve shorter running times +BEGIN { +package Algorithm::Diff; +use strict; +use vars qw($VERSION @EXPORT_OK @ISA @EXPORT); +use integer; # see below in _replaceNextLargerWith() for mod to make + # if you don't use this +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(); +@EXPORT_OK = qw(LCS diff traverse_sequences traverse_balanced sdiff); +$VERSION = sprintf('%d.%02d fast', (q$Revision: 1.15 $ =~ /\d+/g)); + +# Global parameters + +use File::Temp qw/tempfile/; +# if larger number of elements in longestCommonSubsequence smaller than +# this number, then use internal algorithm, otherwise use UNIX diff +use constant THRESHOLD => 100 ; +# Detect whether diff --minimal option is available +# if yes we use it +use constant MINIMAL => ( system('diff','--minimal','/dev/null','/dev/null') >> 8 ==0 ? "--minimal" : "" ) ; + + + +# McIlroy-Hunt diff algorithm +# Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com> +# by Ned Konz, perl@bike-nomad.com + + +# Create a hash that maps each element of $aCollection to the set of positions +# it occupies in $aCollection, restricted to the elements within the range of +# indexes specified by $start and $end. +# The fourth parameter is a subroutine reference that will be called to +# generate a string to use as a key. +# Additional parameters, if any, will be passed to this subroutine. +# +# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); + +sub _withPositionsOfInInterval +{ + my $aCollection = shift; # array ref + my $start = shift; + my $end = shift; + my $keyGen = shift; + my %d; + my $index; + for ( $index = $start ; $index <= $end ; $index++ ) + { + my $element = $aCollection->[$index]; + my $key = &$keyGen( $element, @_ ); + if ( exists( $d{$key} ) ) + { + unshift ( @{ $d{$key} }, $index ); + } + else + { + $d{$key} = [$index]; + } + } + return wantarray ? %d : \%d; +} + +# Find the place at which aValue would normally be inserted into the array. If +# that place is already occupied by aValue, do nothing, and return undef. If +# the place does not exist (i.e., it is off the end of the array), add it to +# the end, otherwise replace the element at that point with aValue. +# It is assumed that the array's values are numeric. +# This is where the bulk (75%) of the time is spent in this module, so try to +# make it fast! + +sub _replaceNextLargerWith +{ + my ( $array, $aValue, $high ) = @_; + $high ||= $#$array; + + # off the end? + if ( $high == -1 || $aValue > $array->[-1] ) + { + push ( @$array, $aValue ); + return $high + 1; + } + + # binary search for insertion point... + my $low = 0; + my $index; + my $found; + while ( $low <= $high ) + { + $index = ( $high + $low ) / 2; + + # $index = int(( $high + $low ) / 2); # without 'use integer' + $found = $array->[$index]; + + if ( $aValue == $found ) + { + return undef; + } + elsif ( $aValue > $found ) + { + $low = $index + 1; + } + else + { + $high = $index - 1; + } + } + + # now insertion point is in $low. + $array->[$low] = $aValue; # overwrite next larger + return $low; +} + +# This method computes the longest common subsequence in $a and $b. + +# Result is array or ref, whose contents is such that +# $a->[ $i ] == $b->[ $result[ $i ] ] +# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. + +# An additional argument may be passed; this is a hash or key generating +# function that should return a string that uniquely identifies the given +# element. It should be the case that if the key is the same, the elements +# will compare the same. If this parameter is undef or missing, the key +# will be the element as a string. + +# By default, comparisons will use "eq" and elements will be turned into keys +# using the default stringizing operator '""'. + +# Additional parameters, if any, will be passed to the key generation routine. + +sub _longestCommonSubsequence +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $keyGen = shift; # code ref + my $compare; # code ref + + # set up code refs + # Note that these are optimized. + if ( !defined($keyGen) ) # optimize for strings + { + $keyGen = sub { $_[0] }; + $compare = sub { my ( $a, $b ) = @_; $a eq $b }; + } + else + { + $compare = sub { + my $a = shift; + my $b = shift; + &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); + }; + } + + my ( $aStart, $aFinish, $bStart, $bFinish, $matchVector ) = + ( 0, $#$a, 0, $#$b, [] ); + + # Check whether to use internal routine (small number of elements) + # or use it as a wrapper for UNIX diff + if ( ( $#$a > $#$b ? $#$a : $#$b) < THRESHOLD ) { + ### print STDERR "DEBUG: regular longestCommonSubsequence\n"; + # First we prune off any common elements at the beginning + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) + { + $matchVector->[ $aStart++ ] = $bStart++; + } + + # now the end + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) + { + $matchVector->[ $aFinish-- ] = $bFinish--; + } + + # Now compute the equivalence classes of positions of elements + my $bMatches = + _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); + my $thresh = []; + my $links = []; + + my ( $i, $ai, $j, $k ); + for ( $i = $aStart ; $i <= $aFinish ; $i++ ) + { + $ai = &$keyGen( $a->[$i], @_ ); + if ( exists( $bMatches->{$ai} ) ) + { + $k = 0; + for $j ( @{ $bMatches->{$ai} } ) + { + + # optimization: most of the time this will be true + if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) + { + $thresh->[$k] = $j; + } + else + { + $k = _replaceNextLargerWith( $thresh, $j, $k ); + } + + # oddly, it's faster to always test this (CPU cache?). + if ( defined($k) ) + { + $links->[$k] = + [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; + } + } + } + } + + if (@$thresh) + { + for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) + { + $matchVector->[ $link->[1] ] = $link->[2]; + } + } + } + else { + my ($fha,$fhb,$fna,$fnb,$ele,$key); + my ($alines,$blines,$alb,$alf,$blb,$blf); + my ($minimal)=MINIMAL; + # large number of elements, use system diff + ### print STDERR "DEBUG: fast (diff) longestCommonSubsequence\n"; + + ($fha,$fna)=tempfile("DiffA-XXXX") or die "_longestCommonSubsequence: Cannot open tempfile for sequence A"; + ($fhb,$fnb)=tempfile("DiffB-XXXX") or die "_longestCommonSubsequence: Cannot open tempfile for sequence B"; + # prepare sequence A + foreach $ele ( @$a ) { + $key=&$keyGen( $ele, @_ ); + $key =~ s/\\/\\\\/g ; + $key =~ s/\n/\\n/sg ; + print $fha "$key\n" ; + } + close($fha); + # prepare sequence B + foreach $ele ( @$b ) { + $key=&$keyGen( $ele, @_ ); + $key =~ s/\\/\\\\/g ; + $key =~ s/\n/\\n/sg ; + print $fhb "$key\n" ; + } + close($fhb); + + open(DIFFPIPE, "diff $minimal $fna $fnb |") or die "_longestCommonSubsequence: Cannot launch diff process. $!" ; + # The diff line numbering begins with 1, but Perl subscripts start with 0 + # We follow the diff numbering but substract 1 when assigning to matchVector + $aStart++; $bStart++ ; $aFinish++ ; $bFinish++ ; + while( <DIFFPIPE> ) { + if ( ($alines,$blines) = ( m/^(\d*(?:,\d*)?)?c(\d*(?:,\d*)?)?$/ ) ) { + ($alb,$alf)=split(/,/,$alines); + ($blb,$blf)=split(/,/,$blines); + $alf=$alb unless defined($alf); + $blf=$blb unless defined($blf); + while($aStart < $alb ) { + $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; + } + # check for consistency + $bStart==$blb or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in changed sequence"; + $aStart=$alf+1; + $bStart=$blf+1; + } + elsif ( ($alb,$blines) = ( m/^(\d*)a(\d*(?:,\d*)?)$/ ) ) { + ($blb,$blf)=split(/,/,$blines); + $blf=$blb unless defined($blf); + while ( $bStart < $blb ) { + $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; + } + $aStart==$alb+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in appended sequence near elements $aStart and $bStart"; + $bStart=$blf+1; + } + elsif ( ($alines,$blb) = ( m/^(\d*(?:,\d*)?)d(\d*)$/ ) ) { + ($alb,$alf)=split(/,/,$alines); + $alf=$alb unless defined($alf); + while ( $aStart < $alb ) { + $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; + } + $bStart==$blb+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in deleted sequence near elements $aStart and $bStart"; + $aStart=$alf+1; + } + elsif ( m/^Binary files/ ) { + # if diff reports it is a binary file force --text mode. I do not like + # to always use this option because it is probably only available in GNU diff + open(DIFFPIPE, "diff --text $fna $fnb |") or die "Cannot launch diff process. $!" ; + } + # Default: just skip line + } + while ($aStart <= $aFinish ) { + $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; + } + $bStart==$bFinish+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency at end"; + close DIFFPIPE; + # check whether a system error has occurred or return status is greater than or equal to 5 + if ( $! || ($? >> 8) > 5) { + print STDERR "diff process failed with exit code ", ($? >> 8), " $!\n"; + die; + } + unlink $fna,$fnb ; + } + return wantarray ? @$matchVector : $matchVector; +} + +sub traverse_sequences +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $finishedACallback = $callbacks->{'A_FINISHED'}; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $finishedBCallback = $callbacks->{'B_FINISHED'}; + my $matchVector = _longestCommonSubsequence( $a, $b, $keyGen, @_ ); + + # Process all the lines in @$matchVector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai; + + for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) + { + my $bLine = $matchVector->[$ai]; + if ( defined($bLine) ) # matched + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; + &$matchCallback( $ai, $bi++, @_ ); + } + else + { + &$discardACallback( $ai, $bi, @_ ); + } + } + + # The last entry (if any) processed was a match. + # $ai and $bi point just past the last matching lines in their sequences. + + while ( $ai <= $lastA or $bi <= $lastB ) + { + + # last A? + if ( $ai == $lastA + 1 and $bi <= $lastB ) + { + if ( defined($finishedACallback) ) + { + &$finishedACallback( $lastA, @_ ); + $finishedACallback = undef; + } + else + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; + } + } + + # last B? + if ( $bi == $lastB + 1 and $ai <= $lastA ) + { + if ( defined($finishedBCallback) ) + { + &$finishedBCallback( $lastB, @_ ); + $finishedBCallback = undef; + } + else + { + &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; + } + } + + &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; + &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; + } + + return 1; +} + +sub traverse_balanced +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $changeCallback = $callbacks->{'CHANGE'}; + my $matchVector = _longestCommonSubsequence( $a, $b, $keyGen, @_ ); + + # Process all the lines in match vector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai = 0; + my $ma = -1; + my $mb; + + while (1) + { + + # Find next match indices $ma and $mb + do { $ma++ } while ( $ma <= $#$matchVector && !defined $matchVector->[$ma] ); + + last if $ma > $#$matchVector; # end of matchVector? + $mb = $matchVector->[$ma]; + + # Proceed with discard a/b or change events until + # next match + while ( $ai < $ma || $bi < $mb ) + { + + if ( $ai < $ma && $bi < $mb ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai < $ma ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi < $mb + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + # Match + &$matchCallback( $ai++, $bi++, @_ ); + } + + while ( $ai <= $lastA || $bi <= $lastB ) + { + if ( $ai <= $lastA && $bi <= $lastB ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai <= $lastA ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi <= $lastB + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + return 1; +} + +sub LCS +{ + my $a = shift; # array ref + my $matchVector = _longestCommonSubsequence( $a, @_ ); + my @retval; + my $i; + for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) + { + if ( defined( $matchVector->[$i] ) ) + { + push ( @retval, $a->[$i] ); + } + } + return wantarray ? @retval : \@retval; +} + +sub diff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $hunk = []; + my $discard = sub { push ( @$hunk, [ '-', $_[0], $a->[ $_[0] ] ] ) }; + my $add = sub { push ( @$hunk, [ '+', $_[1], $b->[ $_[1] ] ] ) }; + my $match = sub { push ( @$retval, $hunk ) if scalar(@$hunk); $hunk = [] }; + traverse_sequences( $a, $b, + { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); + &$match(); + return wantarray ? @$retval : $retval; +} + +sub sdiff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; + my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; + my $change = sub { + push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + my $match = sub { + push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + traverse_balanced( + $a, + $b, + { + MATCH => $match, + DISCARD_A => $discard, + DISCARD_B => $add, + CHANGE => $change, + }, + @_ + ); + return wantarray ? @$retval : $retval; +} + +1; +} +import Algorithm::Diff qw(traverse_sequences); +# End of inserted block for stand-alone version + + +use Getopt::Long ; +use strict ; +use warnings; +use utf8 ; + +my ($algodiffversion)=split(/ /,$Algorithm::Diff::VERSION); + + +my ($versionstring)=<<EOF ; +This is LATEXDIFF 1.0.2 (Algorithm::Diff $Algorithm::Diff::VERSION, Perl $^V) + (c) 2004-2012 F J Tilmann +EOF + +# Configuration variables: these have to be visible from the subroutines +my $MINWORDSBLOCK=3; # minimum number of tokens to form an independent block + # shorter identical blocks will be merged to the previous word +my $FLOATENV='(?:figure|table|plate)[\w\d*@]*' ; # Environments in which FL variants of defined commands are used +my $PICTUREENV='(?:picture|DIFnomarkup)[\w\d*@]*' ; # Environments in which all change markup is removed +my $MATHENV='(?:equation[*]?|displaymath|DOLLARDOLLAR)[*]?' ; # Environments turning on display math mode (code also knows about \[ and \]) +my $MATHREPL='displaymath'; # Environment introducing deleted maths blocks +my $MATHARRENV='(?:eqnarray|align|alignat|gather|multline|flalign)[*]?' ; # Environments turning on eqnarray math mode +my $MATHARRREPL='eqnarray*'; # Environment introducing deleted maths blocks +my $ARRENV='(?:array|[pbvBV]matrix)'; # Environments making arrays in math mode. The underlining style does not cope well with those - as a result in-text math environments are surrounded by \mbox{ } if any of these commands is used in an inline math block +my $COUNTERCMD='(?:footnote|part|chapter|section|subsection|subsubsection|paragraph|subparagraph)'; # textcmds which are associated with a counter + # If any of these commands occur in a deleted block + # they will be succeeded by an \addtocounter{...}{-1} + # for the associated counter such that the overall numbers + # should be the same as in the new file +my $CITECMD=0 ; # \cite-type commands which need to be protected within an mbox in UNDERLINE and other modes using ulem; pattern simply designed to never match; will be overwritten later for selected styles +my $CITE2CMD=0; # \cite-type commands which should be reinstated in deleted blocks + + + +# Markup strings +# If at all possible, do not change these as parts of the program +# depend on the actual name (particularly post-processing) +# At the very least adapt subroutine postprocess to new tokens. +my $ADDMARKOPEN='\DIFaddbegin '; # Token to mark begin of appended text +my $ADDMARKCLOSE='\DIFaddend '; # Token to mark end of appended text +my $ADDOPEN='\DIFadd{'; # To mark begin of added text passage +my $ADDCLOSE='}'; # To mark end of added text passage +my $ADDCOMMENT='DIF > '; # To mark added comment line +my $DELMARKOPEN='\DIFdelbegin '; # Token to mark begin of deleted text +my $DELMARKCLOSE='\DIFdelend '; # Token to mark end of deleted text +my $DELOPEN='\DIFdel{'; # To mark begin of deleted text passage +my $DELCLOSE='}'; # To mark end of deleted text passage +my $DELCMDOPEN='%DIFDELCMD < '; # To mark begin of deleted commands (must begin with %, i.e., be a comment +my $DELCMDCLOSE="%%%\n"; # To mark end of deleted commands (must end with a new line) +my $AUXCMD='%DIFAUXCMD' ; # follows auxiliary commands put in by latexdiff to make difference file legal + # auxiliary commands must be on a line of their own +my $DELCOMMENT='DIF < '; # To mark deleted comment line + + +# main local variables: +my @TEXTCMDLIST=(); # array containing patterns of commands with text arguments +my @TEXTCMDEXCL=(); # array containing patterns of commands without text arguments (if a pattern + # matches both TEXTCMDLIST and TEXTCMDEXCL it is excluded) +my @CONTEXT1CMDLIST=(); # array containing patterns of commands with text arguments (subset of text commands), + # but which cause confusion if used out of context (e.g. \caption). + # In deleted passages, the command will be disabled but its argument is marked up + # Otherwise they behave exactly like TEXTCMD's +my @CONTEXT1CMDEXCL=(); # exclude list for above, but always empty +my @CONTEXT2CMDLIST=(); # array containing patterns of commands with text arguments, but which fail or cause confusion + # if used out of context (e.g. \title). They and their arguments will be disabled in deleted + # passages +my @CONTEXT2CMDEXCL=(); # exclude list for above, but always empty +my @MATHTEXTCMDLIST=(); # treat like textcmd. If a textcmd is in deleted or added block, just wrap the + # whole content with \DIFadd or \DIFdel irrespective of content. This functionality + # is useful for pseudo commands \MATHBLOCK.. into which math environments are being + # transformed +my @MATHTEXTCMDEXCL=(); # + +# Note I need to declare this with "our" instead of "my" because later in the code I have to "local"ise these +our @SAFECMDLIST=(); # array containing patterns of safe commands (which do not break when in the argument of DIFadd or DIFDEL) +our @SAFECMDEXCL=(); + +my ($i,$j,$l); +my ($old,$new); +my ($line); +my ($newpreamble,$oldpreamble); +my (@newpreamble,@oldpreamble,@diffpreamble,@diffbody); +my ($latexdiffpreamble); +my ($oldbody, $newbody, $diffbo); +my ($oldpost, $newpost); +my ($diffall); +# Option names +my ($type,$subtype,$floattype,$config,$preamblefile,$encoding,$nolabel,$visiblelabel, + $showpreamble,$showsafe,$showtext,$showconfig,$showall, + $replacesafe,$appendsafe,$excludesafe, + $replacetext,$appendtext,$excludetext, + $replacecontext1,$appendcontext1, + $replacecontext2,$appendcontext2, + $help,$verbose,$driver,$version,$ignorewarnings, + $enablecitmark,$disablecitmark,$allowspaces,$flatten,$debug); ###$disablemathmark, +# MNEMNONICS for mathmarkup +my $mathmarkup; +use constant { + OFF => 0, + WHOLE => 1, + COARSE => 2, + FINE => 3 +}; + + +my (@configlist,@labels, + @appendsafelist,@excludesafelist, + @appendtextlist,@excludetextlist, + @appendcontext1list,@appendcontext2list, + @packagelist); +my ($assign,@config); +# Hash where keys corresponds to the names of all included packages (including the documentclass as another package +# the optional arguments to the package are the values of the hash elements +my ($pkg,%packages); +# Defaults +$type='UNDERLINE'; +$subtype='SAFE'; +$floattype='FLOATSAFE'; +$mathmarkup=COARSE; + +$verbose=0; +# output debug and intermediate files, set to 0 in final distribution +$debug=0; +# define character properties +sub IsNonAsciiPunct { return <<'END' # Unicode punctuation but excluding ASCII punctuation ++utf8::IsPunct +-utf8::IsASCII +END +} +sub IsNonAsciiS { return <<'END' # Unicode symbol but excluding ASCII ++utf8::IsS +-utf8::IsASCII +END +} + + +my %verbhash; + +Getopt::Long::Configure('bundling'); +GetOptions('type|t=s' => \$type, + 'subtype|s=s' => \$subtype, + 'floattype|f=s' => \$floattype, + 'config|c=s' => \@configlist, + 'preamble|p=s' => \$preamblefile, + 'encoding|e=s' => \$encoding, + 'label|L=s' => \@labels, + 'no-label' => \$nolabel, + 'visible-label' => \$visiblelabel, + 'exclude-safecmd|A=s' => \@excludesafelist, + 'replace-safecmd=s' => \$replacesafe, + 'append-safecmd|a=s' => \@appendsafelist, + 'exclude-textcmd|X=s' => \@excludetextlist, + 'replace-textcmd=s' => \$replacetext, + 'append-textcmd|x=s' => \@appendtextlist, + 'replace-context1cmd=s' => \$replacecontext1, + 'append-context1cmd=s' => \@appendcontext1list, + 'replace-context2cmd=s' => \$replacecontext2, + 'append-context2cmd=s' => \@appendcontext2list, + 'show-preamble' => \$showpreamble, + 'show-safecmd' => \$showsafe, + 'show-textcmd' => \$showtext, + 'show-config' => \$showconfig, + 'show-all' => \$showall, + 'packages=s' => \@packagelist, + 'allow-spaces' => \$allowspaces, + 'math-markup=s' => \$mathmarkup, + 'enable-citation-markup' => \$enablecitmark, + 'disable-citation-markup' => \$disablecitmark, + 'verbose|V' => \$verbose, + 'ignore-warnings' => \$ignorewarnings, + 'driver=s'=> \$driver, + 'flatten' => \$flatten, + 'version' => \$version, + 'help|h|H' => \$help); + +if ( $help ) { + usage() ; +} + + +if ( $version ) { + die $versionstring ; +} + +print STDERR $versionstring if $verbose; + +if (defined($showall)){ + $showpreamble=$showsafe=$showtext=$showconfig=1; +} + +if (defined($mathmarkup)) { + $mathmarkup=~tr/a-z/A-Z/; + if ( $mathmarkup eq 'OFF' ){ + $mathmarkup=OFF; + } elsif ( $mathmarkup eq 'WHOLE' ){ + $mathmarkup=WHOLE; + } elsif ( $mathmarkup eq 'COARSE' ){ + $mathmarkup=COARSE; + } elsif ( $mathmarkup eq 'FINE' ){ + $mathmarkup=FINE; + } elsif ( $mathmarkup !~ m/^[0123]$/ ) { + die "Illegal value: ($mathmarkup) for option--math-markup. Possible values: OFF,WHOLE,COARSE,FINE,0- "; + } + # else use numerical value +} + +# setting extra preamble commands +if (defined($preamblefile)) { + $latexdiffpreamble=join "\n",(extrapream($preamblefile),""); +} else { + $latexdiffpreamble=join "\n",(extrapream($type,$subtype,$floattype),""); +} + +if ( defined($driver) ) { + # for changebar only + $latexdiffpreamble=~s/\[dvips\]/[$driver]/sg; +} +# setting up @SAFECMDLIST and @SAFECMDEXCL +if (defined($replacesafe)) { + init_regex_arr_ext(\@SAFECMDLIST,$replacesafe); +} else { + init_regex_arr_data(\@SAFECMDLIST, "SAFE COMMANDS"); +} +foreach $appendsafe ( @appendsafelist ) { + init_regex_arr_ext(\@SAFECMDLIST, $appendsafe); +} +foreach $excludesafe ( @excludesafelist ) { + init_regex_arr_ext(\@SAFECMDEXCL, $excludesafe); +} + +# Special: treat all cite commands as safe except in UNDERLINE and FONTSTRIKE mode +# (there is a conflict between citation and ulem package, see +# package documentation) +# Use post-processing + +if ( uc($type) ne "UNDERLINE" && uc($type) ne "FONTSTRIKE" && uc($type) ne "CULINECHBAR" ) { + push (@SAFECMDLIST, qr/^cite.*$/); +} else { + ### Experimental: disable text and emph commands + push (@SAFECMDLIST, qr/^cite.*$/) unless $disablecitmark; + push(@SAFECMDEXCL, qr/^emph$/, qr/^text..$/); + # replace \cite{..} by \mbox{\cite{..}} in added or deleted blocks in post-processing + if ( uc($subtype) eq "COLOR" or uc($subtype) eq "DVIPSCOL" ) { + # remove \cite command again from list of safe commands + pop @SAFECMDLIST; + # deleted cite commands + $CITE2CMD='(?:cite\w*|nocite)' unless $disablecitmark ; # \cite-type commands which should be reinstated in deleted blocks + } else { + $CITECMD='(?:cite\w*|nocite)' unless $disablecitmark ; # \cite commands which need to be protected within an mbox in UNDERLINE and other modes using ulem + } +} +$CITECMD='(?:cite\w*|nocite)' if $enablecitmark ; # as above for explicit selection + +# setting up @TEXTCMDLIST and @TEXTCMDEXCL +if (defined($replacetext)) { + init_regex_arr_ext(\@TEXTCMDLIST,$replacetext); +} else { + init_regex_arr_data(\@TEXTCMDLIST, "TEXT COMMANDS"); +} +foreach $appendtext ( @appendtextlist ) { + init_regex_arr_ext(\@TEXTCMDLIST, $appendtext); +} +foreach $excludetext ( @excludetextlist ) { + init_regex_arr_ext(\@TEXTCMDEXCL, $excludetext); +} + + +# setting up @CONTEXT1CMDLIST ( @CONTEXT1CMDEXCL exist but is always empty ) +if (defined($replacecontext1)) { + init_regex_arr_ext(\@CONTEXT1CMDLIST,$replacecontext1); +} else { + init_regex_arr_data(\@CONTEXT1CMDLIST, "CONTEXT1 COMMANDS"); +} +foreach $appendcontext1 ( @appendcontext1list ) { + init_regex_arr_ext(\@CONTEXT1CMDLIST, $appendcontext1); +} + + +# setting up @CONTEXT2CMDLIST ( @CONTEXT2CMDEXCL exist but is always empty ) +if (defined($replacecontext2)) { + init_regex_arr_ext(\@CONTEXT2CMDLIST,$replacecontext2); +} else { + init_regex_arr_data(\@CONTEXT2CMDLIST, "CONTEXT2 COMMANDS"); +} +foreach $appendcontext2 ( @appendcontext2list ) { + init_regex_arr_ext(\@CONTEXT2CMDLIST, $appendcontext2); +} + +# setting configuration variables +@config=(); +foreach $config ( @configlist ) { + if (-f $config ) { + open(FILE,$config) or die ("Couldn't open configuration file $config: $!"); + while (<FILE>) { + chomp; + next if /^\s*#/ || /^\s*%/ || /^\s*$/ ; + push (@config,$_); + } + close(FILE); + } + else { +# foreach ( split(",",$config) ) { +# push @config,$_; +# } + push @config,split(",",$config) + } +} +foreach $assign ( @config ) { + $assign=~ m/\s*(\w*)\s*=\s*(\S*)\s*$/ or die "Illegal assignment $assign in configuration list (must be variable=value)"; + if ( $1 eq "MINWORDSBLOCK" ) { $MINWORDSBLOCK = $2; } + elsif ( $1 eq "FLOATENV" ) { $FLOATENV = $2 ; } + elsif ( $1 eq "PICTUREENV" ) { $PICTUREENV = $2 ; } + elsif ( $1 eq "MATHENV" ) { $MATHENV = $2 ; } + elsif ( $1 eq "MATHREPL" ) { $MATHREPL = $2 ; } + elsif ( $1 eq "MATHARRENV" ) { $MATHARRENV = $2 ; } + elsif ( $1 eq "MATHARRREPL" ) { $MATHARRREPL = $2 ; } + elsif ( $1 eq "ARRENV" ) { $ARRENV = $2 ; } + elsif ( $1 eq "COUNTERCMD" ) { $COUNTERCMD = $2 ; } + else { die "Unknown variable $1 in assignment.";} +} + +if ( $mathmarkup == COARSE || $mathmarkup == WHOLE ) { + push(@MATHTEXTCMDLIST,qr/^MATHBLOCK(?:$MATHENV|$MATHARRENV|SQUAREBRACKET)$/); +} + + + +foreach $pkg ( @packagelist ) { + map { $packages{$_}="" } split(/,/,$pkg) ; +} + +if ($showpreamble) { + print "\nPreamble commands:\n"; + print $latexdiffpreamble ; +} + +if ($showsafe) { + print "\nCommands safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE (unless excluded):\n"; + print_regex_arr(@SAFECMDLIST); + print "\nCommands not safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE :\n"; + print_regex_arr(@SAFECMDEXCL); +} + +if ($showtext) { + print "\nCommands with last argument textual (unless excluded) and safe in every context:\n"; + print_regex_arr(@TEXTCMDLIST); + print "\nContext1 commands (last argument textual, command will be disabled in deleted passages, last argument will be shown as plain text):\n"; + print_regex_arr(@CONTEXT1CMDLIST); + print "\nContext2 commands (last argument textual, command and its argument will be disabled in deleted passages):\n"; + print_regex_arr(@CONTEXT2CMDLIST); + print "\nExclude list of Commands with last argument not textual (overrides patterns above):\n"; + print_regex_arr(@TEXTCMDEXCL); +} + + +if ($showconfig) { + print "Configuration variables:\n"; + print "MINWORDSBLOCK=$MINWORDSBLOCK\n"; + print "FLOATENV=$FLOATENV\n"; + print "PICTUREENV=$PICTUREENV\n"; + print "MATHENV=$MATHENV\n"; + print "MATHREPL=$MATHREPL\n"; + print "MATHARRENV=$MATHARRENV\n"; + print "MATHARRREPL=$MATHARRREPL\n"; + print "ARRENV=$ARRENV\n"; + print "COUNTERCMD=$COUNTERCMD\n"; +} +if ($showconfig || $showtext || $showsafe || $showpreamble) { + exit 0; } +if ( @ARGV != 2 ) { + print STDERR "2 and only 2 non-option arguments required. Write latexdiff -h to get help\n"; + exit(2); +} + +# Are extra spaces between command arguments permissible? +my $extraspace; +if ($allowspaces) { + $extraspace='\s*'; +} else { + $extraspace=''; +} + +# append context lists to text lists (as text property is implied) +push @TEXTCMDLIST, @CONTEXT1CMDLIST; +push @TEXTCMDLIST, @CONTEXT2CMDLIST; + +push @TEXTCMDLIST, @MATHTEXTCMDLIST if $mathmarkup==COARSE; + +# internal additions to SAFECMDLIST +push(@SAFECMDLIST, qr/^QLEFTBRACE$/, qr/^QRIGHTBRACE$/); + + +# Patterns. These are used by some of the subroutines, too +# I can only define them down here because value of extraspace depends on an option + my $pat0 = '(?:[^{}])*'; + my $pat1 = '(?:[^{}]|\{'.$pat0.'\})*'; + my $pat2 = '(?:[^{}]|\{'.$pat1.'\})*'; + my $pat3 = '(?:[^{}]|\{'.$pat2.'\})*'; + my $pat4 = '(?:[^{}]|\{'.$pat3.'\})*'; + my $pat5 = '(?:[^{}]|\{'.$pat4.'\})*'; + my $pat6 = '(?:[^{}]|\{'.$pat5.'\})*'; + my $brat0 = '(?:[^\[\]]|\\\[|\\\])*'; + + my $quotemarks = '(?:\'\')|(?:\`\`)'; + my $punct='[0.,\/\'\`:;\"\?\(\)\[\]!~\p{IsNonAsciiPunct}\p{IsNonAsciiS}]'; + my $number='-?\d*\.\d*'; + my $mathpunct='[+=<>\-\|]'; + my $and = '&'; + my $coords= '[\-.,\s\d]*'; +# word: sequence of letters or accents followed by letter + my $word='(?:[-\w\d*]|\\\\[\"\'\`~^][A-Za-z\*])+'; + my $cmdleftright='\\\\(?:left|right|[Bb]igg?[lrm]?|middle)\s*(?:[()\[\]|]|\\\\(?:[|{}]|\w+))'; + + my $cmdoptseq='\\\\[\w\d\*]+'.$extraspace.'(?:(?:\['.$brat0.'\]|\{'. $pat6 . '\}|\(' . $coords .'\))'.$extraspace.')*'; + my $backslashnl='\\\\\n'; + my $oneletcmd='\\\\.\*?(?:\['.$brat0.'\]|\{'. $pat6 . '\})*'; + my $math='\$(?:[^$]|\\\$)*?\$|\\\\[(].*?\\\\[)]'; +## the current maths command cannot cope with newline within the math expression + + my $comment='%.*?\n'; + my $pat=qr/(?:\A\s*)?(?:${and}|${quotemarks}|${number}|${word}|$cmdleftright|${cmdoptseq}|${math}|${backslashnl}|${oneletcmd}|${comment}|${punct}|${mathpunct}|\{|\})\s*/ ; + + + +# now we are done setting up and can start working +my ($oldfile, $newfile) = @ARGV; +# check for existence of input files +if ( ! -e $oldfile ) { + die "Input file $oldfile does not exist."; +} +if ( ! -e $newfile ) { + die "Input file $newfile does not exist."; +} + + +# set the labels to be included into the file +my ($oldtime,$newtime,$oldlabel,$newlabel); +if (defined($labels[0])) { + $oldlabel=$labels[0] ; +} else { + $oldtime=localtime((stat($oldfile))[9]); + $oldlabel="$oldfile " . " "x(length($newfile)-length($oldfile)) . $oldtime; +} +if (defined($labels[1])) { + $newlabel=$labels[1] ; +} else { + $newtime=localtime((stat($newfile))[9]); + $newlabel="$newfile " . " "x(length($oldfile)-length($newfile)) . $newtime; +} + +$encoding=guess_encoding($newfile) unless defined($encoding); + +$encoding = "utf8" if $encoding =~ m/^utf8/i ; +if (lc($encoding) eq "utf8" ) { + binmode(STDOUT, ":utf8"); + binmode(STDERR, ":utf8"); +} + +$old=read_file_with_encoding($oldfile,$encoding); +$new=read_file_with_encoding($newfile,$encoding); + + + + +# reset time +exetime(1); +($oldpreamble,$oldbody,$oldpost)=splitdoc($old,'\\\\begin\{document\}','\\\\end\{document\}'); + + +($newpreamble,$newbody,$newpost)=splitdoc($new,'\\\\begin\{document\}','\\\\end\{document\}'); + + +if ($flatten) { + $oldbody=flatten($oldbody,$oldpreamble,$oldfile,$encoding); + $newbody=flatten($newbody,$newpreamble,$newfile,$encoding); +} + +my @auxlines; +if ( length $oldpreamble && length $newpreamble ) { + # pre-process preamble by looking for commands used in \maketitle (title, author, date etc commands) + # and marking up content with latexdiff markup + @auxlines=preprocess_preamble($oldpreamble,$newpreamble); + + @oldpreamble = split /\n/, $oldpreamble; + @newpreamble = split /\n/, $newpreamble; + + # If a command is defined in the preamble of the new file, and only uses safe commands, then it can be considered to be safe) (contribution S. Gouezel) + # Base this assessment on the new preamble + add_safe_commands($newpreamble); + + %packages=list_packages(@newpreamble) unless %packages; + if (defined $packages{"hyperref"} ) { + print STDERR "hyperref package detected.\n" if $verbose ; + $latexdiffpreamble =~ s/\{\\DIFadd\}/{\\DIFaddtex}/g; + $latexdiffpreamble =~ s/\{\\DIFdel\}/{\\DIFdeltex}/g; + $latexdiffpreamble .= join "\n",(extrapream("HYPERREF"),""); + } + print STDERR "Differencing preamble.\n" if $verbose; + + # insert dummy first line such that line count begins with line 1 (rather than perl's line 0) - just so that line numbers inserted by linediff are correct + unshift @newpreamble,''; + unshift @oldpreamble,''; + @diffpreamble = linediff(\@oldpreamble, \@newpreamble); + # remove dummy line again + shift @diffpreamble; + # add filenames, modification time and latexdiff mark + defined($nolabel) or splice @diffpreamble,1,0, + "%DIF LATEXDIFF DIFFERENCE FILE", + ,"%DIF DEL $oldlabel", + "%DIF ADD $newlabel"; + if ( @auxlines ) { + push @diffpreamble,"%DIF DELETED TITLE COMMANDS FOR MARKUP"; + push @diffpreamble,join("\n",@auxlines); + } + push @diffpreamble,$latexdiffpreamble; + push @diffpreamble,'\begin{document}'; +} +elsif ( !length $oldpreamble && !length $newpreamble ) { + @diffpreamble=(); +} else { + print STDERR "Either both texts must have preamble or neither text must have the preamble.\n"; + exit(2); +} + +if (defined $packages{"amsmath"} or defined $packages{"amsart"} or defined $packages{"amsbook"} ) { + print STDERR "amsmath package detected.\n" if $verbose ; + $MATHARRREPL='align*'; +} + +print STDERR "Preprocessing body. " if $verbose; +my ($oldleadin,$newleadin)=preprocess($oldbody,$newbody); + + +# run difference algorithm +@diffbody=bodydiff($oldbody, $newbody); +$diffbo=join("",@diffbody); +if ( $debug ) { + open(RAWDIFF,">","latexdiff.debug.bodydiff"); + print RAWDIFF $diffbo; + close(RAWDIFF); +} +print STDERR "(",exetime()," s)\n","Postprocessing body. \n " if $verbose; +postprocess($diffbo); +$diffall =join("\n",@diffpreamble) ; +# add visible labels +if (defined($visiblelabel)) { + # Give information right after \begin{document} (or at the beginning of the text for files without preamble + ### if \date command is used, add information to \date argument, otherwise give right after \begin{document} + ### $diffall=~s/(\\date$extraspace(?:\[$brat0\])?$extraspace)\{($pat6)\}/$1\{$2 \\ LATEXDIFF comparison \\ Old: $oldlabel \\ New: $newlabel \}/ or + $diffbo = "\\begin{verbatim}LATEXDIFF comparison\nOld: $oldlabel\nNew: $newlabel\\end{verbatim}\n$diffbo" ; +} + +$diffall .= "$newleadin$diffbo" ; +$diffall .= "\\end{document}$newpost" if length $newpreamble ; +if ( lc($encoding) ne "utf8" && lc($encoding) ne "ascii" ) { + print STDERR "Encoding output file to $encoding\n" if $verbose; + $diffall=Encode::encode($encoding,$diffall); + binmode STDOUT; +} +print $diffall; + + +print STDERR "(",exetime()," s)\n","Done.\n" if $verbose; + + + +## guess_encoding(filename) +## reads the first 20 lines of filename and looks for call of inputenc package +## if found, return the option of this package (encoding), otherwise return ascii +sub guess_encoding { + my ($filename)=@_; + my ($i,$enc); + open (FH, $filename) or die("Couldn't open $filename: $!"); + $i=0; + while (<FH>) { + next if /^\s*%/; # skip comment lines + if (m/\\usepackage\[(\w*?)\]\{inputenc\}/) { + close(FH); + return($1); + } + last if (++$i > 20 ); # scan at most 20 non-comment lines + } + close(FH); + return("ascii"); +} + + +sub read_file_with_encoding { + my ($output); + my ($filename, $encoding) = @_; + + if (lc($encoding) eq "utf8" ) { + open (FILE, "<:utf8",$filename) or die("Couldn't open $filename: $!"); + local $/ ; # locally set record operator to undefined, ie. enable whole-file mode + $output=<FILE>; + } elsif ( lc($encoding) eq "ascii") { + open (FILE, $filename) or die("Couldn't open $filename: $!"); + local $/ ; # locally set record operator to undefined, ie. enable whole-file mode + $output=<FILE>; + } else { + require Encode; + open (FILE, "<",$filename) or die("Couldn't open $filename: $!"); + local $/ ; # locally set record operator to undefined, ie. enable whole-file mode + $output=<FILE>; + print STDERR "Converting $filename from $encoding to utf8\n" if $verbose; + $output=Encode::decode($encoding,$output); + } + close FILE; + if ($^O eq "linux" ) { + $output =~ s/\r\n/\n/g ; + } + return $output; +} + +# %packages=list_packages(@preamble) +# scans the arguments for \documentclass and \usepackage statements and constructs a hash +# whose keys are the included packages, and whose values are the associated optional arguments +sub list_packages { + my (@preamble)=@_; + my %packages=(); + foreach $line ( @preamble ) { + # get rid of comments + $line=~s/(?<!\\)%.*$// ; + if ( $line =~ m/\\(?:documentclass|usepackage)(?:\[(.+?)\])?\{(.*?)\}/ ) { +# print STDERR "Found something: |$line|\n"; + if (defined($1)) { + $packages{$2}=$1; + } else { + $packages{$2}=""; + } + } + } + return (%packages); +} + +# Subroutine add_safe_commands modified from version provided by S. Gouezel +# add_safe_commands($preamble) +# scans the argument for \newcommand and \DeclareMathOperator, +# and adds the created commands which are clearly safe to @SAFECMDLIST +sub add_safe_commands { + my ($preamble)=@_; + my $added_command = 1; + + $preamble=~s/(?<!\\)%.*$//mg ; + + while ($added_command) { + $added_command = 0; + # get rid of comments + my $to_test = ""; + # test for \DeclareMathOperator{\foo}{myoperator} + while ( $preamble =~ m/\DeclareMathOperator\s*\{\\(\w*?)\}/s) { + $to_test=$1; + if ($to_test ne "" and not iscmd($to_test,\@SAFECMDLIST,\@SAFECMDEXCL) and not iscmd($to_test, \@SAFECMDEXCL, [])) { + # one should add $to_test to the list of safe commands. + init_regex_arr_ext(\@SAFECMDLIST, $to_test); + $added_command = 1; + print STDERR "Adding $to_test to the list of safe commands\n" if $verbose; + } + } + + while ( $preamble =~ m/\\(?:|re|provide)newcommand\s*{\\(\w*)\}(?:|\[\d*\])\s*\{(${pat6})\}/osg ) { + my $maybe_to_test = $1; + my $should_be_safe = $2; + my $success = 0; + # test if all latex commands inside it are safe + $success = 1; + if ($should_be_safe =~ m/\\\\/) { + $success = 0; + } else { + while ($should_be_safe =~ m/\\(\w+)/g) { + $success = 0 unless iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL); ### or $1 eq ""; + } + } + ### } + $to_test = $maybe_to_test if $success; + + if ($to_test ne "" and not iscmd($to_test,\@SAFECMDLIST,\@SAFECMDEXCL) and not iscmd($to_test, \@SAFECMDEXCL, [])) { +# # one should add $to_test to the list of safe commands. + init_regex_arr_ext(\@SAFECMDLIST, $to_test); + $added_command = 1; + print STDERR "Adding $to_test to the list of safe commands\n" if $verbose; + } + } + } +} + + + +# flatten($text,$preamble,$filename,$encoding) +# expands \input and \include commands within text +# preamble is scanned for includeonly commands +# encoding is the encoding +sub flatten { + my ($text,$preamble,$filename,$encoding)=@_; + my ($includeonly,$dirname,$fname,$newpage,$replacement,$begline); + require File::Basename ; + require File::Spec ; + $dirname = File::Basename::dirname($filename); + + if ( ($includeonly) = ($preamble =~ m/\\includeonly{(.*?)}/ ) ) { + $includeonly =~ s/,/|/g; + } else { + $includeonly = '.*?'; + } + + print STDERR "DEBUG: includeonly $includeonly\n" if $debug; + + $text=~s/(^(?:[^%\n]|\\%)*)\\input{(.*?)}|\\include{(${includeonly}(?:\.tex)?)}/{ + $fname = $2 if defined($2) ; + $fname = $3 if defined($3) ; + # # add tex extension unless there is a three letter extension already + $fname .= ".tex" unless $fname =~ m|\.\w{3}|; + print STDERR "DEBUG Beg of line match |$1|\n" if defined($1) && $debug ; + print STDERR "Include file $fname\n" if $verbose; + print STDERR "DEBUG looking for file ",File::Spec->catfile($dirname,$fname), "\n" if $debug; + # content of file becomes replacement value (use recursion), add \newpage if the command was include + ###$replacement=read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; + $replacement=flatten(read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding), $preamble,$filename,$encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; + # \include always starts a new page; use explicit \newpage command to simulate this + $begline=(defined($1)? $1 : "") ; + $newpage=(defined($3)? " \\newpage " : "") ; + "$begline$newpage$replacement$newpage"; + }/exgm; + + return($text); +} + + +# print_regex_arr(@arr) +# prints regex array without x-ism expansion put in by pearl to stdout +sub print_regex_arr { + my $dumstring; + $dumstring = join(" ",@_); # PERL generates string (?-xism:^ref$) for quoted refex ^ref$ + $dumstring =~ s/\(\?-xism:\^(.*?)\$\)/$1/g; # remove string and ^,$ marks before output + print $dumstring,"\n"; +} + + +# @lines=extrapream($type) +# reads line from appendix (end of file after __END__ token) +sub extrapream { + my $type; + my @retval=("%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; + my ($copy); + + while (@_) { + $copy=0; + $type=shift ; + if ( -f $type ) { + open (FILE,$type) or die "Cannot open preamble file $type: $!"; + print STDERR "Reading preamble file $type\n" if $verbose ; + while (<FILE>) { + chomp ; + if ( $_ =~ m/%DIF PREAMBLE/ ) { + push (@retval,"$_"); + } else { + push (@retval,"$_ %DIF PREAMBLE"); + } + } + } + else { # not (-f $type) + $type=uc($type); # upcase argument + print STDERR "Preamble Internal Type $type\n" if $verbose; + while (<DATA>) { + if ( m/^%DIF $type/ ) { + $copy=1; } + elsif ( m/^%DIF END $type/ ) { + last; } + chomp; + push (@retval,"$_ %DIF PREAMBLE") if $copy; + } + if ( $copy == 0 ) { + print STDERR "\nPreamble style $type not implemented.\n"; + print STDERR "Write latexdiff -h to get help with available styles\n"; + exit(2); + } + seek DATA,0,0; # rewind DATA handle to file begin + } + } + push (@retval,"%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; + return @retval; +} + + +# ($part1,$part2,$part3)=splitdoc($text,$word1,$word2) +# splits $text into 3 parts at $word1 and $word2. +# if neither $word1 nor $word2 exist, $part1 and $part3 are empty, $part2 is $text +# If only $word1 or $word2 exist but not the other, output an error message. + +# NB this version avoids $` and $' for performance reason although it only makes a tiny difference +# (in one test gain a tenth of a second for a 30s run) +sub splitdoc { + my ($text,$word1,$word2)=@_; + my ($part1,$part2,$part3)=("","",""); + my ($rest,$pos); + + if ( $text =~ m/(^[^%]*)($word1)/mg ) { + $pos=pos $text; + $part1=substr($text,0,$pos-length($2)); + $rest=substr($text,$pos); + if ( $rest =~ m/(^[^%]*)($word2)/mg ) { + $pos=pos $rest; + $part2=substr($rest,0,$pos-length($2)); + $part3=substr($rest,$pos); + } + else { + die "$word1 and $word2 not in the correct order or not present as a pair." ; + } + } else { + $part2=$text; + die "$word2 present but not $word1." if ( $text =~ m/(^[^%]*)$word2/ms ); + } + return ($part1,$part2,$part3); +} + + + + + +# bodydiff($old,$new) +sub bodydiff { + my ($oldwords, $newwords) = @_; + my @retwords; + + print STDERR "(",exetime()," s)\n","Splitting into latex tokens \n" if $verbose; + print STDERR "Parsing $oldfile \n" if $verbose; + my @oldwords = splitlatex($oldwords); + print STDERR "Parsing $newfile \n" if $verbose; + my @newwords = splitlatex($newwords); + + if ( $debug ) { + open(TOKENOLD,">","latexdiff.debug.tokenold"); + print TOKENOLD join("***\n",@oldwords); + close(TOKENOLD); + open(TOKENNEW,">","latexdiff.debug.tokennew"); + print TOKENNEW join("***\n",@newwords); + close(TOKENNEW); + } + + print STDERR "(",exetime()," s)\n","Pass 1: Expanding text commands and merging isolated identities with changed blocks " if $verbose; + pass1(\@oldwords, \@newwords); + + + print STDERR "(",exetime()," s)\n","Pass 2: inserting DIF tokens and mark up. " if $verbose; + if ( $debug ) { + open(TOKENOLD,">","latexdiff.debug.tokenold2.tex"); + print TOKENOLD join("***\n",@oldwords); + close(TOKENOLD); + open(TOKENNEW,">","latexdiff.debug.tokennew2.tex"); + print TOKENNEW join("***\n",@newwords); + close(TOKENNEW); + } + + @retwords=pass2(\@oldwords, \@newwords); + + return(@retwords); +} + + + + +# @words=splitlatex($string) +# split string according to latex rules +# Each element of words is either +# a word (including trailing spaces and punctuation) +# a latex command +sub splitlatex { + my ($string) = @_ ; + # if input is empty, return empty list + length($string)>0 or return (); + + my @retval=($string =~ m/$pat/osg); + + if (length($string) != length(join("",@retval))) { + print STDERR "\nWARNING: Inconsistency in length of input string and parsed string:\n This often indicates faulty or non-standard latex code.\n In many cases you can ignore this and the following warning messages.\n Note that character numbers in the following are counted beginning after \\begin{document} and are only approximate." unless $ignorewarnings; + print STDERR "DEBUG Original length ",length($string)," Parsed length ",length(join("",@retval)),"\n" if $debug; + print STDERR "DEBUG Input string: |$string|\n" if (length($string)<500) && $debug; + print STDERR "DEBUG Token parsing: |",join("+",@retval),"|\n" if (length($string)<500) && $debug ; + @retval=(); + # slow way only do this if other m//sg method fails + my $last = 0; + while ( $string =~ m/$pat/osg ) { + my $match=$&; + if ($last + length $& != pos $string ) { + my $pos=pos($string); + my $offset=30<$last ? 30 : $last; + my $dum=substr($string,$last-$offset,$pos-$last+2*$offset); + my $dum1=$dum; + my $cnt=$#retval; + my $i; + $dum1 =~ s/\n/ /g; + unless ($ignorewarnings) { + print STDERR "\n$dum1\n"; + print STDERR " " x 30,"^" x ($pos-$last)," " x 30,"\n"; + print STDERR "Missing characters near word " . (scalar @retval) . " character index: " . $last . "-" . pos($string) . " Length: " . length($match) . " Match: |$match| (expected match marked above).\n"; + } + # put in missing characters `by hand' + push (@retval, substr($dum,$offset,$pos-$last-length($match))); +# Note: there seems to be a bug in substr with utf8 that made the following line output substr which were too long, +# using dum instead appears to work +# push (@retval, substr($string,$last, pos($string)-$last-length($match))); + } + push (@retval, $match); + $last=pos $string; + } + + } + return @retval; +} + + +# pass1( \@seq1,\@seq2) +# Look for differences between seq1 and seq2. +# Where an common-subsequence block is flanked by deleted or appended blocks, +# and is shorter than $MINWORDSBLOCK words it is appended +# to the last deleted or appended word. If the block contains tokens other than words +# or punctuation it is not merged. +# Deleted or appended block consisting of words and safe commands only are +# also merged, to prevent break-up in pass2 (after previous isolated words have been removed) +# If there are commands with textual arguments (e.g. \caption) both in corresponding +# appended and deleted blocks split them such that the command and opening bracket +# are one token, then the rest is split up following standard rules, and the closing +# bracket is a separate token, ie. turn +# "\caption{This is a textual argument}" into +# ("\caption{","This ","is ","a ","textual ","argument","}") +# No return value. Destructively changes sequences +sub pass1 { + my $seq1 = shift ; + my $seq2 = shift ; + + my $len1 = scalar @$seq1; + my $len2 = scalar @$seq2; + my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #' + + my ($last1,$last2)=(-1,-1) ; + my $cnt=0; + my $block=[]; + my $addblock=[]; + my $delblock=[]; + my $todo=[]; + my $instruction=[]; + my $i; + my (@delmid,@addmid,@dummy); + + my ($addcmds,$delcmds,$matchindex); + my ($addtextblocks,$deltextblocks); + my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0); + my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0); + + my $adddiscard = sub { + if ($cnt > 0 ) { + $matblkcnt++; + # just after an unchanged block +# print STDERR "Unchanged block $cnt, $last1,$last2 \n"; + if ($cnt < $MINWORDSBLOCK + && $cnt==scalar ( + grep { /^$wpat/ || ( /^\\([\w\d\*]+)((?:\[$brat0\]|\{$pat6\})*)/o + && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) + && scalar(@dummy=split(" ",$2))<3 ) } + @$block) ) { + # merge identical blocks shorter than $MINWORDSBLOCK + # and only containing ordinary words + # with preceding different word + # We cannot carry out this merging immediately as this + # would change the index numbers of seq1 and seq2 and confuse + # the algorithm, instead we store in @$todo where we have to merge + push(@$todo, [ $last1,$last2,$cnt,@$block ]); + } + $block = []; + $cnt=0; $last1=-1; $last2=-1; + } + }; + my $discard=sub { $deltokcnt++; + &$adddiscard; #($_[0],$_[1]); + push(@$delblock,[ $seq1->[$_[0]],$_[0] ]); + $last1=$_[0] }; + + my $add = sub { $addtokcnt++; + &$adddiscard; #($_[0],$_[1]); + push(@$addblock,[ $seq2->[$_[1]],$_[1] ]); + $last2=$_[1] }; + + my $match = sub { $mattokcnt++; + if ($cnt==0) { # first word of matching sequence after changed sequence or at beginning of word sequence + $deltextblocks = extracttextblocks($delblock); + $delblkcnt++ if scalar @$delblock; + $addtextblocks = extracttextblocks($addblock); + $addblkcnt++ if scalar @$addblock; + + $delcmds = extractcommands($delblock); + $addcmds = extractcommands($addblock); + # keygen(third argument of _longestCommonSubsequence) implies to sort on command (0th elements of $addcmd elements) + # the calling format for longestCommonSubsequence has changed between versions of + # Algorithm::Diff so we need to check which one we are using + if ( $algodiffversion > 1.15 ) { + ### Algorithm::Diff 1.19 + $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, 0, sub { $_[0]->[0] } ); + } else { + ### Algorithm::Diff 1.15 + $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, sub { $_[0]->[0] } ); + } + + for ($i=0 ; $i<=$#$matchindex ; $i++) { + if (defined($matchindex->[$i])){ + $j=$matchindex->[$i]; + @delmid=splitlatex($delcmds->[$i][3]); + @addmid=splitlatex($addcmds->[$j][3]); + while (scalar(@$deltextblocks) && $deltextblocks->[0][0]<$delcmds->[$i][1]) { + my ($index,$block,$cnt)=@{ shift(@$deltextblocks) }; + push(@$todo, [$index,-1,$cnt,@$block]); + } + push(@$todo, [ $delcmds->[$i][1],-1,-1,$delcmds->[$i][2],@delmid,$delcmds->[$i][4]]); + + while (scalar(@$addtextblocks) && $addtextblocks->[0][0]<$addcmds->[$j][1]) { + my ($index,$block,$cnt)=@{ shift(@$addtextblocks) }; + push(@$todo, [-1,$index,$cnt,@$block]); + } + push(@$todo, [ -1,$addcmds->[$j][1],-1,$addcmds->[$j][2],@addmid,$addcmds->[$j][4]]); + } + } + # mop up remaining textblocks + while (scalar(@$deltextblocks)) { + my ($index,$block,$cnt)=@{ shift(@$deltextblocks) } ; + push(@$todo, [$index,-1,$cnt,@$block]); + } + while (scalar(@$addtextblocks)) { + my ($index,$block,$cnt)=@{ shift(@$addtextblocks) }; + push(@$todo, [-1,$index,$cnt,@$block]); + } + + $addblock=[]; + $delblock=[]; + } + push(@$block,$seq2->[$_[1]]); + $cnt++ }; + + my $keyfunc = sub { join(" ",split(" ",shift())) }; + + traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); + + + # now carry out the merging/splitting. Refer to elements relative from + # the end (with negative indices) as these offsets don't change before the instruction is executed + # cnt>0: merged small unchanged groups with previous changed blocks + # cnt==-1: split textual commands into components + foreach $instruction ( @$todo) { + ($last1,$last2,$cnt,@$block)=@$instruction ; + if ($cnt>=0) { + splice(@$seq1,$last1-$len1,1+$cnt,join("",$seq1->[$last1-$len1],@$block)) if $last1>=0; + splice(@$seq2,$last2-$len2,1+$cnt,join("",$seq2->[$last2-$len2],@$block)) if $last2>=0; + } else { + splice(@$seq1,$last1-$len1,1,@$block) if $last1>=0; + splice(@$seq2,$last2-$len2,1,@$block) if $last2>=0; + } + } + + if ($verbose) { + print STDERR "\n"; + print STDERR " $mattokcnt matching tokens in $matblkcnt blocks.\n"; + print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n"; + print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n"; + } +} + + +# extracttextblocks(\@blockindex) +# $blockindex has the following format +# [ [ token1, index1 ], [token2, index2],.. ] +# where index refers to the index in the original old or new word sequence +# Returns: reference to an array of the form +# [[ $index, $textblock, $cnt ], .. +# where $index index of block to be merged +# $textblock contains all the words to be merged with the word at $index (but does not contain this word) +# $cnt is length of block +# +# requires: iscmd +# +sub extracttextblocks { + my $block=shift; + my ($i,$token,$index); + my $textblock=[]; + my $last=-1; + my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #' + my $retval=[]; + + for ($i=0;$i< scalar @$block;$i++) { + ($token,$index)=@{ $block->[$i] }; + # store pure text blocks + if ($token =~ /$wpat/ || ( $token =~/^\\([\w\d\*]+)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat6\})*)/o + && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) + && !iscmd($1,\@TEXTCMDLIST,\@TEXTCMDEXCL))) { + # we have text or a command which can be treated as text + if ($last<0) { + # new pure-text block + $last=$index; + } else { + # add to pure-text block + push(@$textblock, $token); + } + } else { + # it is not text + if (scalar(@$textblock)) { + push(@$retval,[ $last, $textblock, scalar(@$textblock) ]); + } + $textblock=[]; + $last=-1; + } + } + # finish processing a possibly unfinished block before returning + if (scalar(@$textblock)) { + push(@$retval,[ $last, $textblock, scalar(@$textblock) ]); + } + return($retval) +} + + + +# extractcommands( \@blockindex ) +# $blockindex has the following format +# [ [ token1, index1 ], [token2, index2],.. ] +# where index refers to the index in the original old or new word sequence +# Returns: reference to an array of the form +# [ [ "\cmd1", index, "\cmd1[optarg]{arg1}{", "arg2" ,"} " ],.. +# where index is just taken from input array +# command must have a textual argument as last argument +# +# requires: iscmd +# +sub extractcommands { + my $block=shift; + my ($i,$token,$index,$cmd,$open,$mid,$closing); + my $retval=[]; + + for ($i=0;$i< scalar @$block;$i++) { + ($token,$index)=@{ $block->[$i] }; + # check if token is an alphanumeric command sequence with at least one non-optional argument + # \cmd[...]{...}{last argument} + # Capturing in the following results in these associations + # $1: \cmd[...]{...}{ + # $2: \cmd + # $3: last argument + # $4: } + trailing spaces + if ( ( $token =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat6\})*${extraspace}\{)($pat6)(\}\s*)$/so ) + && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) ) { + # push(@$retval,[ $2,$index,$1,$3,$4 ]); + ($cmd,$open,$mid,$closing) = ($2,$1,$3,$4) ; + $closing =~ s/\}/\\RIGHTBRACE/ ; + push(@$retval,[ $cmd,$index,$open,$mid,$closing ]); + } + } + return $retval; +} + +# iscmd($cmd,\@regexarray,\@regexexcl) checks +# return 1 if $cmd matches any of the patterns in the +# array $@regexarray, and none of the patterns in \@regexexcl, otherwise return 0 +sub iscmd { + my ($cmd,$regexar,$regexexcl)=@_; + my ($ret)=0; + foreach $pat ( @$regexar ) { + if ( $cmd =~ m/^${pat}$/ ) { + $ret=1 ; + last; + } + } + return 0 unless $ret; + foreach $pat ( @$regexexcl ) { + return 0 if ( $cmd =~ m/^${pat}$/ ); + } + return 1; +} + + +# pass2( \@seq1,\@seq2) +# Look for differences between seq1 and seq2. +# Mark begin and end of deleted and appended sequences with tags $DELOPEN and $DELCLOSE +# and $ADDOPEN and $ADDCLOSE, respectively, however exclude { } & and all comands, unless +# they match an element of the whitelist (SAFECMD) +# For words in TEXTCMD but not in SAFECMD, enclose interior with $ADDOPEN and $ADDCLOSE brackets +# Deleted comment lines are marked with %DIF < +# Added comment lines are marked with %DIF > +sub pass2 { + my $seq1 = shift ; + my $seq2 = shift ; + + my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0); + my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0); + + my $retval = []; + my $delhunk = []; + my $addhunk = []; + + my $discard = sub { $deltokcnt++; + push ( @$delhunk, $seq1->[$_[0]]) }; + + my $add = sub { $addtokcnt++; + push ( @$addhunk, $seq2->[$_[1]]) }; + + my $match = sub { $mattokcnt++; + if ( scalar @$delhunk ) { + $delblkcnt++; + # mark up changes, but comment out commands + push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk); + $delhunk = []; + } + if ( scalar @$addhunk ) { + $addblkcnt++; + # we mark up changes, but simply quote commands + push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk); + $addhunk = []; + } + push(@$retval,$seq2->[$_[1]]) }; + + my $keyfunc = sub { join(" ",split(" ",shift())) }; + + traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); + # clear up unprocessed hunks + push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk) if scalar @$delhunk; + push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk) if scalar @$addhunk; + + + if ($verbose) { + print STDERR "\n"; + print STDERR " $mattokcnt matching tokens. \n"; + print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n"; + print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n"; + } + + return(@$retval); +} + +# marktags($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,\@block) +# returns ($openmark,$open,$block,$close,$closemark) if @block only contains no commands (except white-listed ones), +# braces, ampersands, or comments +# mark comments with $comment +# exclude all other exceptions from scope of open, close like this +# ($openmark, $open,...,$close, $opencmd,command, command,$closecmd, $open, ..., $close, $closemark) +# If $opencmd begins with "%" marktags assumes it is operating on a deleted block, otherwise on an added block +sub marktags { + my ($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,$block)=@_; + my $word; + my (@argtext); + my $retval=[]; + my $noncomment=0; + my $cmd=-1; # -1 at beginning 0: last token written is a ordinary word + # 1: last token written is a command + # for keeping track whether we are just in a command sequence or in a word sequence + my $cmdcomment= ($opencmd =~ m/^%/); # Flag to indicate whether opencmd is a comment (i.e. if we intend to simply comment out changed commands) + my ($command,$commandword,$closingbracket) ; # temporary variables needed below to remember sub-pattern matches + +# split this block to flatten out sequences joined in pass1 + @$block=splitlatex(join "",@$block); + foreach (@$block) { + $word=$_; + if ( $word =~ s/^%/%$comment/ ) { + # a comment + if ($cmd==1) { + push (@$retval,$closecmd) ; + $cmd=-1; + } + push (@$retval,$word); + next; + } + if (! $noncomment) { + push (@$retval,$openmark); + $noncomment=1; + } + # negative lookahead pattern (?!) in second clause is put in to avoid mathcing \( .. \) patterns + # also note that second pattern will match \\ + # Note: the second pattern should really be $word =~ /^\\(?!\()(\\|[\w*@]+)/, ie * replaced by + + # and then all commands \" \' etc declared safe. But as I don't have a complete list of one letter + # commands, and nobody has complained so far, I will eave this as is + if ( $word =~ /^[&{}\[\]]/ || ( $word =~ /^\\(?!\()(\\|[\w*@]*)/ && !iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)) ) { + # word is a command or other significant token (not in SAFECMDLIST) + ## same conditions as in subroutine extractcommand: + # check if token is an alphanumeric command sequence with at least one non-optional argument + # \cmd[...]{...}{last argument} + # Capturing in the following results in these associations + # $1: \cmd[...]{...}{ + # $2: cmd + # $3: last argument + # $4: } + trailing spaces + ### pre-0.3 if ( ( $token =~ m/^(\\([\w\d\*]+)(?:\[$brat0\]|\{$pat6\})*\{)($pat6)(\}\s*)$/so ) + if ( ( $word =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat6\})*${extraspace}\{)($pat6)(\}\s*)$/so ) + && (iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL)|| iscmd($2,\@MATHTEXTCMDLIST,\@MATHTEXTCMDEXCL)) + && ( !$cmdcomment || !iscmd($2,\@CONTEXT2CMDLIST, \@CONTEXT2CMDEXCL) ) ) { + # Condition 1: word is a command? - if yes, $1,$2,.. will be set as above + # Condition 2: word is a text command - we mark up the interior of the word. There is a separate check for MATHTEXTCMDLIST + # because for $mathmarkup=WHOLE, the commands should not be split in pass1 (ie. math mode commands are not in + # TEXTCMDLIST, but the interior of MATHTEXT commands should be highlighted in both deleted and added blocks + # Condition 3: But if we are in a deleted block ($cmdcomment=1) and + # $2 (the command) is in context2, just treat it as an ordinary command (i.e. comment it open with $opencmd) + # Because we do not want to disable this command + # here we do not use $opencmd and $closecmd($opencmd is empty) + if ($cmd==1) { + push (@$retval,$closecmd) ; + } elsif ($cmd==0) { + push (@$retval,$close) ; + } + $command=$1; $commandword=$2; $closingbracket=$4; + @argtext=splitlatex($3); # split textual argument into tokens + # and mark it up (but we do not need openmark and closemark) + # insert command with initial arguments, marked-up final argument, and closing bracket + if ( $cmdcomment && iscmd($commandword,\@CONTEXT1CMDLIST, \@CONTEXT1CMDEXCL) ) { + # context1cmd in a deleted environment; delete command itself but keep last argument, marked up + push (@$retval,$opencmd); + $command =~ s/\n/\n${opencmd}/sg ; # repeat opencmd at the beginning of each line + # argument, note that the additional comment character is included + # to suppress linebreak after opening parentheses, which is important + # for latexrevise + push (@$retval,$command,"%\n{$AUXCMD\n",marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket); + } elsif ( iscmd($commandword,,\@MATHTEXTCMDLIST, \@MATHTEXTCMDEXCL) ) { + # MATHBLOCK pseudo command: consider all commands safe, except & and \\ + # Keep these commands even in deleted blocks, hence set $opencmd and $closecmd (5th and 6th argument of marktags) to + # "" + local @SAFECMDLIST=(".*"); + local @SAFECMDEXCL=('\\','\\\\'); + push(@$retval,$command,marktags("","",$open,$close,"","",$comment,\@argtext)#@argtext + ,$closingbracket); + } else { + # normal textcmd or context1cmd in an added block + push (@$retval,$command,marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket); + } + push (@$retval,$AUXCMD,"\n") if $cmdcomment ; + $cmd=-1 ; + } else { + # ordinary command + push (@$retval,$opencmd) if $cmd==-1 ; + push (@$retval,$close,$opencmd) if $cmd==0 ; + $word =~ s/\n/\n${opencmd}/sg if $cmdcomment ; # if opencmd is a comment, repeat this at the beginning of every line + push (@$retval,$word); + $cmd=1; + } + } else { + # just an ordinary word or word in SAFECMD + push (@$retval,$open) if $cmd==-1 ; + push (@$retval,$closecmd,$open) if $cmd==1 ; + push (@$retval,$word); + $cmd=0; + } + } + push (@$retval,$close) if $cmd==0; + push (@$retval,$closecmd) if $cmd==1; + + push (@$retval,$closemark) if ($noncomment); + return @$retval; +} + +# preprocess($string, ..) +# carry out the following pre-processing steps for all arguments: +# 1. Remove leading white-space +# Change \{ to \LEFTBRACE and \} to \RIGHTBRACE +# #. change begin and end commands within comments to BEGINDIF, ENDDIF +# so they don't disturb the pattern matching (if there are several \begin or \end in one line +# 2. mark all first empty line (in block of several) with \PAR tokens +# 3. Convert all '\%' into '\PERCENTAGE ' to make parsing regular expressions easier +# 4. Convert all \verb|some verbatim text| commands (where | can be an arbitrary character) +# into \verb{hash} +# 5. Convert \begin{verbatim} some verbatim text \end{verbatim} into \verbatim{hash} +# 6. Convert _n into \SUBSCRIPTNB{n} and _{nnn} into \SUBSCRIPT{nn} +# 7. Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn} +# 8. a. Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} +# b. Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} +# 9. Convert all picture environmentent (\begin{PICTUREENV} .. \end{PICTUREENV} \PICTUREBLOCKenv +# For --block-math-markup option -convert all \begin{MATH} .. \end{MATH} +# into \MATHBLOCKmath{...} commands, where MATH/math is any valid math environment + +# 10. Add final token STOP to the very end. This is put in because the algorithm works better if the last token is identical. This is removed again in postprocessing. +# +# NB: step 6 and 7 is likely to convert some "_" inappropriately, e.g. in file +# names or labels but it does not matter because they are converted back in the postprocessing step +# Returns: leading white space removed in step 1 +sub preprocess { + my @leadin=() ; + for (@_) { + s/^(\s*)//s; + push(@leadin,$1); + # Change \{ to \QLEFTBRACE and \} to \QRIGHTBRACE + s/(?<!\\)\\{/\\QLEFTBRACE /sg; + s/(?<!\\)\\}/\\QRIGHTBRACE /sg; + # change begin and end commands within comments such that they + # don't disturb the pattern matching (if there are several \begin or \end in one line + # this substitution is insufficient but that appears unlikely) + s/(%.*)\\begin\{(.*)$/$1\\BEGINDIF\{$2/mg ; + s/(%.*)\\end\{(.*)$/$1\\ENDDIF\{$2/mg ; + + s/\n(\s*?)\n((?:\s*\n)*)/\n$1\\PAR\n$2/g ; + s/(?<!\\)\\%/\\PERCENTAGE /g ; # (?<! is negative lookbehind assertion to prevent \\% from being converted + s/(\\verb\*?)(\S)(.*?)\2/"${1}{". tohash(\%verbhash,"${2}${3}${2}") ."}"/esg; + s/\\begin\{(verbatim\*?)\}(.*?)\\end\{\1\}/"\\${1}{". tohash(\%verbhash,"${2}") . "}"/esg; + # Convert _n or _\cmd into \SUBSCRIPTNB{n} or \SUBSCRIPTNB{\cmd} and _{nnn} into \SUBSCRIPT{nn} + 1 while s/_([^{\\]|\\\w+)/\\SUBSCRIPTNB{$1}/g ; + 1 while s/_{($pat6)}/\\SUBSCRIPT{$1}/g ; + # Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn} + 1 while s/\^([^{\\]|\\\w+)/\\SUPERSCRIPTNB{$1}/g ; + 1 while s/\^{($pat6)}/\\SUPERSCRIPT{$1}/g ; + # Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} + s/\$\$(.*?)\$\$/\\begin{DOLLARDOLLAR}$1\\end{DOLLARDOLLAR}/sg; + # Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} + s/(?<!\\)\\\[/\\begin{SQUAREBRACKET}/sg; + s/\\\]/\\end{SQUAREBRACKET}/sg; + # Convert all picture environmentent (\begin{PICTUREENV} .. \end{PICTUREENV} \PICTUREBLOCKenv + s/\\begin{($PICTUREENV)}(.*?)\\end{\1}/\\PICTUREBLOCK$1\{$2\}/sg; + # For --block-math-markup option -convert all \begin{MATH} .. \end{MATH} + # into \MATHBLOCKMATH{...} commands, where MATH is any valid math environment + # Also convert all array environments into ARRAYBLOCK environments + if ( $mathmarkup != FINE ) { + s/\\begin{($ARRENV)}(.*?)\\end{\1}/\\ARRAYBLOCK$1\{$2\}/sg; + s/\\begin{($MATHENV|$MATHARRENV|SQUAREBRACKET)}(.*?)\\end{\1}/\\MATHBLOCK$1\{$2\}/sg; + } + # add final token " STOP" + $_ .= " STOP" + } + return(@leadin); +} + +#hashstring=tohash(\%hash,$string) +# creates a hash value based on string and stores in %hash +sub tohash { + my ($hash,$string)=@_; + my (@arr,$val); + my ($sum,$i)=(0,1); + my ($hstr); + + @arr=unpack('c*',$string); + + foreach $val (@arr) { + $sum += $i*$val; + $i++; + } + $hstr= "$sum"; + if (defined($hash->{$hstr}) && $string ne $hash->{$hstr}) { + warn "Repeated hash value for verbatim mode in spite of different content."; + $hstr="-$hstr"; + } + $hash->{$hstr}=$string; + return($hstr); +} + +#string=fromhash(\%hash,$fromstring) +# restores string value stored in hash +#string=fromhash(\%hash,$fromstring,$prependstring) +# additionally begins each line with prependstring +sub fromhash { + my ($hash,$hstr)=($_[0],$_[1]); + my $retstr=$hash->{$hstr}; + if ( $#_ >= 2) { + $retstr =~ s/^/$_[2]/mg; + } + return $retstr; +} + + +# postprocess($string, ..) +# carry out the following post-processing steps for all arguments: +# * Remove STOP token from the end +# * Replace \RIGHTBRACE by } +# * change citation commands within comments to protect from processing (using marker CITEDIF) +# 1. Check all deleted blocks: +# a.where a deleted block contains a matching \begin and +# \end environment (these will be disabled by a %DIFDELCMD statements), for selected environments enable +# these commands again (such that for example displayed math in a deleted equation +# is properly within math mode. For math mode environments replace numbered equation +# environments with their display only variety (so that equation numbers in new file and +# diff file are identical). Where the correct type of math environment cannot be determined +# use a place holder MATHMODE +# b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement +# subtracting one from the respective counter to keep numbering consistent with new file +# Replace all MATHMODE environment commands by the correct environment to achieve matching +# pairs +# c. Convert MATHBLOCKmath commands to their uncounted numbers (e.g. convert equation -> displaymath +# (environments defined in $MATHENV will be replaced by $MATHREPL, and environments in $MATHARRENV +# will be replaced by $MATHARRREPL +# d. If in-line math mode contains array environment, enclose the whole environment in \mbox'es +# d. place \cite commands in mbox'es (for UNDERLINE style) +# +# For added blocks: +# c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es +# d. place \cite commands in mbox'es (for UNDERLINE style) +# +# 2. If --block-math-markup option set: Convert \MATHBLOCKmath{..} commands back to environments +# +# Convert all PICTUREblock{..} commands back to the appropriate environments +# 3. Convert DIFadd, DIFdel, DIFFaddbegin , ... into FL varieties +# within floats (currently recognised float environments: plate,table,figure +# plus starred varieties). +# 4. Remove empty %DIFDELCMD < lines +# 4. Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \] +# Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$ +# 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn} +# 6. Convert \SUBSCRIPTNB{n} into _n and \SUBCRIPT{nn} into _{nnn} +# 7. Expand hashes of verb and verbatim environments +# 8. Convert '\PERCENTAGE ' back into '\%' +# 9.. remove all \PAR tokens +# 10. package specific processing: endfloat: make sure \begin{figure} and \end{figure} are always +# on a line by themselves, similarly for table environment +# 4, undo renaming of the \begin and \end in comments +# Change \QLEFTBRACE, \QRIGHTBRACE to \{,\} +# +# Note have to manually synchronize substitution commands below and +# DIF.. command names in the header +sub postprocess { + my ($begin,$len,$cnt,$float,$delblock,$addblock); + # second level blocks + my ($begin2,$cnt2,$len2,$eqarrayblock,$mathblock); + + for (@_) { + + # change $'s in comments to something harmless + 1 while s/(%.*)\$/$1DOLLARDIF/mg ; + + # Remove final STOP token + s/ STOP$//; + # Replace \RIGHTBRACE by } + s/\\RIGHTBRACE/}/g; + + # change citation commands within comments to protect from processing + if ($CITECMD){ + 1 while s/(%.*)\\($CITECMD)/$1\\CITEDIF$2/m ; + } + # Check all deleted blocks: where a deleted block contains a matching \begin and + # \end environment (these will be disabled by a %DIFDELCMD statements), enable + # these commands again (such that for example displayed math in a deleted equation + # is properly within math mode. For math mode environments replace numbered equation + # environments with their display only variety (so that equation numbers in new file and + # diff file are identical + while ( m/\\DIFdelbegin.*?\\DIFdelend/sg ) { + $cnt=0; + $len=length($&); + $begin=pos($_) - $len; + $delblock=$&; + + + ### (.*?[^\n]?)\n? construct is necessary to avoid empty lines in math mode, which result in + ### an error + # displayed math environments + if ($mathmarkup == FINE ) { + $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}.*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHREPL}$AUXCMD\n$1$3\n\\end{$MATHREPL}$AUXCMD\n$4/sg; + # also transform the opposite pair \end{displaymath} .. \begin{displaymath} but we have to be careful not to interfere with the results of the transformation in the line directly above + ### pre-0.42 obsolete version which did not work on eqnarray test $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHENV)\}\s*?\n)(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/$1\\end{$MATHREPL}$AUXCMD\n$3\n\\begin{$MATHREPL}$AUXCMD\n$4/sg; + ###0.5: $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg; + $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}.*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg; + + # now look for unpaired %DIFDELCMD < \begin{MATHENV}; if found add \begin{$MATHREPL} and insert \end{$MATHREPL} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHREPL}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\end{$MATHREPL}$AUXCMD\n$1/s ; + } + # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ; + } + + + ### pre-0.42 # same as above for special case \[.\] (latex abbreviation for displaymath) + ### pre-0.42 $delblock=~ s/(\%DIFDELCMD < \s*\\\[\s*?\n())(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\\])/$1\\\[$AUXCMD\n$3\n\\\]$AUXCMD\n$4/sg; + ### pre-0.42 $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\\]\s*?\n())(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\\[)/$1\\\]$AUXCMD\n$3\n\\\[$AUXCMD\n$4/sg; + # equation array environment + ###pre-0.3 $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?\n)(.*?)(\%DIFDELCMD < \s*\\end\{\2\})/$1\\begin{$MATHARRREPL}$AUXCMD\n$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg; + ###0.5 $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHARRREPL}$AUXCMD\n$1$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg; + $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}.*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHARRREPL}$AUXCMD\n$1$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg; + ### pre-0.42 obsolete version which did not work on eqnarray test $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?\n)(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/$1\\end{$MATHARRREPL}$AUXCMD\n$3\n\\begin{$MATHARRREPL}$AUXCMD\n$4/sg; + $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg; + + # now look for unpaired %DIFDELCMD < \begin{MATHARRENV}; if found add \begin{$MATHARRREPL} and insert \end{$MATHARRREPL} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHARRREPL}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\end{$MATHARRREPL}$AUXCMD\n$1/s ; + } + # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE} + # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted + if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) { + $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ; + } + + # parse $delblock for deleted and reinstated eqnarray* environments - within those reinstate \\ and & commands + while ( $delblock =~ m/\\begin\Q{$MATHARRREPL}$AUXCMD\E\n.*?\n\\end\Q{$MATHARRREPL}$AUXCMD\E\n/sg ) { + $cnt2=0; + $len2=length($&); + $begin2=pos($delblock) - $len2; + $eqarrayblock=$&; + # reinstate deleted & and \\ commands + $eqarrayblock=~ s/(\%DIFDELCMD < \s*(\&|\\\\)\s*?(?:$DELCMDCLOSE|\n))/$1$2$AUXCMD\n/sg ; + + substr($delblock,$begin2,$len2)=$eqarrayblock; + pos($delblock) = $begin2 + length($eqarrayblock); + } + } elsif ( $mathmarkup == COARSE || $mathmarkup == WHOLE ) { +# Convert MATHBLOCKmath commands to their uncounted numbers (e.g. convert equation -> displaymath +# (environments defined in $MATHENV will be replaced by $MATHREPL, and environments in $MATHARRENV +# will be replaced by $MATHARRREPL + $delblock=~ s/\\MATHBLOCK($MATHENV)\{($pat6)\}/\\MATHBLOCK$MATHREPL\{$2\}/sg; + $delblock=~ s/\\MATHBLOCK($MATHARRENV)\{($pat6)\}/\\MATHBLOCK$MATHARRREPL\{$2\}/sg; + } + + +# b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement +# subtracting one from the respective counter to keep numbering consistent with new file + $delblock=~ s/\\($COUNTERCMD)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat6\})*\s*${AUXCMD}\n)/\\$1$2\\addtocounter{$1}{-1}${AUXCMD}\n/sg ; + + +# c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es + while ( $delblock =~ m/($math)(\s*)/sg ) { + $cnt2=0; + $len2=length($&); + $begin2=pos($delblock) - $len2; + $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n"; + next unless $mathblock =~ m/\{$ARRENV\}/ ; + substr($delblock,$begin2,$len2)=$mathblock; + pos($delblock) = $begin2 + length($mathblock); + } + if ($CITE2CMD) { + $delblock=~s/($DELCMDOPEN\s*\\($CITE2CMD)(.*)$DELCMDCLOSE)/ + # Replacement code + {my ($aux,$all); + $aux=$all=$1; + $aux=~s#\n?($DELCMDOPEN|$DELCMDCLOSE)##g; + $all."$aux$AUXCMD\n";}/sge; + } + # or protect \cite commands with \mbox + if ($CITECMD) { + $delblock=~s/(\\($CITECMD)${extraspace}(?:\[$brat0\]${extraspace}){0,2}\{$pat6\})(\s*)/\\mbox{$AUXCMD\n$1\n}$AUXCMD\n/msg ; + } +# splice in modified delblock + substr($_,$begin,$len)=$delblock; + pos = $begin + length($delblock); + } + # make the array modification in added blocks + while ( m/\\DIFaddbegin.*?\\DIFaddend/sg ) { + $cnt=0; + $len=length($&); + $begin=pos($_) - $len; + $addblock=$&; + while ( $addblock =~ m/($math)(\s*)/sg ) { + $cnt2=0; + $len2=length($&); + $begin2=pos($addblock) - $len2; + $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n"; + next unless $mathblock =~ m/\{$ARRENV\}/ ; + substr($addblock,$begin2,$len2)=$mathblock; + pos($addblock) = $begin2 + length($mathblock); + } + if ($CITECMD) { + my $addblockbefore=$addblock; + $addblock=~ s/(\\($CITECMD)${extraspace}(?:\[$brat0\]${extraspace}){0,2}\{$pat2\})(\s*)/\\mbox{$AUXCMD\n$1\n}$AUXCMD\n/msg ; + } +# splice in modified addblock + substr($_,$begin,$len)=$addblock; + pos = $begin + length($addblock); + } + + + ### old place for BEGINDIF, ENDDIF replacement + # change begin and end commands within comments such that they + # don't disturb the pattern matching (if there are several \begin or \end in one line + # this substitution is insufficient but that appears unlikely) + # This needs to be repeated here to also get rid of DIFdelcmd-protected environments + s/(%.*)\\begin\{(.*)$/$1\\BEGINDIF\{$2/mg ; + s/(%.*)\\end\{(.*)$/$1\\ENDDIF\{$2/mg ; + + # Replace MATHMODE environments from step 1a above by the correct Math environment + + # The next line is complicated. The negative look-ahead insertion makes sure that no \end{$MATHENV} (or other mathematical + # environments) are between the \begin{$MATHENV} and \end{MATHMODE} commands. This is necessary as the minimal matching + # is not globally minimal but only 'locally' (matching is beginning from the left side of the string) + if ( $mathmarkup == FINE ) { + 1 while s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}((?:.(?!(?:\\end{(?:(?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}|\\begin{MATHMODE})))*?)\\end{MATHMODE}/\\begin{$1}$2\\end{$1}/s; + 1 while s/\\begin{MATHMODE}((?:.(?!\\end{MATHMODE}))*?)\\end{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}/\\begin{$2}$1\\end{$2}/s; + # convert remaining \begin{MATHMODE} \end{MATHMODE} (and not containing & or \\ )into MATHREPL environments + s/\\begin{MATHMODE}((?:(.(?!(?<!\\)\&|\\\\))*)?)\\end{MATHMODE}/\\begin{$MATHREPL}$1\\end{$MATHREPL}/sg; + # others into MATHARRREPL + s/\\begin{MATHMODE}(.*?)\\end{MATHMODE}/\\begin{$MATHARRREPL}$1\\end{$MATHARRREPL}/sg; + + # now look for AUXCMD math-mode pairs which have only comments (or empty lines between them), and remove the added commands + s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}$AUXCMD\n((?:\s*%.[^\n]*\n)*)\\end{\1}$AUXCMD\n/$2/sg; + } else { + # math modes OFF,WHOLE,COARSE: Convert \MATHBLOCKmath{..} commands back to environments + s/\\MATHBLOCK($MATHENV|$MATHARRENV|SQUAREBRACKET)\{($pat6)\}/\\begin{$1}$2\\end{$1}/sg; + # convert ARRAYBLOCK.. commands back to environments + s/\\ARRAYBLOCK($ARRENV)\{($pat6)\}/\\begin{$1}$2\\end{$1}/sg; + } + # Convert all PICTUREblock{..} commands back to the appropriate environments + s/\\PICTUREBLOCK($PICTUREENV)\{($pat6)\}/\\begin{$1}$2\\end{$1}/sg; +#0.5: # Remove all mark up within picture environments +# while ( m/\\begin\{($PICTUREENV)\}.*?\\end\{\1\}/sg ) { +# $cnt=0; +# $len=length($&); +# $begin=pos($_) - $len; +# $float=$&; +# $float =~ s/\\DIFaddbegin //g; +# $float =~ s/\\DIFaddend //g; +# $float =~ s/\\DIFadd\{($pat6)\}/$1/g; +# $float =~ s/\\DIFdelbegin //g; +# $float =~ s/\\DIFdelend //g; +# $float =~ s/\\DIFdel\{($pat6)\}//g; +# $float =~ s/$DELCMDOPEN.*//g; +# substr($_,$begin,$len)=$float; +# pos = $begin + length($float); +# } + # Convert DIFadd, DIFdel, DIFFaddbegin , ... into varieties + # within floats (currently recognised float environments: plate,table,figure + # plus starred varieties). + while ( m/\\begin\{($FLOATENV)\}.*?\\end\{\1\}/sg ) { + $cnt=0; + $len=length($&); + $begin=pos($_) - $len; + $float=$&; + $float =~ s/\\DIFaddbegin /\\DIFaddbeginFL /g; + $float =~ s/\\DIFaddend /\\DIFaddendFL /g; + $float =~ s/\\DIFadd\{/\\DIFaddFL{/g; + $float =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; + $float =~ s/\\DIFdelend /\\DIFdelendFL /g; + $float =~ s/\\DIFdel\{/\\DIFdelFL{/g; + substr($_,$begin,$len)=$float; + pos = $begin + length($float); + } + ### former location of undo renaming of \begin and \end in comments + + # remove empty DIFCMD < lines + s/^\Q${DELCMDOPEN}\E\n//msg; + + # Expand hashes of verb and verbatim environments (note negative look behind assertion to not leak out of DIFDELCMD comments + s/(\\verb\*?)\{([-\d]*?)\}/"${1}". fromhash(\%verbhash,$2)/esg; + s/${DELCMDOPEN}\\(verbatim\*?)\{([-\d]*?)\}/"${DELCMDOPEN}\\begin{${1}}".fromhash(\%verbhash,$2,$DELCMDOPEN)."${DELCMDOPEN}\\end{${1}}"/esg; + s/\\(verbatim\*?)\{([-\d]*?)\}/"\\begin{${1}}".fromhash(\%verbhash,$2)."\\end{${1}}"/esg; + # Convert '\PERCENTAGE ' back into '\%' + s/\\PERCENTAGE /\\%/g; + # remove all \PAR tokens (taking care to properly keep commented out PAR's + # from introducing uncommented newlines - next line) + s/(%DIF < )([^\n]*?)\\PAR\n/$1$2\n$1\n/sg; + # convert PAR commands which are on a line by themselves + s/\n(\s*?)\\PAR\n/\n\n/sg; + # convert remaining PAR commands (which are preceded by non-white space characters, usually "}" ($ADDCLOSE) + s/\\PAR\n/\n\n/sg; + + # package specific processing: + if ( defined($packages{"endfloat"})) { + #endfloat: make sure \begin{figure} and \end{figure} are always + # on a line by themselves, similarly for table environment + print STDERR "endfloat package detected.\n" if $verbose ; + # eliminate whitespace before and after + s/^(\s*)(\\(?:begin|end)\{(?:figure|table)\})(\s*)$/$2/mg; + # split lines with remaining characters before float enviroment conmmand + s/^(.+)(\\(?:begin|end)\{(?:figure|table)\})/$1\n$2/mg; + # split lines with remaining characters after float enviroment conmmand + s/(\\(?:begin|end)\{(?:figure|table)\})(.+)$/$1\n$2/mg; + } + # undo renaming of the \begin and \end and dollars in comments + s/(%.*)\\BEGINDIF\{(.*)$/$1\\begin\{$2/mg ; + s/(%.*)\\ENDDIF\{(.*)$/$1\\end\{$2/mg ; + 1 while s/(%.*)DOLLARDIF/$1\$/mg ; + # undo renaming of the \cite.. commands in comments + if ( $CITECMD ) { + 1 while s/(%.*)\\CITEDIF($CITECMD)/$1\\$2/mg ; + } +# Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \] + s/\\end{SQUAREBRACKET}/\\\]/sg; + s/\\begin{SQUAREBRACKET}/\\\[/sg; +# 4. Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$ + s/\\begin\{DOLLARDOLLAR\}(.*?)\\end\{DOLLARDOLLAR\}/\$\$$1\$\$/sg; +# 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn} + 1 while s/\\SUPERSCRIPTNB{($pat0)}/^$1/g ; + 1 while s/\\SUPERSCRIPT{($pat6)}/^{$1}/g ; + # Convert \SUBSCRIPNB{n} into _n and \SUBCRIPT{nn} into _{nnn} + 1 while s/\\SUBSCRIPTNB{($pat0)}/_$1/g ; + 1 while s/\\SUBSCRIPT{($pat6)}/_{$1}/g ; +# Change \QLEFTBRACE, \QRIGHTBRACE to \{,\} + s/\\QLEFTBRACE /\\{/sg; + s/\\QRIGHTBRACE /\\}/sg; + + return; + } +} + + +# @auxlines=preprocess_preamble($oldpreamble,$newpreamble); + # pre-process preamble by looking for commands used in \maketitle (title, author, date etc commands) + # if found then use a bodydiff to mark up content, and replace the corresponding commands + # in both preambles by marked up version to 'fool' the linediff (such that only body is marked + # up. + # A special case are e.g. author commands being added (or removed) + # 1. If commands are added, then the entire content is marked up as new, but also the lines are marked as new in the linediff + # 2. If commands are removed, then the linediff will mark the line as deleted. The program returns + # with $auxlines a text to be appended at the end of the preamble, which shows the respective fields as deleted +sub preprocess_preamble { + my ($oldpreambleref,$newpreambleref)=(\$_[0],\$_[1]) ; + my @auxlines=(); + # Remember to use $$oldpreambleref to refer to oldpreamble + my ($titlecmd,$titlecmdpat); + my (@oldtitlecommands,@newtitlecommands ); + my %oldhash = (); + my %newhash = (); + my ($line,$cmd,$optarg,$arg,$optargnew,$optargold,$optargdiff,$argold,$argnew,$argdiff,$auxline); + + # resuse context2cmdlist to define these commands to look out for in preamble + $titlecmd = "(?:".join("|",@CONTEXT2CMDLIST).")"; + # as context2cmdlist is stored as regex, e.g. ((?-xism:^title$), we need to remove ^- fo + # resue in a more complex regex + $titlecmd =~ s/[\$\^]//g; + # make sure to not match on comment lines: + $titlecmdpat=qr/^(?:[^%\n]|\\%)*(\\($titlecmd)$extraspace(?:\[($brat0)\])?(?:\{($pat6)\}))/ms; + @oldtitlecommands= ( $$oldpreambleref =~ m/$titlecmdpat/g ); + @newtitlecommands= ( $$newpreambleref =~ m/$titlecmdpat/g ); + + + while ( @oldtitlecommands ) { + $line=shift @oldtitlecommands; + $cmd=shift @oldtitlecommands; + $optarg=shift @oldtitlecommands; + $arg=shift @oldtitlecommands; + + if ( defined($oldhash{$cmd})) { + warn "$cmd is used twice in preamble of old file. Reverting to pure line diff mode for preamble.\n"; + return; + } + $oldhash{$cmd}=[ $line, $optarg, $arg ]; + } + while ( @newtitlecommands ) { + $line=shift @newtitlecommands; + $cmd=shift @newtitlecommands; + $optarg=shift @newtitlecommands; + $arg=shift @newtitlecommands; + + if ( defined($newhash{$cmd})) { + warn "$cmd is used twice in preamble of new file. Reverting to pure line diff mode for preamble.\n"; + return; + } + $newhash{$cmd}=[ $line, $optarg, $arg ]; + } + foreach $cmd ( keys %newhash ) { + if ( defined($newhash{$cmd}->[1])) { + $optargnew=$newhash{$cmd}->[1]; + } else { + $optargnew=""; + } + if ( defined($oldhash{$cmd}->[1])) { + $optargold=$oldhash{$cmd}->[1]; + } else { + $optargold=""; + } + + if ( defined($oldhash{$cmd}) ) { + $argold=$oldhash{$cmd}->[2]; + } else { + $argold=""; + } + $argnew=$newhash{$cmd}->[2]; + $argdiff="{" . join("",bodydiff($argold,$argnew)) ."}"; + if ( length $optargnew ) { + $optargdiff="[".join("",bodydiff($optargold,$optargnew))."]" ; + $optargdiff =~ s/\\DIFaddbegin /\\DIFaddbeginFL /g; + $optargdiff =~ s/\\DIFaddend /\\DIFaddendFL /g; + $optargdiff =~ s/\\DIFadd\{/\\DIFaddFL{/g; + $optargdiff =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; + $optargdiff =~ s/\\DIFdelend /\\DIFdelendFL /g; + $optargdiff =~ s/\\DIFdel\{/\\DIFdelFL{/g; + } else { + $optargdiff=""; + } + ### print STDERR "DEBUG s/\\Q$newhash{$cmd}->[0]\\E/\\$cmd$optargdiff$argdiff/s\n"; + # Note: \Q and \E force literal interpretation of what it between them but allow + # variable interpolation, such that e.g. \title matches just that and not TAB-itle + $$newpreambleref=~s/\Q$newhash{$cmd}->[0]\E/\\$cmd$optargdiff$argdiff/s; + # replace this in old preamble if necessary + if ( defined($oldhash{$cmd}->[0])) { + $$oldpreambleref=~s/\Q$oldhash{$cmd}->[0]\E/\\$cmd$optargdiff$argdiff/s ; + } + ### print STDERR "DEBUG NEW PRE ".$$newpreambleref."\n"; + } + + foreach $cmd ( keys %oldhash ) { + # if this has already been dealt with above can just skip + next if defined($newhash{$cmd}) ; + if ( defined($oldhash{$cmd}->[1])) { + $optargold=$oldhash{$cmd}->[1]; + $optargdiff="[".join("",bodydiff($optargold,""))."]" ; + $optargdiff =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; + $optargdiff =~ s/\\DIFdelend /\\DIFdelendFL /g; + $optargdiff =~ s/\\DIFdel\{/\\DIFdelFL{/g; + } else { + $optargdiff=""; + } + $argdiff="{" . join("",bodydiff($argold,"")) ."}"; + $auxline = "\\$cmd$optargdiff$argdiff"; + $auxline =~s/$/$AUXCMD/sg; + push @auxlines,$auxline; + } + # add auxcmd comment to highlight added lines + return(@auxlines); +} + + + +# @diffs=linediff(\@seq1, \@seq2) +# mark up lines like this +#%DIF mm-mmdnn +#%< old deleted line(s) +#%DIF ------- +#%DIF mmann-nn +#new appended line %< +#%DIF ------- +# Future extension: mark change explicitly +# Assumes: traverse_sequence traverses deletions before insertions in changed sequences +# all line numbers relative to line 0 (first line of real file) +sub linediff { + my $seq1 = shift ; + my $seq2 = shift ; + + my $block = []; + my $retseq = []; + my @begin=('','',''); # dummy initialisation + my $instring ; + + my $discard = sub { @begin=('d',$_[0],$_[1]) unless scalar @$block ; + push(@$block, "%DIF < " . $seq1->[$_[0]]) }; + my $add = sub { if (! scalar @$block) { + @begin=('a',$_[0],$_[1]) ;} + elsif ( $begin[0] eq 'd' ) { + $begin[0]='c'; $begin[2]=$_[1]; + push(@$block, "%DIF -------") } + push(@$block, $seq2->[$_[1]] . " %DIF > " ) }; + my $match = sub { if ( scalar @$block ) { + if ( $begin[0] eq 'd' && $begin[1]!=$_[0]-1) { + $instring = sprintf "%%DIF %d-%dd%d",$begin[1],$_[0]-1,$begin[2]; } + elsif ( $begin[0] eq 'a' && $begin[2]!=$_[1]-1) { + $instring = sprintf "%%DIF %da%d-%d",$begin[1],$begin[2],$_[1]-1; } + elsif ( $begin[0] eq 'c' ) { + $instring = sprintf "%%DIF %sc%s", + ($begin[1]==$_[0]-1) ? "$begin[1]" : $begin[1]."-".($_[0]-1) , + ($begin[2]==$_[1]-1) ? "$begin[2]" : $begin[2]."-".($_[1]-1) ; } + else { + $instring = sprintf "%%DIF %d%s%d",$begin[1],$begin[0],$begin[2]; } + push @$retseq, $instring,@$block, "%DIF -------" ; + $block = []; + } + push @$retseq, $seq2->[$_[1]] + }; + # key function: remove multiple spaces (such that insertion or deletion of redundant white space is not reported) + my $keyfunc = sub { join(" ",split(" ",shift())) }; + + traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); + push @$retseq, @$block if scalar @$block; + + return wantarray ? @$retseq : $retseq ; +} + + + +# init_regex_arr_data(\@array,"TOKEN INIT") +# scans DATA file handel for line "%% TOKEN INIT" line +# then appends each line not beginning with % into array (as a quoted regex) +sub init_regex_arr_data { + my ($arr,$token)=@_; + my ($copy); + while (<DATA>) { + if ( m/^%%BEGIN $token\s*$/ ) { + $copy=1; } + elsif ( m/^%%END $token\s*/ ) { + last; } + chomp; + push (@$arr,qr/^$_$/) if ( $copy && !/^%/ ) ; + } + seek DATA,0,0; # rewind DATA handle to file begin +} + + +# init_regex_arr_ext(\@array,$arg) +# fills array with regular expressions. +# if arg is a file name, then read in list of regular expressions from that file +# (one expression per line) +# Otherwise treat arg as a comma separated list of regular expressions +sub init_regex_arr_ext { + my ($arr,$arg)=@_; + my $regex; + if ( -f $ arg ) { + open(FILE,"$arg") or die ("Couldn't open $arg: $!"); + while (<FILE>) { + chomp; + next if /^\s*#/ || /^\s*%/ || /^\s*$/ ; + push (@$arr,qr/^$_$/); + } + close(FILE); + } + else { + # assume it is a comma-separated list of reg-ex + foreach $regex (split(qr/(?<!\\),/,$arg)) { + $regex =~ s/\\,/,/g; + push (@$arr,qr/^$regex$/); + } + } +} + +#exetime() returns time since last execution of this command +#exetime(1) resets this time +my $lasttime=-1; # global variable for persistence +sub exetime { + my $reset=0; + my $retval; + if ((scalar @_) >=1) { + $reset=shift; + } + if ($reset) { + $lasttime=times(); + } + else { + $retval=times()-$lasttime; + $lasttime=$lasttime+$retval; + return($retval); + } +} + + +sub usage { + die <<"EOF"; +Usage: $0 [options] old.tex new.tex > diff.tex + +Compares two latex files and writes tex code to stdout, which has the same +format as new.tex but has all changes relative to old.tex marked up or commented. + +--type=markupstyle +-t markupstyle Add code to preamble for selected markup style + Available styles: UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE + CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR + [ Default: UNDERLINE ] + +--subtype=markstyle +-s markstyle Add code to preamble for selected style for bracketing + commands (e.g. to mark changes in margin) + Available styles: SAFE MARGINAL DVIPSCOL COLOR + [ Default: SAFE ] + +--floattype=markstyle +-f markstyle Add code to preamble for selected style which + replace standard marking and markup commands within floats + (e.g., marginal remarks cause an error within floats + so marginal marking can be disabled thus) + Available styles: FLOATSAFE IDENTICAL + [ Default: FLOATSAFE ] + +--encoding=enc +-e enc Specify encoding of old.tex and new.tex. Typical encodings are + ascii, utf8, latin1, latin9. A list of available encodings can be + obtained by executing + perl -MEncode -e 'print join ("\\n",Encode->encodings( ":all" )) ;' + [Default encoding is utf8 unless the first few lines of the preamble contain + an invocation "\\usepackage[..]{inputenc} in which case the + encoding chosen by this command is asssumed. Note that ASCII (standard + latex) is a subset of utf8] + +--preamble=file +-p file Insert file at end of preamble instead of auto-generating + preamble. The preamble must define the following commands + \\DIFaddbegin,\\DIFaddend,\\DIFadd{..}, + \\DIFdelbegin,\\DIFdelend,\\DIFdel{..}, + and varieties for use within floats + \\DIFaddbeginFL,\\DIFaddendFL,\\DIFaddFL{..}, + \\DIFdelbeginFL,\\DIFdelendFL,\\DIFdelFL{..} + (If this option is set -t, -s, and -f options + are ignored.) + +--exclude-safecmd=exclude-file +--exclude-safecmd="cmd1,cmd2,..." +-A exclude-file +--replace-safecmd=replace-file +--append-safecmd=append-file +--append-safecmd="cmd1,cmd2,..." +-a append-file Exclude from, replace or append to the list of regex + matching commands which are safe to use within the + scope of a \\DIFadd or \\DIFdel command. The file must contain + one Perl-RegEx per line (Comment lines beginning with # or % are + ignored). A literal comma within the comma-separated list must be + escaped thus "\\,", Note that the RegEx needs to match the whole of + the token, i.e., /^regex\$/ is implied and that the initial + "\\" of the command is not included. The --exclude-safecmd + and --append-safecmd options can be combined with the --replace-safecmd + option and can be used repeatedly to add cumulatively to the lists. + +--exclude-textcmd=exclude-file +--exclude-textcmd="cmd1,cmd2,..." +-X exclude-file +--replace-textcmd=replace-file +--append-textcmd=append-file +--append-textcmd="cmd1,cmd2,..." +-x append-file Exclude from, replace or append to the list of regex + matching commands whose last argument is text. See + entry for --exclude-safecmd directly above for further details. + +--replace-context1cmd=replace-file +--append-context1cmd=append-file +--append-context1cmd="cmd1,cmd2,..." + Replace or append to the list of regex matching commands + whose last argument is text but which require a particular + context to work, e.g. \\caption will only work within a figure + or table. These commands behave like text commands, except when + they occur in a deleted section, when they are disabled, but their + argument is shown as deleted text. + +--replace-context2cmd=replace-file +--append-context2cmd=append-file +--append-context2cmd="cmd1,cmd2,..." + As corresponding commands for context1. The only difference is that + context2 commands are completely disabled in deleted sections, including + their arguments. + + +--config var1=val1,var2=val2,... +-c var1=val1,.. Set configuration variables. +-c configfile Available variables: + MINWORDSBLOCK (integer) + FLOATENV (RegEx) + PICTUREENV (RegEx) + MATHENV (RegEx) + MATHREPL (String) + MATHARRENV (RegEx) + MATHARRREPL (String) + ARRENV (RegEx) + COUNTERCMD (RegEx) + This option can be repeated. + + +--packages=pkg1,pkg2,.. + Tell latexdiff that .tex file is processed with the packages in list + loaded. This is normally not necessary if the .tex file includes the + preamble, as the preamble is automatically scanned for \\usepackage commands. + Use of the --packages option disables automatic scanning, so if for any + reason package specific parsing needs to be switched off, use --packages=none. + The following packages trigger special behaviour: + endfloat hyperref amsmath + [ Default: scan the preamble for \\usepackage commands to determine + loaded packages.] + +--show-preamble Print generated or included preamble commands to stdout. + +--show-safecmd Print list of regex matching and excluding safe commands. + +--show-textcmd Print list of regex matching and excluding commands with text argument. + +--show-config Show values of configuration variables + +--show-all Show all of the above + + NB For all --show commands, no old.tex or new.tex file needs to be given, and no + differencing takes place. + +Other configuration options: + +--allow-spaces Allow spaces between bracketed or braced arguments to commands + [Default requires arguments to directly follow each other without + intervening spaces] + +--math-markup=level Determine granularity of markup in displayed math environments: + Possible values for level are (both numerical and text labels are acceptable): + off or 0: suppress markup for math environments. Deleted equations will not + appear in diff file. This mode can be used if all the other modes + cause invalid latex code. + whole or 1: Differencing on the level of whole equations. Even trivial changes + to equations cause the whole equation to be marked changed. This + mode can be used if processing in coarse or fine mode results in + invalid latex code. + coarse or 2: Detect changes within equations marked up with a coarse + granularity; changes in equation type (e.g.displaymath to equation) + appear as a change to the complete equation. This mode is recommended + for situations where the content and order of some equations are still + being changed. [Default] + fine or 3: Detect small change in equations and mark up and fine granularity. + This mode is most suitable, if only minor changes to equations are + expected, e.g. correction of typos. + +--disable-citation-markup Suppress citation markup in styles using ulem (UNDERLINE, + FONTSTRIKE, CULINECHBAR) +--enable-citation-markup Protect citation commands in changed sections with \\mbox command + [i.e. use default behaviour for ulem package for other packages] + +Miscelleneous options + +--label=label +-L label Sets the labels used to describe the old and new files. The first use + of this option sets the label describing the old file and the second + use of the option sets the label for the new file. + [Default: use the filename and modification dates for the label] + +--no-label Suppress inclusion of old and new file names as comment in output file + +--visble-label Include old and new filenames (or labels set with --label option) as + visible output + +--flatten Replace \\input and \\include commands within body by the content + of the files in their argument. If \\includeonly is present in the + preamble, only those files are expanded into the document. However, + no recursion is done, i.e. \\input and \\include commands within + included sections are not expanded. The included files are assumed to + be located in the same directories as the old and new master files, + respectively, making it possible to organise files into old and new directories. + --flatten is applied recursively, so inputted files can contain further + \\input statements. + +--help +-h Show this help text. + +--ignore-warnings Suppress warnings about inconsistencies in length between input + and parsed strings and missing characters. + +--verbose +-V Output various status information to stderr during processing. + Default is to work silently. + +--version Show version number. + +For further information, consult http://latexdiff.berlios.de +EOF +} + +=head1 NAME + +latexdiff - determine and markup differences between two latex files + +=head1 SYNOPSIS + +B<latexdiff> [ B<OPTIONS> ] F<old.tex> F<new.tex> > F<diff.tex> + +=head1 DESCRIPTION + +Briefly, I<latexdiff> is a utility program to aid in the management of +revisions of latex documents. It compares two valid latex files, here +called C<old.tex> and C<new.tex>, finds significant differences +between them (i.e., ignoring the number of white spaces and position +of line breaks), and adds special commands to highlight the +differences. Where visual highlighting is not possible, e.g. for changes +in the formatting, the differences are +nevertheless marked up in the source. + +The program treats the preamble differently from the main document. +Differences between the preambles are found using line-based +differencing (similarly to the Unix diff command, but ignoring white +spaces). A comment, "S<C<%DIF E<gt>>>" is appended to each added line, i.e. a +line present in C<new.tex> but not in C<old.tex>. Discarded lines + are deactivated by prepending "S<C<%DIF E<lt>>>". Changed blocks are preceded by +comment lines giving information about line numbers in the original files. Where there are insignificant +differences, the resulting file C<diff.tex> will be similar to +C<new.tex>. At the end of the preamble, the definitions for I<latexdiff> markup commands are inserted. +In differencing the main body of the text, I<latexdiff> attempts to +satisfy the following guidelines (in order of priority): + +=over 3 + +=item 1 + +If both C<old.tex> and C<new.tex> are valid LaTeX, then the resulting +C<diff.tex> should also be valid LateX. (NB If a few plain TeX commands +are used within C<old.tex> or C<new.tex> then C<diff.tex> is not +guaranteed to work but usually will). + +=item 2 + +Significant differences are determined on the level of +individual words. All significant differences, including differences +between comments should be clearly marked in the resulting source code +C<diff.tex>. + +=item 3 + +If a changed passage contains text or text-producing commands, then +running C<diff.tex> through LateX should produce output where added +and discarded passages are highlighted. + +=item 4 + +Where there are insignificant differences, e.g. in the positioning of +line breaks, C<diff.tex> should follow the formatting of C<new.tex> + +=back + +For differencing the same algorithm as I<diff> is used but words +instead of lines are compared. An attempt is made to recognize +blocks which are completely changed such that they can be marked up as a unit. +Comments are differenced line by line +but the number of spaces within comments is ignored. Commands including +all their arguments are generally compared as one unit, i.e., no mark-up +is inserted into the arguments of commands. However, for a selected +number of commands (for example, C<\caption> and all sectioning +commands) the last argument is known to be text. This text is +split into words and differenced just as ordinary text (use options to +show and change the list of text commands, see below). As the +algorithm has no detailed knowledge of LaTeX, it assumes all pairs of +curly braces immediately following a command (i.e. a sequence of +letters beginning with a backslash) are arguments for that command. +As a restriction to condition 1 above it is thus necessary to surround +all arguments with curly braces, and to not insert +extraneous spaces. For example, write + + \section{\textem{This is an emphasized section title}} + +and not + + \section {\textem{This is an emphasized section title}} + +or + + \section\textem{This is an emphasized section title} + +even though all varieties are the same to LaTeX (but see +B<--allow-spaces> option which allows the second variety). + +For environments whose content does not conform to standard LaTeX or +where graphical markup does not make sense all markup commands can be +removed by setting the PICTUREENV configuration variable, set by +default to C<picture> and C<DIFnomarkup> environments; see B<--config> +option). The latter environment (C<DIFnomarkup>) can be used to +protect parts of the latex file where the markup results in illegal +markup. You have to surround the offending passage in both the old and +new file by C<\begin{DIFnomarkup}> and C<\end{DIFnomarkup}>. You must +define the environment in the preambles of both old and new +documents. I prefer to define it as a null-environment, + +C<\newenvironment{DIFnomarkup}{}{}> + +but the choice is yours. Any markup within the environment will be +removed, and generally everything within the environment will just be +taken from the new file. + +It is also possible to difference files which do not have a preamble. + In this case, the file is processed in the main document +mode, but the definitions of the markup commands are not inserted. + +All markup commands inserted by I<latexdiff> begin with "C<\DIF>". Added +blocks containing words, commands or comments which are in C<new.tex> +but not in C<old.tex> are marked by C<\DIFaddbegin> and C<\DIFaddend>. +Discarded blocks are marked by C<\DIFdelbegin> and C<\DIFdelend>. +Within added blocks all text is highlighted with C<\DIFadd> like this: +C<\DIFadd{Added text block}> +Selected `safe' commands can be contained in these text blocks as well +(use options to show and change the list of safe commands, see below). +All other commands as well as braces "{" and "}" are never put within +the scope of C<\DIFadd>. Added comments are marked by prepending +"S<C<%DIF E<gt> >>". + +Within deleted blocks text is highlighted with C<\DIFdel>. Deleted +comments are marked by prepending "S<C<%DIF E<lt> >>". Non-safe command +and curly braces within deleted blocks are commented out with +"S<C<%DIFDELCMD E<lt> >>". + + + +=head1 OPTIONS + +=head2 Preamble + +The following options determine the visual markup style by adding the appropriate +command definitions to the preamble. See the end of this section for a description of +available styles. + +=over 4 + +=item B<--type=markupstyle> or +B<-t markupstyle> + +Add code to preamble for selected markup style. This option defines +C<\DIFadd> and C<\DIFdel> commands. +Available styles: + +C<UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE +CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR> + +[ Default: C<UNDERLINE> ] + +=item B<--subtype=markstyle> or +B<-s markstyle> + +Add code to preamble for selected style for bracketing +commands (e.g. to mark changes in margin). This option defines +C<\DIFaddbegin>, C<\DIFaddend>, C<\DIFdelbegin> and C<\DIFdelend> commands. +Available styles: C<SAFE MARGINAL COLOR DVIPSCOL> + +[ Default: C<SAFE> ] + +=item B<--floattype=markstyle> or +B<-f markstyle> + +Add code to preamble for selected style which +replace standard marking and markup commands within floats +(e.g., marginal remarks cause an error within floats +so marginal marking can be disabled thus). This option defines all +C<\DIF...FL> commands. +Available styles: C<FLOATSAFE TRADITIONALSAFE IDENTICAL> + +[ Default: C<FLOATSAFE> ] + +=item B<--encoding=enc> or +B<-e enc> + +Specify encoding of old.tex and new.tex. Typical encodings are +C<ascii>, C<utf8>, C<latin1>, C<latin9>. A list of available encodings can be +obtained by executing + +C<perl -MEncode -e 'print join ("\n",Encode->encodings( ":all" )) ;' > + +[Default encoding is utf8 unless the first few lines of the preamble contain +an invocation C<\usepackage[..]{inputenc}> in which case the +encoding chosen by this command is asssumed. Note that ASCII (standard +latex) is a subset of utf8] + +=item B<--preamble=file> or +B<-p file> + +Insert file at end of preamble instead of generating +preamble. The preamble must define the following commands +C<\DIFaddbegin, \DIFaddend, \DIFadd{..}, +\DIFdelbegin,\DIFdelend,\DIFdel{..},> +and varieties for use within floats +C<\DIFaddbeginFL, \DIFaddendFL, \DIFaddFL{..}, +\DIFdelbeginFL, \DIFdelendFL, \DIFdelFL{..}> +(If this option is set B<-t>, B<-s>, and B<-f> options +are ignored.) + +=item B<--packages=pkg1,pkg2,..> + +Tell latexdiff that .tex file is processed with the packages in list +loaded. This is normally not necessary if the .tex file includes the +preamble, as the preamble is automatically scanned for C<\usepackage> commands. +Use of the B<--packages> option disables automatic scanning, so if for any +reason package specific parsing needs to be switched off, use B<--packages=none>. +The following packages trigger special behaviour: + +=over 8 + +=item C<amsmath> + +Configuration variable amsmath is set to C<align*> (Default: C<eqnarray*>) + +=item C<endfloat> + +Ensure that C<\begin{figure}> and C<\end{figure}> always appear by themselves on a line. + +=item C<hyperref> + +Change name of C<\DIFadd> and C<\DIFdel> commands to C<\DIFaddtex> and C<\DIFdeltex> and +define new C<\DIFadd> and C<\DIFdel> commands, which provide a wrapper for these commands, +using them for the text but not for the link defining command (where any markup would cause +errors). + +=back + +[ Default: scan the preamble for C<\\usepackage> commands to determine + loaded packages.] + + + +=item B<--show-preamble> + +Print generated or included preamble commands to stdout. + +=back + +=head2 Configuration + +=over 4 + +=item B<--exclude-safecmd=exclude-file> or +B<-A exclude-file> or B<--exclude-safecmd="cmd1,cmd2,..."> + +=item B<--replace-safecmd=replace-file> + +=item B<--append-safecmd=append-file> or +B<-a append-file> or B<--append-safecmd="cmd1,cmd2,..."> + +Exclude from, replace or append to the list of regular expressions (RegEx) +matching commands which are safe to use within the +scope of a C<\DIFadd> or C<\DIFdel> command. The file must contain +one Perl-RegEx per line (Comment lines beginning with # or % are +ignored). Note that the RegEx needs to match the whole of +the token, i.e., /^regex$/ is implied and that the initial +"\" of the command is not included. +The B<--exclude-safecmd> and B<--append-safecmd> options can be combined with the -B<--replace-safecmd> +option and can be used repeatedly to add cumulatively to the lists. + B<--exclude-safecmd> +and B<--append-safecmd> can also take a comma separated list as input. If a +comma for one of the regex is required, escape it thus "\,". In most cases it +will be necessary to protect the comma-separated list from the shell by putting +it in quotation marks. + +=item B<--exclude-textcmd=exclude-file> or +B<-X exclude-file> or B<--exclude-textcmd="cmd1,cmd2,..."> + +=item B<--replace-textcmd=replace-file> + +=item B<--append-textcmd=append-file> or +B<-x append-file> or B<--append-textcmd="cmd1,cmd2,..."> + +Exclude from, replace or append to the list of regular expressions +matching commands whose last argument is text. See +entry for B<--exclude-safecmd> directly above for further details. + + +=item B<--replace-context1cmd=replace-file> + +=item B<--append-context1cmd=append-file> or +=item B<--append-context1cmd="cmd1,cmd2,..."> + +Replace or append to the list of regex matching commands +whose last argument is text but which require a particular +context to work, e.g. \caption will only work within a figure +or table. These commands behave like text commands, except when +they occur in a deleted section, when they are disabled, but their +argument is shown as deleted text. + +=item B<--replace-context2cmd=replace-file> + +=item B<--append-context2cmd=append-file> or +=item B<--append-context2cmd="cmd1,cmd2,..."> +As corresponding commands for context1. The only difference is that +context2 commands are completely disabled in deleted sections, including +their arguments. + + + +=item B<--config var1=val1,var2=val2,...> or B<-c var1=val1,..> + +=item B<-c configfile> + +Set configuration variables. The option can be repeated to set different +variables (as an alternative to the comma-separated list). +Available variables (see below for further explanations): + +C<MINWORDSBLOCK> (integer) + +C<FLOATENV> (RegEx) + +C<PICTUREENV> (RegEx) + +C<MATHENV> (RegEx) + +C<MATHREPL> (String) + +C<MATHARRENV> (RegEx) + +C<MATHARRREPL> (String) + +C<ARRENV> (RegEx) + +C<COUNTERCMD> (RegEx) + +=item B<--show-safecmd> + +Print list of RegEx matching and excluding safe commands. + +=item B<--show-textcmd> + +Print list of RegEx matching and excluding commands with text argument. + +=item B<--show-config> + +Show values of configuration variables. + +=item B<--show-all> + +Combine all --show commands. + +NB For all --show commands, no C<old.tex> or C<new.tex> file needs to be specified, and no +differencing takes place. + +=back + +=head2 Other configuration options: + +=over 4 + +=item B<--allow-spaces> + +Allow spaces between bracketed or braced arguments to commands. Note +that this option might have undesirable side effects (unrelated scope +might get lumpeded with preceding commands) so should only be used if the +default produces erroneous results. (Default requires arguments to +directly follow each other without intervening spaces). + +=item B<--math-markup=level> + +Determine granularity of markup in displayed math environments: +Possible values for level are (both numerical and text labels are acceptable): + +C<off> or C<0>: suppress markup for math environments. Deleted equations will not +appear in diff file. This mode can be used if all the other modes +cause invalid latex code. + +C<whole> or C<1>: Differencing on the level of whole equations. Even trivial changes +to equations cause the whole equation to be marked changed. This +mode can be used if processing in coarse or fine mode results in +invalid latex code. + +C<coarse> or C<2>: Detect changes within equations marked up with a coarse +granularity; changes in equation type (e.g.displaymath to equation) +appear as a change to the complete equation. This mode is recommended +for situations where the content and order of some equations are still +being changed. [Default] + +C<fine> or C<3>: Detect small change in equations and mark up at fine granularity. +This mode is most suitable, if only minor changes to equations are +expected, e.g. correction of typos. + +=item B<--disable-citation-markup> + +Suppress citation markup in styles using ulem (UNDERLINE, +FONTSTRIKE, CULINECHBAR) + +=item B<--enable-citation-markup> + +Protect citation commands in changed sections with \\mbox command [i.e. use default behaviour for ulem package for other packages] + +=back + +=head2 Miscellaneous + +=over 4 +=item B<--verbose> or B<-V> + +Output various status information to stderr during processing. +Default is to work silently. + +=item B<--driver=type> + +Choose driver for changebar package (only relevant for styles using + changebar: CCHANGEBAR CFONTCHBAR CULINECHBAR CHANGEBAR). Possible +drivers are listed in changebar manual, e.g. pdftex,dvips,dvitops + [Default: dvips] + +=item B<--ignore-warnings> + +Suppress warnings about inconsistencies in length between input and +parsed strings and missing characters. These warning messages are +often related to non-standard latex or latex constructions with a +syntax unknown to C<latexdiff> but the resulting difference argument +is often fully functional anyway, particularly if the non-standard +latex only occurs in parts of the text which have not changed. + +=item B<--label=label> or +B<-L label> + +Sets the labels used to describe the old and new files. The first use +of this option sets the label describing the old file and the second +use of the option sets the label for the new file, i.e. set both +labels like this C<-L labelold -L labelnew>. +[Default: use the filename and modification dates for the label] + +=item B<--no-label> + +Suppress inclusion of old and new file names as comment in output file + +=item B<--visble-label> + +Include old and new filenames (or labels set with --label option) as +visible output. + +=item B<--flatten> + +Replace C<\input> and C<\include> commands within body by the content +of the files in their argument. If C<\includeonly> is present in the +preamble, only those files are expanded into the document. However, +no recursion is done, i.e. C<\input> and C<\include> commands within +included sections are not expanded. The included files are assumed to + be located in the same directories as the old and new master files, +respectively, making it possible to organise files into old and new directories. +--flatten is applied recursively, so inputted files can contain further +C<\input> statements. + +Use of this option might result in prohibitive processing times for +larger documents, and the resulting difference document +no longer reflects the structure of the input documents. + +=item B<--help> or +B<-h> + +Show help text + +=item B<--version> + +Show version number + +=back + + + +=head2 Predefined styles + +=head2 Major types + +The major type determine the markup of plain text and some selected latex commands outside floats by defining the markup commands C<\DIFadd{...}> and C<\DIFdel{...}> . + +=over 10 + +=item C<UNDERLINE> + +Added text is wavy-underlined and blue, discarded text is struck out and red +(Requires color and ulem packages). Overstriking does not work in displayed math equations such that deleted parts of equation are underlined, not struck out (this is a shortcoming inherent to the ulem package). + +=item C<CTRADITIONAL> + +Added text is blue and set in sans-serif, and a red footnote is created for each discarded +piece of text. (Requires color package) + +=item C<TRADITIONAL> + +Like C<CTRADITIONAL> but without the use of color. + +=item C<CFONT> + +Added text is blue and set in sans-serif, and discarded text is red and very small size. + +=item C<FONTSTRIKE> + +Added tex is set in sans-serif, discarded text small and struck out + +=item C<CCHANGEBAR> + +Added text is blue, and discarded text is red. Additionally, the changed text is marked with a bar in the margin (Requires color and changebar packages). + +=item C<CFONTCHBAR> + +Like C<CFONT> but with additional changebars (Requires color and changebar packages). + +=item C<CULINECHBAR> + +Like C<UNDERLINE> but with additional changebars (Requires color, ulem and changebar packages). + +=item C<CHANGEBAR> + +No mark up of text, but mark margins with changebars (Requires changebar package). + +=item C<INVISIBLE> + +No visible markup (but generic markup commands will still be inserted. + +=back + +=head2 Subtypes + +The subtype defines the commands that are inserted at the begin and end of added or discarded blocks, irrespectively of whether these blocks contain text or commands (Defined commands: C<\DIFaddbegin, \DIFaddend, \DIFdelbegin, \DIFdelend>) + +=over 10 + +=item C<SAFE> + +No additional markup (Recommended choice) + +=item C<MARGIN> + +Mark beginning and end of changed blocks with symbols in the margin nearby (using +the standard C<\marginpar> command - note that this sometimes moves somewhat +from the intended position. + +=item C<COLOR> + +An alternative way of marking added passages in blue, and deleted ones in red. +(It is recommeneded to use instead the main types to effect colored markup, +although in some cases coloring with dvipscol can be more complete, for example +with citation commands). + +=item C<DVIPSCOL> + +An alternative way of marking added passages in blue, and deleted ones in red. Note +that C<DVIPSCOL> only works with the dvips converter, e.g. not pdflatex. +(it is recommeneded to use instead the main types to effect colored markup, +although in some cases coloring with dvipscol can be more complete). + +=back + +=head2 Float Types + +Some of the markup used in the main text might cause problems when used within +floats (e.g. figures or tables). For this reason alternative versions of all +markup commands are used within floats. The float type defines these alternative commands. + +=over 10 + +=item C<FLOATSAFE> + +Use identical markup for text as in the main body, but set all commands marking the begin and end of changed blocks to null-commands. You have to choose this float type if your subtype is C<MARGIN> as C<\marginpar> does not work properly within floats. + +=item C<TRADITIONALSAFE> + +Mark additions the same way as in the main text. Deleted environments are marked by angular brackets \[ and \] and the deleted text is set in scriptscript size. This float type should always be used with the C<TRADITIONAL> and C<CTRADITIONAL> markup types as the \footnote command does not work properly in floating environments. + +=item C<IDENTICAL> + +Make no difference between the main text and floats. + +=back + + +=head2 Configuration Variables + +=over 10 + +=item C<MINWORDSBLOCK> + +Minimum number of tokens required to form an independent block. This value is +used in the algorithm to detect changes of complete blocks by merging identical text parts of less than C<MINWORDSBLOCK> to the preceding added and discarded parts. + +[ Default: 3 ] + +=item C<FLOATENV> + +Environments whose name matches the regular expression in C<FLOATENV> are +considered floats. Within these environments, the I<latexdiff> markup commands +are replaced by their FL variaties. + +[ Default: S<C<(?:figure|table|plate)[\w\d*@]*> >] + +=item C<PICTUREENV> + +Within environments whose name matches the regular expression in C<PICTUREENV> +all latexdiff markup is removed (in pathologic cases this might lead to + inconsistent markup but this situation should be rare). + +[ Default: S<C<(?:picture|DIFnomarkup)[\w\d*@]*> >] + +=item C<MATHENV>,C<MATHREPL> + +If both \begin and \end for a math environment (environment name matching C<MATHENV> +or \[ and \]) +are within the same deleted block, they are replaced by a \begin and \end commands for C<MATHREPL> +rather than being commented out. + +[ Default: C<MATHENV>=S<C<(?:displaymath|equation)> >, C<MATHREPL>=S<C<displaymath> >] + +=item C<MATHARRENV>,C<MATHARRREPL> + +as C<MATHENV>,C<MATHREPL> but for equation arrays + +[ Default: C<MATHARRENV>=S<C<eqnarray\*?> >, C<MATHREPL>=S<C<eqnarray> >] + +=item C<ARRENV> + +If a match to C<ARRENV> is found within an inline math environment within a deleted or added block, then the inlined math +is surrounded by C<\mbox{>...C<}>. This is necessary as underlining does not work within inlined array environments. + +[ Default: C<ARRENV>=S<C<(?:array|[pbvBV]matrix)> > + +=item C<COUNTERCMD> + +If a command in a deleted block which is also in the textcmd list matches C<COUNTERCMD> then an +additional command C<\addtocounter{>F<cntcmd>C<}{-1}>, where F<cntcmd> is the matching command, is appended in the diff file such that the numbering in the diff file remains synchronized with the +numbering in the new file. + +[ Default: C<COUNTERCMD>=C<(?:footnote|part|section|subsection> ... + +C<|subsubsection|paragraph|subparagraph)> ] + +=back + +=head1 COMMON PROBLEMS + +=over 10 + +=item Citations result in overfull boxes + +There is an incompatibility between the C<ulem> package, which C<latexdiff> uses for underlining and striking out in the UNDERLINE style, +the default style. In order to be able to mark up citations properly, they are placed with an C<\mbox> command in post-processing. As mboxes +cannot be broken across lines, this procedure frequently results in overfull boxes, possibly obscuring the content as it extends beyond the right margin. If this is a problem, you have two possibilities: + +1. Use C<COLOR> or C<DVIPSCOL> subtype markup (option C<-s COLOR>): If this markup is chosen, then changed citations are no longer marked up +with the wavy line (additions) or struck out (deletions), but are still highlighted in the appropriate color. + +2. Choose option C<--disable-citation-markup> which turns off the marking up of citations: deleted citations are no longer shown, and +added ctations are shown without markup. (This was the default behaviour of latexdiff at versions 0.6 and older) + +=item Changes in complicated mathematical equations result in latex processing errors + +Try options C<--math-markup=whole>. If even that fails, you can turn off mark up for equations with C<--math-markup=off>. + +=back + +=head1 BUGS + +Option allow-spaces not implemented entirely consistently. It breaks +the rules that number and type of white space does not matter, as +different numbers of inter-argument spaces are treated as significant. + +Please submit bug reports on the latexdiff project page I<http://latexdiff.berlios.de>, +send them to user discussion list C<latexdiff-users@lists.berlios,de> (prior subscription +to list required, also on project webpage) +or send them to I<tilmann@gfz-potsdam.de>. Include the serial number of I<latexdiff> +(from comments at the top of the source or use B<--version>). If you come across latex +files that are error-free and conform to the specifications set out +above, and whose differencing still does not result in error-free +latex, please send me those files, ideally edited to only contain the +offending passage as long as that still reproduces the problem. If your +file relies on non-standard class files, you must include those. I will not +look at examples where I have trouble to latex the original files. + +=head1 SEE ALSO + +L<latexrevise>, L<latexdiff-vc> + +=head1 PORTABILITY + +I<latexdiff> does not make use of external commands and thus should run +on any platform supporting Perl 5.6 or higher. If files with encodings +other than ASCII or UTF-8 are processed, Perl 5.8 or higher is required. + +The standard version of I<latexdiff> requires installation of the Perl package +C<Algorithm::Diff> (available from I<www.cpan.org> - +I<http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15>) but a stand-alone +version, I<latexdiff-so>, which has this package inlined, is available, too. +I<latexdiff-fast> requires the I<diff> command to be present. + +=head1 AUTHOR + +Version 1.0.2 +Copyright (C) 2004-2012 Frederik Tilmann + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 + +Contributors of fixes and additions: V. Kuhlmann, J. Paisley, N. Becker, T. Doerges, K. Huebner, +T. Connors, Sebastian Gouezel and many others. +Thanks to the many people who send in bug reports, feature suggestions, and other feedback. + +=cut + +__END__ +%%BEGIN SAFE COMMANDS +% Regex matching commands which can safely be in the +% argument of a \DIFadd or \DIFdel command (leave out the \) +arabic +dashbox +emph +fbox +framebox +hspace +math.* +makebox +mbox +pageref +ref +symbol +raisebox +rule +text.* +shortstack +usebox +dag +ddag +copyright +pounds +S +P +oe +OE +ae +AE +aa +AA +o +O +l +L +frac +ss +sqrt +ldots +cdots +vdots +ddots +alpha +beta +gamma +delta +epsilon +varepsilon +zeta +eta +theta +vartheta +iota +kappa +lambda +mu +nu +xi +pi +varpi +rho +varrho +sigma +varsigma +tau +upsilon +phi +varphi +chi +psi +omega +Gamma +Delta +Theta +Lambda +Xi +Pi +Sigma +Upsilon +Phi +Psi +Omega +ps +mp +times +div +ast +star +circ +bullet +cdot +cap +cup +uplus +sqcap +vee +wedge +setminus +wr +diamond +(?:big)?triangle.* +lhd +rhd +unlhd +unrhd +oplus +ominus +otimes +oslash +odot +bigcirc +d?dagger +amalg +leq +prec +preceq +ll +(?:sq)?su[bp]set(?:eq)? +in +vdash +geq +succ(?:eq)? +gg +ni +dashv +equiv +sim(?:eq)? +asymp +approx +cong +neq +doteq +propto +models +perp +mid +parallel +bowtie +Join +smile +frown +.*arrow +(?:long)?mapsto +.*harpoon.* +leadsto +aleph +hbar +imath +jmath +ell +wp +Re +Im +mho +prime +emptyset +nabla +surd +top +bot +angle +forall +exists +neg +flat +natural +sharp +backslash +partial +infty +Box +Diamond +triangle +clubsuit +diamondsuit +heartsuit +spadesuit +sum +prod +coprod +int +oint +big(?:sq)?c[au]p +bigvee +bigwedge +bigodot +bigotimes +bigoplus +biguplus +(?:arc)?(?:cos|sin|tan|cot)h? +csc +arg +deg +det +dim +exp +gcd +hom +inf +ker +lg +lim +liminf +limsup +ln +log +max +min +Pr +sec +sup +(SUPER|SUB)SCRIPTNB +(SUPER|SUB)SCRIPT +%%END SAFE COMMANDS + +%%BEGIN TEXT COMMANDS +% Regex matching commands with a text argument (leave out the \) +addcontents.* +cc +closing +chapter +dashbox +emph +encl +fbox +framebox +footnote +footnotetext +framebox +part +(sub){0,2}section\*? +(sub)?paragraph\*? +makebox +mbox +opening +parbox +raisebox +savebox +sbox +shortstack +signature +text.* +value +underline +sqrt +(SUPER|SUB)SCRIPT +%%END TEXT COMMANDS + +%%BEGIN CONTEXT1 COMMANDS +% Regex matching commands with a text argument (leave out the \), which will fail out of context, but whose argument should be printed as plain text +caption +%%END CONTEXT1 COMMANDS + +%%BEGIN CONTEXT2 COMMANDS +% Regex matching commands with a text argument (leave out the \), which will fail out of context, but whose argument should be printed as plain text +title +author +date +institute +%%END CONTEXT2 COMMANDS + + +%% TYPES (Commands for highlighting changed blocks) + +%DIF UNDERLINE PREAMBLE +\RequirePackage[normalem]{ulem} +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{{\protect\color{blue}\uwave{#1}}} +\providecommand{\DIFdel}[1]{{\protect\color{red}\sout{#1}}} +%DIF END UNDERLINE PREAMBLE + +%DIF CTRADITIONAL PREAMBLE +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\RequirePackage[stable]{footmisc} +\providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}} +\providecommand{\DIFdel}[1]{{\protect\color{red} [..\footnote{removed: #1} ]}} +%DIF END CTRADITIONAL PREAMBLE + +%DIF TRADITIONAL PREAMBLE +\RequirePackage[stable]{footmisc} +\providecommand{\DIFadd}[1]{{\sf #1}} +\providecommand{\DIFdel}[1]{{[..\footnote{removed: #1} ]}} +%DIF END TRADITIONAL PREAMBLE + +%DIF CFONT PREAMBLE +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}} +\providecommand{\DIFdel}[1]{{\protect\color{red} \scriptsize #1}} +%DIF END CFONT PREAMBLE + +%DIF FONTSTRIKE PREAMBLE +\RequirePackage[normalem]{ulem} +\providecommand{\DIFadd}[1]{{\sf #1}} +\providecommand{\DIFdel}[1]{{\footnotesize \sout{#1}}} +%DIF END FONTSTRIKE PREAMBLE + +%DIF CCHANGEBAR PREAMBLE +\RequirePackage[dvips]{changebar} +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}#1}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}#1}\protect\cbdelete} +%DIF END CCHANGEBAR PREAMBLE + +%DIF CFONTCHBAR PREAMBLE +\RequirePackage[dvips]{changebar} +\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} +\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\sf #1}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\scriptsize #1}\protect\cbdelete} +%DIF END CFONTCHBAR PREAMBLE + +%DIF CULINECHBAR PREAMBLE +\RequirePackage[normalem]{ulem} +\RequirePackage[dvips]{changebar} +\RequirePackage{color} +\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\uwave{#1}}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\sout{#1}}\protect\cbdelete} +%DIF END CULINECHBAR PREAMBLE + +%DIF CHANGEBAR PREAMBLE +\RequirePackage[dvips]{changebar} +\providecommand{\DIFadd}[1]{\protect\cbstart{#1}\protect\cbend} +\providecommand{\DIFdel}[1]{\protect\cbdelete} +%DIF END CHANGEBAR PREAMBLE + +%DIF INVISIBLE PREAMBLE +\providecommand{\DIFadd}[1]{#1} +\providecommand{\DIFdel}[1]{} +%DIF END INVISIBLE PREAMBLE + + +%% SUBTYPES (Markers for beginning and end of changed blocks) + +%DIF SAFE PREAMBLE +\providecommand{\DIFaddbegin}{} +\providecommand{\DIFaddend}{} +\providecommand{\DIFdelbegin}{} +\providecommand{\DIFdelend}{} +%DIF END SAFE PREAMBLE + +%DIF MARGIN PREAMBLE +\providecommand{\DIFaddbegin}{\protect\marginpar{a[}} +\providecommand{\DIFaddend}{\protect\marginpar{]}} +\providecommand{\DIFdelbegin}{\protect\marginpar{d[}} +\providecommand{\DIFdelend}{\protect\marginpar{]}} +%DIF END BRACKET PREAMBLE + +%DIF DVIPSCOL PREAMBLE +%Note: only works with dvips converter +\RequirePackage{color} +\RequirePackage{dvipscol} +\providecommand{\DIFaddbegin}{\protect\nogroupcolor{blue}} +\providecommand{\DIFaddend}{\protect\nogroupcolor{black}} +\providecommand{\DIFdelbegin}{\protect\nogroupcolor{red}} +\providecommand{\DIFdelend}{\protect\nogroupcolor{black}} +%DIF END DVIPSCOL PREAMBLE + +%DIF COLOR PREAMBLE +\RequirePackage{color} +\providecommand{\DIFaddbegin}{\protect\color{blue}} +\providecommand{\DIFaddend}{\protect\color{black}} +\providecommand{\DIFdelbegin}{\protect\color{red}} +\providecommand{\DIFdelend}{\protect\color{black}} +%DIF END COLOR PREAMBLE + + +%% FLOAT TYPES + +%DIF FLOATSAFE PREAMBLE +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} +\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} +\providecommand{\DIFaddbeginFL}{} +\providecommand{\DIFaddendFL}{} +\providecommand{\DIFdelbeginFL}{} +\providecommand{\DIFdelendFL}{} +%DIF END FLOATSAFE PREAMBLE + +%DIF IDENTICAL PREAMBLE +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} +\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} +\providecommand{\DIFaddbeginFL}{\DIFaddbegin} +\providecommand{\DIFaddendFL}{\DIFaddend} +\providecommand{\DIFdelbeginFL}{\DIFdelbegin} +\providecommand{\DIFdelendFL}{\DIFdelend} +%DIF END IDENTICAL PREAMBLE + +%DIF TRADITIONALSAFE PREAMBLE +% procidecommand color to make this work for TRADITIONAL and CTRADITIONAL +\providecommand{\color}[1]{} +\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} +\providecommand{\DIFdel}[1]{{\protect\color{red}[..{\scriptsize {removed: #1}} ]}} +\providecommand{\DIFaddbeginFL}{} +\providecommand{\DIFaddendFL}{} +\providecommand{\DIFdelbeginFL}{} +\providecommand{\DIFdelendFL}{} +%DIF END FLOATSAFE PREAMBLE + +%% SPECIAL PACKAGE PREAMBLE COMMANDS + +% Standard \DIFadd and \DIFdel are redefined as \DIFaddtex and \DIFdeltex +% when hyperref package is included. +%DIF HYPERREF PREAMBLE +\providecommand{\DIFadd}[1]{\texorpdfstring{\DIFaddtex{#1}}{#1}} +\providecommand{\DIFdel}[1]{\texorpdfstring{\DIFdeltex{#1}}{}} +%DIF END HYPERREF PACKAGE |