├── .gitignore ├── .readthedocs.yaml ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── _README.md ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── api │ ├── freeplane.rst │ └── modules.rst │ ├── conf.py │ ├── index.rst │ └── user │ ├── features.rst │ ├── install.rst │ ├── intro.rst │ └── license.rst ├── setup.py ├── src └── freeplane.py └── versions.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | MANIFEST 4 | LICENSE.txt 5 | __pycache__/ 6 | _stuff/ 7 | _test/ 8 | *.bak 9 | *.pyc 10 | *.c 11 | .session.vim 12 | _tools/ 13 | freeplane_io.egg-info/ 14 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | sphinx: 9 | configuration: docs/source/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | - method: pip 15 | path: . 16 | extra_requirements: 17 | - doc 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | freeplane-python-io 2 | =================== 3 | 4 | This package provides the user a convenient way to create, read, update and 5 | delete information stored inside Freeplan mindmap files. As an alternative or 6 | an enhancement to working with mindmaps through the original graphical user 7 | interface (GUI) which is provided by the brilliant Freeplane Mindmap Editor, 8 | this package was designed to implement an application programming interface 9 | (API) for Python as well as a command line interface (CLI) both to interact 10 | with Freeplane mindmap files, directly. 11 | 12 | 13 | These are the main features of the package: 14 | 15 | **create, read and modify Freeplane mindmaps** 16 | at least in theory, this package 17 | will not touch anything it does not know within an opened mindmap. so, you 18 | can read big maps, change them where you like and save them without any 19 | information loss. 20 | 21 | **transparent handling of different mindmap file versions** 22 | different freeplane file 23 | versions are handled seamlessly. even old Freemind mindmaps should work. 24 | 25 | **management of each node's creation and modification dates** 26 | dates will be 27 | translated into human-readable date strings. when creating or modifying nodes, 28 | the correct dates will be set. 29 | 30 | **search and find nodes within a mindmap** 31 | based on the node's id, core text, 32 | attributes, details, notes, link or icons any node can be found within a mindmap 33 | using the mindmap's or node's `find_nodes` or `find_children` methods. 34 | 35 | **navigate through the mindmap trees** 36 | based on the node object's `parent`, 37 | `children`, `next` and `get_child_by_index` attributes / methods it is possible 38 | to reach every node from every starting point within the mindmap. 39 | 40 | **modify information within arbitrary nodes** 41 | the original attributes of each 42 | node (core text / html as `plaintext`, `notes`, `details`, `link`, `icons`, ...) can 43 | be read and modified. by using the node's `set_attribute`, `get_attribute` and 44 | `attribute` methods, the Freeplane' node attributes can be accessed. 45 | 46 | **manage node links** 47 | hyperlinks between nodes within the same mindmap as well 48 | as accross different mindmaps are dealt with by using the `hyperlink` attribute 49 | of a node object. 50 | 51 | **set and manage node styles** 52 | in Freeplane, "styles" are used to set and manage 53 | the design of nodes. using the `styles` attribute and the `add_style` attribute of 54 | a map object or the `style` attribute of a node object, the management is done. 55 | 56 | **create and manage arrow links** 57 | besides hyperlinks, "arrow links" can be used 58 | to connect nodes on (this time on a visual level). the node object's 59 | `add_arrowlink` method helps connecting nodes visually. 60 | 61 | 62 | installation 63 | ------------ 64 | 65 | .. code:: bash 66 | 67 | pip install freeplane-io 68 | 69 | 70 | usage 71 | ----- 72 | 73 | .. code:: python 74 | 75 | import freeplane 76 | 77 | 78 | 79 | 80 | # 81 | # load existing mindmap 82 | # 83 | 84 | # in order to access a mindmap, you first have to open it using 85 | # the following function. please provide a valid path to your 86 | # already existing Freeplane mindmap within the argument of the 87 | # following function. 88 | 89 | # load 90 | mindmap = freeplane.Mindmap('./example_IN.mm') 91 | 92 | # show available node styles 93 | mindmap.styles 94 | 95 | 96 | 97 | 98 | # 99 | # check for GTD tasks 100 | # 101 | 102 | # there is a Freeplane addon "GTD+" which uses exclamation mark 103 | # icons as identifiers for a GTD element within a Freeplane 104 | # mindmap. In order to get a list of all these GTD elements, 105 | # you can use the following method. 106 | 107 | tasks = mindmap.find_nodes(icon=freeplane.ICON_EXCLAMATION) 108 | 109 | 110 | 111 | 112 | # 113 | # search for any core text 114 | # 115 | 116 | # in order to search the whole mindmap for a specific text string 117 | # expected within the core section of a node, the following 118 | # method can be used. 119 | 120 | # search whole mindmap for "test" 121 | nodes = mindmap.find_nodes(core="test", exact=True) 122 | 123 | # search whole mindmap for "test", "tEST", ... 124 | if not nodes: 125 | nodes = mindmap.find_nodes(core="test") 126 | 127 | # get first node from list 128 | node = nodes[0] 129 | 130 | # printout its plain text 131 | print(node.plaintext) 132 | 133 | 134 | 135 | 136 | # 137 | # write into existing mindmap 138 | # 139 | 140 | # modify test node's core text and color 141 | node.plaintext = 'found and changed' 142 | 143 | # create a test style 144 | mindmap.add_style("test", {"bgcolor": "#999999"}) 145 | 146 | # set test style in node 147 | node.style = "test" 148 | 149 | 150 | 151 | 152 | # 153 | # save mindmap 154 | # 155 | 156 | mindmap.save('./example_OUT.mm') 157 | 158 | 159 | documentation 160 | ------------- 161 | 162 | For more information, please visit our documentation_ at ReadTheDocs. 163 | 164 | .. _documentation: https://freeplane-python-io.readthedocs.io/en/latest/ -------------------------------------------------------------------------------- /_README.md: -------------------------------------------------------------------------------- 1 | # freeplane-python-io 2 | 3 | This package provides the user a convenient way to create, read, update and 4 | delete information stored inside Freeplan mindmap files. As an alternative or 5 | an enhancement to working with mindmaps through the original graphical user 6 | interface (GUI) which is provided by the brilliant Freeplane Mindmap Editor, 7 | this package was designed to implement an application programming interface 8 | (API) for Python as well as a command line interface (CLI) both to interact 9 | with Freeplane mindmap files, directly. 10 | 11 | ## features 12 | 13 | These are the main features of the package: 14 | 15 | - **create, read and modify Freeplane mindmaps**
at least in theory, this package 16 | will not touch anything it does not know within an opened mindmap. so, you 17 | can read big maps, change them where you like and save them without any 18 | information loss. 19 | 20 | - **transparent handling of different mindmap file versions**
different freeplane file 21 | versions are handled seamlessly. even old Freemind mindmaps should work. 22 | 23 | - **management of each node's creation and modification dates**
dates will be 24 | translated into human-readable date strings. when creating or modifying nodes, 25 | the correct dates will be set. 26 | 27 | - **search and find nodes within a mindmap**
based on the node's id, core text, 28 | attributes, details, notes, link or icons any node can be found within a mindmap 29 | using the mindmap's or node's `find_nodes` or `find_children` methods. 30 | 31 | - **navigate through the mindmap trees**
based on the node object's `parent`, 32 | `children`, `next` and `get_child_by_index` attributes / methods it is possible 33 | to reach every node from every starting point within the mindmap. 34 | 35 | - **modify information within arbitrary nodes**
the original attributes of each 36 | node (core text / html as `plaintext`, `notes`, `details`, `link`, `icons`, ...) can 37 | be read and modified. by using the node's `set_attribute`, `get_attribute` and 38 | `attribute` methods, the Freeplane' node attributes can be accessed. 39 | 40 | - **manage node links**
hyperlinks between nodes within the same mindmap as well 41 | as accross different mindmaps are dealt with by using the `hyperlink` attribute 42 | of a node object. 43 | 44 | - **set and manage node styles**
in Freeplane, "styles" are used to set and manage 45 | the design of nodes. using the `styles` attribute and the `add_style` attribute of 46 | a map object or the `style` attribute of a node object, the management is done. 47 | 48 | - **create and manage arrow links**
besides hyperlinks, "arrow links" can be used 49 | to connect nodes on (this time on a visual level). the node object's 50 | `add_arrowlink` method helps connecting nodes visually. 51 | 52 | ## installation 53 | 54 | ```bash 55 | pip install freeplane-io 56 | ``` 57 | 58 | ## usage 59 | 60 | ```python 61 | import freeplane 62 | 63 | 64 | 65 | 66 | # 67 | # load existing mindmap 68 | # 69 | 70 | # in order to access a mindmap, you first have to open it using 71 | # the following function. please provide a valid path to your 72 | # already existing Freeplane mindmap within the argument of the 73 | # following function. 74 | 75 | # load 76 | mindmap = freeplane.Mindmap('./example_IN.mm') 77 | 78 | # show available node styles 79 | mindmap.styles 80 | 81 | 82 | 83 | 84 | # 85 | # check for GTD tasks 86 | # 87 | 88 | # there is a Freeplane addon "GTD+" which uses exclamation mark 89 | # icons as identifiers for a GTD element within a Freeplane 90 | # mindmap. In order to get a list of all these GTD elements, 91 | # you can use the following method. 92 | 93 | tasks = mindmap.find_nodes(icon=freeplane.ICON_EXCLAMATION) 94 | 95 | 96 | 97 | 98 | # 99 | # search for any core text 100 | # 101 | 102 | # in order to search the whole mindmap for a specific text string 103 | # expected within the core section of a node, the following 104 | # method can be used. 105 | 106 | # search whole mindmap for "test" 107 | nodes = mindmap.find_nodes(core="test", exact=True) 108 | 109 | # search whole mindmap for "test", "tEST", ... 110 | if not nodes: 111 | nodes = mindmap.find_nodes(core="test") 112 | 113 | # get first node from list 114 | node = nodes[0] 115 | 116 | # printout its plain text 117 | print(node.plaintext) 118 | 119 | 120 | 121 | 122 | # 123 | # write into existing mindmap 124 | # 125 | 126 | # modify test node's core text and color 127 | node.plaintext = 'found and changed' 128 | 129 | # create a test style 130 | mindmap.add_style("test", {"bgcolor": "#999999"}) 131 | 132 | # set test style in node 133 | node.style = "test" 134 | 135 | 136 | 137 | 138 | # 139 | # save mindmap 140 | # 141 | 142 | mindmap.save('./example_OUT.mm') 143 | ``` 144 | 145 | ## the code base 146 | 147 | ### naming conventions for classes, functions, ... and variables 148 | 149 | Using Python, developers are always encouraged to respect e.g. naming 150 | conventions as defined in [PEP8](https://peps.python.org/pep-0008/). On the 151 | practical side, the `freeplane-io` package is strongly related to the 152 | functionalities implemented within the [Freeplane](https://freeplane.org/) 153 | project (JAVA language) and accessible by its built-in Groovy scripting 154 | environment. In effect, the `freeplane-io` package tries to provide a good 155 | external API representation for the features, the Groovy-API provides within 156 | the Freeplane editor. Thus, another objective is to keep this package as close 157 | as possible to the Groovy-API. The syntax and naming will be some kind of mix. 158 | 159 | ### deliberate deviations from conventions 160 | 161 | When browsing the code base of `freeplane-io`, you will see some deviations from 162 | the PEP8 recommendations and from Freeplane's Groovy-API syntax. Some deviations result from the fact that the 163 | developer was too inexperienced when he started this project, other deviations 164 | are intentional as they help extending the machine-based readability of the 165 | code in the way the developer likes it. One eye-catching deviation will be the representation of comments within the code. For the `freeplane-io` package, there are two distinct kinds of comments: 166 | 167 | - **block comments** -> these comments are used as a kind of heading for the following block of code. It describes in veri few words (one line of text) what is being implemented within the next block of code. 168 | 169 | - **concept comments** -> these multi-line comments are used to describe the implementation concept. Somtimes, it is not too obvious, how a programmer implements something. So the concept comments might help understanding. 170 | 171 | 172 | ### developing freeplane-python-io 173 | 174 | The following steps should be performed in order to build a working local 175 | development environment. for this, the standard dos / bash / ... console should be used. 176 | 177 | 1. clone this project into a new local project folder 178 | ```bash 179 | git clone https://... 180 | ``` 181 | 182 | 2. create a Python virtual environment locally (make sure Python v3.x is being used, here) 183 | ```bash 184 | python -m venv env 185 | ``` 186 | 187 | 3. install all necessary packages using pip 188 | ```bash 189 | pip install html2text 190 | 191 | ``` 192 | 193 | ... 194 | 195 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==6.2.1 2 | sphinx-rtd-theme==1.2.2 3 | pillow==5.4.1 4 | mock==1.0.1 5 | commonmark==0.9.1 6 | recommonmark==0.5.0 -------------------------------------------------------------------------------- /docs/source/api/freeplane.rst: -------------------------------------------------------------------------------- 1 | freeplane module 2 | ================ 3 | 4 | .. automodule:: freeplane 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :exclude-members: ArrowStyles, Branch, getCoreTextFromNode, 9 | getText, get_version_specific_file_encoding, reduce_node_list, 10 | update_date_attribute_in_node 11 | -------------------------------------------------------------------------------- /docs/source/api/modules.rst: -------------------------------------------------------------------------------- 1 | Modules 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | freeplane 8 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | import os 10 | import sys 11 | 12 | # If extensions (or modules to document with autodoc) are in another directory, 13 | # add these directories to sys.path here. If the directory is relative to the 14 | # documentation root, use os.path.abspath to make it absolute, like shown here. 15 | 16 | sys.path.insert(0, os.path.abspath("../src")) 17 | 18 | from freeplane import __version__ 19 | 20 | project = 'freeplane-python-io' 21 | copyright = '2016 - 2024, nnako' 22 | author = 'nnako' 23 | release = __version__ 24 | version = __version__ 25 | 26 | # -- General configuration --------------------------------------------------- 27 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 28 | 29 | extensions = [ 30 | "sphinx.ext.autosectionlabel", 31 | "sphinx.ext.autodoc", 32 | "sphinx_rtd_theme", 33 | # "sphinx.ext.inheritance_diagram", 34 | # "sphinx.ext.viewcode", 35 | ] 36 | 37 | templates_path = ['_templates'] 38 | exclude_patterns = [] 39 | 40 | 41 | 42 | # -- Options for HTML output ------------------------------------------------- 43 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 44 | 45 | #html_theme = 'alabaster' 46 | html_theme = 'sphinx_rtd_theme' 47 | html_static_path = ['_static'] 48 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Release v\ |version| (:ref:`Installation `) 2 | 3 | User Guide 4 | ---------- 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | user/intro 10 | user/getting_started 11 | user/install 12 | user/features 13 | api/modules 14 | dev/development 15 | user/license 16 | 17 | .. include:: ../../README.rst 18 | -------------------------------------------------------------------------------- /docs/source/user/features.rst: -------------------------------------------------------------------------------- 1 | .. _feat: 2 | 3 | Features 4 | ======== 5 | 6 | These are the main features of the package: 7 | 8 | **create, read and modify Freeplane mindmaps** 9 | at least in theory, this package 10 | will not touch anything it does not know within an opened mindmap. so, you 11 | can read big maps, change them where you like and save them without any 12 | information loss. 13 | 14 | **transparent handling of different mindmap file versions** 15 | different freeplane file 16 | versions are handled seamlessly. even old Freemind mindmaps should work. 17 | 18 | **management of each node's creation and modification dates** 19 | dates will be 20 | translated into human-readable date strings. when creating or modifying nodes, 21 | the correct dates will be set. 22 | 23 | **search and find nodes within a mindmap** 24 | based on the node's id, core text, 25 | attributes, details, notes, link or icons any node can be found within a mindmap 26 | using the mindmap's or node's `find_nodes` or `find_children` methods. 27 | 28 | **navigate through the mindmap trees** 29 | based on the node object's `parent`, 30 | `children`, `next` and `get_child_by_index` attributes / methods it is possible 31 | to reach every node from every starting point within the mindmap. 32 | 33 | **modify information within arbitrary nodes** 34 | the original attributes of each 35 | node (core text / html as `plaintext`, `notes`, `details`, `link`, `icons`, ...) can 36 | be read and modified. by using the node's `set_attribute`, `get_attribute` and 37 | `attribute` methods, the Freeplane' node attributes can be accessed. 38 | 39 | **manage node links** 40 | hyperlinks between nodes within the same mindmap as well 41 | as accross different mindmaps are dealt with by using the `hyperlink` attribute 42 | of a node object. 43 | 44 | **set and manage node styles** 45 | in Freeplane, "styles" are used to set and manage 46 | the design of nodes. using the `styles` attribute and the `add_style` attribute of 47 | a map object or the `style` attribute of a node object, the management is done. 48 | 49 | **create and manage arrow links** 50 | besides hyperlinks, "arrow links" can be used 51 | to connect nodes on (this time on a visual level). the node object's 52 | `add_arrowlink` method helps connecting nodes visually. 53 | -------------------------------------------------------------------------------- /docs/source/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installing 4 | ========== 5 | 6 | *freeplane-python-io* is hosted on PyPI, so installing with `pip` is simple:: 7 | 8 | pip install freeplane-io 9 | 10 | *freeplane-python-io* depends mainly on the ``lxml`` package. The html parsing is realized 11 | using ``html2text``, which might be replaced by Python's builtin package, 12 | soon. Both ``pip`` and ``easy_install`` will take care of satisfying these 13 | dependencies for you, but if you use the ``setup.py`` installation method 14 | you might need to install the dependencies yourself. 15 | 16 | Currently *freeplane-python-io* requires Python 3.3, 3.4, or 3.6 or higher. 17 | 18 | Dependencies 19 | ------------ 20 | 21 | * Python 3.3, 3.4, or 3.6 or higher 22 | * lxml 23 | * html2text (to be used for parsing html contents within nodes) -------------------------------------------------------------------------------- /docs/source/user/intro.rst: -------------------------------------------------------------------------------- 1 | .. _intro: 2 | 3 | Introduction 4 | ============ 5 | 6 | *freeplane-python-io* is a Python library for creating and updating Freeplane mindmap 7 | files (.mm). 8 | 9 | A typical use would be to automate parsing of mindmap files for any structured 10 | information or todo items, then creating representations (presentations) and 11 | reports in other file formats or for specific platforms like PPTX , Confluence, 12 | JIRA, ... . It might help for any information processing task where the user or 13 | developer does not want to sit in front of the computer screen. But wants 14 | scripting means to takes over and generate high quality information output 15 | especially within recurring processes. 16 | 17 | This user guide is tutorial in nature, introducing concepts along with code 18 | examples that we hope will allow any reader to learn what is needed while addressing 19 | the real-life information management challenges based on mindmap sources. 20 | -------------------------------------------------------------------------------- /docs/source/user/license.rst: -------------------------------------------------------------------------------- 1 | .. _lic: 2 | 3 | License 4 | ======= 5 | 6 | GNU GENERAL PUBLIC LICENSE 7 | Version 3, 29 June 2007 8 | 9 | Copyright (C) 2007 Free Software Foundation, Inc. 10 | Everyone is permitted to copy and distribute verbatim copies 11 | of this license document, but changing it is not allowed. 12 | 13 | Preamble 14 | 15 | The GNU General Public License is a free, copyleft license for 16 | software and other kinds of works. 17 | 18 | The licenses for most software and other practical works are designed 19 | to take away your freedom to share and change the works. By contrast, 20 | the GNU General Public License is intended to guarantee your freedom to 21 | share and change all versions of a program--to make sure it remains free 22 | software for all its users. We, the Free Software Foundation, use the 23 | GNU General Public License for most of our software; it applies also to 24 | any other work released this way by its authors. You can apply it to 25 | your programs, too. 26 | 27 | When we speak of free software, we are referring to freedom, not 28 | price. Our General Public Licenses are designed to make sure that you 29 | have the freedom to distribute copies of free software (and charge for 30 | them if you wish), that you receive source code or can get it if you 31 | want it, that you can change the software or use pieces of it in new 32 | free programs, and that you know you can do these things. 33 | 34 | To protect your rights, we need to prevent others from denying you 35 | these rights or asking you to surrender the rights. Therefore, you have 36 | certain responsibilities if you distribute copies of the software, or if 37 | you modify it: responsibilities to respect the freedom of others. 38 | 39 | For example, if you distribute copies of such a program, whether 40 | gratis or for a fee, you must pass on to the recipients the same 41 | freedoms that you received. You must make sure that they, too, receive 42 | or can get the source code. And you must show them these terms so they 43 | know their rights. 44 | 45 | Developers that use the GNU GPL protect your rights with two steps: 46 | (1) assert copyright on the software, and (2) offer you this License 47 | giving you legal permission to copy, distribute and/or modify it. 48 | 49 | For the developers' and authors' protection, the GPL clearly explains 50 | that there is no warranty for this free software. For both users' and 51 | authors' sake, the GPL requires that modified versions be marked as 52 | changed, so that their problems will not be attributed erroneously to 53 | authors of previous versions. 54 | 55 | Some devices are designed to deny users access to install or run 56 | modified versions of the software inside them, although the manufacturer 57 | can do so. This is fundamentally incompatible with the aim of 58 | protecting users' freedom to change the software. The systematic 59 | pattern of such abuse occurs in the area of products for individuals to 60 | use, which is precisely where it is most unacceptable. Therefore, we 61 | have designed this version of the GPL to prohibit the practice for those 62 | products. If such problems arise substantially in other domains, we 63 | stand ready to extend this provision to those domains in future versions 64 | of the GPL, as needed to protect the freedom of users. 65 | 66 | Finally, every program is threatened constantly by software patents. 67 | States should not allow patents to restrict development and use of 68 | software on general-purpose computers, but in those that do, we wish to 69 | avoid the special danger that patents applied to a free program could 70 | make it effectively proprietary. To prevent this, the GPL assures that 71 | patents cannot be used to render the program non-free. 72 | 73 | The precise terms and conditions for copying, distribution and 74 | modification follow. 75 | 76 | TERMS AND CONDITIONS 77 | 78 | 0. Definitions. 79 | 80 | "This License" refers to version 3 of the GNU General Public License. 81 | 82 | "Copyright" also means copyright-like laws that apply to other kinds of 83 | works, such as semiconductor masks. 84 | 85 | "The Program" refers to any copyrightable work licensed under this 86 | License. Each licensee is addressed as "you". "Licensees" and 87 | "recipients" may be individuals or organizations. 88 | 89 | To "modify" a work means to copy from or adapt all or part of the work 90 | in a fashion requiring copyright permission, other than the making of an 91 | exact copy. The resulting work is called a "modified version" of the 92 | earlier work or a work "based on" the earlier work. 93 | 94 | A "covered work" means either the unmodified Program or a work based 95 | on the Program. 96 | 97 | To "propagate" a work means to do anything with it that, without 98 | permission, would make you directly or secondarily liable for 99 | infringement under applicable copyright law, except executing it on a 100 | computer or modifying a private copy. Propagation includes copying, 101 | distribution (with or without modification), making available to the 102 | public, and in some countries other activities as well. 103 | 104 | To "convey" a work means any kind of propagation that enables other 105 | parties to make or receive copies. Mere interaction with a user through 106 | a computer network, with no transfer of a copy, is not conveying. 107 | 108 | An interactive user interface displays "Appropriate Legal Notices" 109 | to the extent that it includes a convenient and prominently visible 110 | feature that (1) displays an appropriate copyright notice, and (2) 111 | tells the user that there is no warranty for the work (except to the 112 | extent that warranties are provided), that licensees may convey the 113 | work under this License, and how to view a copy of this License. If 114 | the interface presents a list of user commands or options, such as a 115 | menu, a prominent item in the list meets this criterion. 116 | 117 | 1. Source Code. 118 | 119 | The "source code" for a work means the preferred form of the work 120 | for making modifications to it. "Object code" means any non-source 121 | form of a work. 122 | 123 | A "Standard Interface" means an interface that either is an official 124 | standard defined by a recognized standards body, or, in the case of 125 | interfaces specified for a particular programming language, one that 126 | is widely used among developers working in that language. 127 | 128 | The "System Libraries" of an executable work include anything, other 129 | than the work as a whole, that (a) is included in the normal form of 130 | packaging a Major Component, but which is not part of that Major 131 | Component, and (b) serves only to enable use of the work with that 132 | Major Component, or to implement a Standard Interface for which an 133 | implementation is available to the public in source code form. A 134 | "Major Component", in this context, means a major essential component 135 | (kernel, window system, and so on) of the specific operating system 136 | (if any) on which the executable work runs, or a compiler used to 137 | produce the work, or an object code interpreter used to run it. 138 | 139 | The "Corresponding Source" for a work in object code form means all 140 | the source code needed to generate, install, and (for an executable 141 | work) run the object code and to modify the work, including scripts to 142 | control those activities. However, it does not include the work's 143 | System Libraries, or general-purpose tools or generally available free 144 | programs which are used unmodified in performing those activities but 145 | which are not part of the work. For example, Corresponding Source 146 | includes interface definition files associated with source files for 147 | the work, and the source code for shared libraries and dynamically 148 | linked subprograms that the work is specifically designed to require, 149 | such as by intimate data communication or control flow between those 150 | subprograms and other parts of the work. 151 | 152 | The Corresponding Source need not include anything that users 153 | can regenerate automatically from other parts of the Corresponding 154 | Source. 155 | 156 | The Corresponding Source for a work in source code form is that 157 | same work. 158 | 159 | 2. Basic Permissions. 160 | 161 | All rights granted under this License are granted for the term of 162 | copyright on the Program, and are irrevocable provided the stated 163 | conditions are met. This License explicitly affirms your unlimited 164 | permission to run the unmodified Program. The output from running a 165 | covered work is covered by this License only if the output, given its 166 | content, constitutes a covered work. This License acknowledges your 167 | rights of fair use or other equivalent, as provided by copyright law. 168 | 169 | You may make, run and propagate covered works that you do not 170 | convey, without conditions so long as your license otherwise remains 171 | in force. You may convey covered works to others for the sole purpose 172 | of having them make modifications exclusively for you, or provide you 173 | with facilities for running those works, provided that you comply with 174 | the terms of this License in conveying all material for which you do 175 | not control copyright. Those thus making or running the covered works 176 | for you must do so exclusively on your behalf, under your direction 177 | and control, on terms that prohibit them from making any copies of 178 | your copyrighted material outside their relationship with you. 179 | 180 | Conveying under any other circumstances is permitted solely under 181 | the conditions stated below. Sublicensing is not allowed; section 10 182 | makes it unnecessary. 183 | 184 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 185 | 186 | No covered work shall be deemed part of an effective technological 187 | measure under any applicable law fulfilling obligations under article 188 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 189 | similar laws prohibiting or restricting circumvention of such 190 | measures. 191 | 192 | When you convey a covered work, you waive any legal power to forbid 193 | circumvention of technological measures to the extent such circumvention 194 | is effected by exercising rights under this License with respect to 195 | the covered work, and you disclaim any intention to limit operation or 196 | modification of the work as a means of enforcing, against the work's 197 | users, your or third parties' legal rights to forbid circumvention of 198 | technological measures. 199 | 200 | 4. Conveying Verbatim Copies. 201 | 202 | You may convey verbatim copies of the Program's source code as you 203 | receive it, in any medium, provided that you conspicuously and 204 | appropriately publish on each copy an appropriate copyright notice; 205 | keep intact all notices stating that this License and any 206 | non-permissive terms added in accord with section 7 apply to the code; 207 | keep intact all notices of the absence of any warranty; and give all 208 | recipients a copy of this License along with the Program. 209 | 210 | You may charge any price or no price for each copy that you convey, 211 | and you may offer support or warranty protection for a fee. 212 | 213 | 5. Conveying Modified Source Versions. 214 | 215 | You may convey a work based on the Program, or the modifications to 216 | produce it from the Program, in the form of source code under the 217 | terms of section 4, provided that you also meet all of these conditions: 218 | 219 | a) The work must carry prominent notices stating that you modified 220 | it, and giving a relevant date. 221 | 222 | b) The work must carry prominent notices stating that it is 223 | released under this License and any conditions added under section 224 | 7. This requirement modifies the requirement in section 4 to 225 | "keep intact all notices". 226 | 227 | c) You must license the entire work, as a whole, under this 228 | License to anyone who comes into possession of a copy. This 229 | License will therefore apply, along with any applicable section 7 230 | additional terms, to the whole of the work, and all its parts, 231 | regardless of how they are packaged. This License gives no 232 | permission to license the work in any other way, but it does not 233 | invalidate such permission if you have separately received it. 234 | 235 | d) If the work has interactive user interfaces, each must display 236 | Appropriate Legal Notices; however, if the Program has interactive 237 | interfaces that do not display Appropriate Legal Notices, your 238 | work need not make them do so. 239 | 240 | A compilation of a covered work with other separate and independent 241 | works, which are not by their nature extensions of the covered work, 242 | and which are not combined with it such as to form a larger program, 243 | in or on a volume of a storage or distribution medium, is called an 244 | "aggregate" if the compilation and its resulting copyright are not 245 | used to limit the access or legal rights of the compilation's users 246 | beyond what the individual works permit. Inclusion of a covered work 247 | in an aggregate does not cause this License to apply to the other 248 | parts of the aggregate. 249 | 250 | 6. Conveying Non-Source Forms. 251 | 252 | You may convey a covered work in object code form under the terms 253 | of sections 4 and 5, provided that you also convey the 254 | machine-readable Corresponding Source under the terms of this License, 255 | in one of these ways: 256 | 257 | a) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by the 259 | Corresponding Source fixed on a durable physical medium 260 | customarily used for software interchange. 261 | 262 | b) Convey the object code in, or embodied in, a physical product 263 | (including a physical distribution medium), accompanied by a 264 | written offer, valid for at least three years and valid for as 265 | long as you offer spare parts or customer support for that product 266 | model, to give anyone who possesses the object code either (1) a 267 | copy of the Corresponding Source for all the software in the 268 | product that is covered by this License, on a durable physical 269 | medium customarily used for software interchange, for a price no 270 | more than your reasonable cost of physically performing this 271 | conveying of source, or (2) access to copy the 272 | Corresponding Source from a network server at no charge. 273 | 274 | c) Convey individual copies of the object code with a copy of the 275 | written offer to provide the Corresponding Source. This 276 | alternative is allowed only occasionally and noncommercially, and 277 | only if you received the object code with such an offer, in accord 278 | with subsection 6b. 279 | 280 | d) Convey the object code by offering access from a designated 281 | place (gratis or for a charge), and offer equivalent access to the 282 | Corresponding Source in the same way through the same place at no 283 | further charge. You need not require recipients to copy the 284 | Corresponding Source along with the object code. If the place to 285 | copy the object code is a network server, the Corresponding Source 286 | may be on a different server (operated by you or a third party) 287 | that supports equivalent copying facilities, provided you maintain 288 | clear directions next to the object code saying where to find the 289 | Corresponding Source. Regardless of what server hosts the 290 | Corresponding Source, you remain obligated to ensure that it is 291 | available for as long as needed to satisfy these requirements. 292 | 293 | e) Convey the object code using peer-to-peer transmission, provided 294 | you inform other peers where the object code and Corresponding 295 | Source of the work are being offered to the general public at no 296 | charge under subsection 6d. 297 | 298 | A separable portion of the object code, whose source code is excluded 299 | from the Corresponding Source as a System Library, need not be 300 | included in conveying the object code work. 301 | 302 | A "User Product" is either (1) a "consumer product", which means any 303 | tangible personal property which is normally used for personal, family, 304 | or household purposes, or (2) anything designed or sold for incorporation 305 | into a dwelling. In determining whether a product is a consumer product, 306 | doubtful cases shall be resolved in favor of coverage. For a particular 307 | product received by a particular user, "normally used" refers to a 308 | typical or common use of that class of product, regardless of the status 309 | of the particular user or of the way in which the particular user 310 | actually uses, or expects or is expected to use, the product. A product 311 | is a consumer product regardless of whether the product has substantial 312 | commercial, industrial or non-consumer uses, unless such uses represent 313 | the only significant mode of use of the product. 314 | 315 | "Installation Information" for a User Product means any methods, 316 | procedures, authorization keys, or other information required to install 317 | and execute modified versions of a covered work in that User Product from 318 | a modified version of its Corresponding Source. The information must 319 | suffice to ensure that the continued functioning of the modified object 320 | code is in no case prevented or interfered with solely because 321 | modification has been made. 322 | 323 | If you convey an object code work under this section in, or with, or 324 | specifically for use in, a User Product, and the conveying occurs as 325 | part of a transaction in which the right of possession and use of the 326 | User Product is transferred to the recipient in perpetuity or for a 327 | fixed term (regardless of how the transaction is characterized), the 328 | Corresponding Source conveyed under this section must be accompanied 329 | by the Installation Information. But this requirement does not apply 330 | if neither you nor any third party retains the ability to install 331 | modified object code on the User Product (for example, the work has 332 | been installed in ROM). 333 | 334 | The requirement to provide Installation Information does not include a 335 | requirement to continue to provide support service, warranty, or updates 336 | for a work that has been modified or installed by the recipient, or for 337 | the User Product in which it has been modified or installed. Access to a 338 | network may be denied when the modification itself materially and 339 | adversely affects the operation of the network or violates the rules and 340 | protocols for communication across the network. 341 | 342 | Corresponding Source conveyed, and Installation Information provided, 343 | in accord with this section must be in a format that is publicly 344 | documented (and with an implementation available to the public in 345 | source code form), and must require no special password or key for 346 | unpacking, reading or copying. 347 | 348 | 7. Additional Terms. 349 | 350 | "Additional permissions" are terms that supplement the terms of this 351 | License by making exceptions from one or more of its conditions. 352 | Additional permissions that are applicable to the entire Program shall 353 | be treated as though they were included in this License, to the extent 354 | that they are valid under applicable law. If additional permissions 355 | apply only to part of the Program, that part may be used separately 356 | under those permissions, but the entire Program remains governed by 357 | this License without regard to the additional permissions. 358 | 359 | When you convey a copy of a covered work, you may at your option 360 | remove any additional permissions from that copy, or from any part of 361 | it. (Additional permissions may be written to require their own 362 | removal in certain cases when you modify the work.) You may place 363 | additional permissions on material, added by you to a covered work, 364 | for which you have or can give appropriate copyright permission. 365 | 366 | Notwithstanding any other provision of this License, for material you 367 | add to a covered work, you may (if authorized by the copyright holders of 368 | that material) supplement the terms of this License with terms: 369 | 370 | a) Disclaiming warranty or limiting liability differently from the 371 | terms of sections 15 and 16 of this License; or 372 | 373 | b) Requiring preservation of specified reasonable legal notices or 374 | author attributions in that material or in the Appropriate Legal 375 | Notices displayed by works containing it; or 376 | 377 | c) Prohibiting misrepresentation of the origin of that material, or 378 | requiring that modified versions of such material be marked in 379 | reasonable ways as different from the original version; or 380 | 381 | d) Limiting the use for publicity purposes of names of licensors or 382 | authors of the material; or 383 | 384 | e) Declining to grant rights under trademark law for use of some 385 | trade names, trademarks, or service marks; or 386 | 387 | f) Requiring indemnification of licensors and authors of that 388 | material by anyone who conveys the material (or modified versions of 389 | it) with contractual assumptions of liability to the recipient, for 390 | any liability that these contractual assumptions directly impose on 391 | those licensors and authors. 392 | 393 | All other non-permissive additional terms are considered "further 394 | restrictions" within the meaning of section 10. If the Program as you 395 | received it, or any part of it, contains a notice stating that it is 396 | governed by this License along with a term that is a further 397 | restriction, you may remove that term. If a license document contains 398 | a further restriction but permits relicensing or conveying under this 399 | License, you may add to a covered work material governed by the terms 400 | of that license document, provided that the further restriction does 401 | not survive such relicensing or conveying. 402 | 403 | If you add terms to a covered work in accord with this section, you 404 | must place, in the relevant source files, a statement of the 405 | additional terms that apply to those files, or a notice indicating 406 | where to find the applicable terms. 407 | 408 | Additional terms, permissive or non-permissive, may be stated in the 409 | form of a separately written license, or stated as exceptions; 410 | the above requirements apply either way. 411 | 412 | 8. Termination. 413 | 414 | You may not propagate or modify a covered work except as expressly 415 | provided under this License. Any attempt otherwise to propagate or 416 | modify it is void, and will automatically terminate your rights under 417 | this License (including any patent licenses granted under the third 418 | paragraph of section 11). 419 | 420 | However, if you cease all violation of this License, then your 421 | license from a particular copyright holder is reinstated (a) 422 | provisionally, unless and until the copyright holder explicitly and 423 | finally terminates your license, and (b) permanently, if the copyright 424 | holder fails to notify you of the violation by some reasonable means 425 | prior to 60 days after the cessation. 426 | 427 | Moreover, your license from a particular copyright holder is 428 | reinstated permanently if the copyright holder notifies you of the 429 | violation by some reasonable means, this is the first time you have 430 | received notice of violation of this License (for any work) from that 431 | copyright holder, and you cure the violation prior to 30 days after 432 | your receipt of the notice. 433 | 434 | Termination of your rights under this section does not terminate the 435 | licenses of parties who have received copies or rights from you under 436 | this License. If your rights have been terminated and not permanently 437 | reinstated, you do not qualify to receive new licenses for the same 438 | material under section 10. 439 | 440 | 9. Acceptance Not Required for Having Copies. 441 | 442 | You are not required to accept this License in order to receive or 443 | run a copy of the Program. Ancillary propagation of a covered work 444 | occurring solely as a consequence of using peer-to-peer transmission 445 | to receive a copy likewise does not require acceptance. However, 446 | nothing other than this License grants you permission to propagate or 447 | modify any covered work. These actions infringe copyright if you do 448 | not accept this License. Therefore, by modifying or propagating a 449 | covered work, you indicate your acceptance of this License to do so. 450 | 451 | 10. Automatic Licensing of Downstream Recipients. 452 | 453 | Each time you convey a covered work, the recipient automatically 454 | receives a license from the original licensors, to run, modify and 455 | propagate that work, subject to this License. You are not responsible 456 | for enforcing compliance by third parties with this License. 457 | 458 | An "entity transaction" is a transaction transferring control of an 459 | organization, or substantially all assets of one, or subdividing an 460 | organization, or merging organizations. If propagation of a covered 461 | work results from an entity transaction, each party to that 462 | transaction who receives a copy of the work also receives whatever 463 | licenses to the work the party's predecessor in interest had or could 464 | give under the previous paragraph, plus a right to possession of the 465 | Corresponding Source of the work from the predecessor in interest, if 466 | the predecessor has it or can get it with reasonable efforts. 467 | 468 | You may not impose any further restrictions on the exercise of the 469 | rights granted or affirmed under this License. For example, you may 470 | not impose a license fee, royalty, or other charge for exercise of 471 | rights granted under this License, and you may not initiate litigation 472 | (including a cross-claim or counterclaim in a lawsuit) alleging that 473 | any patent claim is infringed by making, using, selling, offering for 474 | sale, or importing the Program or any portion of it. 475 | 476 | 11. Patents. 477 | 478 | A "contributor" is a copyright holder who authorizes use under this 479 | License of the Program or a work on which the Program is based. The 480 | work thus licensed is called the contributor's "contributor version". 481 | 482 | A contributor's "essential patent claims" are all patent claims 483 | owned or controlled by the contributor, whether already acquired or 484 | hereafter acquired, that would be infringed by some manner, permitted 485 | by this License, of making, using, or selling its contributor version, 486 | but do not include claims that would be infringed only as a 487 | consequence of further modification of the contributor version. For 488 | purposes of this definition, "control" includes the right to grant 489 | patent sublicenses in a manner consistent with the requirements of 490 | this License. 491 | 492 | Each contributor grants you a non-exclusive, worldwide, royalty-free 493 | patent license under the contributor's essential patent claims, to 494 | make, use, sell, offer for sale, import and otherwise run, modify and 495 | propagate the contents of its contributor version. 496 | 497 | In the following three paragraphs, a "patent license" is any express 498 | agreement or commitment, however denominated, not to enforce a patent 499 | (such as an express permission to practice a patent or covenant not to 500 | sue for patent infringement). To "grant" such a patent license to a 501 | party means to make such an agreement or commitment not to enforce a 502 | patent against the party. 503 | 504 | If you convey a covered work, knowingly relying on a patent license, 505 | and the Corresponding Source of the work is not available for anyone 506 | to copy, free of charge and under the terms of this License, through a 507 | publicly available network server or other readily accessible means, 508 | then you must either (1) cause the Corresponding Source to be so 509 | available, or (2) arrange to deprive yourself of the benefit of the 510 | patent license for this particular work, or (3) arrange, in a manner 511 | consistent with the requirements of this License, to extend the patent 512 | license to downstream recipients. "Knowingly relying" means you have 513 | actual knowledge that, but for the patent license, your conveying the 514 | covered work in a country, or your recipient's use of the covered work 515 | in a country, would infringe one or more identifiable patents in that 516 | country that you have reason to believe are valid. 517 | 518 | If, pursuant to or in connection with a single transaction or 519 | arrangement, you convey, or propagate by procuring conveyance of, a 520 | covered work, and grant a patent license to some of the parties 521 | receiving the covered work authorizing them to use, propagate, modify 522 | or convey a specific copy of the covered work, then the patent license 523 | you grant is automatically extended to all recipients of the covered 524 | work and works based on it. 525 | 526 | A patent license is "discriminatory" if it does not include within 527 | the scope of its coverage, prohibits the exercise of, or is 528 | conditioned on the non-exercise of one or more of the rights that are 529 | specifically granted under this License. You may not convey a covered 530 | work if you are a party to an arrangement with a third party that is 531 | in the business of distributing software, under which you make payment 532 | to the third party based on the extent of your activity of conveying 533 | the work, and under which the third party grants, to any of the 534 | parties who would receive the covered work from you, a discriminatory 535 | patent license (a) in connection with copies of the covered work 536 | conveyed by you (or copies made from those copies), or (b) primarily 537 | for and in connection with specific products or compilations that 538 | contain the covered work, unless you entered into that arrangement, 539 | or that patent license was granted, prior to 28 March 2007. 540 | 541 | Nothing in this License shall be construed as excluding or limiting 542 | any implied license or other defenses to infringement that may 543 | otherwise be available to you under applicable patent law. 544 | 545 | 12. No Surrender of Others' Freedom. 546 | 547 | If conditions are imposed on you (whether by court order, agreement or 548 | otherwise) that contradict the conditions of this License, they do not 549 | excuse you from the conditions of this License. If you cannot convey a 550 | covered work so as to satisfy simultaneously your obligations under this 551 | License and any other pertinent obligations, then as a consequence you may 552 | not convey it at all. For example, if you agree to terms that obligate you 553 | to collect a royalty for further conveying from those to whom you convey 554 | the Program, the only way you could satisfy both those terms and this 555 | License would be to refrain entirely from conveying the Program. 556 | 557 | 13. Use with the GNU Affero General Public License. 558 | 559 | Notwithstanding any other provision of this License, you have 560 | permission to link or combine any covered work with a work licensed 561 | under version 3 of the GNU Affero General Public License into a single 562 | combined work, and to convey the resulting work. The terms of this 563 | License will continue to apply to the part which is the covered work, 564 | but the special requirements of the GNU Affero General Public License, 565 | section 13, concerning interaction through a network will apply to the 566 | combination as such. 567 | 568 | 14. Revised Versions of this License. 569 | 570 | The Free Software Foundation may publish revised and/or new versions of 571 | the GNU General Public License from time to time. Such new versions will 572 | be similar in spirit to the present version, but may differ in detail to 573 | address new problems or concerns. 574 | 575 | Each version is given a distinguishing version number. If the 576 | Program specifies that a certain numbered version of the GNU General 577 | Public License "or any later version" applies to it, you have the 578 | option of following the terms and conditions either of that numbered 579 | version or of any later version published by the Free Software 580 | Foundation. If the Program does not specify a version number of the 581 | GNU General Public License, you may choose any version ever published 582 | by the Free Software Foundation. 583 | 584 | If the Program specifies that a proxy can decide which future 585 | versions of the GNU General Public License can be used, that proxy's 586 | public statement of acceptance of a version permanently authorizes you 587 | to choose that version for the Program. 588 | 589 | Later license versions may give you additional or different 590 | permissions. However, no additional obligations are imposed on any 591 | author or copyright holder as a result of your choosing to follow a 592 | later version. 593 | 594 | 15. Disclaimer of Warranty. 595 | 596 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 597 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 598 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 599 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 600 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 601 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 602 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 603 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 604 | 605 | 16. Limitation of Liability. 606 | 607 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 608 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 609 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 610 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 611 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 612 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 613 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 614 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 615 | SUCH DAMAGES. 616 | 617 | 17. Interpretation of Sections 15 and 16. 618 | 619 | If the disclaimer of warranty and limitation of liability provided 620 | above cannot be given local legal effect according to their terms, 621 | reviewing courts shall apply local law that most closely approximates 622 | an absolute waiver of all civil liability in connection with the 623 | Program, unless a warranty or assumption of liability accompanies a 624 | copy of the Program in return for a fee. 625 | 626 | END OF TERMS AND CONDITIONS 627 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | # get long description that is used within the README file of this project 4 | # (available on the GitHub platform) for display within PyPi platform 5 | 6 | with open("README.rst", "r") as fh: 7 | long_description = fh.read() 8 | 9 | # for obtaining current version information directly from the source code, the 10 | # local freeplane code file is to be consulted, prior to its installation on 11 | # the local system. as it seems that at this point of the operation no LXML 12 | # package is available (although it had previously been installed), its import 13 | # statement within freeplane.py must be held in try / except clause. 14 | 15 | import src.freeplane as freeplane 16 | 17 | # do the settings for PyPi 18 | setup( 19 | name='freeplane-io', 20 | #version="0.7.2", 21 | version=freeplane.__version__, 22 | py_modules=['freeplane'], 23 | author='nnako', 24 | author_email='nnako@web.de', 25 | url="https://github.com/nnako/freeplane-python-io", 26 | description='provide create, read, update and delete of freeplane nodes via file access', 27 | package_dir={"": "src"}, 28 | long_description=long_description, 29 | long_description_content_type="text/markdown", 30 | install_requires=[ 31 | "html2text ~= 2020.1.16", 32 | "lxml", 33 | ], 34 | classifiers=[ 35 | "Programming Language :: Python :: 3", 36 | "Programming Language :: Python :: 3.6", 37 | "Programming Language :: Python :: 3.7", 38 | "Programming Language :: Python :: 3.8", 39 | "Programming Language :: Python :: 3.9", 40 | "Programming Language :: Python :: 3.10", 41 | "Programming Language :: Python :: 3.11", 42 | "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", 43 | "Operating System :: OS Independent", 44 | ], 45 | extras_require={ 46 | "doc": [ 47 | "sphinx~=5.2.3", 48 | ], 49 | }, 50 | ) 51 | -------------------------------------------------------------------------------- /src/freeplane.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding: utf-8 -*- 3 | 4 | 5 | 6 | 7 | # 8 | # DESCRIPTION 9 | # 10 | # a library holding useful and object-oriented functionalities to interface with 11 | # a (freeplane) mindmap. using this library, information can easily be extracted 12 | # and used in a programmatical way without having to browse through the mindmap 13 | # itself. 14 | # 15 | # 16 | # internally, the following object model is used, where the symbols follow this 17 | # definition: 18 | # 19 | # M Mindmap object - holding general map information 20 | # R root Node object - the first user-accessible Node within a mindmap 21 | # N Node object - any Node attached to a mindmap 22 | # B Branch object - a separate information structure organizing detached elements 23 | # DH Detached Head object - the head of a detached branch 24 | # DN Detached Node object - any branch node below a detached head 25 | # XMLNODE object - an lxml node element representing a real node in Freeplane 26 | # 27 | # 28 | # _path 29 | # _type .---------------------------- . _map .-----------. 30 | # _version | . _node ------------->| XMLNODE | 31 | # _mindmap | . _branch ---| '-----------' 32 | # _root | ^ 33 | # _parentmap | | | 34 | # v | | 35 | # | .---. .----. .----. .----. .--------. | 36 | # '----------- | M | | R +-+-+ N +-+ N +-+ N +- ... | 37 | # '---' '----' | '----' '----' '--------' | 38 | # ^ | | 39 | # | | .----. .----. | 40 | # | '-+ N +-+ N +- ... | 41 | # | '----' '----' |--- . _map | 42 | # | . _node ---' 43 | # | .------------------------------------------ . _branch 44 | # _map -------' | 45 | # _parentmap | | 46 | # v | 47 | # | .---. .----. .----. .----. .----. .----. .-------. 48 | # '----------- | B | | DH +-+-+ DN +-+ DN +-+ DN +-+ DN +-+ DN +- ... 49 | # '---' '----' | '----' '----' '----' '----' '-------' 50 | # | 51 | # | .----. .----. 52 | # '-+ DN +-+ DN +- ... 53 | # '----' '----' 54 | # 55 | # 56 | # AUTHOR 57 | # 58 | # - nnako, started in 2016 59 | # 60 | 61 | 62 | # built-ins 63 | from __future__ import print_function 64 | import argparse 65 | import datetime 66 | import html 67 | import importlib.util 68 | import io 69 | import logging 70 | import logging.config 71 | import os 72 | import re 73 | import sys 74 | 75 | # xml format 76 | try: 77 | import lxml.etree as ET 78 | except: 79 | print("at this point, lxml package is not available. shouldn't be a problem, though.") 80 | 81 | # html format 82 | try: 83 | import html2text 84 | except: 85 | print("at this point, html2text package is not available. shouldn't be a problem, though.") 86 | 87 | 88 | # version 89 | __version__ = '0.10.1' 90 | 91 | 92 | # BUILTIN ICONS 93 | ICON_EXCLAMATION = 'yes' 94 | ICON_LIST = 'list' 95 | ICON_QUESTION = 'help' 96 | ICON_CHECKED = 'button_ok' 97 | ICON_BOOKMARK = 'bookmark' 98 | ICON_PRIO1 = 'full-1' 99 | ICON_PRIO2 = 'full-2' 100 | 101 | # LOGGER CONFIGURATION 102 | LOGGING_CONFIG = { 103 | "version" : 1, 104 | "disable_existing_loggers" : False, 105 | "formatters": { 106 | "simple": { 107 | "format" : '[ %(name)-12s ] %(levelname)-8s %(message)s', 108 | }, 109 | "detailed": { 110 | "format" : '%(asctime)s [ %(name)-12s ] %(levelname)-8s L%(lineno)-5d] %(message)s', 111 | "datefmt" : "%Y-%m-%dT%H:%M:%S%z", 112 | } 113 | }, 114 | "handlers": { 115 | "stderr": { 116 | "class" : "logging.StreamHandler", 117 | "level" : "INFO", 118 | "formatter" : "simple", 119 | "stream" : "ext://sys.stderr", 120 | }, 121 | }, 122 | "loggers": { 123 | "root": { 124 | "level" : "DEBUG", 125 | "handlers": [ 126 | "stderr", 127 | ] 128 | } 129 | } 130 | } 131 | 132 | 133 | 134 | 135 | # 136 | # functions 137 | # 138 | 139 | def sanitized(text): 140 | 141 | # when reading text from mindmaps, sometimes there will be special 142 | # characters representing an ordinary character. especially when 143 | # dealing with html or richtext nodes and converting them to plain text. 144 | # these are replaced by ordinary characters. 145 | 146 | return text.replace("\xa0", " ").strip() 147 | 148 | 149 | 150 | 151 | # 152 | # mindmap class 153 | # 154 | 155 | class Mindmap(object): 156 | 157 | """ 158 | representation of Freeplane mindmap file as a container for nodes. access 159 | styles and other general features from here. 160 | 161 | """ 162 | 163 | # number of available map objects this session 164 | _num_of_maps = 0 165 | 166 | # global node id per session and incremented 167 | # each time a node is created will be used to 168 | # increment a session date. this gives 10000 169 | # possible new nodes before the id string is 170 | # added another digit (initially 10 digits). 171 | _global_node_id_incr = 0 172 | _global_node_id_seed = datetime.datetime.now().strftime('%y%m%d') 173 | 174 | 175 | def __init__( 176 | self, 177 | path='', 178 | mtype='freeplane', 179 | version='1.3.0', 180 | _id='', 181 | log_level="", 182 | ): 183 | 184 | 185 | 186 | 187 | # 188 | # IF directly started from command line 189 | # 190 | 191 | # do this only if called from the command line 192 | if _id == 'cli': 193 | 194 | # define information 195 | parser = argparse.ArgumentParser( 196 | description='Operation on Freeplane mindmap', 197 | usage='''%s [] 198 | 199 | Possible commands are: 200 | getText return text portion of a node 201 | test test this library 202 | ... ...''' % os.path.basename(sys.argv[0])) 203 | 204 | # define command argument 205 | parser.add_argument( 206 | 'command', 207 | help='Subcommand to run' 208 | ) 209 | 210 | 211 | 212 | 213 | # 214 | # create logger 215 | # 216 | 217 | # create own logger (as there is no calling application) 218 | self._logger = logging.getLogger(__name__) 219 | logging.config.dictConfig(LOGGING_CONFIG) 220 | 221 | 222 | 223 | 224 | # 225 | # read out CLI and execute main command 226 | # 227 | 228 | # get main arguments from user 229 | args = parser.parse_args(sys.argv[1:2]) 230 | 231 | # check if command is provided in script 232 | if not hasattr(self, args.command): 233 | 234 | self._logger.error('Unrecognized command. EXITING.') 235 | parser.print_help() 236 | sys.exit(1) 237 | 238 | # use dispatch pattern to invoke method with same name 239 | getattr(self, args.command)() 240 | 241 | 242 | 243 | 244 | # 245 | # ELSE module was called from application 246 | # 247 | 248 | else: 249 | 250 | 251 | 252 | 253 | # 254 | # connect to existing logger 255 | # 256 | 257 | # the logging functionality will be used in a way that all settings will be 258 | # configured in the calling application's root logger. when there is no calling 259 | # application, this module will create its own. 260 | 261 | self._logger = logging.getLogger(__name__) 262 | 263 | # it is expected that the 1st handler of the root logger is the one 264 | # used to log onto the command line. so, set the level according to 265 | # API input 266 | if len(self._logger.parent.handlers) == 0: 267 | 268 | # create own logger (as there seems to be no calling application) 269 | logging.config.dictConfig(LOGGING_CONFIG) 270 | 271 | 272 | 273 | 274 | # 275 | # adjust logging level to user's wishes 276 | # 277 | 278 | if log_level.lower() == "debug": 279 | self._logger.parent.handlers[0].setLevel(logging.DEBUG) 280 | elif log_level.lower() == "info": 281 | self._logger.parent.handlers[0].setLevel(logging.INFO) 282 | elif log_level.lower() == "warning": 283 | self._logger.parent.handlers[0].setLevel(logging.WARNING) 284 | elif log_level.lower() == "error": 285 | self._logger.parent.handlers[0].setLevel(logging.ERROR) 286 | elif log_level != "": 287 | self._logger.warning(f'level string "{log_level}" is no valid log level specification. log level not changed') 288 | 289 | 290 | 291 | 292 | # 293 | # update class variables 294 | # 295 | 296 | Mindmap._num_of_maps += 1 297 | 298 | 299 | 300 | 301 | # 302 | # reload specific packages for enhanced functionality 303 | # 304 | 305 | # load local modules as packages to enhance functionality to be used 306 | # within e.g. the node objects 307 | 308 | _packages= [ 309 | "model", 310 | "grpc", 311 | ] 312 | for _package in _packages: 313 | self._load_package_if_exists(_package) 314 | 315 | 316 | 317 | 318 | # 319 | # access instance variables 320 | # 321 | 322 | # path of instance's mindmap file 323 | self._path = path 324 | 325 | # type, version 326 | self._type = mtype 327 | 328 | 329 | 330 | 331 | # 332 | # read mindmap in case path is given 333 | # 334 | 335 | # when a file name was given as CLI argument, it will be checked if an 336 | # appropriate file is present. if so, the mindmap will be loaded into 337 | # memory. 338 | 339 | # check for validity of file 340 | if os.path.isfile(self._path): 341 | 342 | 343 | 344 | 345 | # 346 | # determine file's map version 347 | # 348 | 349 | # before load of the actual mindmap into memory, the file version 350 | # is to be determined. this is due to the fact that the character 351 | # encoding of older freeplane files was not stable. so, detecting 352 | # the encoding before load prevents some encoding errors. 353 | 354 | # open mindmap file and read first row 355 | retry = False 356 | try: 357 | with io.open(self._path, "r", encoding="utf-8") as fpMap: 358 | strFirstLine = fpMap.readline() 359 | except: 360 | self._logger.info("format mismatch in mindmap file vs. UTF-8. TRYING WORKAROUND.") 361 | retry = True 362 | 363 | # in case there are wrong encodings when trying to read as UTF-8, 364 | # it is tried to use Window's native encoding scheme to read the 365 | # file. this will be most likely the case and might be a good 366 | # workaround 367 | 368 | if retry: 369 | try: 370 | with io.open(self._path, "r", encoding="windows-1252") as fpMap: 371 | strFirstLine = fpMap.readline() 372 | self._logger.info("format mismatch could be worked around, successfully") 373 | except: 374 | self._logger.warning("format mismatch in mindmap file vs. windows-1252. FURTHER PROBLEMS WILL FOLLOW.") 375 | 376 | # now, analyze the characters in the first line of the mindmap file 377 | # and try to find the "freeplane" token which will contain the 378 | # version information. 379 | 380 | # detect from '' 381 | idxFpToken = strFirstLine.find("freeplane") 382 | idxSpace = strFirstLine[idxFpToken:].find(" ") + idxFpToken 383 | idxVer = idxSpace+1 384 | idxClQuote = strFirstLine[idxVer:].find('"') + idxVer 385 | self._version = strFirstLine[idxVer:idxClQuote] 386 | 387 | 388 | 389 | 390 | # 391 | # set parser encoding due to map version 392 | # 393 | 394 | # now use the freeplane file version to determine the encoding. 395 | 396 | # check for fitting encoding 397 | encoding = get_version_specific_file_encoding(self._version) 398 | 399 | # set encoding to be read 400 | xmlparser = ET.XMLParser(encoding=encoding) 401 | # xmlparser = ET.XMLParser(encoding="latin1") 402 | # xmlparser = ET.XMLParser(encoding="utf-8") 403 | 404 | 405 | 406 | 407 | # 408 | # read entire mindmap and evaluate structure 409 | # 410 | 411 | # some Freeplane versions produce invalid XML syntax when writing 412 | # the mindmap into file. here, these invalid syntaxes are to be 413 | # removed from the file, before using and parsing the file. 414 | 415 | try: 416 | self._mindmap = ET.parse(self._path, parser=xmlparser) 417 | 418 | except ET.XMLSyntaxError: 419 | self._logger.warning("invalid XML syntax. will try to fix it temporarily...") 420 | 421 | # write sanitized file into temporary file 422 | _basename = "_" + os.path.basename(self._path) 423 | _dirname = os.path.dirname(self._path) 424 | 425 | # ensure temp file is not yet present 426 | while os.path.isfile(os.path.join(_dirname, _basename)): 427 | _basename = "_" + _basename 428 | _temp_file = os.path.join(_dirname, _basename) 429 | 430 | # read original XML file 431 | with io.open(self._path, "r", encoding="utf-8") as _file: 432 | _content = _file.read() 433 | 434 | # sanitize content 435 | _content = _content.replace(" ", " ") 436 | 437 | # create and write temp file 438 | with io.open(_temp_file, "w", encoding="utf-8") as _file: 439 | _file.write(_content) 440 | 441 | # repeat open of mindmap 442 | self._mindmap = ET.parse(_temp_file, parser=xmlparser) 443 | 444 | # remove temporary file 445 | os.remove(_temp_file) 446 | 447 | self._logger.info("... XML source was successfully sanitized.") 448 | 449 | # now that the XML file has been read in in a valid way, the normal 450 | # XML parsing is to take place within the module's functionalities. 451 | 452 | # get root of mindmap 453 | self._root = self._mindmap.getroot() 454 | 455 | # find and get first node element of etree 456 | self._rootnode = self._root.find('node') 457 | 458 | # build parent map (using ElementTree nodes) 459 | self._parentmap = {c:p for p in self._rootnode.iter() for c in p} 460 | 461 | 462 | 463 | 464 | return 465 | 466 | 467 | 468 | 469 | # 470 | # create mindmap if path is invalid or empty 471 | # 472 | 473 | # if there was no path given or the path does not correspond to a valid 474 | # file, a mindmap structure is created within memory. the basis is a 475 | # XML structure containing a lot of standard settings identified within 476 | # the normal freeplane files. 477 | 478 | # set version 479 | self._version = version 480 | 481 | # init parentmap dictionary in order to facilitate quick identification 482 | # of parent nodes of valid node objects (using ElementTree nodes as 483 | # keys and values) 484 | self._parentmap = {} 485 | 486 | # create map element as XML node containing the version information 487 | self._mindmap = ET.Element('map') 488 | self._mindmap.attrib['version'] = 'freeplane ' + self._version 489 | 490 | # get root of mindmap (necessary for save operation) 491 | self._root = self._mindmap 492 | 493 | # set some attributes for visibility within freeplane editor 494 | _node = ET.Element('attribute_registry') 495 | _node.attrib['SHOW_ATTRIBUTES'] = 'hide' 496 | self._mindmap.append(_node) 497 | 498 | # create 1st visible node element containing standard TEXT 499 | self._rootnode = ET.Element('node') 500 | self._rootnode.attrib["TEXT"] = "new_mindmap" 501 | self._rootnode.attrib["FOLDED"] = "false" 502 | self._rootnode.attrib["ID"] = Mindmap.create_node_id() 503 | self._mindmap.append(self._rootnode) 504 | 505 | # create some standard edge styles 506 | _node = ET.Element('edge') 507 | _node.attrib['STYLE'] = 'horizontal' 508 | _node.attrib['COLOR'] = '#cccccc' 509 | self._rootnode.append(_node) 510 | 511 | # 512 | # hook element and properties 513 | # 514 | 515 | _hook = ET.Element('hook') 516 | _hook.attrib["NAME"] = "MapStyle" 517 | _hook.attrib["zoom"] = "1.00" 518 | self._rootnode.append(_hook) 519 | # sub element properties 520 | _node = ET.Element('properties') 521 | _node.attrib["show_icon_for_attributes"] = "false" 522 | _node.attrib["show_note_icons"] = "false" 523 | _hook.append(_node) 524 | 525 | # 526 | # map styles 527 | # 528 | 529 | # sub element map styles 530 | _mapstyles = ET.Element('map_styles') 531 | _hook.append(_mapstyles) 532 | # sub sub element stylenode 533 | _stylenode = ET.Element('stylenode') 534 | _stylenode.attrib["LOCALIZED_TEXT"] = "styles.root_node" 535 | _mapstyles.append(_stylenode) 536 | 537 | # 538 | # predefined styles 539 | # 540 | 541 | # sub sub sub element stylenode 542 | _node = ET.Element('stylenode') 543 | _node.attrib["LOCALIZED_TEXT"] = "styles.predefined" 544 | _node.attrib["POSITION"] = "right" 545 | _stylenode.append(_node) 546 | # sub sub sub element stylenode 547 | _node2 = ET.Element('stylenode') 548 | _node2.attrib["LOCALIZED_TEXT"] = "default" 549 | _node2.attrib["MAX_WIDTH"] = "600" 550 | _node2.attrib["COLOR"] = "#000000" 551 | _node2.attrib["STYLE"] = "as_parent" 552 | _node.append(_node2) 553 | # sub sub sub sub element stylenode 554 | _node3 = ET.Element('font') 555 | _node3.attrib["NAME"] = "Segoe UI" 556 | _node3.attrib["SIZE"] = "12" 557 | _node3.attrib["BOLD"] = "false" 558 | _node3.attrib["ITALIC"] = "false" 559 | _node2.append(_node3) 560 | # sub sub sub element stylenode 561 | _node2 = ET.Element('stylenode') 562 | _node2.attrib["LOCALIZED_TEXT"] = "defaultstyle.details" 563 | _node.append(_node2) 564 | # sub sub sub element stylenode 565 | _node2 = ET.Element('stylenode') 566 | _node2.attrib["LOCALIZED_TEXT"] = "defaultstyle.note" 567 | _node.append(_node2) 568 | # sub sub sub element stylenode 569 | _node2 = ET.Element('stylenode') 570 | _node2.attrib["LOCALIZED_TEXT"] = "defaultstyle.floating" 571 | _node.append(_node2) 572 | # sub sub sub sub element stylenode 573 | _node3 = ET.Element('edge') 574 | _node3.attrib["STYLE"] = "hide edge" 575 | _node2.append(_node3) 576 | # sub sub sub sub element stylenode 577 | _node3 = ET.Element('cloud') 578 | _node3.attrib["COLOR"] = "#0f0f0f" 579 | _node3.attrib["SHAPE"] = "ROUND_RECT" 580 | _node2.append(_node3) 581 | 582 | # 583 | # user styles 584 | # 585 | 586 | # sub sub sub element stylenode 587 | _node = ET.Element('stylenode') 588 | _node.attrib["LOCALIZED_TEXT"] = "styles.user-defined" 589 | _node.attrib["POSITION"] = "right" 590 | _stylenode.append(_node) 591 | # sub sub sub element stylenode 592 | _node2 = ET.Element('stylenode') 593 | _node2.attrib["LOCALIZED_TEXT"] = "styles.topic" 594 | _node2.attrib["COLOR"] = "#18898b" 595 | _node2.attrib["STYLE"] = "fork" 596 | _node.append(_node2) 597 | # sub sub sub sub element stylenode 598 | _node3 = ET.Element('font') 599 | _node3.attrib["NAME"] = "Liberation Sans" 600 | _node3.attrib["SIZE"] = "12" 601 | _node3.attrib["BOLD"] = "true" 602 | _node2.append(_node3) 603 | # sub sub sub element stylenode 604 | _node2 = ET.Element('stylenode') 605 | _node2.attrib["LOCALIZED_TEXT"] = "styles.subtopic" 606 | _node2.attrib["COLOR"] = "#cc3300" 607 | _node2.attrib["STYLE"] = "fork" 608 | _node.append(_node2) 609 | # sub sub sub sub element stylenode 610 | _node3 = ET.Element('font') 611 | _node3.attrib["NAME"] = "Liberation Sans" 612 | _node3.attrib["SIZE"] = "12" 613 | _node3.attrib["BOLD"] = "true" 614 | _node2.append(_node3) 615 | # sub sub sub element stylenode 616 | _node2 = ET.Element('stylenode') 617 | _node2.attrib["LOCALIZED_TEXT"] = "styles.subsubtopic" 618 | _node2.attrib["COLOR"] = "#669900" 619 | _node.append(_node2) 620 | # sub sub sub sub element stylenode 621 | _node3 = ET.Element('font') 622 | _node3.attrib["NAME"] = "Liberation Sans" 623 | _node3.attrib["SIZE"] = "12" 624 | _node3.attrib["BOLD"] = "true" 625 | _node2.append(_node3) 626 | # sub sub sub element stylenode 627 | _node2 = ET.Element('stylenode') 628 | _node2.attrib["LOCALIZED_TEXT"] = "styles.important" 629 | _node.append(_node2) 630 | # sub sub sub sub element stylenode 631 | _node3 = ET.Element('icon') 632 | _node3.attrib["BUILTIN"] = "yes" 633 | _node2.append(_node3) 634 | 635 | # MAP 636 | 637 | @classmethod 638 | def get_num_of_maps(cls): 639 | """ 640 | return the number of maps already created within the current session 641 | 642 | :returns: integer 643 | """ 644 | 645 | return cls._num_of_maps 646 | 647 | @classmethod 648 | def create_node_id(cls, mindmap=None): 649 | """ 650 | create a valid node id. this node id is incremented automatically, 651 | whenever a new XML node is created. even if it is discarded later. 652 | the node id, here consists of three parts: 653 | 654 | 1. the id token "ID_" which is used for all nodes directly created 655 | within freeplane editor 656 | 657 | 2. and kind of session seed which is the current date 658 | 659 | 3. and a standard 4-digit integer value constantly incremented 660 | """ 661 | 662 | # increment future part of node id 663 | cls._global_node_id_incr += 1 664 | 665 | # set the node id 666 | _id = 'ID_' + \ 667 | cls._global_node_id_seed + \ 668 | '{:04}'.format(cls._global_node_id_incr) 669 | 670 | 671 | 672 | 673 | # 674 | # resolve overlapping ids 675 | # 676 | 677 | # check if the originally intended node id is already present within 678 | # the mindmap. if it is, increment the node id counter, generate the 679 | # node id again and check again. do this until a node id was found 680 | # which does not yet exist within the mindmap. 681 | 682 | # only if valid mindmap pointer was given 683 | if mindmap is not None: 684 | 685 | bLeave = False 686 | while not bLeave: 687 | 688 | # check for calculated id already used 689 | lstOfNodesMatchingId = mindmap._root.xpath("//node[@ID='" + _id + "']") 690 | if len(lstOfNodesMatchingId): 691 | 692 | # increment global node id counter 693 | cls._global_node_id_incr += 1 694 | 695 | # set the node id string 696 | _id = 'ID_' + \ 697 | cls._global_node_id_seed + \ 698 | '{:04}'.format(cls._global_node_id_incr) 699 | 700 | else: 701 | bLeave = True 702 | 703 | 704 | 705 | 706 | # return new node id 707 | return _id 708 | 709 | @classmethod 710 | def create_node(cls, 711 | core='', 712 | link='', 713 | id='', 714 | style='', 715 | modified='', # timestamp format, milliseconds since 1.1.1970 716 | created='', # timestamp format, milliseconds since 1.1.1970 717 | ): 718 | 719 | # 720 | # create and init element 721 | # 722 | 723 | # core 724 | _node = ET.Element('node') 725 | node = Node(_node, None) 726 | node.plaintext = core 727 | 728 | 729 | 730 | 731 | # 732 | # set current creation and modification dates 733 | # 734 | 735 | update_date_attribute_in_node( 736 | node=_node, 737 | key="MODIFIED", 738 | ) 739 | 740 | update_date_attribute_in_node( 741 | node=_node, 742 | key="CREATED", 743 | ) 744 | 745 | 746 | 747 | 748 | # create temporary branch with local (empty) parent_map reference 749 | node._branch = Branch() 750 | 751 | # check own id choice 752 | if id: 753 | node.id = id 754 | if not node.id == id: 755 | # print("[ WARNING: node id must follow Freplane's format rules. nothing done. ]") 756 | return None 757 | 758 | # link 759 | if link: 760 | node.hyperlink = link 761 | 762 | # style 763 | if style: 764 | self._logger.warning("style attribute not implemented, yet") 765 | 766 | return node 767 | 768 | 769 | @property 770 | def rootnode(self): 771 | return Node(self._rootnode, self) 772 | 773 | 774 | @property 775 | def styles(self): 776 | _style = {} 777 | 778 | _stylenode_user = self._mindmap.find('.//stylenode[@LOCALIZED_TEXT="styles.user-defined"]') 779 | _lst = _stylenode_user.findall('./stylenode[@TEXT]') 780 | for _sty in _lst: 781 | _item = {} 782 | 783 | # style name 784 | _name = _sty.get('TEXT', '') 785 | 786 | # foreground color 787 | _color = _sty.get('COLOR', '') 788 | if _color: 789 | _item['color'] = _color 790 | 791 | # background color 792 | _bgcolor = _sty.get('BACKGROUND_COLOR', '') 793 | if _bgcolor: 794 | _item['bgcolor'] = _bgcolor 795 | 796 | # font 797 | _sty_sub = _sty.find('./font') 798 | if _sty_sub is not None: 799 | # font name 800 | _fontname = _sty_sub.get('NAME', '') 801 | _item['fontname'] = _fontname 802 | # font size 803 | _fontsize = _sty_sub.get('SIZE', '') 804 | _item['fontsize'] = _fontsize 805 | 806 | # ... 807 | 808 | # add to dict 809 | _style[_name] = _item 810 | 811 | return _style 812 | 813 | 814 | def add_style(self, 815 | name='', 816 | settings={}, 817 | ): 818 | """ 819 | This functions adds a style to a mindmap 820 | """ 821 | 822 | 823 | 824 | 825 | # 826 | # create new style within mindmap 827 | # 828 | 829 | if name: 830 | 831 | 832 | 833 | # 834 | # check validity of requests 835 | # 836 | 837 | # look for parent element 838 | _stylenode_user = self._mindmap.find('.//stylenode[@LOCALIZED_TEXT="styles.user-defined"]') 839 | 840 | # get list of existing style elements 841 | _lst = _stylenode_user.findall('./stylenode[@TEXT]') 842 | 843 | # leave function if style is already existing 844 | for _sty in _lst: 845 | if name.lower() == _sty.get('TEXT').lower(): 846 | logger.warning('style "' + name + '" is already existing. ignoring request.') 847 | return False 848 | 849 | # create element 850 | _sty = ET.Element("stylenode", TEXT=name) 851 | 852 | # append element to list of styles 853 | _stylenode_user.append(_sty) 854 | 855 | 856 | 857 | 858 | # 859 | # set attributes 860 | # 861 | 862 | # foreground color 863 | _check = 'color' 864 | if _check in settings.keys(): 865 | _sty.set('COLOR', settings[_check]) 866 | 867 | # background color 868 | _check = 'bgcolor' 869 | if _check in settings.keys(): 870 | _sty.set('BACKGROUND_COLOR', settings[_check]) 871 | 872 | # font name 873 | _check = 'fontname' 874 | if _check in settings.keys(): 875 | _item = ET.Element('font', NAME=settings[_check]) 876 | # add item to style 877 | _sty.append(_item) 878 | 879 | # font size 880 | _check = 'fontsize' 881 | if _check in settings.keys(): 882 | _item = _sty.find('./font') 883 | if _item is None: 884 | # create new font element 885 | _item = ET.Element('font', SIZE=settings[_check]) 886 | _sty.append(_item) 887 | else: 888 | # add size attribute to font element 889 | _item.set("SIZE", settings[_check]) 890 | 891 | return True 892 | 893 | return False 894 | 895 | 896 | def find_nodes( 897 | self, 898 | core='', 899 | link='', 900 | id='', 901 | attrib='', 902 | details='', 903 | notes='', 904 | icon='', 905 | style=[], 906 | exact=False, 907 | generalpathsep=False, 908 | caseinsensitive=False, 909 | keep_link_specials=False, 910 | ): 911 | 912 | 913 | 914 | 915 | # 916 | # find list of nodes in map 917 | # 918 | 919 | # start with ALL nodes within the mindmap and strip down to the number 920 | # of nodes matching all given arguments 921 | 922 | # list all nodes regardless of further properties 923 | lstXmlNodes = self._root.findall(".//node") 924 | 925 | # do the checks on the base of the list 926 | lstXmlNodes = reduce_node_list( 927 | lstXmlNodes=lstXmlNodes, 928 | id=id, 929 | core=core, 930 | attrib=attrib, 931 | details=details, 932 | notes=notes, 933 | link=link, 934 | icon=icon, 935 | style=style, 936 | exact=exact, 937 | generalpathsep=generalpathsep, 938 | caseinsensitive=caseinsensitive, 939 | keep_link_specials=False, 940 | ) 941 | 942 | 943 | 944 | 945 | 946 | # 947 | # create Node instances 948 | # 949 | 950 | lstNodesRet = [] 951 | for _node in lstXmlNodes: 952 | 953 | # create reference to parent lxml node 954 | #... 955 | 956 | # apend to list 957 | lstNodesRet.append(Node(_node, self)) 958 | 959 | return lstNodesRet 960 | 961 | 962 | def save(self, strPath, encoding=''): 963 | 964 | 965 | 966 | 967 | # 968 | # auto-determine and set encoding 969 | # 970 | 971 | # check for fitting encoding 972 | if not encoding: 973 | encoding = get_version_specific_file_encoding(self._version) 974 | 975 | 976 | 977 | 978 | # 979 | # create XML formatted output string 980 | # 981 | 982 | # create output string 983 | _outputstring = ET.tostring( 984 | self._root, 985 | pretty_print=True, 986 | method='xml', 987 | encoding=encoding, 988 | ).decode(encoding) 989 | 990 | 991 | 992 | 993 | # 994 | # sanitize string content 995 | # 996 | 997 | # prior to v1.8.0 the mindmap file was not a real XML and also not 998 | # consequently encoded in a specific code format. rather the encoding 999 | # is a mixture between "latin1" and "windows-1252". thus, in Germany, 1000 | # at least the german special characters must be corrected to be 1001 | # properly displayed within freeplane. 1002 | 1003 | _version = self._version.split('.') 1004 | if int(_version[0]) == 1 and int(_version[1]) < 8: 1005 | 1006 | # #160 characters representing 1007 | _outputstring = _outputstring.replace( chr(160),' ') 1008 | 1009 | # at least substitute encoded german special characters 1010 | # with characters fitting to the UTF-8 HTML encoding 1011 | 1012 | _outputstring = _outputstring.replace( 'ä','ä') # ä 1013 | _outputstring = _outputstring.replace( 'ö','ö') # ö 1014 | _outputstring = _outputstring.replace( 'ü','ü') # ü 1015 | _outputstring = _outputstring.replace( 'Ä','Ä') 1016 | _outputstring = _outputstring.replace( 'Ö','Ö') 1017 | _outputstring = _outputstring.replace( 'Ü','Ü') 1018 | _outputstring = _outputstring.replace( 'ß','ß') 1019 | 1020 | # by copy/paste from other applications into the mindmap, there 1021 | # might be further character sequences not wanted within this file 1022 | 1023 | # alternative double quotes 1024 | # _outputstring = _outputstring.replace( '“','"') 1025 | # _outputstring = _outputstring.replace( '„','"') 1026 | 1027 | # three subsequent dots (e.g. from EXCEL's auto chars) 1028 | # _outputstring = _outputstring.replace( '…','...') 1029 | # _outputstring = _outputstring.replace( chr(0x2026);','...') 1030 | # _outputstring = _outputstring.replace( chr(133),'...') 1031 | 1032 | 1033 | 1034 | 1035 | # 1036 | # write content into file 1037 | # 1038 | 1039 | # remove first line if not starting with "\n' + \ 1968 | # ' \n' + \ 1969 | # '\n' + \ 1970 | # ' \n' + \ 1971 | # ' \n' + \ 1972 | # '

\n' + \ 1973 | # ' ' + strDetails + '\n' + \ 1974 | # '

\n' + \ 1975 | # ' \n' + \ 1976 | # '\n' 1977 | 1978 | # append element 1979 | _node = self._node.append(_element) 1980 | 1981 | # return self.details 1982 | 1983 | 1984 | @property 1985 | def notes(self): 1986 | """ 1987 | get the value of the node's notes attribute. 1988 | 1989 | :return: the plaintext value of the notes attribute (preserving newlines) 1990 | :rtype: string 1991 | """ 1992 | 1993 | _text = '' 1994 | 1995 | # check for notes node 1996 | _lstNotesNodes = self._node.findall("./richcontent[@TYPE='NOTE']") 1997 | if _lstNotesNodes: 1998 | _text = ''.join(_lstNotesNodes[0].itertext()).strip() 1999 | 2000 | return _text 2001 | 2002 | 2003 | @notes.setter 2004 | def notes(self, strNotes): 2005 | """ 2006 | write a plaintext into the node's notes attribute. 2007 | 2008 | :param strNotes: the plaintext value to be written 2009 | :type strNotes: string 2010 | """ 2011 | 2012 | # remove existing notes element 2013 | _lstNotesNodes = self._node.findall("./richcontent[@TYPE='NOTE']") 2014 | if _lstNotesNodes: 2015 | self._node.remove(_lstNotesNodes[0]) 2016 | 2017 | # create new notes element 2018 | if strNotes: 2019 | 2020 | # build html structure 2021 | _element = ET.Element("richcontent", TYPE='NOTE') 2022 | _html = ET.SubElement(_element, "html") 2023 | _head = ET.SubElement(_html, "head") 2024 | _body = ET.SubElement(_html, "body") 2025 | for strLine in strNotes.split('\n'): 2026 | _p = ET.SubElement(_body, "p") 2027 | _p.text = strLine 2028 | 2029 | # append element 2030 | _node = self._node.append(_element) 2031 | 2032 | 2033 | @property 2034 | def parent(self): 2035 | 2036 | # if non-detached node 2037 | if self.is_map_node: 2038 | # ensure existing parent 2039 | if self._node in self._map._parentmap.keys(): 2040 | return Node(self._map._parentmap[self._node], self._map) 2041 | else: 2042 | return None 2043 | 2044 | # if detached node 2045 | elif self.is_detached_node: 2046 | # read from branch object 2047 | return Node(self._branch._parentmap[self._node], self._map) 2048 | 2049 | # if detached branch head 2050 | elif self.is_detached_head: 2051 | logger.warning("a detached branch head has no other parent.") 2052 | return None 2053 | 2054 | else: 2055 | #print("[ ERROR : local parentmap has not been created for detached node. ]") 2056 | return None 2057 | 2058 | 2059 | @property 2060 | def previous(self): 2061 | 2062 | # ensure existing parent 2063 | _previous = self._node.getprevious() 2064 | if _previous is not None: 2065 | 2066 | # create Node instance 2067 | fpnode = Node(_previous, self._map) 2068 | 2069 | # update branch reference in case of detached node 2070 | if not self.is_root_node and not self.is_map_node: 2071 | fpnode._map = None 2072 | fpnode._branch = self._branch 2073 | 2074 | # append node object 2075 | return fpnode 2076 | else: 2077 | return None 2078 | 2079 | 2080 | @property 2081 | def next(self): 2082 | 2083 | # ensure existing parent 2084 | _next = self._node.getnext() 2085 | if _next is not None: 2086 | 2087 | # create Node instance 2088 | fpnode = Node(_next, self._map) 2089 | 2090 | # update branch reference in case of detached node 2091 | if not self.is_root_node and not self.is_map_node: 2092 | fpnode._map = None 2093 | fpnode._branch = self._branch 2094 | 2095 | # append node object 2096 | return fpnode 2097 | else: 2098 | return None 2099 | 2100 | 2101 | @property 2102 | def icons(self): 2103 | _icons = [] 2104 | _lst = self._node.findall('icon') 2105 | for _icon in _lst: 2106 | _name = _icon.get('BUILTIN', '') 2107 | if _name: 2108 | _icons.append(_name) 2109 | return _icons 2110 | 2111 | 2112 | def add_icon(self, 2113 | icon='', 2114 | ): 2115 | """ 2116 | This functions adds a Freeplane-Icon to a node 2117 | """ 2118 | 2119 | 2120 | 2121 | 2122 | # 2123 | # add icon to node 2124 | # 2125 | 2126 | if icon: 2127 | 2128 | _icon = ET.Element('icon') 2129 | _icon.attrib['BUILTIN'] = icon 2130 | 2131 | self._node.append(_icon) 2132 | 2133 | # return self.icons 2134 | 2135 | 2136 | def remove(self, 2137 | ): 2138 | """ 2139 | This functions removes the current Freeplane node from a branch 2140 | """ 2141 | 2142 | # get parent element 2143 | parent = self.parent 2144 | 2145 | # remove the current node 2146 | parent._node.remove(self._node) 2147 | 2148 | return True 2149 | 2150 | 2151 | def del_icon(self, 2152 | icon='', 2153 | ): 2154 | """ 2155 | This functions removes a Freeplane-Icon from a node 2156 | """ 2157 | 2158 | 2159 | 2160 | 2161 | # 2162 | # search for icon 2163 | # 2164 | 2165 | if icon: 2166 | 2167 | _icons = [] 2168 | _lst = self._node.findall('icon') 2169 | for _icon in _lst: 2170 | 2171 | if _icon.get('BUILTIN', '').lower() == icon.lower(): 2172 | 2173 | 2174 | 2175 | 2176 | # 2177 | # remove icon from node's icon list 2178 | # 2179 | 2180 | self._node.remove(_icon) 2181 | break 2182 | 2183 | # return self.icons 2184 | 2185 | 2186 | @property 2187 | def children(self): 2188 | lstNodesRet = [] 2189 | for _node in self._node.findall("./node"): 2190 | 2191 | # create Node instance 2192 | fpnode = Node(_node, self._map) 2193 | 2194 | # update branch reference in case of detached node 2195 | if not self.is_root_node and not self.is_map_node: 2196 | fpnode._map = None 2197 | fpnode._branch = self._branch 2198 | 2199 | # append node object 2200 | lstNodesRet.append(fpnode) 2201 | 2202 | return lstNodesRet 2203 | 2204 | 2205 | @property 2206 | def index(self): 2207 | # valid child index values can be determined in case the node is not a 2208 | # root node and has a parent 2209 | if not self.is_root_node and self.parent: 2210 | return self.parent._node.index(self._node) 2211 | return 0 2212 | 2213 | 2214 | def get_child_by_index(self, idx=0): 2215 | # check if node has children 2216 | _children = self._node.findall("./node") 2217 | if len(_children): 2218 | # run through all child nodes 2219 | for _i, _child in enumerate(_children): 2220 | # check for matching index 2221 | if _i == idx: 2222 | 2223 | # create Node instance 2224 | fpnode = Node(_child, self._map) 2225 | 2226 | # update branch reference in case of detached node 2227 | if not self.is_root_node and not self.is_map_node: 2228 | fpnode._map = None 2229 | fpnode._branch = self._branch 2230 | 2231 | # append node object 2232 | return fpnode 2233 | 2234 | # index not found 2235 | else: 2236 | return None 2237 | # no children present 2238 | else: 2239 | return None 2240 | 2241 | def get_indexchain_until(self, node): 2242 | """ 2243 | determine the list of index values which have to be used in order to 2244 | find the given node. the process is started from the self object and 2245 | continued until the given node was found. the actual implementation 2246 | works from backwards. starting at the given node and determining its 2247 | parents until the base node (self) was found. then reversing the list 2248 | order. 2249 | """ 2250 | 2251 | # default and error return 2252 | lstIdxValues = [] 2253 | 2254 | # init 2255 | _run = node 2256 | 2257 | # check if given node (or it's parents) is not rootnode 2258 | while not _run.is_rootnode: 2259 | 2260 | # break loop if start of chain reached 2261 | if self.id == _run.id: 2262 | break 2263 | 2264 | # get parent of current node (go back one level) 2265 | parent = _run.parent 2266 | 2267 | # determine node's child idx below it's parent 2268 | for _i, child in enumerate(parent.children): 2269 | if child.id == _run.id: 2270 | lstIdxValues.append(_i) 2271 | break 2272 | 2273 | # next loop 2274 | _run = parent 2275 | 2276 | # reverse results 2277 | return list(reversed(lstIdxValues)) 2278 | 2279 | 2280 | def is_descendant_of(self, node): 2281 | """ 2282 | determine if the current node object has a direct relational connection 2283 | to a given node element. so, if the current node object is a child, 2284 | grand-child, ... of that given node element. 2285 | """ 2286 | 2287 | # walk up the parent elements until the given element is found or the 2288 | # search ends with the root node 2289 | 2290 | # get 1st parent element 2291 | parent = self.parent 2292 | 2293 | # loop 2294 | while parent: 2295 | 2296 | # check for match 2297 | if parent.id == node.id: 2298 | return True 2299 | 2300 | # leave function if we reached the root node 2301 | if parent.id == self._map.rootnode: 2302 | return False 2303 | 2304 | # get next parent further up 2305 | parent = parent.parent 2306 | 2307 | # this statement shouldn't be reached 2308 | return False 2309 | 2310 | 2311 | @property 2312 | def is_rootnode(self): 2313 | if self._map._rootnode == self._node \ 2314 | and not self._branch: 2315 | return True 2316 | return False 2317 | 2318 | 2319 | @property 2320 | def is_comment(self): 2321 | if not self._node.get('STYLE_REF') is None \ 2322 | and self._node.attrib['STYLE_REF'] == 'klein und grau': 2323 | return True 2324 | return False 2325 | 2326 | 2327 | @property 2328 | def has_children(self): 2329 | if not self._node.findall('./node'): 2330 | return False 2331 | return True 2332 | 2333 | 2334 | def find_nodes( 2335 | self, 2336 | core='', 2337 | link='', 2338 | id='', 2339 | attrib='', 2340 | details='', 2341 | notes='', 2342 | icon='', 2343 | style=[], 2344 | exact=False, 2345 | generalpathsep=False, 2346 | caseinsensitive=False, 2347 | find_in_self=False, 2348 | keep_link_specials=False, 2349 | ): 2350 | 2351 | 2352 | 2353 | 2354 | # 2355 | # find list of nodes below node 2356 | # 2357 | 2358 | # list all nodes regardless of further properties 2359 | # starting from below the current node 2360 | lstXmlNodes = self._node.findall(".//node") 2361 | 2362 | # add self to the list of nodes if desired 2363 | if find_in_self: 2364 | lstXmlNodes = [ self._node ] + lstXmlNodes 2365 | 2366 | # do the checks on the base of the list 2367 | lstXmlNodes = reduce_node_list( 2368 | lstXmlNodes=lstXmlNodes, 2369 | id=id, 2370 | core=core, 2371 | attrib=attrib, 2372 | details=details, 2373 | notes=notes, 2374 | link=link, 2375 | icon=icon, 2376 | style=style, 2377 | exact=exact, 2378 | generalpathsep=generalpathsep, 2379 | caseinsensitive=caseinsensitive, 2380 | keep_link_specials=False, 2381 | ) 2382 | 2383 | 2384 | 2385 | 2386 | # 2387 | # create Node instances 2388 | # 2389 | 2390 | lstNodesRet = [] 2391 | for _node in lstXmlNodes: 2392 | 2393 | # create Node instance 2394 | fpnode = Node(_node, self._map) 2395 | 2396 | # update branch reference in case of detached node 2397 | if not self.is_root_node and not self.is_map_node: 2398 | fpnode._map = None 2399 | fpnode._branch = self._branch 2400 | 2401 | # append node object 2402 | lstNodesRet.append(fpnode) 2403 | 2404 | return lstNodesRet 2405 | 2406 | 2407 | def find_children( 2408 | self, 2409 | core='', 2410 | link='', 2411 | id='', 2412 | attrib='', 2413 | details='', 2414 | notes='', 2415 | icon='', 2416 | style=[], 2417 | exact=False, 2418 | generalpathsep=False, 2419 | caseinsensitive=False, 2420 | keep_link_specials=False, 2421 | ): 2422 | 2423 | 2424 | 2425 | 2426 | # 2427 | # find list of nodes directly below node 2428 | # 2429 | 2430 | # list all nodes regardless of further properties 2431 | lstXmlNodes = self._node.findall("./node") 2432 | 2433 | # do the checks on the base of the list 2434 | lstXmlNodes = reduce_node_list( 2435 | lstXmlNodes=lstXmlNodes, 2436 | id=id, 2437 | core=core, 2438 | attrib=attrib, 2439 | details=details, 2440 | notes=notes, 2441 | link=link, 2442 | icon=icon, 2443 | style=style, 2444 | exact=exact, 2445 | generalpathsep=generalpathsep, 2446 | caseinsensitive=caseinsensitive, 2447 | keep_link_specials=False, 2448 | ) 2449 | 2450 | 2451 | 2452 | 2453 | # 2454 | # create Node instances 2455 | # 2456 | 2457 | lstNodesRet = [] 2458 | for _node in lstXmlNodes: 2459 | 2460 | # create Node instance 2461 | fpnode = Node(_node, self._map) 2462 | 2463 | # update branch reference in case of detached node 2464 | if not self.is_root_node and not self.is_map_node: 2465 | fpnode._map = None 2466 | fpnode._branch = self._branch 2467 | 2468 | # append node object 2469 | lstNodesRet.append(fpnode) 2470 | 2471 | return lstNodesRet 2472 | 2473 | 2474 | def getSubText(self, token=''): 2475 | 2476 | # initialize contents 2477 | text = "" 2478 | commentnode = None 2479 | 2480 | 2481 | 2482 | 2483 | # 2484 | # find node's INTERMEDIATE child node 2485 | # 2486 | 2487 | # skip tokennode if token present 2488 | if not token == "": 2489 | 2490 | # check for token node 2491 | tokennode = self._node.findall("./node[@TEXT='" + token + "']") 2492 | if not tokennode == []: 2493 | 2494 | # go further to find the comment text 2495 | commentnode = tokennode[0].find('./node') 2496 | 2497 | else: 2498 | 2499 | # get first node node as comment node 2500 | commentnode = self._node.find('./node') 2501 | 2502 | 2503 | 2504 | 2505 | # 2506 | # access text portion of target node 2507 | # 2508 | 2509 | # if comment node exists 2510 | if commentnode is not None: 2511 | 2512 | # get comment text 2513 | text = getCoreTextFromNode(commentnode, bOnlyFirstLine=False) 2514 | 2515 | else: 2516 | 2517 | # text is invalid 2518 | text = "" 2519 | 2520 | return text 2521 | 2522 | 2523 | def attach(self, 2524 | attached_node=None, 2525 | pos=-1, 2526 | ): 2527 | """ 2528 | This functions appends an existing but previously detached 2529 | Freeplane-Node as a child to this node object. 2530 | """ 2531 | 2532 | # CAUTION 2533 | # 2534 | # after using this function, node references targetting the attached 2535 | # branch will not be valid anymore. this is due to changes which 2536 | # currently cannot be updated within the reference objects on the user 2537 | # side. so, please, ensure that after using the attach function, all 2538 | # needed node references are re-created e.g. by using find() on the 2539 | # map. 2540 | 2541 | 2542 | 2543 | 2544 | # 2545 | # check if attached node is valid 2546 | # 2547 | 2548 | if attached_node is None: 2549 | logger.warning("no attached_node given to be attached.") 2550 | return False 2551 | 2552 | 2553 | 2554 | 2555 | # 2556 | # check if to-be-attached-node is already attached 2557 | # 2558 | 2559 | # in case, the node-to-be-attached is already part of the target 2560 | # branch, this activity is to be aborted as it would lead to confusion 2561 | # of the XML structure and the user's references. 2562 | 2563 | # check if object is child within map 2564 | if self.is_map_node or self.is_root_node: 2565 | if attached_node._node in self._map._parentmap.keys(): 2566 | logger.warning('node "' + str(attached_node) + \ 2567 | '" already attached to a map. NOTHING DONE.') 2568 | return False 2569 | elif attached_node.is_detached_node: 2570 | logger.warning('node "' + str(attached_node) + \ 2571 | '" is part of a detached branch. NOTHING DONE. please only attach branch head.') 2572 | return False 2573 | 2574 | 2575 | 2576 | 2577 | # 2578 | # DIFFERENT CASES 2579 | # 2580 | 2581 | # in order to leave the nodes in a consistent status, there are 2582 | # different cases to be evaluated. as there are different kind of nodes 2583 | # and the necessary operations differ depending on the node types 2584 | # involved during attachment, there must be a kind of 2585 | # "Fallunterscheidung". 2586 | 2587 | 2588 | 2589 | 2590 | # 2591 | # handle attach of detached head to map node 2592 | # 2593 | 2594 | if (self.is_map_node or self.is_root_node) and attached_node.is_detached_head: 2595 | 2596 | # 2597 | # update old branch head's _map member 2598 | # 2599 | 2600 | # the pointer to the map object of the attached node 2601 | # is to be the same as the map object attached to 2602 | attached_node._map = self._map 2603 | 2604 | # 2605 | # set parent node within map's parentmap 2606 | # 2607 | 2608 | self._map._parentmap[attached_node._node] = self._node 2609 | 2610 | # 2611 | # append map's parent dict from branch's dict 2612 | # 2613 | 2614 | self._map._parentmap.update(attached_node._branch._parentmap) 2615 | 2616 | # 2617 | # save new map reference in old branch object 2618 | # 2619 | 2620 | # store the new map reference within the old branch object 2621 | # for later reference when one of the former branch nodes is 2622 | # to be checked. thus, the _map member can be corrected. 2623 | attached_node._branch._map = self._map 2624 | 2625 | # 2626 | # insert appropriate XML nodes 2627 | # 2628 | 2629 | if pos == -1: 2630 | self._node.append(attached_node._node) 2631 | else: 2632 | self._node.insert(pos, attached_node._node) 2633 | 2634 | # leave function 2635 | # return attached_node 2636 | return True 2637 | 2638 | 2639 | 2640 | 2641 | # 2642 | # handle attach of detached head to detached branch 2643 | # 2644 | 2645 | if (self.is_detached_node or self.is_detached_head) and attached_node.is_detached_head: 2646 | 2647 | # 2648 | # update old branch head's _branch member 2649 | # 2650 | 2651 | # the pointer to the map object of the attached node 2652 | # is to be the same as the map object attached to 2653 | attached_node._branch = self._branch 2654 | 2655 | # 2656 | # set parent node within new branch's parentmap 2657 | # 2658 | 2659 | self._branch._parentmap[attached_node._node] = self._node 2660 | 2661 | # 2662 | # append new branch's parent dict from branch's dict 2663 | # 2664 | 2665 | self._branch._parentmap.update(attached_node._branch._parentmap) 2666 | 2667 | # 2668 | # insert appropriate XML nodes 2669 | # 2670 | 2671 | if pos == -1: 2672 | self._node.append(attached_node._node) 2673 | else: 2674 | self._node.insert(pos, attached_node._node) 2675 | 2676 | # leave function 2677 | return True 2678 | 2679 | 2680 | 2681 | 2682 | # 2683 | # handle attach of detached head to detached branch 2684 | # 2685 | 2686 | if attached_node.is_detached_node: 2687 | logger.warning('attach of "' \ 2688 | + str(attached_node) \ 2689 | + '" not possible. generally, only the heads of detached branches attachable.') 2690 | return False 2691 | 2692 | 2693 | 2694 | 2695 | logger.warning('host / child configuration for attach is not defined.') 2696 | return False 2697 | 2698 | 2699 | def add_arrowlink(self, 2700 | node=None, 2701 | style='', 2702 | shape='', 2703 | color='', 2704 | width='', 2705 | transparency='', 2706 | dash='', 2707 | fontsize='', 2708 | font='', 2709 | startinclination='', 2710 | endinclination='', 2711 | startarrow='NONE', 2712 | endarrow='DEFAULT', 2713 | ): 2714 | """ 2715 | draw an arrow link from the current node to the given one. 2716 | 2717 | the arrow starts at the host object and extends to an arbitrary node. 2718 | it's appearance can be configured using the following parameters: 2719 | 2720 | :param node: the node object an arrow is to be drawn to 2721 | :type node: freeplane.Node 2722 | :param shape: the shape of the arrow (e.g. "CUBIC_CURVE") 2723 | :type shape: string 2724 | :param color: the color of the arrow (e.g. "#FF0000" for red) 2725 | :type color: string 2726 | :param transparency: the transparency of the arrow over the background (e.g. "80" for 80%) 2727 | :type transparency: string 2728 | 2729 | """ 2730 | 2731 | if node: 2732 | 2733 | 2734 | 2735 | 2736 | # 2737 | # create arrow link node 2738 | # 2739 | 2740 | _node = ET.Element('arrowlink') 2741 | 2742 | 2743 | 2744 | 2745 | # 2746 | # append arrow link node to node object 2747 | # 2748 | 2749 | self._node.append(_node) 2750 | 2751 | 2752 | 2753 | 2754 | # 2755 | # IF named style definition was given 2756 | # 2757 | 2758 | if style: 2759 | 2760 | 2761 | 2762 | 2763 | # 2764 | # set style according to style definition 2765 | # 2766 | 2767 | pass 2768 | 2769 | 2770 | 2771 | 2772 | # 2773 | # ELSE 2774 | # 2775 | 2776 | else: 2777 | 2778 | 2779 | 2780 | 2781 | # 2782 | # set individual style members 2783 | # 2784 | 2785 | if not shape: 2786 | _node.set('SHAPE', 'CUBIC_CURVE') 2787 | else: 2788 | _node.set('SHAPE', shape) 2789 | if not color: 2790 | _node.set('COLOR', '#000000') 2791 | else: 2792 | _node.set('COLOR', color) 2793 | if not width: 2794 | _node.set('WIDTH', '2') 2795 | else: 2796 | _node.set('WIDTH', width) 2797 | if not transparency: 2798 | _node.set('TRANSPARENCY', '80') 2799 | else: 2800 | _node.set('TRANSPARENCY', transparency) 2801 | if dash: 2802 | _node.set('DASH', dash) 2803 | if not fontsize: 2804 | _node.set('FONT_SIZE', '9') 2805 | else: 2806 | _node.set('FONT_SIZE', fontsize) 2807 | if not font: 2808 | _node.set('FONT_FAMILY', 'SansSerif') 2809 | else: 2810 | _node.set('FONT_FAMILY', font) 2811 | if not startinclination: 2812 | _node.set('STARTINCLINATION', '131;0;') 2813 | else: 2814 | _node.set('STARTINCLINATION', startinclination) 2815 | if not endinclination: 2816 | _node.set('ENDINCLINATION', '131;0;') 2817 | else: 2818 | _node.set('ENDINCLINATION', endinclination) 2819 | if not startarrow: 2820 | _node.set('STARTARROW', 'NONE') 2821 | else: 2822 | _node.set('STARTARROW', startarrow) 2823 | if not endarrow: 2824 | _node.set('ENDARROW', 'DEFAULT') 2825 | else: 2826 | _node.set('ENDARROW', endarrow) 2827 | 2828 | # destination 2829 | _node.set('DESTINATION', node.id) 2830 | 2831 | 2832 | 2833 | 2834 | return False 2835 | 2836 | 2837 | @property 2838 | def arrowlinks(self): 2839 | """ 2840 | get list of nodes connected via outgoing arrowlinks 2841 | 2842 | :returns: list of Node elements 2843 | """ 2844 | lstNodesRet = [] 2845 | for _arrowlink in self._node.findall("./arrowlink"): 2846 | 2847 | # get the destination id of target node 2848 | _nodeid = _arrowlink.attrib.get('DESTINATION', "") 2849 | 2850 | # find node in local mindmap 2851 | _xmlnode = self._map._root.find('.//node[@ID="' + _nodeid + '"]') 2852 | 2853 | # create target Node instance 2854 | fpnode = Node(_xmlnode, self._map) 2855 | 2856 | # update branch reference in case of detached node 2857 | if not self.is_root_node and not self.is_map_node: 2858 | fpnode._map = None 2859 | fpnode._branch = self._branch 2860 | 2861 | # append node object 2862 | lstNodesRet.append(fpnode) 2863 | 2864 | return lstNodesRet 2865 | 2866 | 2867 | def del_arrowlink(self, 2868 | ident=0, 2869 | ): 2870 | """ 2871 | remove arrowlink from node 2872 | 2873 | :param ident: identifier for node to which the arrowlink connection is to be removed 2874 | :type ident: Node - the target node reference itself 2875 | int - the index of target node according to the return of the arrowlinks method 2876 | str - the node id of the target node to which the connection is to be removed 2877 | :returns: True - if the desired coonnection was removed 2878 | False - if target node could not be found 2879 | """ 2880 | 2881 | # arrowlinked nodes 2882 | fpnodes = self.arrowlinks 2883 | 2884 | # provide possibility to use ID or fpnode reference 2885 | # instead of list index 2886 | if isinstance(ident, str): 2887 | _nodeid = ident 2888 | elif isinstance(ident, int): 2889 | if ident > len(fpnodes)-1: 2890 | return False 2891 | _nodeid = fpnodes[ident].id 2892 | elif isinstance(ident, Node): 2893 | _nodeid = ident.id 2894 | 2895 | # check for node id to be removed from arrowlinks 2896 | _xmlarrowlinks = self._node.findall('./arrowlink[@DESTINATION="' + _nodeid + '"]') 2897 | 2898 | # remove arrowlink 2899 | if len(_xmlarrowlinks) <= 0: 2900 | return False 2901 | self._node.remove(_xmlarrowlinks[0]) 2902 | 2903 | return True 2904 | 2905 | 2906 | @property 2907 | def arrowlinked(self): 2908 | """ 2909 | get list of nodes connecting to the node (incoming arrowlinks) 2910 | 2911 | :returns: list of Node elements 2912 | """ 2913 | lstNodesRet = [] 2914 | 2915 | # find xmlnodes in local mindmap 2916 | _nodeid = self.id 2917 | _xmlarrowlinks = self._map._root.findall('.//arrowlink[@DESTINATION="' + _nodeid + '"]') 2918 | 2919 | for _xmlarrowlink in _xmlarrowlinks: 2920 | 2921 | # create target Node instance 2922 | fpnode = Node(_xmlarrowlink.getparent(), self._map) 2923 | 2924 | # update branch reference in case of detached node 2925 | if not self.is_root_node and not self.is_map_node: 2926 | fpnode._map = None 2927 | fpnode._branch = self._branch 2928 | 2929 | # append node object 2930 | lstNodesRet.append(fpnode) 2931 | 2932 | return lstNodesRet 2933 | 2934 | 2935 | def add_child(self, 2936 | core='', 2937 | link='', 2938 | id='', 2939 | pos=-1, 2940 | style='', 2941 | ): 2942 | """ 2943 | This functions adds a Freeplane-Node as a child to this Node. Further 2944 | more a XML-node ist added to the XML-Tree 2945 | """ 2946 | 2947 | 2948 | 2949 | 2950 | # 2951 | # create and init element 2952 | # 2953 | 2954 | _node = ET.Element('node') 2955 | node = Node(_node, self._map) 2956 | node.plaintext = core 2957 | 2958 | 2959 | 2960 | 2961 | # 2962 | # overwrite standard id 2963 | # 2964 | 2965 | if id: 2966 | node.id = id 2967 | if not node.id == id: 2968 | return None 2969 | 2970 | 2971 | 2972 | 2973 | # 2974 | # set link portion 2975 | # 2976 | 2977 | if link: 2978 | node.hyperlink = link 2979 | 2980 | 2981 | 2982 | 2983 | # 2984 | # set style 2985 | # 2986 | 2987 | if style: 2988 | node.style = style 2989 | 2990 | 2991 | 2992 | 2993 | # 2994 | # set node's position within children 2995 | # 2996 | 2997 | if pos == -1: 2998 | self._node.append(_node) 2999 | else: 3000 | self._node.insert(pos, _node) 3001 | 3002 | 3003 | 3004 | 3005 | # 3006 | # update parentmap dict 3007 | # 3008 | 3009 | # check if this node is attached to a map 3010 | if self.is_root_node or self.is_map_node: 3011 | 3012 | # add this object as parent to new object 3013 | self._map._parentmap[_node] = self._node 3014 | 3015 | else: 3016 | 3017 | # create _branch and _parentmap nodes in new child 3018 | node._branch = self._branch 3019 | 3020 | # add this object as parent to new object within detached branch 3021 | self._branch._parentmap[_node] = self._node 3022 | 3023 | 3024 | 3025 | 3026 | return node 3027 | 3028 | 3029 | def add_sibling(self, 3030 | core="", 3031 | link="", 3032 | id='', 3033 | pos=-1, 3034 | style=None, 3035 | ): 3036 | """ 3037 | This functions adds a Freeplane-Node as a Sibling. Further more a 3038 | XML-node ist added to the XML-Tree at the corresponding position 3039 | """ 3040 | 3041 | 3042 | 3043 | # 3044 | # create and init element 3045 | # 3046 | 3047 | _node = ET.Element('node') 3048 | node = Node(_node, self._map) 3049 | node.plaintext = core 3050 | 3051 | 3052 | 3053 | 3054 | # overwrite standard id 3055 | if id: 3056 | node.id = id 3057 | if not node.id == id: 3058 | # print("[ WARNING: node id must follow Freplane's format rules. nothing done. ]") 3059 | return None 3060 | 3061 | 3062 | 3063 | 3064 | # 3065 | # set link portion 3066 | # 3067 | 3068 | if link: 3069 | node.hyperlink = link 3070 | 3071 | 3072 | 3073 | 3074 | # 3075 | # set style 3076 | # 3077 | 3078 | if style: 3079 | node.style = style 3080 | 3081 | 3082 | 3083 | 3084 | # 3085 | # set node's position within siblings 3086 | # 3087 | 3088 | if pos == -1: 3089 | self._node.getparent().append(_node) 3090 | else: 3091 | self._node.getparent().insert(pos, _node) 3092 | 3093 | 3094 | 3095 | 3096 | # 3097 | # update parentmap dict 3098 | # 3099 | 3100 | # check if this node is attached to a map 3101 | if self.is_root_node or self.is_map_node: 3102 | 3103 | # add this object as parent to new object 3104 | self._map._parentmap[_node] = self._node.getparent() 3105 | 3106 | # check if this node is attached to a branch 3107 | elif self._node in self._branch._parentmap.keys(): 3108 | self._branch._parentmap[_node] = self._node.getparent() 3109 | 3110 | else: 3111 | 3112 | # output warning 3113 | logger.warning("it is not possible to add a sibling to a detached node. please use the create_node function.") 3114 | return None 3115 | 3116 | 3117 | 3118 | 3119 | return node 3120 | 3121 | 3122 | # 3123 | # HELPERS 3124 | # 3125 | 3126 | def update_date_attribute_in_node( 3127 | node=None, 3128 | date="", 3129 | key="MODIFIED", 3130 | ): 3131 | 3132 | # leave if inappropriate arguments 3133 | if node is None: 3134 | return False 3135 | 3136 | # calculate current date in milliseconds 3137 | _current_time = datetime.datetime.now() 3138 | _current_timestamp = str(int(_current_time.timestamp()*1000)) 3139 | 3140 | # set modification date 3141 | if date: 3142 | node.set(key, date) 3143 | else: 3144 | # set current date 3145 | node.set(key, _current_timestamp) 3146 | 3147 | return True 3148 | 3149 | 3150 | def get_version_specific_file_encoding(version): 3151 | 3152 | # file encoding was changed from "latin1" or "windows-1252" 3153 | # to "utf-8" with Freeplane version 1.8.0 3154 | 3155 | lstVersionItems = version.split('.') 3156 | if len(lstVersionItems)>=2: 3157 | if int(lstVersionItems[0]) == 1 and int(lstVersionItems[1]) <= 6: 3158 | # return "latin1" 3159 | return "windows-1252" 3160 | elif int(lstVersionItems[0]) == 1 and int(lstVersionItems[1]) > 6: 3161 | return "utf-8" 3162 | 3163 | 3164 | # CONVENIENCE FUNCTIONS 3165 | 3166 | def getCoreTextFromNode(node, bOnlyFirstLine=False): 3167 | 3168 | # initialize text content 3169 | text = "" 3170 | 3171 | 3172 | 3173 | 3174 | # 3175 | # get TEXT attribute of node if present 3176 | # 3177 | 3178 | if not node.get('TEXT') is None: 3179 | 3180 | # read out text content 3181 | text = node.attrib['TEXT'] 3182 | 3183 | 3184 | 3185 | 3186 | # 3187 | # strip text from RICHTEXT content if present 3188 | # 3189 | 3190 | elif not node.find('richcontent') is None: 3191 | 3192 | # get richtext node 3193 | richnode = node.find('richcontent') 3194 | 3195 | # get html node 3196 | htmlnode = richnode.find('html') 3197 | 3198 | # get html body node 3199 | html_body = htmlnode.find('body') 3200 | 3201 | # filter out plain text content 3202 | sanitized_text = extract_sanitized_body_content(html_body) 3203 | 3204 | 3205 | 3206 | 3207 | # 3208 | # filter first line if desired 3209 | # 3210 | 3211 | if bOnlyFirstLine: 3212 | 3213 | # take only first line of text content 3214 | text = sanitized_text.strip().split('\n')[0].strip() 3215 | 3216 | else: 3217 | 3218 | # remove leading / trailing whitespace 3219 | text = sanitized_text.strip() 3220 | 3221 | 3222 | return text 3223 | 3224 | 3225 | def extract_sanitized_body_content(body_elem): 3226 | parts = [] 3227 | 3228 | def process_element(el): 3229 | 3230 | # process text before children 3231 | if el.text and el.text.strip(): 3232 | parts.append(html.unescape(el.text)) 3233 | 3234 | # process children recursively 3235 | for child in el: 3236 | 3237 | # into child element and evaluate 3238 | process_element(child) 3239 | 3240 | # when only whitespace -> discard 3241 | if child.tail and child.tail.strip(): 3242 | parts.append(html.unescape(child.tail)) 3243 | 3244 | # add NEWLINE only if on body level 3245 | if child.tag == "p" and child.tail and child.tail.strip() == "": 3246 | parts.append("\n") 3247 | 3248 | process_element(body_elem) 3249 | 3250 | # join parts, strip trailing whitespace, and preserve non-breaking spaces 3251 | result = ''.join(parts).strip('\n') 3252 | 3253 | return result 3254 | 3255 | 3256 | def reduce_node_list( 3257 | lstXmlNodes=[], 3258 | id='', 3259 | core='', 3260 | attrib='', 3261 | details='', 3262 | notes='', 3263 | link='', 3264 | icon='', 3265 | style=[], 3266 | exact=False, 3267 | generalpathsep=False, 3268 | caseinsensitive=False, 3269 | keep_link_specials=False, 3270 | ): 3271 | 3272 | # check for identical ID 3273 | if id: 3274 | _lstNodes = [] 3275 | for _node in lstXmlNodes: 3276 | if id.lower() == _node.attrib.get("ID", "").lower(): 3277 | _lstNodes.append(_node) 3278 | lstXmlNodes = _lstNodes 3279 | 3280 | # check for TEXT within a node's CORE 3281 | if core: 3282 | _lstNodes = [] 3283 | for _node in lstXmlNodes: 3284 | if exact: 3285 | if not caseinsensitive and core == _node.attrib.get("TEXT", ""): 3286 | _lstNodes.append(_node) 3287 | elif caseinsensitive and core.lower() == _node.attrib.get("TEXT", "").lower(): 3288 | _lstNodes.append(_node) 3289 | else: 3290 | if core.lower() in _node.attrib.get("TEXT", "").lower(): 3291 | _lstNodes.append(_node) 3292 | lstXmlNodes = _lstNodes 3293 | 3294 | # check for all ATTRIBUTES within a node 3295 | if attrib: 3296 | _lstNodes = [] 3297 | # check for current list of nodes 3298 | for _node in lstXmlNodes: 3299 | # get attributes of node 3300 | for _attribnode in _node.findall("./attribute"): 3301 | _key = _attribnode.attrib.get("NAME", "") 3302 | _value = "" 3303 | if _key: 3304 | _value = _attribnode.attrib.get("VALUE", "") 3305 | # check all given attributes 3306 | iFound = 0 3307 | for _check_key, _check_value in attrib.items(): 3308 | 3309 | # key found in node 3310 | if _key == _check_key: 3311 | 3312 | # exact match desired 3313 | if exact: 3314 | 3315 | # generalized path separator 3316 | if generalpathsep: 3317 | 3318 | # case-sensitivity desired 3319 | if caseinsensitive and _value.replace("\\", "/").lower() == _check_value.replace("\\", "/").lower(): 3320 | iFound += 1 3321 | # case-insensitivity desired 3322 | elif not caseinsensitive and _value.replace("\\", "/") == _check_value.replace("\\", "/"): 3323 | iFound += 1 3324 | 3325 | # original path separator 3326 | else: 3327 | # case-sensitivity desired 3328 | if caseinsensitive and _value.lower() == _check_value.lower(): 3329 | iFound += 1 3330 | # case-insensitivity desired 3331 | elif not caseinsensitive and _value == _check_value: 3332 | iFound += 1 3333 | 3334 | # approximate match 3335 | else: 3336 | if _value.lower() in _check_value.lower(): 3337 | iFound += 1 3338 | 3339 | # check for matches of ALL given attribute 3340 | if iFound == len(attrib.items()): 3341 | _lstNodes.append(_node) 3342 | lstXmlNodes = _lstNodes 3343 | 3344 | # check for LINK within a node's LINK TEXT 3345 | if link: 3346 | _lstNodes = [] 3347 | for _node in lstXmlNodes: 3348 | 3349 | # Freeplane internally, sometimes modifies link strings so that 3350 | # they contain "fixed" spaces. these can cause a string-based 3351 | # equality comparison to fail. for this case, strings like "%20" 3352 | # will by default be replaced with ordinary strings " " before 3353 | # comparison, here. using the switch "keep_link_specials", equality 3354 | # comparisons can be made without replacing these special 3355 | # characters. 3356 | 3357 | if not keep_link_specials: 3358 | _link = _node.attrib.get("LINK", "").replace("\\","/").replace("%20", " ") 3359 | else: 3360 | _link = _node.attrib.get("LINK", "").replace("\\","/") 3361 | 3362 | # now do the comparison 3363 | if exact: 3364 | # case-sensitive test 3365 | if not caseinsensitive and (link.replace("\\","/") == _link): 3366 | _lstNodes.append(_node) 3367 | # case-insensitive test 3368 | elif caseinsensitive and (link.replace("\\","/").lower() == _link.lower()): 3369 | _lstNodes.append(_node) 3370 | else: 3371 | if link.replace("\\", "/").lower() in _link.lower(): 3372 | _lstNodes.append(_node) 3373 | 3374 | lstXmlNodes = _lstNodes 3375 | 3376 | # check for BUILTIN ICON at node 3377 | if icon: 3378 | _lstNodes = [] 3379 | for _node in lstXmlNodes: 3380 | # check for icon node 3381 | _lstIconNodes = _node.findall("./icon[@BUILTIN='" + icon + "']") 3382 | if _lstIconNodes: 3383 | _lstNodes.append(_node) 3384 | lstXmlNodes = _lstNodes 3385 | 3386 | # check for node's DETAILS 3387 | if details: 3388 | _lstNodes = [] 3389 | for _node in lstXmlNodes: 3390 | # check for details node 3391 | _lstDetailsNodes = _node.findall("./richcontent[@TYPE='DETAILS']") 3392 | if _lstDetailsNodes: 3393 | _text = ''.join(_lstDetailsNodes[0].itertext()) 3394 | if exact: 3395 | if not caseinsensitive and details == _text: 3396 | _lstNodes.append(_node) 3397 | elif caseinsensitive and details.lower() == _text.lower(): 3398 | _lstNodes.append(_node) 3399 | else: 3400 | if details.lower() in _text.lower(): 3401 | _lstNodes.append(_node) 3402 | lstXmlNodes = _lstNodes 3403 | 3404 | # check for node's NOTES 3405 | if notes: 3406 | _lstNodes = [] 3407 | for _node in lstXmlNodes: 3408 | # check for notes node 3409 | _lstNotesNodes = _node.findall("./richcontent[@TYPE='NOTE']") 3410 | if _lstNotesNodes: 3411 | _text = ''.join(_lstNotesNodes[0].itertext()) 3412 | if exact: 3413 | if not caseinsensitive and notes == _text: 3414 | _lstNodes.append(_node) 3415 | elif caseinsensitive and notes.lower() == _text.lower(): 3416 | _lstNodes.append(_node) 3417 | else: 3418 | if notes.lower() in _text.lower(): 3419 | _lstNodes.append(_node) 3420 | lstXmlNodes = _lstNodes 3421 | 3422 | # check for node's style(s) 3423 | if style: 3424 | # convert to list if not already so 3425 | if not type(style)==list: 3426 | style=[style] 3427 | _lstNodes = [] 3428 | for _node in lstXmlNodes: 3429 | # check for node style 3430 | _style = _node.attrib.get("STYLE_REF", "") 3431 | if _style.lower() in [_.lower() for _ in style]: 3432 | _lstNodes.append(_node) 3433 | lstXmlNodes = _lstNodes 3434 | 3435 | # and back 3436 | return lstXmlNodes 3437 | 3438 | 3439 | # OLD 3440 | 3441 | # read text paragraph from mindmap 3442 | # CLI FUNCTIONS 3443 | 3444 | def getText(self, strRootAttribute, strTitleText, strPortion): 3445 | 3446 | # get list of all attributes 3447 | lstAttributes = self._mindmap.getElementsByTagName('attribute') 3448 | 3449 | # search for ROOT ATTRIBUTE NODE 3450 | for item in lstAttributes: 3451 | if item.attributes['NAME'].value == 'type' and \ 3452 | item.attributes['VALUE'].value == strRootAttribute: 3453 | rootnode = item.parentNode 3454 | 3455 | # get list of all nodes below 3456 | lstNodes = rootnode.getElementsByTagName('node') 3457 | 3458 | # look for node containing TITLE STRING 3459 | for item in lstNodes: 3460 | if item.hasAttribute('TEXT'): 3461 | if item.getAttribute('TEXT') == strTitleText: 3462 | titlenode = item 3463 | 3464 | # get list of all nodes below 3465 | lstNodes = titlenode.getElementsByTagName('node') 3466 | 3467 | # look for node containing PORTION STRING 3468 | for item in lstNodes: 3469 | if item.hasAttribute('TEXT'): 3470 | if item.getAttribute('TEXT') == strPortion: 3471 | portionnode = item 3472 | 3473 | # if there is no richtext content ... 3474 | if not portionnode.getElementsByTagName('richcontent'): 3475 | 3476 | # get next following single node 3477 | textnode = portionnode.getElementsByTagName('node')[0] 3478 | 3479 | # get standard TEXT attribute 3480 | strText = textnode.getAttribute('TEXT') 3481 | 3482 | else: 3483 | 3484 | # look for HTML content 3485 | richcontents = portionnode.getElementsByTagName('richcontent') 3486 | 3487 | # convert content to HTML 3488 | strHtml = richcontents[0].toxml() 3489 | 3490 | # convert HTML to MARKDOWN ASCII 3491 | strText = html2text.html2text(strHtml) 3492 | 3493 | # replace cryptic text passages 3494 | strText = strText.replace('<', '<') 3495 | strText = strText.replace('>', '>') 3496 | 3497 | # return value back to caller 3498 | return strText 3499 | 3500 | 3501 | # 3502 | # execute this module code 3503 | # 3504 | 3505 | if __name__ == "__main__": 3506 | 3507 | # create execute class init with command line environment 3508 | Mindmap(_id='cli') 3509 | 3510 | -------------------------------------------------------------------------------- /versions.txt: -------------------------------------------------------------------------------- 1 | package versions 2 | 3 | 4 | v0.10.1 5 | 02.03.2025 6 | 7 | - FIX: re-work logging functionality in a way that the freeplane module 8 | will use an existing logging configuration provided by the calling 9 | application if possible. otherwise it will create its own. the 10 | calling application should use the built-in logging package and 11 | configure a handler[0] to report to the command line. 12 | 13 | - FIX: node's set_image method now respects specific protocols 14 | 15 | 16 | v0.10.0 17 | 27.10.2024 18 | 19 | - NEW: there are new attributes and methods for the object concerning 20 | the use of arrowlinks: 21 | 22 | - .arrowlinks will return a list of nodes connected 23 | with outgoing arrowlinks 24 | 25 | - .arrowlinked will return a list of nodes which connect 26 | to the arrowlinks (incoming) 27 | 28 | - .del_arrowlink() will remove one of 's outgoing 29 | arrowlinks 30 | 31 | the method .add_arrowlink() was implemented already. 32 | 33 | 34 | v0.9.0 35 | 26.09.2024 36 | 37 | - NEW: the attribute .notes might be used to get and set a plaintext 38 | value to the node's notes. analogous to the details attribute. 39 | consequently, the keyword argument "notes" can be used within the 40 | methods .find_nodes(), .find_nodes() and 41 | .find_children() to look for appropriate nodes. 42 | 43 | - NEW: the keyword argument "caseinsensitive=True" can be used in addition 44 | to the argument "exact=True" within the methods .find_nodes(), 45 | .find_nodes() and .find_children() to ensure that all 46 | textual comparisons will be performed in a case-insensitive way, 47 | although the text is expected to be exact. this setting will be 48 | relevant for checking the attributes core, link, details and notes. 49 | 50 | 51 | v0.8.0 52 | 09.07.2023 53 | 54 | - FIX: prevent from necessity to insert version multiple times (deployment) 55 | - NEW: method .is_descendant_of() determines if a given node element 56 | is located within a direct line of parents up towards the mindmap root 57 | - FIX: correct and stabilize set_image() function 58 | 59 | 60 | v0.7.2 61 | 20.05.2023 62 | 63 | - FIX: creation of own styles now correct 64 | - FIX: proper HTML format for multi-lined details 65 | 66 | v0.7.1 67 | 20.12.2023 68 | 69 | - FIX: determination of mindmap's encoding via try / except 70 | - FIX: workaround to neutralize invalid XML usage concerning NBSP 71 | 72 | v0.7.0 73 | 13.12.2022 74 | 75 | - NEW: handle in-line images 76 | - FIX: add lxml dependency in setup.py (to get installed automatically) 77 | - FIX: some bug fixes (see commit history) 78 | 79 | 80 | v0.6.0 81 | 29.09.2022 82 | 83 | - MOD: re-work API for mindmap and node objects 84 | - FIX: last preparations for PyPi and GitHub deployment 85 | 86 | 87 | v0.5.1 (old v1.2.1) 88 | 12.06.2022 89 | 90 | - FIX: some bug fixes (see commit history) 91 | 92 | v0.5.0 (old v1.2.0) 93 | 12.06.2022 94 | 95 | - NEW: management of creation and modification dates 96 | - FIX: some bug fixes (see commit history) 97 | 98 | 99 | v0.4.0 (old v1.1.0) 100 | 02.12.2021 101 | 102 | - NEW: determination of "descendants index chain" 103 | - NEW: check for node type (e.g. root node) 104 | - NEW: delete / remove node from map 105 | - NEW: set and management of node's attributes 106 | - NEW: management of styles for arrow links 107 | - FIX: some bug fixes (see commit history) 108 | 109 | 110 | v0.3.0 (old v1.0) 111 | 26.01.2021 112 | 113 | - NEW: creation and management of "detached" nodes 114 | - NEW: creation and management of proper node ids 115 | - NEW: manage arrow links between nodes 116 | - NEW: distinguish between different freeplane versions 117 | - NEW: manage node's style 118 | - NEW: create mindmap file, in addition to changing existing ones 119 | - FIX: some bug fixes (see commit history) 120 | 121 | 122 | v0.2.0 (old v0.9) 123 | 10.10.2020 124 | 125 | - NEW: manage single attributes for a node 126 | - FIX: some bug fixes (see commit history) 127 | 128 | 129 | v0.1.0 (old v0.8) 130 | 17.06.2020 131 | 132 | - NEW: first operational version used "professionally" within own applications 133 | --------------------------------------------------------------------------------