├── LICENSE ├── Math in Mathcad15.png ├── Parsed to LaTeX.png ├── README.md ├── main.py └── symbol_parser.py /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 58 | Public License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 63 | ("Public License"). To the extent this Public License may be 64 | interpreted as a contract, You are granted the Licensed Rights in 65 | consideration of Your acceptance of these terms and conditions, and the 66 | Licensor grants You such rights in consideration of benefits the 67 | Licensor receives from making the Licensed Material available under 68 | these terms and conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-NC-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution, NonCommercial, and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. NonCommercial means not primarily intended for or directed towards 126 | commercial advantage or monetary compensation. For purposes of 127 | this Public License, the exchange of the Licensed Material for 128 | other material subject to Copyright and Similar Rights by digital 129 | file-sharing or similar means is NonCommercial provided there is 130 | no payment of monetary compensation in connection with the 131 | exchange. 132 | 133 | l. Share means to provide material to the public by any means or 134 | process that requires permission under the Licensed Rights, such 135 | as reproduction, public display, public performance, distribution, 136 | dissemination, communication, or importation, and to make material 137 | available to the public including in ways that members of the 138 | public may access the material from a place and at a time 139 | individually chosen by them. 140 | 141 | m. Sui Generis Database Rights means rights other than copyright 142 | resulting from Directive 96/9/EC of the European Parliament and of 143 | the Council of 11 March 1996 on the legal protection of databases, 144 | as amended and/or succeeded, as well as other essentially 145 | equivalent rights anywhere in the world. 146 | 147 | n. You means the individual or entity exercising the Licensed Rights 148 | under this Public License. Your has a corresponding meaning. 149 | 150 | 151 | Section 2 -- Scope. 152 | 153 | a. License grant. 154 | 155 | 1. Subject to the terms and conditions of this Public License, 156 | the Licensor hereby grants You a worldwide, royalty-free, 157 | non-sublicensable, non-exclusive, irrevocable license to 158 | exercise the Licensed Rights in the Licensed Material to: 159 | 160 | a. reproduce and Share the Licensed Material, in whole or 161 | in part, for NonCommercial purposes only; and 162 | 163 | b. produce, reproduce, and Share Adapted Material for 164 | NonCommercial purposes only. 165 | 166 | 2. Exceptions and Limitations. For the avoidance of doubt, where 167 | Exceptions and Limitations apply to Your use, this Public 168 | License does not apply, and You do not need to comply with 169 | its terms and conditions. 170 | 171 | 3. Term. The term of this Public License is specified in Section 172 | 6(a). 173 | 174 | 4. Media and formats; technical modifications allowed. The 175 | Licensor authorizes You to exercise the Licensed Rights in 176 | all media and formats whether now known or hereafter created, 177 | and to make technical modifications necessary to do so. The 178 | Licensor waives and/or agrees not to assert any right or 179 | authority to forbid You from making technical modifications 180 | necessary to exercise the Licensed Rights, including 181 | technical modifications necessary to circumvent Effective 182 | Technological Measures. For purposes of this Public License, 183 | simply making modifications authorized by this Section 2(a) 184 | (4) never produces Adapted Material. 185 | 186 | 5. Downstream recipients. 187 | 188 | a. Offer from the Licensor -- Licensed Material. Every 189 | recipient of the Licensed Material automatically 190 | receives an offer from the Licensor to exercise the 191 | Licensed Rights under the terms and conditions of this 192 | Public License. 193 | 194 | b. Additional offer from the Licensor -- Adapted Material. 195 | Every recipient of Adapted Material from You 196 | automatically receives an offer from the Licensor to 197 | exercise the Licensed Rights in the Adapted Material 198 | under the conditions of the Adapter's License You apply. 199 | 200 | c. No downstream restrictions. You may not offer or impose 201 | any additional or different terms or conditions on, or 202 | apply any Effective Technological Measures to, the 203 | Licensed Material if doing so restricts exercise of the 204 | Licensed Rights by any recipient of the Licensed 205 | Material. 206 | 207 | 6. No endorsement. Nothing in this Public License constitutes or 208 | may be construed as permission to assert or imply that You 209 | are, or that Your use of the Licensed Material is, connected 210 | with, or sponsored, endorsed, or granted official status by, 211 | the Licensor or others designated to receive attribution as 212 | provided in Section 3(a)(1)(A)(i). 213 | 214 | b. Other rights. 215 | 216 | 1. Moral rights, such as the right of integrity, are not 217 | licensed under this Public License, nor are publicity, 218 | privacy, and/or other similar personality rights; however, to 219 | the extent possible, the Licensor waives and/or agrees not to 220 | assert any such rights held by the Licensor to the limited 221 | extent necessary to allow You to exercise the Licensed 222 | Rights, but not otherwise. 223 | 224 | 2. Patent and trademark rights are not licensed under this 225 | Public License. 226 | 227 | 3. To the extent possible, the Licensor waives any right to 228 | collect royalties from You for the exercise of the Licensed 229 | Rights, whether directly or through a collecting society 230 | under any voluntary or waivable statutory or compulsory 231 | licensing scheme. In all other cases the Licensor expressly 232 | reserves any right to collect such royalties, including when 233 | the Licensed Material is used other than for NonCommercial 234 | purposes. 235 | 236 | 237 | Section 3 -- License Conditions. 238 | 239 | Your exercise of the Licensed Rights is expressly made subject to the 240 | following conditions. 241 | 242 | a. Attribution. 243 | 244 | 1. If You Share the Licensed Material (including in modified 245 | form), You must: 246 | 247 | a. retain the following if it is supplied by the Licensor 248 | with the Licensed Material: 249 | 250 | i. identification of the creator(s) of the Licensed 251 | Material and any others designated to receive 252 | attribution, in any reasonable manner requested by 253 | the Licensor (including by pseudonym if 254 | designated); 255 | 256 | ii. a copyright notice; 257 | 258 | iii. a notice that refers to this Public License; 259 | 260 | iv. a notice that refers to the disclaimer of 261 | warranties; 262 | 263 | v. a URI or hyperlink to the Licensed Material to the 264 | extent reasonably practicable; 265 | 266 | b. indicate if You modified the Licensed Material and 267 | retain an indication of any previous modifications; and 268 | 269 | c. indicate the Licensed Material is licensed under this 270 | Public License, and include the text of, or the URI or 271 | hyperlink to, this Public License. 272 | 273 | 2. You may satisfy the conditions in Section 3(a)(1) in any 274 | reasonable manner based on the medium, means, and context in 275 | which You Share the Licensed Material. For example, it may be 276 | reasonable to satisfy the conditions by providing a URI or 277 | hyperlink to a resource that includes the required 278 | information. 279 | 3. If requested by the Licensor, You must remove any of the 280 | information required by Section 3(a)(1)(A) to the extent 281 | reasonably practicable. 282 | 283 | b. ShareAlike. 284 | 285 | In addition to the conditions in Section 3(a), if You Share 286 | Adapted Material You produce, the following conditions also apply. 287 | 288 | 1. The Adapter's License You apply must be a Creative Commons 289 | license with the same License Elements, this version or 290 | later, or a BY-NC-SA Compatible License. 291 | 292 | 2. You must include the text of, or the URI or hyperlink to, the 293 | Adapter's License You apply. You may satisfy this condition 294 | in any reasonable manner based on the medium, means, and 295 | context in which You Share Adapted Material. 296 | 297 | 3. You may not offer or impose any additional or different terms 298 | or conditions on, or apply any Effective Technological 299 | Measures to, Adapted Material that restrict exercise of the 300 | rights granted under the Adapter's License You apply. 301 | 302 | 303 | Section 4 -- Sui Generis Database Rights. 304 | 305 | Where the Licensed Rights include Sui Generis Database Rights that 306 | apply to Your use of the Licensed Material: 307 | 308 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 309 | to extract, reuse, reproduce, and Share all or a substantial 310 | portion of the contents of the database for NonCommercial purposes 311 | only; 312 | 313 | b. if You include all or a substantial portion of the database 314 | contents in a database in which You have Sui Generis Database 315 | Rights, then the database in which You have Sui Generis Database 316 | Rights (but not its individual contents) is Adapted Material, 317 | including for purposes of Section 3(b); and 318 | 319 | c. You must comply with the conditions in Section 3(a) if You Share 320 | all or a substantial portion of the contents of the database. 321 | 322 | For the avoidance of doubt, this Section 4 supplements and does not 323 | replace Your obligations under this Public License where the Licensed 324 | Rights include other Copyright and Similar Rights. 325 | 326 | 327 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 328 | 329 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 330 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 331 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 332 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 333 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 334 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 335 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 336 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 337 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 338 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 339 | 340 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 341 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 342 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 343 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 344 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 345 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 346 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 347 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 348 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 349 | 350 | c. The disclaimer of warranties and limitation of liability provided 351 | above shall be interpreted in a manner that, to the extent 352 | possible, most closely approximates an absolute disclaimer and 353 | waiver of all liability. 354 | 355 | 356 | Section 6 -- Term and Termination. 357 | 358 | a. This Public License applies for the term of the Copyright and 359 | Similar Rights licensed here. However, if You fail to comply with 360 | this Public License, then Your rights under this Public License 361 | terminate automatically. 362 | 363 | b. Where Your right to use the Licensed Material has terminated under 364 | Section 6(a), it reinstates: 365 | 366 | 1. automatically as of the date the violation is cured, provided 367 | it is cured within 30 days of Your discovery of the 368 | violation; or 369 | 370 | 2. upon express reinstatement by the Licensor. 371 | 372 | For the avoidance of doubt, this Section 6(b) does not affect any 373 | right the Licensor may have to seek remedies for Your violations 374 | of this Public License. 375 | 376 | c. For the avoidance of doubt, the Licensor may also offer the 377 | Licensed Material under separate terms or conditions or stop 378 | distributing the Licensed Material at any time; however, doing so 379 | will not terminate this Public License. 380 | 381 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 382 | License. 383 | 384 | 385 | Section 7 -- Other Terms and Conditions. 386 | 387 | a. The Licensor shall not be bound by any additional or different 388 | terms or conditions communicated by You unless expressly agreed. 389 | 390 | b. Any arrangements, understandings, or agreements regarding the 391 | Licensed Material not stated herein are separate from and 392 | independent of the terms and conditions of this Public License. 393 | 394 | 395 | Section 8 -- Interpretation. 396 | 397 | a. For the avoidance of doubt, this Public License does not, and 398 | shall not be interpreted to, reduce, limit, restrict, or impose 399 | conditions on any use of the Licensed Material that could lawfully 400 | be made without permission under this Public License. 401 | 402 | b. To the extent possible, if any provision of this Public License is 403 | deemed unenforceable, it shall be automatically reformed to the 404 | minimum extent necessary to make it enforceable. If the provision 405 | cannot be reformed, it shall be severed from this Public License 406 | without affecting the enforceability of the remaining terms and 407 | conditions. 408 | 409 | c. No term or condition of this Public License will be waived and no 410 | failure to comply consented to unless expressly agreed to by the 411 | Licensor. 412 | 413 | d. Nothing in this Public License constitutes or may be interpreted 414 | as a limitation upon, or waiver of, any privileges and immunities 415 | that apply to the Licensor or You, including from the legal 416 | processes of any jurisdiction or authority. 417 | 418 | ======================================================================= 419 | 420 | Creative Commons is not a party to its public 421 | licenses. Notwithstanding, Creative Commons may elect to apply one of 422 | its public licenses to material it publishes and in those instances 423 | will be considered the “Licensor.” The text of the Creative Commons 424 | public licenses is dedicated to the public domain under the CC0 Public 425 | Domain Dedication. Except for the limited purpose of indicating that 426 | material is shared under a Creative Commons public license or as 427 | otherwise permitted by the Creative Commons policies published at 428 | creativecommons.org/policies, Creative Commons does not authorize the 429 | use of the trademark "Creative Commons" or any other trademark or logo 430 | of Creative Commons without its prior written consent including, 431 | without limitation, in connection with any unauthorized modifications 432 | to any of its public licenses or any other arrangements, 433 | understandings, or agreements concerning use of licensed material. For 434 | the avoidance of doubt, this paragraph does not form part of the 435 | public licenses. 436 | 437 | Creative Commons may be contacted at creativecommons.org. 438 | -------------------------------------------------------------------------------- /Math in Mathcad15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtificialTruth/PTC-Mathcad-to-LaTeX-parser/0e2cd4ac57e0997ae40fabb263dcf6dc3d99d287/Math in Mathcad15.png -------------------------------------------------------------------------------- /Parsed to LaTeX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtificialTruth/PTC-Mathcad-to-LaTeX-parser/0e2cd4ac57e0997ae40fabb263dcf6dc3d99d287/Parsed to LaTeX.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PTC-Mathcad-to-LaTeX-parser 2 | A Python 3.x application to read PTC Mathcad 15 files and output them as LaTeX files. 3 | Doesn't fully work with everything yet. I've mainly added support for the math I use myself. 4 | Only tested on Mathcad 15 *.xmcd* files, the structure is probably different for other versions? 5 | 6 | This work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-nc-sa/4.0/). 7 | [![Creative Commons License](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | 9 | ![Creative Commons License](https://github.com/ArtificialTruth/PTC-Mathcad-to-LaTeX-parser/blob/master/Math%20in%20Mathcad15.png) 10 | ![Creative Commons License](https://github.com/ArtificialTruth/PTC-Mathcad-to-LaTeX-parser/blob/master/Parsed%20to%20LaTeX.png) 11 | 12 | ## Quick (and dirty) guide 13 | 1. Install Anaconda for Python 3.x at https://www.continuum.io/downloads 14 | 2. Reboot? 15 | 3. Git clone this repository (Download all files) 16 | 4. Doubleclick main.py (or run the file from shell) 17 | 5. Click some buttons in the GUI 18 | 6. Pray that all your desired math is supported 19 | 20 | View the generated LaTeX file with a editor, or generate a pdf. 21 | I can recommend http://www.xm1math.net/texmaker/, which works on Windows, MacOsX and Linux. 22 | To be documented... 23 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import xml.etree.ElementTree as ElemTree # Import the XML ElementTree module aliased as ET 3 | import io # Import used for more advanced file writing 4 | import os # Used for file name handling 5 | import numpy # Used for arrays, and more advanced math manipulation 6 | import base64 # Used for reading base64 encoded pictures 7 | from tkinter.filedialog import askopenfilename # Tkinter libs or selecting files in OS file picker 8 | from tkinter import Tk, Frame, Button, Label, StringVar, Entry # Used for the GUI 9 | # Other files 10 | from symbol_parser import symbol_parser # Import function which formats special charecters 11 | 12 | 13 | class ParseGUI(object): 14 | """Class used for the GUI 15 | The object parameter is the root widget for the Tkinter GUI 16 | """ 17 | 18 | def __init__(self, master): 19 | """Constructor method 20 | 21 | :param master: A "master" wigdet 22 | """ 23 | self.mainframe = Frame(master) # Create a Frame child widget 24 | self.mainframe.pack() # Make the widget visible 25 | 26 | self.path = '' # Define default path for the .xmcd file 27 | self.texfile_path = '' # Define path for the .tex file 28 | 29 | self.name = Label(self.mainframe, text="Welcome to Mathcad to LaTeX converter") # Create a static text label 30 | self.name.pack(side="top") # Make the widget visible and define location 31 | 32 | self.filename = StringVar() # Create a dynamic string variable 33 | self.filename.set("Current selected file: none") # Set the string value 34 | self.filename_label = Label(self.mainframe, textvariable=self.filename) # Create a label with the dynamic var 35 | self.filename_label.pack() 36 | self.text_updater = Entry(self.mainframe, textvariable=self.filename) # Create a Entry widget for auto updates 37 | 38 | self.status = StringVar() # Used for displaying the status of the file operation 39 | self.status.set("Status: Not parsed") 40 | self.status_label = Label(self.mainframe, textvariable=self.status) 41 | self.status_label.pack() 42 | self.text_updater2 = Entry(self.mainframe, textvariable=self.status) 43 | 44 | self.parse_file = Button(self.mainframe, text="Parse and save!", command=self.parse_file) # Button for parsing 45 | self.parse_file.pack(side="right") 46 | 47 | self.parse_file = Button(self.mainframe, text="Open LaTeX file", command=self.open_file) 48 | self.parse_file.pack(side="right") 49 | 50 | self.select_file = Button(self.mainframe, text="Select file", command=self.select_file) # Runs a class method 51 | self.select_file.pack(side="right") 52 | 53 | def select_file(self): # Method used for selecting a file 54 | self.path = askopenfilename() # Display native os file dialog for choosing a file 55 | self.filename.set("Current selected file: " + os.path.basename(self.path)) # Change the dynamic variable 56 | self.status.set("Status: Not parsed") # Set status 57 | 58 | def open_file(self): # Method used for opening the parsed LaTeX file 59 | self.texfile_path = os.path.dirname(self.path) + '/ParsedLatexFile/' + \ 60 | os.path.splitext(os.path.basename(self.path))[0] + '.tex' 61 | if self.status.get() == "Status: File tried parsed! Look under the folder \ParsedLatexFile !": 62 | os.system("start " + "\"\" \"" + self.texfile_path + "\"") 63 | 64 | def parse_file(self): # Method for parsing the chosen file 65 | # Make sure a file is selected and it is a Mathcad file before trying to parse it 66 | if self.filename.get() != 'Current selected file: none' and os.path.splitext(self.path)[1] == '.xmcd': 67 | self.status.set("Status: Tring to parse... (most files takes a few seconds)") 68 | MathcadXMLParser(self.path) # Call the MathcadXMLParser class with the path 69 | self.status.set("Status: File tried parsed! Look under the folder \ParsedLatexFile !") 70 | # Display a error message to the user 71 | else: 72 | self.status.set("Status: You need to select a .xmcd (Mathcad) file!") 73 | 74 | 75 | class MathcadXMLParser(object): 76 | """Class used for reading Mathcad files and writing LaTeX files 77 | 78 | Parses the results from the file reading and saving it 79 | Takes in a object (the file path) 80 | """ 81 | 82 | def __init__(self, filename): 83 | """The constructor method 84 | 85 | :param filename: A path to the targeted file 86 | """ 87 | 88 | self.target_file = filename # Define the filename as a class variable 89 | self.filename = os.path.basename(self.target_file).replace('.xmcd', '') # Get the file name, remove extension 90 | math_tree_ok = True # As a starting point, the file is ok 91 | 92 | try: # Try to parse the file into a ElementTree 93 | self.math_tree = ElemTree.parse(self.target_file).getroot() # Our full XML as an ElementTree 94 | 95 | except ElemTree.ParseError: # Run if XML structure is corrupted 96 | print("Corrupted Mathcad document! Could not parse.") 97 | math_tree_ok = False # Make sure rest of file is not tried parsed 98 | 99 | self.ml = "{http://schemas.mathsoft.com/math30}" # Variable for namespaces as URI, used in prefixes 100 | self.ws = "{http://schemas.mathsoft.com/worksheet30}" 101 | 102 | self.output_folder = os.path.dirname(self.target_file) + "/ParsedLatexFile" 103 | if not os.path.exists(self.output_folder): # If this folder doesn't exist 104 | os.makedirs(self.output_folder) # Create it 105 | 106 | # Open a new tex file for writing. Encoding for Danish chars etc. Save in a folder 107 | self.tex_file = io.open(self.output_folder + "/" + self.filename + '.tex', 'w', encoding="utf-8") 108 | 109 | # Standard LaTeX document info as strings 110 | self.start_latex_doc = "\\documentclass[10pt,a4paper]{report}\n\\usepackage[utf8]{inputenc}\n" \ 111 | "\\usepackage[T1]{fontenc}\n\\usepackage{amsmath}\n\\usepackage{amsfonts}\n" \ 112 | "\\usepackage{amssymb}\n\\usepackage{graphicx}\n\\begin{document}\n\\noindent\n" 113 | 114 | self.end_latex_doc = "\\end{document}" 115 | 116 | self.matrix_array = [] # Array to use for multiple values in matrixes 117 | 118 | self.current_region_no = 0 # To keep track of regions in mathcad file, as they are being read 119 | 120 | self.debug = True # Toggle debug messages 121 | 122 | if math_tree_ok: # Only run if file isn't corrupted 123 | self.main() # Run main method 124 | 125 | def math_reader(self, elem): 126 | """Method for getting the math out of the XML file 127 | 128 | This method is used for reading the XML file (the ElementTree). 129 | Generally speaking this method gathers the data, the 130 | latex_formatter method uses to format the data into LaTeX. 131 | So no LaTeX-specific formatting is done in this method. 132 | Recursive method for efficiency and simplicity. 133 | 134 | This method is used for math Mathcad regions. 135 | :param elem: A ElementTree 136 | :return: Full LaTeX formatted math expressions 137 | """ 138 | elem.tag = elem.tag.replace(self.ml, "") # Only leave operator name left without prefix 139 | 140 | if elem.tag == "apply": # If current Element's tag is apply 141 | # We need two cases; One for apply tag which includes a operator, and everything else 142 | # The first will always be the operator, if there's one 143 | if self.debug: print("Apply tag found") 144 | 145 | # Either there's a operator (2 or 3 childs, first element is always the operator) 146 | if bool(elem[0]) is False and elem[0].text is None: # Checks if the elem has children and doesn't have text 147 | if self.debug: # Only prints debug messages if debug = True 148 | print("Apply tag includes a operator") 149 | 150 | if len(elem) == 3: # Either there's 3 parts (normal mathemathical expression) 151 | if self.debug: 152 | print("len(elem)", len(elem)) 153 | val1 = self.math_reader(elem[1]) # Call this method again with 2nd child again to get first value 154 | val2 = self.math_reader(elem[2]) # Call this method again with 3rd child again to get second value 155 | # Return the formatted result (by calling math_formatter), to the original caller of this method 156 | return self.latex_formatter(elem[0].tag, val1, val2) # Sends the operator and the two values 157 | 158 | elif len(elem) == 2: # Used for other operators where there's only "two" parts 159 | if self.debug: print("len(elem)", len(elem)) 160 | val1 = self.math_reader(elem[1]) # Call this method again, skip the operator tag elem 161 | return self.latex_formatter(elem[0].tag, val1) # Get first child's tag which is the operator 162 | 163 | # ToDo: Make a more general way of handling apply tags? 164 | # Or there's no operator - this is the case for ex cos(x) 165 | # One of the 2 childs, must have children too, or text 166 | elif bool(elem[0]) or bool(elem[1]) or elem[0].text or elem[1].text is not None: 167 | val1 = self.math_reader(elem[0]) 168 | val2 = self.math_reader(elem[1]) 169 | return self.latex_formatter(elem.tag, val1, val2) 170 | 171 | # ToDo?: The following else tag checks, are only for non-operator tags, elements with no children or text 172 | elif elem.tag == "parens": # Handle parenteses 173 | return self.latex_formatter(elem.tag, self.math_reader(elem[0])) # Only 1 value between parenteses 174 | 175 | # Current tag is some kind of equal sign 176 | elif elem.tag == "define" or elem.tag == "symEval": 177 | if self.debug: print("A type of equal expression found.") 178 | return self.latex_formatter(elem.tag, self.math_reader(elem[0]), self.math_reader(elem[1])) 179 | 180 | # Eval works like normal equal operator, if the result is not defined using "define" 181 | # ToDo: add a way to handle non-user defined units in the result, and possibly add library to format to LaTeX? 182 | elif elem.tag == "eval": 183 | # Either the evaluated expression doesn't have a user defined unit in the result 184 | if len(elem) == 2: 185 | return self.latex_formatter(elem.tag, self.math_reader(elem[0]), self.math_reader(elem[1])) 186 | 187 | # Or it does, and we can use that as the unit 188 | elif len(elem) == 3: 189 | value_and_unit = self.math_reader(elem[2]) + self.math_reader(elem[1]) 190 | return self.latex_formatter(elem.tag, self.math_reader(elem[0]), value_and_unit) 191 | 192 | elif elem.tag == "provenance": # Interesting Mathcad structure handled here 193 | return self.math_reader(elem[len(elem) - 1]) # Simply call this method again with the last child element 194 | 195 | elif elem.tag == "id": # Current tag is pure text 196 | if self.debug: print("Text found:", elem.text) 197 | return self.latex_formatter(elem.tag, elem) # Call external function with text 198 | 199 | elif elem.tag == "real": # Current is a real number 200 | if self.debug: print("Number found:", elem.text) 201 | return elem.text # Simply return the value as string 202 | 203 | # Result is used with equal signs, boundVars is used for special variables 204 | # degree is used for n'te degree derivatives 205 | # symResult is a result from a symbolic evaluation 206 | elif elem.tag == "result" or elem.tag == "boundVars" or elem.tag == "degree" or elem.tag == "symResult": 207 | if self.debug: print("Result found.") 208 | return self.math_reader(elem[0]) 209 | # return elem[0][0].text # Simply return the value as string 210 | 211 | elif elem.tag == "vectorize": # Current tag is a vector notation 212 | if self.debug: print("Vector found.") 213 | return self.latex_formatter(elem.tag, self.math_reader(elem[0])) 214 | 215 | elif elem.tag == "matrix": # Currrent tag is a matrix 216 | if self.debug: print("Matrix found.") 217 | 218 | # Run through every entity in the matrix, and add it to a list 219 | for entity in elem: 220 | # Convert every entity in to string, for supporting advanced expressions in matrix 221 | self.matrix_array.append(str(self.math_reader(entity))) # Recursive, to handling everything :D 222 | 223 | numpy_matrix_array = numpy.array(self.matrix_array) # Convert from list to a numpy array for manipulation 224 | array_dimensions = (int(elem.attrib["rows"]), int(elem.attrib["cols"])) # Grap the ints from the attribute 225 | numpy_matrix_array = numpy.reshape(numpy_matrix_array, array_dimensions) # Transform from flat array 226 | if array_dimensions[0] > 1 and array_dimensions[1] > 1: # Only transpose if there's more than 1 col or row 227 | numpy_matrix_array = numpy.transpose(numpy_matrix_array) # Flip array around to fit LaTeX structure 228 | 229 | self.matrix_array = [] # Reset matrix array 230 | return self.latex_formatter(elem.tag, numpy_matrix_array, array_dimensions) # Send array and the dimensons 231 | 232 | elif elem.tag == "placeholder": # Current tag is just a placeholder 233 | if self.debug: 234 | print("Empty placeholder found.") 235 | return " " # Return space 236 | 237 | elif elem.tag == "lambda" or elem.tag == "bounds": 238 | # lambda is used for both derivative and integral (+ more?!). 239 | # bounds is used for integral with limits 240 | # Therefore the latex_formatter must handle it, we can't go backwards in elements? 241 | return elem 242 | 243 | elif elem.tag == "unitedValue" or elem.tag == "unitOverride": # Value and/or unit 244 | if self.debug: 245 | print("Unit stuff found.") 246 | return self.math_reader(elem[0]) 247 | 248 | elif elem.tag == "function": 249 | if self.debug: 250 | print("Function found.") 251 | return self.latex_formatter(elem.tag, self.math_reader(elem[0]), self.math_reader(elem[1])) 252 | 253 | else: # For unsupported tags 254 | print("Error, non-supported tag found at region", 255 | self.current_region_no) # Print the problematic region number 256 | print("Current Elem.tag:", elem.tag) # Debug message 257 | 258 | def text_reader(self, elem): 259 | """Parses Mathcad text regions paragraph by paragraph. 260 | It loops through each paragraph, and add new 261 | :param elem: The ElementTree that contains text, structure: ... 262 | :return: Formatted text with escape chars 263 | """ 264 | text = "" # Variable to store found text 265 | 266 | if self.debug: 267 | print("Type: Text region.") 268 | 269 | # For each paragraph

...

, in the text "element" object: ... 270 | for i, paragraph in enumerate(elem, start=1): 271 | 272 | # Add text from start of paragraph 273 | if paragraph.text is not None and ("\t" not in paragraph.text): 274 | text += paragraph.text 275 | 276 | # If paragraph has children (can be multiple lines of text, math region, or bold/italic etc text) 277 | text += self.text_piece_formatter(paragraph) 278 | 279 | # Add tailing text for paragraph 280 | if (paragraph.tail is not None) and ("\t" not in paragraph.tail): 281 | text += paragraph.tail 282 | 283 | # Add newline after every paragraph that isn't the last 284 | if i <= len(elem): 285 | text += " \\\\\n" 286 | 287 | # For the last paragraph (no newline) 288 | else: 289 | text += " \\\\" 290 | 291 | # Make sure symbols are parsed correctly 292 | return symbol_parser(text, False) 293 | 294 | def text_piece_formatter(self, paragraph_elem): 295 | """Method to handle pieces of text in a paragraph. 296 | Can be boldface, italic etc, with different combinations of these 297 | We use depth first search to find text and format it using latex syntax. 298 | 299 | :param paragraph_elem: The ElementTree that contains a line of text to format 300 | :return: String with formatted using LaTeX syntax""" 301 | 302 | # String to store our text 303 | text = "" 304 | 305 | # Mapping from Mathcad xml tags to latex 306 | emphasis_latex = { 307 | "b": "\\textbf", 308 | "i": "\\textit", 309 | "u": "\\underline", 310 | "sup": "\\textsuperscript" 311 | } 312 | 313 | 314 | # Loop through each piece of text in the paragraph. The order of below if statments follows mathcad structure 315 | for text_piece in paragraph_elem: 316 | # Remove self.ws from tag for easier lookup 317 | current_emphasis_tag = text_piece.tag.replace(self.ws, "") 318 | 319 | # Check if current tag is a emphasis formatting tag recognized in LaTeX 320 | if current_emphasis_tag in emphasis_latex: 321 | # Add start of latex formatting tag. Remove self.ws to lookup in our dict 322 | text += emphasis_latex[current_emphasis_tag] + "{" 323 | 324 | # Check if there's text to grab, if yes, add it to the string 325 | if text_piece.text is not None: 326 | 327 | # Check text is not only tab charecters 328 | if "\t" not in text_piece.text: 329 | text += text_piece.text 330 | 331 | # If the tag is "region" then we have found a math region 332 | if current_emphasis_tag == "region": 333 | 334 | # Call math_reader to format the math 335 | text += " $ " + self.math_reader(text_piece[0][0]) + " $ " 336 | 337 | # Add tailing text, if it exists, not if it's just tab (\t) 338 | if (text_piece.tail is not None) and ("\t" not in text_piece.tail): 339 | text += text_piece.tail 340 | 341 | # Skip sibling tags since they are handled with above code 342 | continue 343 | 344 | # Add (ace) charecters in mid-sentences specified by attribute "count" for tag 345 | if current_emphasis_tag == "sp": 346 | 347 | # Check if "count" attribute (key) exists 348 | if "count" in text_piece.attrib: 349 | number_of_spaces = int(text_piece.attrib["count"]) 350 | 351 | # If there's no attribute, there's only one space 352 | else: 353 | number_of_spaces = 1 354 | 355 | text += " " * number_of_spaces 356 | 357 | # Go deeper if possible 358 | # If theres children, there's no text to grap, we need to go a level deeper to find text 359 | # (Check if text_piece has children which occurs using nested-formatting (eg __**bold&underlined**__)) 360 | # The "f" tag is used for specificng fonts in mathcad, but we don't care about font went exporting to LaTeX 361 | if bool(text_piece): 362 | # Go one level deeper in the tree 363 | text += self.text_piece_formatter(text_piece) 364 | 365 | # Add the last part of LaTeX formatting 366 | if current_emphasis_tag in emphasis_latex: 367 | # Add text and end of latex formatting tag } 368 | text += "}" 369 | 370 | # Add tailing text, if it exists, not if it's just tab (\t) 371 | if (text_piece.tail is not None) and ("\t" not in text_piece.tail): 372 | text += text_piece.tail 373 | 374 | 375 | return text 376 | 377 | def picture_reader(self, elem): 378 | """Method for reading binary picture data 379 | 380 | This method converts the desired picture from base64 data to a .png image 381 | :param elem: The picture region as a ElementTree 382 | :return: The LaTeX code including the image file 383 | """ 384 | image_id = int(elem[0].attrib["item-idref"]) # Grap the image ID from the elements attributes 385 | image_base64_data = self.math_tree[4][image_id - 1].text # Find the image data in the binaryContent part 386 | image_base64_data = image_base64_data.encode(encoding='UTF-8') # Encode string as bytes instead 387 | filename = self.filename + "_" + str(image_id) + ".png" 388 | filename_no_ext = self.filename + "_" + str(image_id) 389 | 390 | # Open a new file for writing, name it the filename_id 391 | with open(self.output_folder + "/" + filename, "wb") as imagefile: # Open the file, and close it afterwards 392 | imagefile.write(base64.decodebytes(image_base64_data)) # Write the decoded base64 bytes to the file 393 | 394 | return "\\includegraphics{\"" + filename_no_ext + "\"}" 395 | 396 | def latex_formatter(self, operator, x, y=None): # Define the value of y 397 | """LaTeX math syntax formatter metod 398 | 399 | This method takes in the data math_reader() has found in the XML file, 400 | and formats it into LaTeX expressions. 401 | 402 | :param operator: A math operator or similar 403 | :param x: The first part of the expression 404 | :param y: The last part of the expression - Default value: None 405 | :return: Math expression formatted in LaTeX format 406 | """ 407 | operator = operator.replace(self.ml, "") # Remove prefix from operator 408 | 409 | operators_mathcad_tag_to_latex = { 410 | 'plus': "{x} + {y}", 411 | 'minus': "{x} - {y}", 412 | 'mult': "{x} \\cdot {y}", 413 | 'div': "\\frac{{{x}}}{{{y}}}", 414 | 'eval': '{x} = {y}', 415 | 'equal': '{x} = {y}', 416 | 'define': '{x} = {y}', 417 | 'symEval': '{x} = {y}', 418 | 'pow': '{x}^{{{y}}}', 419 | 'nthRoot': "\\sqrt[{x}]{{{y}}}", 420 | 'lessThan': '{x} < {y}', 421 | 'greaterThan': '{x} > {y}', 422 | 'lessOrEqual': '{x} \\leq {y}', 423 | 'greaterOrEqual': '{x} \\geq {y}', 424 | 'and': '{x} \\land {y}', 425 | 'or': '{x} \\lor {y}', 426 | 'apply': '{x}\\left({y}\\right)', # Return two strings that needs to be merged 427 | 'function': '{x}\\left({y}\\right)', 428 | 'indexer': '{x}_{{{y}}}', 429 | 'parens': '\\left({x}\\right)', 430 | 'sqrt': '\\sqrt{{{x}}}', # Latex output: \sqrt{x} double {{ to esc 431 | 'absval': '\\left|{x}\\right|', 432 | 'neg': '-{x}', 433 | 'vectorize': '\\vec{{{x}}}' 434 | } 435 | 436 | if y is not None: # y exists, so there's two components to parse 437 | if self.debug: print("y given") 438 | 439 | if operator in operators_mathcad_tag_to_latex: 440 | return operators_mathcad_tag_to_latex[operator].format(x=x, y=y) 441 | 442 | elif operator == "matrix": 443 | string = "\\begin{pmatrix}\n" 444 | i2 = 1 445 | # Rows and cols in the matrix, taken from the y tuple 446 | rows = y[0] 447 | cols = y[1] 448 | 449 | # We have 2 counters; one to keep track of current row (i), one for col (i2) 450 | for i in range(0, rows): # Run this loop the amount of rows there exists 451 | for entity in x[i, :]: # For each value in the i'nte row 452 | if i2 == cols: # RNS checkmate 453 | string += entity 454 | else: 455 | string = string + entity + " & " 456 | i2 += 1 457 | i2 = 1 458 | string += "\\\\\n" 459 | 460 | string += "\\end{pmatrix}" 461 | return string 462 | 463 | elif operator == "integral": # For integrals with limits 464 | lim_a = self.math_reader(y[0]) 465 | lim_b = self.math_reader(y[1]) 466 | var = self.math_reader(x[0]) 467 | func = self.math_reader(x[1]) 468 | return "\\int_{" + lim_a + "}^{" + lim_b + "} " + func + " d" + var 469 | 470 | elif operator == "derivative": # For n'te derivative notation 471 | return "\\frac{d^" + y + "}{d" + self.math_reader(x[0]) + "^" + y + "}" + self.math_reader(x[1]) 472 | 473 | else: 474 | return "Unhandled tag (y given) :(" 475 | 476 | else: # Else, there is only 1 value 477 | if self.debug: print("No y given") 478 | 479 | if operator in operators_mathcad_tag_to_latex: 480 | print(operators_mathcad_tag_to_latex[operator].format(x=x)) 481 | return operators_mathcad_tag_to_latex[operator].format(x=x) 482 | 483 | if operator == "id": 484 | # Make sure subscript attribute exists 485 | if x.get("subscript") is not None: 486 | 487 | upper_text = symbol_parser(x.text, True) 488 | 489 | # Get attribute's values and send to symbol_parser 490 | sub_text = symbol_parser(x.attrib["subscript"], True) 491 | 492 | return upper_text + "_{" + sub_text + "}" 493 | else: 494 | return symbol_parser(x.text, True) 495 | 496 | elif operator == "derivative": # For derivative notation 497 | return "\\frac{d}{d" + self.math_reader(x[0]) + "}" + self.math_reader(x[1]) 498 | 499 | elif operator == "integral": # For integrals. 500 | var = self.math_reader(x[0]) 501 | func = self.math_reader(x[1]) 502 | return "\\int " + func + " d" + var 503 | 504 | else: 505 | return "Unhandled tag (y given) :(" 506 | 507 | def main(self): 508 | """Method for controlling file writing 509 | 510 | Writes the final results to the new .tex file 511 | """ 512 | self.tex_file.write(self.start_latex_doc) # Write start of LaTeX document 513 | 514 | for child in self.math_tree[3]: # Run for each region containing math or text 515 | self.current_region_no += 1 # Update counter 516 | print("\nTrying to parse the " + str(self.current_region_no) + "' region") # Line separator for ouput 517 | # ToDo: Print the actual region-id 518 | # print("(region-id: " + child[0].attr["region-id"] + ")") 519 | 520 | try: # Try to parse 521 | # ToDo: Smart align, that check for next region, if it's text or not? 522 | if child[0].tag == self.ws + "math": # Math region 523 | if self.debug: print("Type: Math region.") 524 | # Write result of the region by calling fuction which sends the current element 525 | self.tex_file.write("\\begin{align}\n" + self.math_reader(child[0][0]) + "\n\\end{align}\\\\\n\n") 526 | 527 | elif child[0].tag == self.ws + "text": # Handle pure text regions 528 | if self.debug: print("Type: Text region.") 529 | self.tex_file.write(self.text_reader(child[0]) + "\n") 530 | 531 | elif child[0].tag == self.ws + "picture": # Handle pure picture regions 532 | if self.debug: print("Type: Picture region.") 533 | self.tex_file.write(self.picture_reader(child[0]) + "\\\\\n") 534 | 535 | # Catch the most common error 536 | except TypeError as e: 537 | print("Unsupported expessions found OR error occured, could not parse region", self.current_region_no, "Error:", str(e)) 538 | 539 | self.tex_file.write(self.end_latex_doc) # Write end of LaTeX document 540 | 541 | 542 | root_widget = Tk() # Initialize Tkinter by creating a root widget others will be children of 543 | new_app = ParseGUI(root_widget) # Create a new instance of the ParseGUI class using the root widget as a parent 544 | 545 | root_widget.wm_title("Mathcad to LaTeX converter") # Set window title 546 | root_widget.geometry('380x90') # Set window size 547 | root_widget.resizable(width=False, height=False) # Make the window non-resizable 548 | root_widget.mainloop() # Start a loop that ends when the quit event is called (X button on window) 549 | -------------------------------------------------------------------------------- /symbol_parser.py: -------------------------------------------------------------------------------- 1 | # coding=UTF-8 2 | 3 | # Define a few special charecters in a dictonary 4 | symbol_dictionary = {'&': '\\&', 5 | 'π': '\\pi ', 6 | 'α': '\\alpha ', 7 | '': '\\epsilon ', 8 | 'φ': '\\phi ', 9 | 'θ': '\\theta ', 10 | 'ρ': '\\rho ', 11 | 'µ': '\\mu ', 12 | '∆': '\\Delta ', 13 | 'ε': '\\varepsilon ', 14 | 'ϕ': '\\Phi ', 15 | # For now, don't use these chars in mathmode... 16 | #'æ': '\\textit{æ} ', 17 | #'Æ': '\\textit{Æ} ', 18 | #'ø': '\\textit{ø} ', 19 | #'Ø': '\\textit{Ø} ', 20 | #'å': '\\textit{å} ', 21 | #'Å': '\\textit{Å} ', 22 | '⇕': '\\Updownarrow ', 23 | '⇔': '\\Leftrightarrow ', 24 | 'ω': '\\omega', 25 | 'Ω': '\\Omega', 26 | '&': '\\&', 27 | 'γ' : '\\gamma', 28 | 'τ' : '\\tau', 29 | 'σ' : '\\sigma'} # ToDo: Add more of these... 30 | 31 | 32 | def symbol_parser(str_with_symbols, mathmode): 33 | """Parses symbols into LaTeX symbols 34 | 35 | :param str_with_symbols: A string which is the given symbol that needs to be checked 36 | :return: Formatted LaTeX symbol or what was given 37 | """ 38 | if mathmode: # return the "pure" string 39 | for unicode_symbol in symbol_dictionary: # Check entire dictionary 40 | try: # Try to replace the given symbol 41 | str_with_symbols = str_with_symbols.replace(unicode_symbol, symbol_dictionary[unicode_symbol]) 42 | except unicode_symbolError: # If it fails, just skip it 43 | pass 44 | 45 | return str_with_symbols # Return the parsed symbol 46 | 47 | elif mathmode is False: # return the parsed string with $ $ to create a LaTeX math mode region 48 | for unicode_symbol in symbol_dictionary: # Check entire dictionary 49 | try: # Try to replace the given symbol 50 | str_with_symbols = str_with_symbols.replace(unicode_symbol, "$" + symbol_dictionary[unicode_symbol] + "$") 51 | except unicode_symbolError: # If it fails, just skip it 52 | pass 53 | 54 | return str_with_symbols # Return the parsed symbol 55 | --------------------------------------------------------------------------------