├── .gitignore ├── LICENSE ├── README.htm ├── README.md ├── TestFile ├── MarkDown Support Test.md └── markdownlogo.png ├── images ├── ContentPage.png └── MDTextEditorLight.png └── source ├── MarkdownCommonMark.pas ├── MarkdownDaringFireball.pas ├── MarkdownMathCode.pas ├── MarkdownProcessor.pas ├── MarkdownTables.pas ├── MarkdownTxtMark.pas └── MarkdownUtils.pas /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | 48 | # Delphi autogenerated files (duplicated info) 49 | *.cfg 50 | *.hpp 51 | *Resource.rc 52 | 53 | # Delphi local files (user-specific info) 54 | *.local 55 | *.identcache 56 | *.projdata 57 | *.tvsconfig 58 | *.dsk 59 | 60 | # Delphi history and backups 61 | __history/ 62 | __recovery/ 63 | *.~* 64 | 65 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 66 | *.stat 67 | 68 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 69 | modules/ 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.htm: -------------------------------------------------------------------------------- 1 | 32 |

Markdown Processor License

33 |

A Markdown Processor Library for Delphi, to process/convert markdown files to HTML.

34 |

Latest Version 1.2.0 - 08 Apr 2025

35 |

36 |

Basic Information

37 |

This is a Pascal (Delphi) library that processes markdown to HTML. 38 | At present the following dialects of markdown are supported:

39 | 47 |

Using the Library with Delphi

48 |

Declare a variable of the class TMarkdownProcessor:

49 |
     var
 50 |        md : TMarkdownProcessor;
 51 | 
52 |

Create a TMarkdownProcessor (MarkdownProcessor.pas) of the dialect you want:

53 |
       md := TMarkdownProcessor.createDialect(mdDaringFireball)
 54 | 
55 |

Decide whether you want to allow active content

56 |
       md.AllowUnSafe := true;
 57 | 
58 |

Note: you should only set this to true if you need to - active content can be a significant safety/security issue.

59 |

Generate HTML fragments from Markdown content:

60 |
       html := md.process(markdown);
 61 | 
62 |

Note that the HTML returned is an HTML fragment, not a full HTML page.

63 |

Do not forget to dispose of the object after the use:

64 |
       md.free
 65 | 
66 |

Large rework was made for adding support for tables, math formulas, etc.

67 |

Examples

68 |

this library is used in two projects:

69 | 72 |

A collection of tools for markdown files, to edit and view content, with an advanced Editor:

73 |

Markdown Text Editor

74 | 77 |

An integrated help system based on files in Markdown format (and also html), for Delphi applications:

78 |

Markdown HelpViewer

79 |

Release Notes

80 |

08 Apr 2025: ver. 1.2.0

81 | 84 |

16 Dec 2024: ver. 1.1.0

85 | 88 |

22 Oct 2023: ver. 1.0.0

89 | 94 |

License

95 |

Copyright (c) Ethea S.r.l.

96 |

Licensed under the Apache License, Version 2.0 (the “License”);

97 |

you may not use this file except in compliance with the License.

98 |

You may obtain a copy of the License at

99 |

http://www.apache.org/licenses/LICENSE-2.0

100 |

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

101 |

Contributors

102 |

MarkdownProcessor implementation is a fork of FPC-markdown by Miguel A. Risco-Castillo 103 | FPC-markdown

104 |

FPC-markdown implementation is a fork of Grahame Grieve pascal port 105 | Delphi-markdown

106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Markdown Processor [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://opensource.org/licenses/Apache-2.0) 2 | 3 | A Markdown Processor Library for Delphi, to process/convert markdown files to HTML. 4 | 5 | **Latest Version 1.2.0 - 08 Apr 2025** 6 | 7 | ============ 8 | 9 | Basic Information 10 | ----------------- 11 | 12 | This is a Pascal (Delphi) library that processes markdown to HTML. 13 | At present the following dialects of markdown are supported: 14 | 15 | * The Daring Fireball dialect 16 | (see ) 17 | * Almost complete support for CommonMark dialect 18 | (translated from ) 19 | * Enhanced TxtMark dialect 20 | (translated from ) 21 | 22 | 23 | 24 | Using the Library with Delphi 25 | ----------------------------- 26 | 27 | Declare a variable of the class TMarkdownProcessor: 28 | 29 | ```Pascal 30 | var 31 | md : TMarkdownProcessor; 32 | ``` 33 | 34 | Create a TMarkdownProcessor (MarkdownProcessor.pas) of the dialect you want: 35 | 36 | ```Pascal 37 | md := TMarkdownProcessor.createDialect(mdDaringFireball) 38 | ``` 39 | 40 | Decide whether you want to allow active content 41 | 42 | ```Pascal 43 | md.AllowUnSafe := true; 44 | ``` 45 | 46 | Note: you should only set this to true if you *need* to - active content can be a significant safety/security issue. 47 | 48 | Generate HTML fragments from Markdown content: 49 | 50 | ```Pascal 51 | html := md.process(markdown); 52 | ``` 53 | 54 | Note that the HTML returned is an HTML fragment, not a full HTML page. 55 | 56 | Do not forget to dispose of the object after the use: 57 | 58 | ```Pascal 59 | md.free 60 | ``` 61 | 62 | Large rework was made for adding support for tables, math formulas, etc. 63 | 64 | Examples 65 | -------- 66 | 67 | this library is used in two projects: 68 | 69 | - [MarkdownShellExtensions](https://github.com/EtheaDev/MarkdownShellExtensions) 70 | 71 | A collection of tools for markdown files, to edit and view content, with an advanced Editor: 72 | 73 | ![Markdown Text Editor](./images/MDTextEditorLight.png) 74 | 75 | - [MarkdownHelpViewer](https://github.com/EtheaDev/MarkdownHelpViewer) 76 | 77 | An integrated help system based on files in Markdown format (and also html), for Delphi applications: 78 | 79 | ![Markdown HelpViewer](./images/ContentPage.png) 80 | 81 | ## Release Notes ## 82 | 83 | 08 Apr 2025: ver. 1.2.0 84 | - Fixed const parameters 85 | 86 | 16 Dec 2024: ver. 1.1.0 87 | - Updated Demo for FireMonkey 88 | 89 | 22 Oct 2023: ver. 1.0.0 90 | - Project forked from FPC-markdown by Miguel A. Risco-Castillo 91 | - Removed unused Dialect mdAsciiDoc 92 | - changed position of enumerated dialect mdCommonMark for backward compatibility with Delphi-Markdown 93 | 94 | ## License 95 | 96 | Copyright (c) Ethea S.r.l. 97 | 98 | Licensed under the Apache License, Version 2.0 (the "License"); 99 | 100 | you may not use this file except in compliance with the License. 101 | 102 | You may obtain a copy of the License at 103 | 104 | 105 | 106 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 107 | 108 | ## Contributors 109 | 110 | MarkdownProcessor implementation is a fork of FPC-markdown by **Miguel A. Risco-Castillo** 111 | [FPC-markdown](https://github.com/mriscoc/fpc-markdown) 112 | 113 | FPC-markdown implementation is a fork of **Grahame Grieve** pascal port 114 | [Delphi-markdown](https://github.com/grahamegrieve/delphi-markdown) 115 | 116 | -------------------------------------------------------------------------------- /TestFile/MarkDown Support Test.md: -------------------------------------------------------------------------------- 1 | Markdown support test 2 | ===================== 3 | 4 | ![Markdown logo](markdownlogo.png) 5 | 6 | This page is a small demonstration of Markdown support. 7 | 8 | The file is encoded in UTF-8 format with BOM (this is a UTF-8 symbol: €) 9 | 10 | 11 | 12 | Referenced link: From [CommonMark]: 13 | >Markdown is a plain text format for writing structured documents, 14 | >based on conventions for indicating formatting in email and Usenet posts. 15 | >It was developed by John Gruber (with help from Aaron Swartz) 16 | >and released in 2004 in the form of a syntax description and a 17 | >Perl script (Markdown.pl) for converting Markdown to HTML. 18 | >In the next decade, dozens of implementations were developed in many languages. 19 | 20 | [CommonMark]:http://spec.commonmark.org/0.28/ 21 | 22 | # heading 1 23 | ## heading 2 24 | ### heading 3 25 | 26 | *Italic* or _Italic_ 27 | 28 | **Bold** or __Bold__ 29 | 30 | `` 31 | 32 | ~~Strike~~ 33 | 34 | ++Underline++ 35 | 36 | Subscript text 37 | 38 | Superscript text 39 | 40 | ==Mark== 41 | 42 | ```Delphi 43 | procedure HelloWorld; 44 | begin 45 | ShowMessage('Hello World'); 46 | end; 47 | ``` 48 | * Unordered List one 49 | * Unordered List two 50 | * Unordered List three 51 | 52 | 1. Ordered List one 53 | 1. Ordered List two 54 | 1. Ordered List three 55 | 56 | > Block quote 57 | 58 | --- 59 | ### Horizontal rule 60 | 61 | ## Tables 62 | 63 | | First Header | Second Header | Third Header | 64 | | :----------- | :-----------: | -----------: | 65 | | Left | Center | Right | 66 | | Second row | **strong** | *italic* | 67 | 68 | ## Formulas 69 | 70 | It is possible to use the [Google Chart API] using `TeX` language, 71 | but the translated formula can only to be seen using a browser, 72 | for insert a formula using `TeX` enclose the code between `$` 73 | without `spaces`: 74 | 75 | Quadratic formula |Zeta formula 76 | ---------------------------|----------------------------- 77 | $x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}$ | $\zeta(s)=\sum_{n=1}^\infty\frac{1}{n^s}$ 78 | 79 | [Google Chart API]:https://developers.google.com/chart/infographics/docs/formulas 80 | -------------------------------------------------------------------------------- /TestFile/markdownlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EtheaDev/MarkdownProcessor/c1e1c1deb0e733607c0d1cd2fce5035329aa90d9/TestFile/markdownlogo.png -------------------------------------------------------------------------------- /images/ContentPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EtheaDev/MarkdownProcessor/c1e1c1deb0e733607c0d1cd2fce5035329aa90d9/images/ContentPage.png -------------------------------------------------------------------------------- /images/MDTextEditorLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EtheaDev/MarkdownProcessor/c1e1c1deb0e733607c0d1cd2fce5035329aa90d9/images/MDTextEditorLight.png -------------------------------------------------------------------------------- /source/MarkdownCommonMark.pas: -------------------------------------------------------------------------------- 1 | {******************************************************************************} 2 | { } 3 | { MarkDown Processor } 4 | { Delphi version of FPC-markdown by Miguel A. Risco-Castillo } 5 | { } 6 | { Copyright (c) 2022-2025 (Ethea S.r.l.) } 7 | { Author: Carlo Barazzetta } 8 | { } 9 | { https://github.com/EtheaDev/MarkdownProcessor } 10 | { } 11 | {******************************************************************************} 12 | { } 13 | { Licensed under the Apache License, Version 2.0 (the "License"); } 14 | { you may not use this file except in compliance with the License. } 15 | { You may obtain a copy of the License at } 16 | { } 17 | { http://www.apache.org/licenses/LICENSE-2.0 } 18 | { } 19 | { Unless required by applicable law or agreed to in writing, software } 20 | { distributed under the License is distributed on an "AS IS" BASIS, } 21 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } 22 | { See the License for the specific language governing permissions and } 23 | { limitations under the License. } 24 | { } 25 | {******************************************************************************} 26 | Unit MarkdownCommonMark; 27 | 28 | { 29 | Copyright (c) 2011+, Health Intersections Pty Ltd (http://www.healthintersections.com.au) 30 | All rights reserved. 31 | 32 | Redistribution and use in source and binary forms, with or without modification, 33 | are permitted provided that the following conditions are met: 34 | 35 | * Redistributions of source code must retain the above copyright notice, this 36 | list of conditions and the following disclaimer. 37 | * Redistributions in binary form must reproduce the above copyright notice, 38 | this list of conditions and the following disclaimer in the documentation 39 | and/or other materials provided with the distribution. 40 | * Neither the name of HL7 nor the names of its contributors may be used to 41 | endorse or promote products derived from this software without specific 42 | prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND 45 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 46 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 47 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 48 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 49 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 50 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 51 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 52 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 53 | POSSIBILITY OF SUCH DAMAGE. 54 | } 55 | interface 56 | 57 | uses 58 | System.SysUtils 59 | , System.Classes 60 | , System.TypInfo 61 | , MarkdownProcessor 62 | , MarkdownDaringFireball 63 | , MarkdownUtils 64 | ; 65 | 66 | Type 67 | 68 | TMarkdownCommonMark = class(TMarkdownDaringFireball) 69 | private 70 | protected 71 | public 72 | Constructor Create; 73 | Destructor Destroy; override; 74 | function Process(const ASource: string): string; override; 75 | end; 76 | 77 | implementation 78 | 79 | 80 | { TMarkdownCommonMark } 81 | 82 | constructor TMarkdownCommonMark.Create; 83 | begin 84 | inherited; 85 | Config.Dialect:=mdCommonMark; 86 | end; 87 | 88 | destructor TMarkdownCommonMark.Destroy; 89 | begin 90 | inherited; 91 | end; 92 | 93 | function TMarkdownCommonMark.Process(const ASource: string): string; 94 | begin 95 | result := inherited Process(ASource); 96 | end; 97 | 98 | 99 | end. 100 | -------------------------------------------------------------------------------- /source/MarkdownDaringFireball.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EtheaDev/MarkdownProcessor/c1e1c1deb0e733607c0d1cd2fce5035329aa90d9/source/MarkdownDaringFireball.pas -------------------------------------------------------------------------------- /source/MarkdownMathCode.pas: -------------------------------------------------------------------------------- 1 | {******************************************************************************} 2 | { } 3 | { MarkDown Processor } 4 | { Delphi version of FPC-markdown by Miguel A. Risco-Castillo } 5 | { } 6 | { Copyright (c) 2022-2025 (Ethea S.r.l.) } 7 | { Author: Carlo Barazzetta } 8 | { } 9 | { https://github.com/EtheaDev/MarkdownProcessor } 10 | { } 11 | {******************************************************************************} 12 | { } 13 | { Licensed under the Apache License, Version 2.0 (the "License"); } 14 | { you may not use this file except in compliance with the License. } 15 | { You may obtain a copy of the License at } 16 | { } 17 | { http://www.apache.org/licenses/LICENSE-2.0 } 18 | { } 19 | { Unless required by applicable law or agreed to in writing, software } 20 | { distributed under the License is distributed on an "AS IS" BASIS, } 21 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } 22 | { See the License for the specific language governing permissions and } 23 | { limitations under the License. } 24 | { } 25 | {******************************************************************************} 26 | unit MarkdownMathCode; 27 | 28 | interface 29 | 30 | uses 31 | Classes, SysUtils, MarkdownUtils; 32 | 33 | function checkMathCode(out_: TStringBuilder; s: String; start: integer): integer; 34 | 35 | implementation 36 | 37 | function checkMathCode(out_: TStringBuilder; s: String; start: integer 38 | ): integer; 39 | var 40 | temp: TStringBuilder; 41 | position: integer; 42 | code: String; 43 | begin 44 | temp := TStringBuilder.Create(); 45 | try 46 | // Check for mathcode {a^2+b^2=c^2} and generate link 47 | temp.Clear; 48 | position := TUtils.readUntil(temp, s, start + 1, [' ', '$', #10]); 49 | if (position <> -1) and (s[1 + position] = '$') and 50 | (s[position] <> '\') then 51 | begin 52 | code:= temp.ToString(); 53 | out_.append('');
56 |       TUtils.appendValue(out_, code, 0, Length(code));
57 |       out_.append(' '); 58 | exit(position); 59 | end; 60 | result := -1; 61 | finally 62 | temp.Free; 63 | end; 64 | end; 65 | 66 | end. 67 | 68 | -------------------------------------------------------------------------------- /source/MarkdownProcessor.pas: -------------------------------------------------------------------------------- 1 | {******************************************************************************} 2 | { } 3 | { MarkDown Processor } 4 | { Delphi version of FPC-markdown by Miguel A. Risco-Castillo } 5 | { } 6 | { Copyright (c) 2022-2025 (Ethea S.r.l.) } 7 | { Author: Carlo Barazzetta } 8 | { } 9 | { https://github.com/EtheaDev/MarkdownProcessor } 10 | { } 11 | {******************************************************************************} 12 | { } 13 | { Licensed under the Apache License, Version 2.0 (the "License"); } 14 | { you may not use this file except in compliance with the License. } 15 | { You may obtain a copy of the License at } 16 | { } 17 | { http://www.apache.org/licenses/LICENSE-2.0 } 18 | { } 19 | { Unless required by applicable law or agreed to in writing, software } 20 | { distributed under the License is distributed on an "AS IS" BASIS, } 21 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } 22 | { See the License for the specific language governing permissions and } 23 | { limitations under the License. } 24 | { } 25 | {******************************************************************************} 26 | { 27 | Copyright (c) 2011+, Health Intersections Pty Ltd (http://www.healthintersections.com.au) 28 | All rights reserved. 29 | 30 | Redistribution and use in source and binary forms, with or without modification, 31 | are permitted provided that the following conditions are met: 32 | 33 | * Redistributions of source code must retain the above copyright notice, this 34 | list of conditions and the following disclaimer. 35 | * Redistributions in binary form must reproduce the above copyright notice, 36 | this list of conditions and the following disclaimer in the documentation 37 | and/or other materials provided with the distribution. 38 | * Neither the name of HL7 nor the names of its contributors may be used to 39 | endorse or promote products derived from this software without specific 40 | prior written permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND 43 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 44 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 45 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 46 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 47 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 48 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 49 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 50 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 51 | POSSIBILITY OF SUCH DAMAGE. 52 | } 53 | Unit MarkdownProcessor; 54 | 55 | interface 56 | 57 | uses 58 | System.Classes 59 | , System.SysUtils 60 | , MarkdownUtils 61 | ; 62 | 63 | Type 64 | 65 | { TMarkdownProcessor } 66 | 67 | TMarkdownProcessor = {abstract} class 68 | private 69 | FConfig: TConfiguration; 70 | protected 71 | function GetAllowUnSafe: boolean; virtual; abstract; 72 | procedure SetAllowUnSafe(const Value: boolean); virtual; abstract; 73 | public 74 | class function CreateDialect(dialect : TMarkdownProcessorDialect) : TMarkdownProcessor; 75 | function Process(const ASource : string) : string; virtual; abstract; 76 | function ProcessFile(const AFileName: TFileName; 77 | const AEncoding: TEncoding = nil): string; virtual; 78 | property config: TConfiguration read FConfig write FConfig; 79 | // when AllowUnsafe = true, then the processor can create scripts etc. 80 | property AllowUnsafe : boolean read GetAllowUnSafe write SetAllowUnSafe; 81 | end; 82 | 83 | implementation 84 | 85 | uses 86 | MarkdownDaringFireball 87 | , MarkdownCommonMark 88 | , MarkdownTxtMark 89 | ; 90 | 91 | { TMarkdownProcessor } 92 | 93 | class function TMarkdownProcessor.CreateDialect(dialect: TMarkdownProcessorDialect): TMarkdownProcessor; 94 | begin 95 | case dialect of 96 | mdDaringFireball : result := TMarkdownDaringFireball.Create; 97 | mdCommonMark : result := TMarkdownCommonMark.Create; 98 | mdTxtMark : result := TMarkdownTxtMark.Create; 99 | else 100 | raise Exception.Create('Unknown Markdown dialect'); 101 | end; 102 | end; 103 | 104 | function TMarkdownProcessor.ProcessFile(const AFileName: TFileName; 105 | const AEncoding: TEncoding): string; 106 | var 107 | LMarkdown: TStringList; 108 | begin 109 | result:=''; 110 | LMarkdown := TStringList.Create; 111 | try 112 | LMarkdown.LoadFromFile(AFileName, AEncoding); 113 | result:=process(LMarkdown.Text); 114 | finally 115 | if assigned(LMarkdown) then Lmarkdown.Free; 116 | end; 117 | end; 118 | 119 | end. 120 | -------------------------------------------------------------------------------- /source/MarkdownTables.pas: -------------------------------------------------------------------------------- 1 | {******************************************************************************} 2 | { } 3 | { MarkDown Processor } 4 | { Delphi version of FPC-markdown by Miguel A. Risco-Castillo } 5 | { } 6 | { Copyright (c) 2022-2025 (Ethea S.r.l.) } 7 | { Author: Carlo Barazzetta } 8 | { } 9 | { https://github.com/EtheaDev/MarkdownProcessor } 10 | { } 11 | {******************************************************************************} 12 | { } 13 | { Licensed under the Apache License, Version 2.0 (the "License"); } 14 | { you may not use this file except in compliance with the License. } 15 | { You may obtain a copy of the License at } 16 | { } 17 | { http://www.apache.org/licenses/LICENSE-2.0 } 18 | { } 19 | { Unless required by applicable law or agreed to in writing, software } 20 | { distributed under the License is distributed on an "AS IS" BASIS, } 21 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } 22 | { See the License for the specific language governing permissions and } 23 | { limitations under the License. } 24 | { } 25 | {******************************************************************************} 26 | unit MarkdownTables; 27 | 28 | interface 29 | 30 | uses 31 | Classes, SysUtils, MarkdownUtils; 32 | 33 | type 34 | 35 | { TTable } 36 | 37 | TTable = class 38 | public 39 | class var 40 | Rows:integer; 41 | Cols:integer; 42 | Emitter:TEmitter; 43 | class procedure RowSplit (Input: string; const Strings: TStrings); 44 | class function ProcTable(var L: TLine): string; 45 | class procedure emitTableLines(out_: TStringBuilder; lines: TLine); 46 | class function hasFormatChars(L: Tline): integer; 47 | class function isRow(L: Tline): boolean; 48 | class function ColCount(L: Tline): integer; 49 | class procedure AlignFormat(f: string; sl: TStringList); 50 | end; 51 | 52 | 53 | implementation 54 | 55 | class procedure TTable.RowSplit (Input: string; const Strings: TStrings); 56 | var 57 | i,j:integer; 58 | s:string; 59 | begin 60 | if not Assigned(Strings) then exit; 61 | i:=1; 62 | j:=Length(Input); 63 | if Input[i]='|' then inc(i); 64 | if (Input[j]='|') and (Input[j-1]<>'\') then dec(j); 65 | s:=''; 66 | Strings.Clear; 67 | while (i<=j) do 68 | begin 69 | if ((Input[i]='|') and (Input[i-1]<>'\')) then 70 | begin 71 | Strings.Append(s); 72 | s:=''; 73 | end else s:=s+Input[i]; 74 | inc(i); 75 | end; 76 | Strings.Append(s); 77 | end; 78 | 79 | class function TTable.ProcTable(var L: TLine): string; 80 | (* 81 | | First Header | Second Header | Third Header | 82 | | :----------- | :-----------: | -----------: | 83 | | Left | Center | Right | 84 | | Second row | **strong** | *italic* | 85 | *) 86 | var 87 | cell,outs,t,t2:string; 88 | orig:TLine; 89 | first:boolean; 90 | col:integer; 91 | tsl:TStringList; 92 | ColAlign:TStringList; 93 | 94 | begin 95 | outs:=''; 96 | first:=true; 97 | tsl:=TStringList.Create; 98 | ColAlign:=TStringList.Create; 99 | orig:=L; 100 | 101 | while (orig<>nil) and not(orig.isEmpty) do 102 | begin 103 | t:=trim(orig.Value); 104 | t2:=''; 105 | RowSplit(t, tsl); 106 | if (first) then // get header 107 | begin 108 | AlignFormat(orig.next.Value,ColAlign); // get format row 109 | col:=0; 110 | for cell in tsl do 111 | begin 112 | t2:=t2+' '#10; 113 | inc(col); 114 | end; 115 | first:=false; 116 | orig:=orig.next; // skip table format row 117 | end else 118 | begin 119 | col:=0; 120 | for cell in tsl do 121 | begin 122 | t2:=t2+' '#10; 123 | inc(col); 124 | end; 125 | end; 126 | if t2<>'' then 127 | begin 128 | outs := outs+' '#10+t2+' '#10; 129 | inc(Rows); 130 | end; 131 | orig:=orig.next; 132 | end; 133 | t:=''#10; 134 | t:=t+outs+'
'#10; 135 | tsl.free; 136 | ColAlign.free; 137 | ProcTable:=t; 138 | end; 139 | 140 | class procedure TTable.emitTableLines(out_: TStringBuilder; lines: TLine); 141 | var line:TLine; 142 | begin 143 | line := lines; 144 | if Assigned(Emitter) then Emitter.recursiveEmitLine(out_, ProcTable(line), 0, mtNONE); 145 | end; 146 | 147 | class function TTable.hasFormatChars(L: Tline): integer; 148 | var 149 | i,j:integer; 150 | begin 151 | result:=0;Cols:=0; 152 | if not Assigned(L) or L.isEmpty then exit(0); 153 | i:=L.leading+1; 154 | j:=Length(L.value)-L.trailing; 155 | if i>4 then exit(0); // more of 4 spaces of identation 156 | if L.value[i]='|' then inc(i); 157 | if L.value[j]='|' then dec(j); 158 | while (i<=j) do 159 | begin 160 | if not CharInSet(L.value[i], [' ','|','-',':']) then 161 | exit(0); 162 | if L.value[i]='|' then inc(result); 163 | inc(i); 164 | end; 165 | Cols:=result; 166 | end; 167 | 168 | class function TTable.isRow(L: Tline): boolean; 169 | var 170 | c:integer; 171 | begin 172 | c:=ColCount(L); 173 | exit((c>0) and (c<=Cols)) 174 | end; 175 | 176 | class function TTable.ColCount(L: Tline): integer; 177 | var 178 | i,j,r:integer; 179 | begin 180 | if not Assigned(L) or L.isEmpty then exit(0); 181 | r:=0; 182 | i:=L.leading+1; 183 | j:=Length(L.value)-L.trailing; 184 | if i>4 then exit(0); // more of 4 spaces of identation 185 | if L.value[i]='|' then inc(i); 186 | if L.value[j]='|' then dec(j); 187 | while (i<=j) do 188 | begin 189 | if (L.value[i]='|') and (L.value[i-1]<>'\') then inc(r); 190 | inc(i); 191 | end; 192 | exit(r); 193 | end; 194 | 195 | class procedure TTable.AlignFormat(f: string; sl: TStringList); 196 | var 197 | i:integer; 198 | begin 199 | f:=trim(f); 200 | if f[1]='|' then f:=Copy(f,2); 201 | if f[length(f)]='|' then f:=Copy(f,1,length(f)-1); 202 | RowSplit(f, sl); 203 | for i:=0 to sl.Count-1 do 204 | begin 205 | if sl[i].Contains(':-') and sl[i].Contains('-:') then sl[i]:=' align="center">' 206 | else if sl[i].Contains(':-') then sl[i]:=' align="left">' 207 | else if sl[i].Contains('-:') then sl[i]:=' align="right">' 208 | else sl[i]:='>'; 209 | end; 210 | end; 211 | 212 | end. 213 | 214 | -------------------------------------------------------------------------------- /source/MarkdownTxtMark.pas: -------------------------------------------------------------------------------- 1 | {******************************************************************************} 2 | { } 3 | { MarkDown Processor } 4 | { Delphi version of FPC-markdown by Miguel A. Risco-Castillo } 5 | { } 6 | { Copyright (c) 2022-2025 (Ethea S.r.l.) } 7 | { Author: Carlo Barazzetta } 8 | { } 9 | { https://github.com/EtheaDev/MarkdownProcessor } 10 | { } 11 | {******************************************************************************} 12 | { } 13 | { Licensed under the Apache License, Version 2.0 (the "License"); } 14 | { you may not use this file except in compliance with the License. } 15 | { You may obtain a copy of the License at } 16 | { } 17 | { http://www.apache.org/licenses/LICENSE-2.0 } 18 | { } 19 | { Unless required by applicable law or agreed to in writing, software } 20 | { distributed under the License is distributed on an "AS IS" BASIS, } 21 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } 22 | { See the License for the specific language governing permissions and } 23 | { limitations under the License. } 24 | { } 25 | {******************************************************************************} 26 | Unit MarkdownTxtMark; 27 | 28 | interface 29 | 30 | uses 31 | System.SysUtils 32 | , System.Classes 33 | , System.TypInfo 34 | , MarkdownDaringFireball 35 | , MarkdownUtils; 36 | 37 | Type 38 | 39 | TMarkdownTxtMark = class(TMarkdownDaringFireball) 40 | private 41 | protected 42 | public 43 | Constructor Create; 44 | Destructor Destroy; override; 45 | function Process(const ASource: string): string; override; 46 | end; 47 | 48 | implementation 49 | 50 | 51 | { TMarkdownTxtMark } 52 | 53 | constructor TMarkdownTxtMark.Create; 54 | begin 55 | inherited; 56 | Config.Dialect:=mdTxtMark; 57 | end; 58 | 59 | destructor TMarkdownTxtMark.Destroy; 60 | begin 61 | 62 | inherited; 63 | end; 64 | 65 | function TMarkdownTxtMark.Process(const ASource: string): string; 66 | begin 67 | result := inherited Process(ASource); 68 | end; 69 | 70 | 71 | end. 72 | -------------------------------------------------------------------------------- /source/MarkdownUtils.pas: -------------------------------------------------------------------------------- 1 | {******************************************************************************} 2 | { } 3 | { MarkDown Processor } 4 | { Delphi version of FPC-markdown by Miguel A. Risco-Castillo } 5 | { } 6 | { Copyright (c) 2022-2025 (Ethea S.r.l.) } 7 | { Author: Carlo Barazzetta } 8 | { } 9 | { https://github.com/EtheaDev/MarkdownProcessor } 10 | { } 11 | {******************************************************************************} 12 | { } 13 | { Licensed under the Apache License, Version 2.0 (the "License"); } 14 | { you may not use this file except in compliance with the License. } 15 | { You may obtain a copy of the License at } 16 | { } 17 | { http://www.apache.org/licenses/LICENSE-2.0 } 18 | { } 19 | { Unless required by applicable law or agreed to in writing, software } 20 | { distributed under the License is distributed on an "AS IS" BASIS, } 21 | { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } 22 | { See the License for the specific language governing permissions and } 23 | { limitations under the License. } 24 | { } 25 | {******************************************************************************} 26 | Unit MarkdownUtils; 27 | 28 | interface 29 | 30 | uses 31 | System.SysUtils 32 | , System.StrUtils 33 | , System.Classes 34 | , System.Character 35 | , System.TypInfo 36 | , System.Math 37 | ; 38 | 39 | Type 40 | TMarkdownProcessorDialect = (mdDaringFireball, mdCommonMark, mdTxtMark); 41 | TSeTMarkdownProcessorDialect = set of TMarkdownProcessorDialect; 42 | 43 | THTMLElement = (heNONE, hea, heabbr, heacronym, headdress, heapplet, hearea, heb, hebase, hebasefont, hebdo, hebig, heblockquote, hebody, hebr, hebutton, hecaption, hecite, 44 | hecode, hecol, hecolgroup, hedd, hedel, hedfn, hediv, hedl, hedt, heem, hefieldset, hefont, heform, heframe, heframeset, heh1, heh2, heh3, heh4, heh5, heh6, hehead, hehr, 45 | hehtml, hei, heiframe, heimg, heinput, heins, hekbd, helabel, helegend, heli, helink, hemap, hemeta, henoscript, heobject, heol, heoptgroup, heoption, hep, heparam, hepre, heq, 46 | hes, hesamp, hescript, heselect, hesmall, hespan, hestrike, hestrong, hestyle, hesub, hesup, hetable, hetbody, hetd, hetextarea, hetfoot, heth, hethead, hetitle, hetr, hett, 47 | heu, heul, hevar); 48 | 49 | const 50 | // pstfix 51 | ENTITY_NAMES: array[0..249] of String = ('Â', 'â', '´', 'Æ', 'æ', 'À', 'à', 'ℵ', 'Α', 'α', '&', '∧', '∠', 52 | ''', 'Å', 'å', '≈', 'Ã', 'ã', 'Ä', 'ä', '„', 'Β', 'β', '¦', '•', '∩', 'Ç', 'ç', 53 | '¸', '¢', 'Χ', 'χ', 'ˆ', '♣', '≅', '©', '↵', '∪', '¤', '‡', '†', '⇓', '↓', '°', 'Δ', 54 | 'δ', '♦', '÷', 'É', 'é', 'Ê', 'ê', 'È', 'è', '∅', ' ', ' ', 'Ε', 'ε', '≡', 55 | 'Η', 'η', 'Ð', 'ð', 'Ë', 'ë', '€', '∃', 'ƒ', '∀', '½', '¼', '¾', '⁄', 'Γ', 'γ', '≥', 56 | '>', '⇔', '↔', '♥', '…', 'Í', 'í', 'Î', 'î', '¡', 'Ì', 'ì', 'ℑ', '∞', '∫', 'Ι', 57 | 'ι', '¿', '∈', 'Ï', 'ï', 'Κ', 'κ', 'Λ', 'λ', '⟨', '«', '⇐', '←', '⌈', '“', '≤', 58 | '⌊', '∗', '◊', '‎', '‹', '‘', '<', '¯', '—', 'µ', '·', '−', 'Μ', 'μ', '∇', ' ', '–', 59 | '≠', '∋', '¬', '∉', '⊄', 'Ñ', 'ñ', 'Ν', 'ν', 'Ó', 'ó', 'Ô', 'ô', 'Œ', 'œ', 'Ò', 60 | 'ò', '‾', 'Ω', 'ω', 'Ο', 'ο', '⊕', '∨', 'ª', 'º', 'Ø', 'ø', 'Õ', 'õ', '⊗', 61 | 'Ö', 'ö', '¶', '∂', '‰', '⊥', 'Φ', 'φ', 'Π', 'π', 'ϖ', '±', '£', '″', '′', '∏', '∝', 62 | 'Ψ', 'ψ', '"', '√', '⟩', '»', '⇒', '→', '⌉', '”', 'ℜ', '®', '⌋', 'Ρ', 'ρ', '‏', '›', 63 | '’', '‚', 'Š', 'š', '⋅', '§', '­', 'Σ', 'σ', 'ς', '∼', '♠', '⊂', '⊆', '∑', '⊃', '¹', 64 | '²', '³', '⊇', 'ß', 'Τ', 'τ', '∴', 'Θ', 'θ', 'ϑ', ' ', 'þ', '˜', '×', '™', 'Ú', 65 | 'ú', '⇑', '↑', 'Û', 'û', 'Ù', 'ù', '¨', 'ϒ', 'Υ', 'υ', 'Ü', 'ü', '℘', 'Ξ', 'ξ', 66 | 'Ý', 'ý', '¥', 'Ÿ', 'ÿ', 'Ζ', 'ζ', '‍', '‌'); 67 | 68 | // Characters corresponding to ENTITY_NAMES. */ 69 | // pstfix 70 | ENTITY_CHARS: array[0..249] of integer = ($00C2, $00E2, $00B4, $00C6, $00E6, $00C0, $00E0, $2135, $0391, $03B1, $0026, $2227, $2220, ord(''''), $00C5, $00E5, $2248, $00C3, $00E3, $00C4, 71 | $00E4, $201E, $0392, $03B2, $00A6, $2022, $2229, $00C7, $00E7, $00B8, $00A2, $03A7, $03C7, $02C6, $2663, $2245, $00A9, $21B5, $222A, $00A4, $2021, $2020, $21D3, $2193, $00B0, 72 | $0394, $03B4, $2666, $00F7, $00C9, $00E9, $00CA, $00EA, $00C8, $00E8, $2205, $2003, $2002, $0395, $03B5, $2261, $0397, $03B7, $00D0, $00F0, $00CB, $00EB, $20AC, $2203, $0192, 73 | $2200, $00BD, $00BC, $00BE, $2044, $0393, $03B3, $2265, $003E, $21D4, $2194, $2665, $2026, $00CD, $00ED, $00CE, $00EE, $00A1, $00CC, $00EC, $2111, $221E, $222B, $0399, $03B9, 74 | $00BF, $2208, $00CF, $00EF, $039A, $03BA, $039B, $03BB, $2329, $00AB, $21D0, $2190, $2308, $201C, $2264, $230A, $2217, $25CA, $200E, $2039, $2018, $003C, $00AF, $2014, $00B5, 75 | $00B7, $2212, $039C, $03BC, $2207, $00A0, $2013, $2260, $220B, $00AC, $2209, $2284, $00D1, $00F1, $039D, $03BD, $00D3, $00F3, $00D4, $00F4, $0152, $0153, $00D2, $00F2, $203E, 76 | $03A9, $03C9, $039F, $03BF, $2295, $2228, $00AA, $00BA, $00D8, $00F8, $00D5, $00F5, $2297, $00D6, $00F6, $00B6, $2202, $2030, $22A5, $03A6, $03C6, $03A0, $03C0, $03D6, $00B1, 77 | $00A3, $2033, $2032, $220F, $221D, $03A8, $03C8, $0022, $221A, $232A, $00BB, $21D2, $2192, $2309, $201D, $211C, $00AE, $230B, $03A1, $03C1, $200F, $203A, $2019, $201A, $0160, 78 | $0161, $22C5, $00A7, $00AD, $03A3, $03C3, $03C2, $223C, $2660, $2282, $2286, $2211, $2283, $00B9, $00B2, $00B3, $2287, $00DF, $03A4, $03C4, $2234, $0398, $03B8, $03D1, $00DE, 79 | $00FE, $02DC, $00D7, $2122, $00DA, $00FA, $21D1, $2191, $00DB, $00FB, $00D9, $00F9, $00A8, $03D2, $03A5, $03C5, $00DC, $00FC, $2118, $039E, $03BE, $00DD, $00FD, $00A5, $0178, 80 | $00FF, $0396, $03B6, $200D, $200C); 81 | 82 | LINK_PREFIXES: array[0..3] of String = ('http', 'https', 'ftp', 'ftps'); 83 | 84 | BLOCK_ELEMENTS: set of THTMLElement = [headdress, heblockquote, hedel, hediv, hedl, hefieldset, heform, heh1, heh2, heh3, heh4, heh5, heh6, hehr, heins, henoscript, heol, hep, 85 | hepre, hetable, heul]; 86 | 87 | UNSAFE_ELEMENTS: set of THTMLElement = [heapplet, hehead, hehtml, hebody, heframe, heframeset, heiframe, hescript, heobject]; 88 | 89 | BUFFER_INCREMENT_SIZE = 1024; 90 | 91 | Type 92 | TMarkdownReader = class 93 | private 94 | FValue: String; 95 | FCursor: integer; 96 | public 97 | Constructor Create(source: String); 98 | function read: char; 99 | end; 100 | 101 | {$IFDEF FPC} 102 | TStringBuilder = class 103 | private 104 | FContent : String; 105 | FLength : Integer; 106 | FBufferSize : integer; 107 | function GetChar(index: integer): char; 108 | public 109 | Constructor Create; 110 | procedure Clear; 111 | procedure Append(value : String); overload; 112 | procedure Append(value : integer); overload; 113 | procedure Append(value : TStringBuilder); overload; 114 | property ch[index : integer] : char read GetChar; default; 115 | function toString : String; override; 116 | Property Length : Integer Read FLength; 117 | end; 118 | {$ENDIF} 119 | 120 | { TUtils } 121 | 122 | TUtils = class 123 | public 124 | // Skips spaces in the given String. return The new position or -1 if EOL has been reached. 125 | class function skipSpaces(s: String; start: integer): integer; 126 | 127 | // Process the given escape sequence. return The new position. 128 | class function escape(out_: TStringBuilder; ch: char; position: integer): integer; 129 | 130 | // Reads characters until any 'end' character is encountered. return The new position or -1 if no 'end' char was found. 131 | class function readUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; overload; 132 | 133 | // Reads characters until the 'end' character is encountered. return The new position or -1 if no 'end' char was found. 134 | class function readUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer; overload; 135 | 136 | // Reads a markdown link. return The new position or -1 if this is no valid markdown link. 137 | class function readMdLink(out_: TStringBuilder; s: String; start: integer): integer; 138 | class function readMdLinkId(out_: TStringBuilder; s: String; start: integer): integer; 139 | 140 | // Reads characters until any 'end' character is encountered, ignoring escape sequences. 141 | class function readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; overload; 142 | 143 | // Reads characters until the end character is encountered, taking care of HTML/XML strings. 144 | class function readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer; overload; 145 | 146 | // Reads characters until any 'end' character is encountered, ignoring escape sequences. 147 | class function readXMLUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; 148 | 149 | // Appends the given string encoding special HTML characters. 150 | class procedure appendCode(out_: TStringBuilder; s: String; start: integer; e: integer); 151 | 152 | // Appends the given string encoding special HTML characters (used in HTML 153 | class procedure appendValue(out_: TStringBuilder; s: String; start: integer; e: integer); 154 | 155 | // Append the given char as a decimal HTML entity. 156 | class procedure appendDecEntity(out_: TStringBuilder; value: char); 157 | 158 | // Append the given char as a hexadecimal HTML entity. 159 | class procedure appendHexEntity(out_: TStringBuilder; value: char); 160 | 161 | // Appends the given mailto link using obfuscation. 162 | class procedure appendMailto(out_: TStringBuilder; s: String; start: integer; e: integer); 163 | 164 | // Extracts the tag from an XML element. 165 | class procedure getXMLTag(out_: TStringBuilder; bin: TStringBuilder); overload; 166 | 167 | // Extracts the tag from an XML element. 168 | class procedure getXMLTag(out_: TStringBuilder; s: String); overload; 169 | 170 | // Reads an XML element. 171 | // return The new position or -1 if this is no valid XML element. 172 | class function readXML(out_: TStringBuilder; s: String; start: integer; safeMode: boolean): integer; 173 | 174 | // Appends the given string to the given StringBuilder, replacing '&', '<' and '>' by their respective HTML entities. 175 | class procedure codeEncode(out_: TStringBuilder; value: String; offset: integer); 176 | 177 | // Removes trailing ` or ~ and trims spaces. 178 | class function getMetaFromFence(fenceLine: String): String; 179 | 180 | // Changes unsafe chars to %xx 181 | class function encodeURL(url: String): String; 182 | end; 183 | 184 | { THTML } 185 | 186 | THTML = class 187 | public 188 | class function isLinkPrefix(s: String): boolean; 189 | class function isEntity(s: String): boolean; 190 | class function isUnsafeHtmlElement(s: String): boolean; 191 | class function isHtmlBlockElement(s: String): boolean; 192 | end; 193 | 194 | { TDecorator } 195 | 196 | TDecorator = class 197 | private 198 | public 199 | procedure openParagraph(out_: TStringBuilder); virtual; 200 | procedure closeParagraph(out_: TStringBuilder); virtual; 201 | 202 | procedure openBlockQuote(out_: TStringBuilder); virtual; 203 | procedure closeBlockQuote(out_: TStringBuilder); virtual; 204 | 205 | procedure openCodeBlock(out_: TStringBuilder); virtual; 206 | procedure openFencedCodeBlock(out_: TStringBuilder; classlabel:string); 207 | procedure closeCodeBlock(out_: TStringBuilder); virtual; 208 | 209 | procedure openCodeSpan(out_: TStringBuilder); virtual; 210 | procedure closeCodeSpan(out_: TStringBuilder); virtual; 211 | 212 | procedure openHeadline(out_: TStringBuilder; level: integer); virtual; 213 | procedure closeHeadline(out_: TStringBuilder; level: integer); virtual; 214 | 215 | procedure openStrong(out_: TStringBuilder); virtual; 216 | procedure closeStrong(out_: TStringBuilder); virtual; 217 | 218 | procedure openEmphasis(out_: TStringBuilder); virtual; 219 | procedure closeEmphasis(out_: TStringBuilder); virtual; 220 | 221 | procedure openStrike(out_: TStringBuilder); virtual; 222 | procedure closeStrike(out_: TStringBuilder); virtual; 223 | 224 | procedure openIns(out_: TStringBuilder); virtual; 225 | procedure closeIns(out_: TStringBuilder); virtual; 226 | 227 | procedure openMark(out_: TStringBuilder); virtual; 228 | procedure closeMark(out_: TStringBuilder); virtual; 229 | 230 | procedure openSuper(out_: TStringBuilder); virtual; 231 | procedure closeSuper(out_: TStringBuilder); virtual; 232 | 233 | procedure openSub(out_: TStringBuilder); virtual; 234 | procedure closeSub(out_: TStringBuilder); virtual; 235 | 236 | procedure openOrderedList(out_: TStringBuilder); virtual; 237 | procedure closeOrderedList(out_: TStringBuilder); virtual; 238 | 239 | procedure openUnOrderedList(out_: TStringBuilder); virtual; 240 | procedure closeUnOrderedList(out_: TStringBuilder); virtual; 241 | 242 | procedure openListItem(out_: TStringBuilder); virtual; 243 | procedure closeListItem(out_: TStringBuilder); virtual; 244 | 245 | procedure checkedItem(out_: TStringBuilder); virtual; 246 | procedure uncheckedItem(out_: TStringBuilder); virtual; 247 | 248 | procedure horizontalRuler(out_: TStringBuilder); virtual; 249 | 250 | procedure openLink(out_: TStringBuilder); virtual; 251 | procedure closeLink(out_: TStringBuilder); virtual; 252 | 253 | procedure openImage(out_: TStringBuilder); virtual; 254 | procedure closeImage(out_: TStringBuilder); virtual; 255 | 256 | end; 257 | 258 | TSpanEmitter = class 259 | public 260 | procedure emitSpan(out_: TStringBuilder; content: String); virtual; abstract; 261 | end; 262 | 263 | TBlockEmitter = class 264 | public 265 | procedure emitBlock(out_: TStringBuilder; lines: TStringList; meta: String); virtual; abstract; 266 | end; 267 | 268 | { TConfiguration } 269 | 270 | TConfiguration = class 271 | private 272 | Fdecorator: TDecorator; 273 | FDialect: TMarkdownProcessorDialect; 274 | FsafeMode: boolean; 275 | FallowSpacesInFencedDelimiters: boolean; 276 | FcodeBlockEmitter: TBlockEmitter; 277 | FpanicMode: boolean; 278 | FspecialLinkEmitter: TSpanEmitter; 279 | procedure SetDialect(AValue: TMarkdownProcessorDialect); 280 | public 281 | Constructor Create(safe : boolean); 282 | Destructor Destroy; override; 283 | function isDialect(Dialects:TSeTMarkdownProcessorDialect):boolean; 284 | property Dialect:TMarkdownProcessorDialect read FDialect write SetDialect; 285 | property safeMode: boolean read FsafeMode write FsafeMode; 286 | property panicMode: boolean read FpanicMode write FpanicMode; 287 | property decorator: TDecorator read Fdecorator write Fdecorator; 288 | property codeBlockEmitter: TBlockEmitter read FcodeBlockEmitter write FcodeBlockEmitter; 289 | property allowSpacesInFencedDelimiters: boolean read FallowSpacesInFencedDelimiters write FallowSpacesInFencedDelimiters; 290 | property specialLinkEmitter: TSpanEmitter read FspecialLinkEmitter write FspecialLinkEmitter; 291 | end; 292 | 293 | TLineType = ( 294 | // Empty line. */ 295 | ltEMPTY, 296 | // Undefined content. */ 297 | ltOTHER, 298 | // A markdown headline. */ 299 | ltHEADLINE, ltHEADLINE1, ltHEADLINE2, 300 | // A code block line. */ 301 | ltCODE, 302 | // A list. */ 303 | ltULIST, //Unordered 304 | ltOLIST, //Ordered with dot 305 | ltBLIST, //Ordered with brackets 306 | // A block quote. */ 307 | ltBQUOTE, 308 | // A horizontal ruler. */ 309 | ltHR, 310 | // Start of a XML block. */ 311 | ltXML, 312 | // Fenced code block start/end */ 313 | ltFENCED_CODE, 314 | // Start of Table 315 | ltTABLE 316 | ); 317 | 318 | TLine = class 319 | private 320 | FXmlEndLine: TLine; 321 | FPrevEmpty: boolean; 322 | FPrevious: TLine; 323 | FPosition: integer; 324 | FValue: string; 325 | FIsEmpty: boolean; 326 | FTrailing: integer; 327 | FNextEmpty: boolean; 328 | FLeading: integer; 329 | FNext: TLine; 330 | function countChars(ch: char): integer; 331 | function countCharsStart(ch: char; allowSpaces: boolean): integer; 332 | function readXMLComment(firstLine: TLine; start: integer): integer; 333 | function checkHTML(): boolean; 334 | public 335 | Constructor Create; 336 | Destructor Destroy; Override; 337 | // Current cursor position. 338 | property position: integer read FPosition write FPosition; 339 | // Leading and trailing spaces. 340 | property leading: integer read FLeading write FLeading; 341 | property trailing: integer read FTrailing write FTrailing; 342 | // Is this line empty? 343 | property isEmpty: boolean read FIsEmpty write FIsEmpty; 344 | // This line's value. 345 | property value: string read FValue write FValue; 346 | // Previous and next line. 347 | property previous: TLine read FPrevious write FPrevious; 348 | property next: TLine read FNext write FNext; 349 | // Is previous/next line empty? 350 | property prevEmpty: boolean read FPrevEmpty write FPrevEmpty; 351 | property nextEmpty: boolean read FNextEmpty write FNextEmpty; 352 | // Final line of a XML block. 353 | property xmlEndLine: TLine read FXmlEndLine write FXmlEndLine; 354 | procedure Init; 355 | procedure InitLeading; 356 | function skipSpaces: boolean; 357 | function readUntil(chend: TSysCharSet): String; 358 | procedure setEmpty; 359 | function getLineType(config: TConfiguration): TLineType; 360 | function stripID: String; 361 | 362 | end; 363 | 364 | TLinkRef = class 365 | private 366 | FLink: String; 367 | FTitle: String; 368 | FIsAbbrev: boolean; 369 | public 370 | Constructor Create(link, title: String; isAbbrev: boolean); 371 | property link: String read FLink write FLink; 372 | property title: String read FTitle write FTitle; 373 | property isAbbrev: boolean read FIsAbbrev write FIsAbbrev; 374 | end; 375 | 376 | TBlockType = ( 377 | // Unspecified. Used for root block and list items without paragraphs. 378 | btNONE, 379 | // A block quote. 380 | btBLOCKQUOTE, 381 | // A code block. 382 | btCODE, 383 | // A fenced code block. 384 | btFENCED_CODE, 385 | // A headline. 386 | btHEADLINE, 387 | // A list item. 388 | btLIST_ITEM, 389 | // An ordered list 390 | btORDERED_LIST, 391 | // A paragraph. 392 | btPARAGRAPH, 393 | // A horizontal ruler. 394 | btRULER, 395 | // An unordered list. 396 | btUNORDERED_LIST, 397 | // A XML block. 398 | btXML, 399 | // A table elements block. 400 | btTABLE 401 | ); 402 | 403 | TBlock = class 404 | private 405 | FType: TBlockType; 406 | FId: String; 407 | FBlocks: TBlock; 408 | FBlockTail: TBlock; 409 | FLines: TLine; 410 | FLineTail: TLine; 411 | FHlDepth: integer; 412 | FNext: TBlock; 413 | FMeta: String; 414 | 415 | public 416 | procedure AppendLine(line: TLine); 417 | function split(line: TLine): TBlock; 418 | procedure removeListIndent(config: TConfiguration); 419 | function removeLeadingEmptyLines: boolean; 420 | procedure removeTrailingEmptyLines; 421 | procedure transfromHeadline; 422 | procedure expandListParagraphs; 423 | function hasLines: boolean; 424 | procedure removeSurroundingEmptyLines; 425 | procedure removeBlockQuotePrefix; 426 | procedure removeLine(line: TLine); 427 | Constructor Create; 428 | Destructor Destroy; Override; 429 | // This block's type. 430 | property type_: TBlockType read FType write FType; 431 | property lines: TLine read FLines; 432 | property lineTail: TLine read FLineTail; 433 | // child blocks. 434 | property blocks: TBlock read FBlocks; 435 | property blockTail: TBlock read FBlockTail; 436 | // Next block. 437 | property next: TBlock read FNext write FNext; 438 | // Depth of headline BlockType. 439 | property hlDepth: integer read FHlDepth write FHlDepth; 440 | // ID for headlines and list items 441 | property id: String read FId write FId; 442 | // Block meta information 443 | property meta: String read FMeta write FMeta; 444 | end; 445 | 446 | TMarkToken = ( 447 | // No token. 448 | mtNONE, 449 | // * 450 | mtEM_STAR, // x*x 451 | // _ 452 | mtEM_UNDERSCORE, // x_x 453 | // ~ 454 | mtSUB_TILDE, // x~x 455 | // ** 456 | mtSTRONG_STAR, // x**x 457 | // __ 458 | mtSTRONG_UNDERSCORE, // x__x 459 | // ~~ 460 | mtSTRIKE_TILDE, // x~~x 461 | // ++ 462 | mtINS_PLUS, // x++x 463 | // == 464 | mtMARK_EQ, // x==x 465 | // $ 466 | mtMATH_DOLLAR, // $ 467 | // ` 468 | mtCODE_SINGLE, // ` 469 | // `` 470 | mtCODE_DOUBLE, // `` 471 | // [ 472 | mtLINK, // [ 473 | // < 474 | mtHTML, // < 475 | // ![ 476 | mtIMAGE, // ![ 477 | // & 478 | mtENTITY, // & 479 | // \ 480 | mtESCAPE, // \x 481 | // Extended: ^ 482 | mtSUPER, // ^ 483 | // Extended: (C) 484 | mtX_COPY, // (C) 485 | // Extended: (R) 486 | mtX_REG, // (R) 487 | // Extended: (TM) 488 | mtX_TRADE, // (TM) 489 | // Extended: << 490 | mtX_LAQUO, // << 491 | // Extended: >> 492 | mtX_RAQUO, // >> 493 | // Extended: -- 494 | mtX_NDASH, // -- 495 | // Extended: --- 496 | mtX_MDASH, // --- 497 | // Extended: ... 498 | mtX_HELLIP, // ... 499 | // Extended: "x 500 | mtX_RDQUO, // " 501 | // Extended: x" 502 | mtX_LDQUO, // " 503 | // [[ 504 | mtX_LINK_OPEN, // [[ 505 | // ]] 506 | mtX_LINK_CLOSE // ]] 507 | ); 508 | 509 | // Emitter class responsible for generating HTML output. 510 | 511 | { TEmitter } 512 | 513 | TEmitter = class 514 | private 515 | linkRefs: TStringList; 516 | FConfig: TConfiguration; 517 | public 518 | Constructor Create(config: TConfiguration); 519 | Destructor Destroy; override; 520 | procedure emitCodeLines(out_: TStringBuilder; lines: TLine; meta: String; removeIndent: boolean); 521 | procedure emitRawLines(out_: TStringBuilder; lines: TLine); 522 | procedure emitMarkedLines(out_: TStringBuilder; lines: TLine); 523 | function findToken(s: String; start: integer; token: TMarkToken): integer; 524 | function getToken(s: String; position: integer): TMarkToken; 525 | function checkLink(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer; 526 | function recursiveEmitLine(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer; 527 | function checkHTML(out_: TStringBuilder; s: String; start: integer): integer; 528 | class function checkEntity(out_: TStringBuilder; s: String; start: integer): integer; 529 | class function whitespaceToSpace(c: char): char; 530 | procedure addLinkRef(key: String; linkRef: TLinkRef); 531 | procedure emit(out_: TStringBuilder; root: TBlock); 532 | procedure emitLines(out_: TStringBuilder; block: TBlock); 533 | end; 534 | 535 | Function StringsContains(Const aNames: Array Of String; Const sName: String): boolean; 536 | function StringToEnum(ATypeInfo: PTypeInfo; const AStr: String; defValue: integer): integer; 537 | 538 | implementation 539 | 540 | uses MarkdownTables, MarkdownMathCode; 541 | 542 | Function StringsContains(Const aNames: Array Of String; Const sName: String): boolean; 543 | var 544 | i: integer; 545 | Begin 546 | for i := 0 to length(aNames) - 1 do 547 | if sName = aNames[i] then 548 | exit(true); 549 | result := false; 550 | End; 551 | 552 | function StringToEnum(ATypeInfo: PTypeInfo; const AStr: String; defValue: integer): integer; 553 | var 554 | LTypeData: PTypeData; 555 | LPChar: PAnsiChar; 556 | LValue: ShortString; 557 | begin 558 | LValue := ShortString(AStr); 559 | 560 | if ATypeInfo^.Kind = tkEnumeration then 561 | begin 562 | LTypeData := GetTypeData(ATypeInfo); 563 | if LTypeData^.MinValue <> 0 then 564 | exit(defValue); 565 | LPChar := @LTypeData^.NameList[0]; 566 | result := 0; 567 | while (result <= LTypeData^.MaxValue) and (ShortString(pointer(LPChar)^) <> LValue) do 568 | begin 569 | inc(LPChar, ord(LPChar^) + 1); // move to next string 570 | inc(result); 571 | end; 572 | if result > LTypeData^.MaxValue then 573 | exit(defValue); 574 | end 575 | else 576 | exit(defValue); 577 | end; 578 | 579 | { TLine } 580 | 581 | constructor TLine.Create; 582 | begin 583 | inherited; 584 | FIsEmpty := true; 585 | end; 586 | 587 | destructor TLine.Destroy; 588 | begin 589 | FNext.Free; 590 | inherited; 591 | end; 592 | 593 | { TConfiguration } 594 | 595 | procedure TConfiguration.SetDialect(AValue: TMarkdownProcessorDialect); 596 | begin 597 | if FDialect=AValue then Exit; 598 | FDialect:=AValue; 599 | end; 600 | 601 | constructor TConfiguration.Create(safe : boolean); 602 | begin 603 | inherited Create; 604 | FallowSpacesInFencedDelimiters := true; 605 | Fdecorator := TDecorator.Create; 606 | FsafeMode := safe; 607 | end; 608 | 609 | destructor TConfiguration.Destroy; 610 | begin 611 | FcodeBlockEmitter.Free; 612 | Fdecorator.Free; 613 | FspecialLinkEmitter.Free; 614 | inherited; 615 | end; 616 | 617 | function TConfiguration.isDialect(Dialects: TSeTMarkdownProcessorDialect): boolean; 618 | var 619 | i: TMarkdownProcessorDialect; 620 | Begin 621 | for i in Dialects do 622 | if Dialect = i then 623 | exit(true); 624 | result := false; 625 | end; 626 | 627 | { TDecorator } 628 | 629 | procedure TDecorator.openParagraph(out_: TStringBuilder); 630 | begin 631 | out_.append('

'); 632 | end; 633 | 634 | procedure TDecorator.closeParagraph(out_: TStringBuilder); 635 | begin 636 | out_.append('

'#10); 637 | end; 638 | 639 | procedure TDecorator.openBlockQuote(out_: TStringBuilder); 640 | begin 641 | out_.append('
'); 642 | end; 643 | 644 | procedure TDecorator.closeBlockQuote(out_: TStringBuilder); 645 | begin 646 | out_.append('
'#10); 647 | end; 648 | 649 | procedure TDecorator.openCodeBlock(out_: TStringBuilder); 650 | begin 651 | out_.append('
');
 652 | end;
 653 | 
 654 | procedure TDecorator.openFencedCodeBlock(out_: TStringBuilder;
 655 |   classlabel: string);
 656 | begin
 657 |   out_.append('
');
 658 | end;
 659 | 
 660 | procedure TDecorator.closeCodeBlock(out_: TStringBuilder);
 661 | begin
 662 |   out_.append('
'#10); 663 | end; 664 | 665 | procedure TDecorator.openCodeSpan(out_: TStringBuilder); 666 | begin 667 | out_.append(''); 668 | end; 669 | 670 | procedure TDecorator.closeCodeSpan(out_: TStringBuilder); 671 | begin 672 | out_.append(''); 673 | end; 674 | 675 | procedure TDecorator.openHeadline(out_: TStringBuilder; level: integer); 676 | begin 677 | out_.append(''#10); 686 | end; 687 | 688 | procedure TDecorator.openStrong(out_: TStringBuilder); 689 | begin 690 | out_.append(''); 691 | end; 692 | 693 | procedure TDecorator.closeStrong(out_: TStringBuilder); 694 | begin 695 | out_.append(''); 696 | end; 697 | 698 | procedure TDecorator.openEmphasis(out_: TStringBuilder); 699 | begin 700 | out_.append(''); 701 | end; 702 | 703 | procedure TDecorator.closeEmphasis(out_: TStringBuilder); 704 | begin 705 | out_.append(''); 706 | end; 707 | 708 | procedure TDecorator.openStrike(out_: TStringBuilder); 709 | begin 710 | out_.append(''); 711 | end; 712 | 713 | procedure TDecorator.closeStrike(out_: TStringBuilder); 714 | begin 715 | out_.append(''); 716 | end; 717 | 718 | procedure TDecorator.openIns(out_: TStringBuilder); 719 | begin 720 | out_.append(''); 721 | end; 722 | 723 | procedure TDecorator.closeIns(out_: TStringBuilder); 724 | begin 725 | out_.append(''); 726 | end; 727 | 728 | procedure TDecorator.openMark(out_: TStringBuilder); 729 | begin 730 | out_.append(''); 731 | end; 732 | 733 | procedure TDecorator.closeMark(out_: TStringBuilder); 734 | begin 735 | out_.append(''); 736 | end; 737 | 738 | procedure TDecorator.openSuper(out_: TStringBuilder); 739 | begin 740 | out_.append(''); 741 | end; 742 | 743 | procedure TDecorator.closeSuper(out_: TStringBuilder); 744 | begin 745 | out_.append(''); 746 | end; 747 | 748 | procedure TDecorator.openSub(out_: TStringBuilder); 749 | begin 750 | out_.append(''); 751 | end; 752 | 753 | procedure TDecorator.closeSub(out_: TStringBuilder); 754 | begin 755 | out_.append(''); 756 | end; 757 | 758 | procedure TDecorator.openOrderedList(out_: TStringBuilder); 759 | begin 760 | out_.append('
    '#10); 761 | end; 762 | 763 | procedure TDecorator.closeOrderedList(out_: TStringBuilder); 764 | begin 765 | out_.append('
'#10); 766 | end; 767 | 768 | procedure TDecorator.openUnOrderedList(out_: TStringBuilder); 769 | begin 770 | out_.append('
    '#10); 771 | end; 772 | 773 | procedure TDecorator.closeUnOrderedList(out_: TStringBuilder); 774 | begin 775 | out_.append('
'#10); 776 | end; 777 | 778 | procedure TDecorator.openListItem(out_: TStringBuilder); 779 | begin 780 | out_.append(''#10); 786 | end; 787 | 788 | procedure TDecorator.checkedItem(out_: TStringBuilder); 789 | begin 790 | out_.append(''); 791 | end; 792 | 793 | procedure TDecorator.uncheckedItem(out_: TStringBuilder); 794 | begin 795 | out_.append(''); 796 | end; 797 | 798 | procedure TDecorator.horizontalRuler(out_: TStringBuilder); 799 | begin 800 | out_.append('
'#10); 801 | end; 802 | 803 | procedure TDecorator.openLink(out_: TStringBuilder); 804 | begin 805 | out_.append(''); 811 | end; 812 | 813 | procedure TDecorator.openImage(out_: TStringBuilder); 814 | begin 815 | out_.append(''); 821 | end; 822 | 823 | { TEmitter } 824 | 825 | constructor TEmitter.Create(config: TConfiguration); 826 | begin 827 | inherited Create; 828 | FConfig := config; 829 | linkRefs := TStringList.Create; 830 | linkRefs.Sorted := true; 831 | linkRefs.Duplicates := dupError; 832 | end; 833 | 834 | destructor TEmitter.Destroy; 835 | var 836 | i : integer; 837 | begin 838 | for i := 0 to linkRefs.Count - 1 do 839 | linkRefs.Objects[i].Free; 840 | linkRefs.Free; 841 | inherited; 842 | end; 843 | 844 | procedure TEmitter.addLinkRef(key: String; linkRef: TLinkRef); 845 | var 846 | k : String; 847 | i : integer; 848 | begin 849 | k := LowerCase(key); 850 | if linkRefs.find(k, i) then 851 | begin 852 | linkRefs.Objects[i].Free; 853 | linkRefs.Objects[i] := linkRef; 854 | end 855 | else 856 | linkRefs.AddObject(k, linkRef); 857 | end; 858 | 859 | procedure TEmitter.emit(out_: TStringBuilder; root: TBlock); 860 | var 861 | block: TBlock; 862 | begin 863 | root.removeSurroundingEmptyLines(); 864 | 865 | case root.type_ of 866 | btRULER: 867 | begin 868 | FConfig.decorator.horizontalRuler(out_); 869 | exit; 870 | end; 871 | btNONE, btXML: 872 | ; // nothing 873 | btHEADLINE: 874 | begin 875 | FConfig.decorator.openHeadline(out_, root.hlDepth); 876 | if FConfig.isDialect([mdTxtMark,mdCommonMark]) and (root.id <> '') then 877 | begin 878 | out_.append(' id="'); 879 | TUtils.appendCode(out_, root.id, 0, Length(root.id)); 880 | out_.append('"'); 881 | end; 882 | out_.append('>'); 883 | end; 884 | btPARAGRAPH: 885 | FConfig.decorator.openParagraph(out_); 886 | btCODE: 887 | if (FConfig.codeBlockEmitter = nil) then 888 | FConfig.decorator.openCodeBlock(out_); 889 | btFENCED_CODE: 890 | if (FConfig.codeBlockEmitter = nil) then 891 | FConfig.decorator.openFencedCodeBlock(out_,root.meta); 892 | btBLOCKQUOTE: 893 | FConfig.decorator.openBlockQuote(out_); 894 | btUNORDERED_LIST: 895 | FConfig.decorator.openUnOrderedList(out_); 896 | btORDERED_LIST: 897 | FConfig.decorator.openOrderedList(out_); 898 | btLIST_ITEM: 899 | begin 900 | FConfig.decorator.openListItem(out_); 901 | if FConfig.isDialect([mdTxtMark,mdCommonMark]) and (root.id <> '') then 902 | begin 903 | out_.append(' id="'); 904 | TUtils.appendCode(out_, root.id, 0, Length(root.id)); 905 | out_.append('"'); 906 | end; 907 | out_.append('>'); 908 | end; 909 | end; 910 | 911 | if (root.hasLines()) then 912 | emitLines(out_, root) 913 | else 914 | begin 915 | block := root.blocks; 916 | while (block <> nil) do 917 | begin 918 | emit(out_, block); 919 | block := block.next; 920 | end; 921 | end; 922 | 923 | case (root.type_) of 924 | btRULER, btNONE, btXML: 925 | ; // nothing 926 | btHEADLINE: 927 | FConfig.decorator.closeHeadline(out_, root.hlDepth); 928 | btPARAGRAPH: 929 | FConfig.decorator.closeParagraph(out_); 930 | btCODE, btFENCED_CODE: 931 | if (FConfig.codeBlockEmitter = nil) then 932 | FConfig.decorator.closeCodeBlock(out_); 933 | btBLOCKQUOTE: 934 | FConfig.decorator.closeBlockQuote(out_); 935 | btUNORDERED_LIST: 936 | FConfig.decorator.closeUnOrderedList(out_); 937 | btORDERED_LIST: 938 | FConfig.decorator.closeOrderedList(out_); 939 | btLIST_ITEM: 940 | FConfig.decorator.closeListItem(out_); 941 | end; 942 | end; 943 | 944 | procedure TEmitter.emitLines(out_: TStringBuilder; block: TBlock); 945 | begin 946 | case (block.type_) of 947 | btCODE: 948 | emitCodeLines(out_, block.lines, block.meta, true); 949 | btFENCED_CODE: 950 | emitCodeLines(out_, block.lines, block.meta, false); 951 | btXML: 952 | emitRawLines(out_, block.lines); 953 | btTABLE: 954 | TTable.emitTableLines(out_,block.lines); 955 | else 956 | emitMarkedLines(out_, block.lines); 957 | end; 958 | end; 959 | 960 | function TEmitter.findToken(s: String; start: integer; token: TMarkToken): integer; 961 | var 962 | position: integer; 963 | begin 964 | position := start; 965 | while (position < Length(s)) do 966 | begin 967 | if getToken(s, position) = token then 968 | exit(position); 969 | inc(position); 970 | end; 971 | result := -1; 972 | end; 973 | 974 | function TEmitter.checkLink(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer; 975 | var 976 | isAbbrev, useLt, hasLink: boolean; 977 | position, oldPos, i: integer; 978 | temp: TStringBuilder; 979 | name, link, comment, id: String; 980 | lr: TLinkRef; 981 | begin 982 | isAbbrev := false; 983 | if (token = mtLINK) then 984 | position := start + 1 985 | else 986 | position := start + 2; 987 | temp := TStringBuilder.Create; 988 | try 989 | position := TUtils.readMdLinkId(temp, s, position); 990 | if (position < start) then 991 | exit(-1); 992 | name := temp.ToString(); 993 | link := ''; 994 | hasLink := false; 995 | comment := ''; 996 | oldPos := position; 997 | inc(position); 998 | position := TUtils.skipSpaces(s, position); 999 | if (position < start) then 1000 | begin 1001 | if linkRefs.find(LowerCase(name), i) then 1002 | begin 1003 | lr := TLinkRef(linkRefs.Objects[i]); 1004 | isAbbrev := lr.isAbbrev; 1005 | link := lr.link; 1006 | hasLink := true; 1007 | comment := lr.title; 1008 | position := oldPos; 1009 | end 1010 | else 1011 | exit(-1); 1012 | end 1013 | else if (s[1 + position] = '(') then 1014 | begin 1015 | inc(position); 1016 | position := TUtils.skipSpaces(s, position); 1017 | if (position < start) then 1018 | exit(-1); 1019 | temp.Clear; 1020 | useLt := s[1 + position] = '<'; 1021 | if useLt then 1022 | position := TUtils.readUntil(temp, s, position + 1, '>') 1023 | else 1024 | position := TUtils.readMdLink(temp, s, position); 1025 | if (position < start) then 1026 | exit(-1); 1027 | if (useLt) then 1028 | inc(position); 1029 | link := temp.ToString(); 1030 | hasLink := true; 1031 | if (s[1 + position] = ' ') then 1032 | begin 1033 | position := TUtils.skipSpaces(s, position); 1034 | if (position > start) and (s[1 + position] = '"') then 1035 | begin 1036 | inc(position); 1037 | temp.Clear; 1038 | position := TUtils.readUntil(temp, s, position, '"'); 1039 | if (position < start) then 1040 | exit(-1); 1041 | comment := temp.ToString(); 1042 | inc(position); 1043 | position := TUtils.skipSpaces(s, position); 1044 | if (position = -1) then 1045 | exit(-1); 1046 | end; 1047 | end; 1048 | if (s[1 + position] <> ')') then 1049 | exit(-1); 1050 | end 1051 | else if (s[1 + position] = '[') then 1052 | begin 1053 | inc(position); 1054 | temp.Clear; 1055 | position := TUtils.readRawUntil(temp, s, position, ']'); 1056 | if (position < start) then 1057 | exit(-1); 1058 | if temp.length > 0 then 1059 | id := temp.ToString() 1060 | else 1061 | id := name; 1062 | if linkRefs.find(LowerCase(id), i) then 1063 | begin 1064 | lr := TLinkRef(linkRefs.Objects[i]); 1065 | link := lr.link; 1066 | hasLink := true; 1067 | comment := lr.title; 1068 | end 1069 | end 1070 | else 1071 | begin 1072 | if linkRefs.find(LowerCase(name), i) then 1073 | begin 1074 | lr := TLinkRef(linkRefs.Objects[i]); 1075 | isAbbrev := lr.isAbbrev; 1076 | link := lr.link; 1077 | hasLink := true; 1078 | comment := lr.title; 1079 | position := oldPos; 1080 | end 1081 | else 1082 | exit(-1); 1083 | end; 1084 | if (not hasLink) then 1085 | exit(-1); 1086 | 1087 | if (token = mtLINK) then 1088 | begin 1089 | if (isAbbrev) and (comment <> '') then 1090 | begin 1091 | out_.append(''); 1094 | recursiveEmitLine(out_, name, 0, mtNONE); 1095 | out_.append(''); 1096 | end 1097 | else 1098 | begin 1099 | FConfig.decorator.openLink(out_); 1100 | out_.append(' href="'); 1101 | TUtils.codeEncode(out_, link, 0); 1102 | out_.append('"'); 1103 | if (comment <> '') then 1104 | begin 1105 | out_.append(' title="'); 1106 | TUtils.appendValue(out_, comment, 0, Length(comment)); 1107 | out_.append('"'); 1108 | end; 1109 | out_.append('>'); 1110 | recursiveEmitLine(out_, name, 0, mtNONE); 1111 | FConfig.decorator.closeLink(out_); 1112 | end 1113 | end 1114 | else 1115 | begin 1116 | FConfig.decorator.openImage(out_); 1117 | out_.append(' src="'); 1118 | TUtils.codeEncode(out_, link, 0); 1119 | out_.append('" alt="'); 1120 | TUtils.appendValue(out_, name, 0, Length(name)); 1121 | out_.append('"'); 1122 | if (comment <> '') then 1123 | begin 1124 | out_.append(' title="'); 1125 | TUtils.appendValue(out_, comment, 0, Length(comment)); 1126 | out_.append('"'); 1127 | end; 1128 | FConfig.decorator.closeImage(out_); 1129 | end; 1130 | result := position; 1131 | finally 1132 | temp.Free; 1133 | end; 1134 | end; 1135 | 1136 | function TEmitter.checkHTML(out_: TStringBuilder; s: String; start: integer): integer; 1137 | var 1138 | temp: TStringBuilder; 1139 | position: integer; 1140 | link: String; 1141 | begin 1142 | temp := TStringBuilder.Create(); 1143 | try 1144 | // Check for auto links 1145 | temp.Clear; 1146 | position := TUtils.readUntil(temp, s, start + 1, [':', ' ', '>', #10]); 1147 | // if (position <> -1) and (s[1 + position] = ':') and (THTML.isLinkPrefix(temp.ToString())) then 1148 | if (position <> -1) and (s[1 + position] = ':') then 1149 | begin 1150 | position := TUtils.readUntil(temp, s, position, ['>']); 1151 | if (position <> -1) then 1152 | begin 1153 | link := temp.ToString(); 1154 | FConfig.decorator.openLink(out_); 1155 | out_.append(' href="'); 1156 | TUtils.codeEncode(out_, link, 0); 1157 | out_.append('">'); 1158 | TUtils.appendValue(out_, link, 0, Length(link)); 1159 | FConfig.decorator.closeLink(out_); 1160 | exit(position); 1161 | end; 1162 | end; 1163 | 1164 | // Check for mailto auto link 1165 | temp.Clear; 1166 | position := TUtils.readUntil(temp, s, start + 1, ['@', ' ', '>', #10]); 1167 | if (position <> -1) and (s[1 + position] = '@') then 1168 | begin 1169 | position := TUtils.readUntil(temp, s, position, '>'); 1170 | if (position <> -1) then 1171 | begin 1172 | link := temp.ToString(); 1173 | FConfig.decorator.openLink(out_); 1174 | out_.append(' href="'); 1175 | TUtils.appendMailto(out_, 'mailto:', 0, 7); 1176 | TUtils.appendMailto(out_, link, 0, Length(link)); 1177 | out_.append('">'); 1178 | TUtils.appendMailto(out_, link, 0, Length(link)); 1179 | FConfig.decorator.closeLink(out_); 1180 | exit(position); 1181 | end; 1182 | end; 1183 | 1184 | // Check for inline html 1185 | if (start + 2 < Length(s)) then 1186 | begin 1187 | temp.Clear; 1188 | exit(TUtils.readXML(out_, s, start, FConfig.safeMode)); 1189 | end; 1190 | 1191 | result := -1; 1192 | finally 1193 | temp.Free; 1194 | end; 1195 | end; 1196 | 1197 | class function TEmitter.checkEntity(out_: TStringBuilder; s: String; start: integer): integer; 1198 | var 1199 | position, i: integer; 1200 | c: char; 1201 | begin 1202 | position := TUtils.readUntil(out_, s, start, ';'); 1203 | if (position < 0) or (out_.length < 3) then 1204 | exit(-1); 1205 | if (out_[1] = '#') then 1206 | begin 1207 | if (out_[2] = 'x') or (out_[2] = 'X') then 1208 | begin 1209 | if (out_.length < 4) then 1210 | exit(-1); 1211 | for i := 3 to out_.length-1 do 1212 | begin 1213 | c := out_[i]; 1214 | if ((c < '0') or (c > '9')) and (((c < 'a') or (c > 'f')) and ((c < 'A') or (c > 'F'))) then 1215 | exit(-1); 1216 | end; 1217 | end 1218 | else 1219 | begin 1220 | for i := 2 to out_.length-1 do 1221 | begin 1222 | c := out_[i]; 1223 | if (c < '0') or (c > '9') then 1224 | exit(-1); 1225 | end; 1226 | end; 1227 | out_.append(';'); 1228 | end 1229 | else 1230 | begin 1231 | for i := 1 to out_.length - 1 do 1232 | begin 1233 | c := out_[i]; // zero based 1234 | if (not c.isLetterOrDigit) then 1235 | exit(-1); 1236 | end; 1237 | out_.append(';'); 1238 | if THTML.isEntity(out_.ToString()) then 1239 | exit(position) 1240 | else 1241 | exit(-1); 1242 | end; 1243 | 1244 | result := position; 1245 | end; 1246 | 1247 | function TEmitter.recursiveEmitLine(out_: TStringBuilder; s: String; start: integer; token: TMarkToken): integer; 1248 | var 1249 | position, a, b: integer; 1250 | temp: TStringBuilder; 1251 | mt: TMarkToken; 1252 | begin 1253 | position := start; 1254 | temp := TStringBuilder.Create(); 1255 | try 1256 | while (position < Length(s)) do 1257 | begin 1258 | mt := getToken(s, position); 1259 | if (token <> mtNONE) and ((mt = token) or ((token = mtEM_STAR) and (mt = mtSTRONG_STAR)) or ((token = mtEM_UNDERSCORE) and (mt = mtSTRONG_UNDERSCORE))) then 1260 | exit(position); 1261 | 1262 | case mt of 1263 | mtIMAGE, mtLINK: 1264 | begin 1265 | temp.Clear; 1266 | b := checkLink(temp, s, position, mt); 1267 | if (b > 0) then 1268 | begin 1269 | out_.append(temp); 1270 | position := b; 1271 | end 1272 | else 1273 | out_.append(s[1 + position]); 1274 | end; 1275 | mtEM_STAR, mtEM_UNDERSCORE: 1276 | begin 1277 | temp.Clear; 1278 | b := recursiveEmitLine(temp, s, position + 1, mt); 1279 | if (b > 0) then 1280 | begin 1281 | FConfig.decorator.openEmphasis(out_); 1282 | out_.append(temp); 1283 | FConfig.decorator.closeEmphasis(out_); 1284 | position := b; 1285 | end 1286 | else 1287 | out_.append(s[1 + position]); 1288 | end; 1289 | mtSTRONG_STAR, mtSTRONG_UNDERSCORE: 1290 | begin 1291 | temp.Clear; 1292 | b := recursiveEmitLine(temp, s, position + 2, mt); 1293 | if (b > 0) then 1294 | begin 1295 | FConfig.decorator.openStrong(out_); 1296 | out_.append(temp); 1297 | FConfig.decorator.closeStrong(out_); 1298 | position := b + 1; 1299 | end 1300 | else 1301 | out_.append(s[1 + position]); 1302 | end; 1303 | mtSTRIKE_TILDE: 1304 | begin 1305 | temp.Clear; 1306 | b := recursiveEmitLine(temp, s, position + 2, mt); 1307 | if (b > 0) then 1308 | begin 1309 | FConfig.decorator.openStrike(out_); 1310 | out_.append(temp); 1311 | FConfig.decorator.closeStrike(out_); 1312 | position := b + 1; 1313 | end 1314 | else 1315 | out_.append(s[1 + position]); 1316 | end; 1317 | mtINS_PLUS: 1318 | begin 1319 | temp.Clear; 1320 | b := recursiveEmitLine(temp, s, position + 2, mt); 1321 | if (b > 0) then 1322 | begin 1323 | FConfig.decorator.openIns(out_); 1324 | out_.append(temp); 1325 | FConfig.decorator.closeIns(out_); 1326 | position := b + 1; 1327 | end 1328 | else 1329 | out_.append(s[1 + position]); 1330 | end; 1331 | mtMARK_EQ: 1332 | begin 1333 | temp.Clear; 1334 | b := recursiveEmitLine(temp, s, position + 2, mt); 1335 | if (b > 0) then 1336 | begin 1337 | FConfig.decorator.openMark(out_); 1338 | out_.append(temp); 1339 | FConfig.decorator.closeMark(out_); 1340 | position := b + 1; 1341 | end 1342 | else 1343 | out_.append(s[1 + position]); 1344 | end; 1345 | mtSUPER: 1346 | begin 1347 | temp.Clear; 1348 | b := recursiveEmitLine(temp, s, position + 1, mt); 1349 | if (b > 0) then 1350 | begin 1351 | FConfig.decorator.openSuper(out_); 1352 | out_.append(temp); 1353 | FConfig.decorator.closeSuper(out_); 1354 | position := b; 1355 | end 1356 | else 1357 | out_.append(s[1 + position]); 1358 | end; 1359 | mtSUB_TILDE: 1360 | begin 1361 | temp.Clear; 1362 | b := recursiveEmitLine(temp, s, position + 1, mt); 1363 | if (b > 0) then 1364 | begin 1365 | FConfig.decorator.openSub(out_); 1366 | out_.append(temp); 1367 | FConfig.decorator.closeSub(out_); 1368 | position := b; 1369 | end 1370 | else 1371 | out_.append(s[1 + position]); 1372 | end; 1373 | mtCODE_SINGLE, mtCODE_DOUBLE: 1374 | begin 1375 | if mt = mtCODE_DOUBLE then 1376 | a := position + 2 1377 | else 1378 | a := position + 1; 1379 | b := findToken(s, a, mt); 1380 | if (b > 0) then 1381 | begin 1382 | if mt = mtCODE_DOUBLE then 1383 | position := b + 1 1384 | else 1385 | position := b + 0; 1386 | while (a < b) and (s[1 + a] = ' ') do 1387 | inc(a); 1388 | if (a < b) then 1389 | begin 1390 | while (s[1 + b - 1] = ' ') do 1391 | dec(b); 1392 | end; 1393 | FConfig.decorator.openCodeSpan(out_); 1394 | TUtils.appendCode(out_, s, a, b); 1395 | FConfig.decorator.closeCodeSpan(out_); 1396 | end 1397 | else 1398 | out_.append(s[1 + position]); 1399 | end; 1400 | mtHTML: 1401 | begin 1402 | temp.Clear; 1403 | b := checkHTML(temp, s, position); 1404 | if (b > 0) then 1405 | begin 1406 | out_.append(temp); 1407 | position := b; 1408 | end 1409 | else 1410 | out_.append('<'); 1411 | end; 1412 | mtENTITY: 1413 | begin 1414 | temp.Clear; 1415 | b := checkEntity(temp, s, position); 1416 | if (b > 0) then 1417 | begin 1418 | out_.append(temp); 1419 | position := b; 1420 | end 1421 | else 1422 | out_.append('&'); 1423 | end; 1424 | mtX_LINK_OPEN: 1425 | begin 1426 | temp.Clear; 1427 | b := recursiveEmitLine(temp, s, position + 2, mtX_LINK_CLOSE); 1428 | if (b > 0) and (FConfig.specialLinkEmitter <> nil) then 1429 | begin 1430 | FConfig.specialLinkEmitter.emitSpan(out_, temp.ToString()); 1431 | position := b + 1; 1432 | end 1433 | else 1434 | out_.append(s[1 + position]); 1435 | end; 1436 | mtX_COPY: 1437 | begin 1438 | out_.append('©'); 1439 | inc(position, 2); 1440 | end; 1441 | mtX_REG: 1442 | begin 1443 | out_.append('®'); 1444 | inc(position, 2); 1445 | end; 1446 | mtX_TRADE: 1447 | begin 1448 | out_.append('™'); 1449 | inc(position, 3); 1450 | end; 1451 | mtX_NDASH: 1452 | begin 1453 | out_.append('–'); 1454 | inc(position); 1455 | end; 1456 | mtX_MDASH: 1457 | begin 1458 | out_.append('—'); 1459 | inc(position, 2); 1460 | end; 1461 | mtX_HELLIP: 1462 | begin 1463 | out_.append('…'); 1464 | inc(position, 2); 1465 | end; 1466 | mtX_LAQUO: 1467 | begin 1468 | out_.append('«'); 1469 | inc(position); 1470 | end; 1471 | mtX_RAQUO: 1472 | begin 1473 | out_.append('»'); 1474 | inc(position); 1475 | end; 1476 | mtX_RDQUO: 1477 | out_.append('”'); 1478 | mtX_LDQUO: 1479 | out_.append('“'); 1480 | mtESCAPE: 1481 | begin 1482 | inc(position); 1483 | out_.append(s[1 + position]); 1484 | end; 1485 | mtMATH_DOLLAR: 1486 | begin 1487 | temp.Clear; 1488 | b := checkMathCode(temp, s, position); 1489 | if (b > 0) then 1490 | begin 1491 | out_.append(temp); 1492 | position := b; 1493 | end 1494 | else 1495 | out_.append('$'); 1496 | end; 1497 | 1498 | // $FALL-THROUGH$ 1499 | else 1500 | out_.append(s[1 + position]); 1501 | end; 1502 | inc(position); 1503 | end; 1504 | result := -1; 1505 | finally 1506 | temp.Free; 1507 | end; 1508 | end; 1509 | 1510 | class function TEmitter.whitespaceToSpace(c: char): char; 1511 | begin 1512 | if c.isWhitespace then 1513 | result := ' ' 1514 | else 1515 | result := c; 1516 | end; 1517 | 1518 | function TEmitter.getToken(s: String; position: integer): TMarkToken; 1519 | var 1520 | c0, c, c1, c2, c3: char; 1521 | begin 1522 | 1523 | result := mtNONE; 1524 | if (position > 0) then 1525 | c0 := whitespaceToSpace(s[1 + position - 1]) 1526 | else 1527 | c0 := ' '; 1528 | c := whitespaceToSpace(s[1 + position]); 1529 | if (position + 1 < Length(s)) then 1530 | c1 := whitespaceToSpace(s[1 + position + 1]) 1531 | else 1532 | c1 := ' '; 1533 | if (position + 2 < Length(s)) then 1534 | c2 := whitespaceToSpace(s[1 + position + 2]) 1535 | else 1536 | c2 := ' '; 1537 | if (position + 3 < Length(s)) then 1538 | c3 := whitespaceToSpace(s[1 + position + 3]) 1539 | else 1540 | c3 := ' '; 1541 | 1542 | case (c) of 1543 | '*': 1544 | if (c1 = '*') then 1545 | begin 1546 | if (c0 <> ' ') or (c2 <> ' ') then 1547 | exit(mtSTRONG_STAR) 1548 | else 1549 | exit(mtNONE); 1550 | end 1551 | else if (c0 <> ' ') or (c1 <> ' ') then 1552 | exit(mtEM_STAR) 1553 | else 1554 | exit(mtNONE); 1555 | '_': 1556 | if (c1 = '_') then 1557 | begin 1558 | if (c0 <> ' ') or (c2 <> ' ') then 1559 | exit(mtSTRONG_UNDERSCORE) 1560 | else 1561 | exit(mtNONE); 1562 | end 1563 | else if FConfig.isDialect([mdTxtMark,mdCommonMark]) then 1564 | begin 1565 | if (c0.isLetterOrDigit) and (c0 <> '_') and (c1.isLetterOrDigit) then 1566 | exit(mtNONE) 1567 | else 1568 | exit(mtEM_UNDERSCORE); 1569 | end 1570 | else if (c0 <> ' ') or (c1 <> ' ') then 1571 | exit(mtEM_UNDERSCORE) 1572 | else 1573 | exit(mtNONE); 1574 | '!': 1575 | if (c1 = '[') then 1576 | exit(mtIMAGE) 1577 | else 1578 | exit(mtNONE); 1579 | '[': 1580 | if FConfig.isDialect([mdTxtMark,mdCommonMark]) and (c1 = '[') then 1581 | exit(mtX_LINK_OPEN) 1582 | else 1583 | exit(mtLINK); 1584 | ']': 1585 | if FConfig.isDialect([mdTxtMark,mdCommonMark]) and (c1 = ']') then 1586 | exit(mtX_LINK_CLOSE) 1587 | else 1588 | exit(mtNONE); 1589 | '`': 1590 | if (c1 = '`') then 1591 | exit(mtCODE_DOUBLE) 1592 | else 1593 | exit(mtCODE_SINGLE); 1594 | '\': 1595 | if CharInSet(c1, ['\', '[', ']', '(', ')', '{', '}', '#', '"', '''', '.', '>', '<', '*', '+', '-', '_', '!', '`', '~', '^', '$', '|']) then 1596 | exit(mtESCAPE) 1597 | else 1598 | exit(mtNONE); 1599 | '<': 1600 | if FConfig.isDialect([mdTxtMark,mdCommonMark]) and (c1 = '<') then 1601 | exit(mtX_LAQUO) 1602 | else 1603 | exit(mtHTML); 1604 | '&': 1605 | exit(mtENTITY); 1606 | else 1607 | if FConfig.isDialect([mdTxtMark,mdCommonMark]) then 1608 | case (c) of 1609 | '-': 1610 | if (c1 = '-') and (c2 = '-') then 1611 | exit(mtX_MDASH) 1612 | else if (c1 = '-') then 1613 | exit(mtX_NDASH); 1614 | '^': 1615 | if (c0 = '^') or (c1 = '^') then 1616 | exit(mtNONE) 1617 | else 1618 | exit(mtSUPER); 1619 | '>': 1620 | if (c1 = '>') then 1621 | exit(mtX_RAQUO); 1622 | '.': 1623 | if (c1 = '.') and (c2 = '.') then 1624 | exit(mtX_HELLIP); 1625 | '(': 1626 | begin 1627 | if (c1 = 'C') and (c2 = ')') then 1628 | exit(mtX_COPY); 1629 | if (c1 = 'R') and (c2 = ')') then 1630 | exit(mtX_REG); 1631 | if (c1 = 'T') and (c2 = 'M') and (c3 = ')') then 1632 | exit(mtX_TRADE); 1633 | end; 1634 | '"': 1635 | begin 1636 | if (not c0.isLetterOrDigit) and (c1 <> ' ') then 1637 | exit(mtX_LDQUO); 1638 | if (c0 <> ' ') and (not c1.isLetterOrDigit) then 1639 | exit(mtX_RDQUO); 1640 | exit(mtNONE); 1641 | end; 1642 | end; 1643 | if FConfig.isDialect([mdCommonMark]) then 1644 | case (c) of 1645 | '~': 1646 | if (c1 = '~') then 1647 | begin 1648 | if (c0 <> ' ') or (c2 <> ' ') then exit(mtSTRIKE_TILDE) 1649 | end else 1650 | if (c0 <> ' ') or (c1 <> ' ') then exit(mtSUB_TILDE); 1651 | '+': 1652 | if (c1 = '+') then 1653 | begin 1654 | if (c0 <> ' ') or (c2 <> ' ') then exit(mtINS_PLUS) 1655 | end; 1656 | '=': 1657 | if (c1 = '=') then 1658 | begin 1659 | if (c0 <> ' ') or (c2 <> ' ') then exit(mtMARK_EQ) 1660 | end; 1661 | '$': 1662 | if (c0 <> ' ') or (c1 <> ' ') then exit(mtMATH_DOLLAR) 1663 | end; 1664 | end; 1665 | end; 1666 | 1667 | procedure TEmitter.emitMarkedLines(out_: TStringBuilder; lines: TLine); 1668 | var 1669 | s: TStringBuilder; 1670 | line: TLine; 1671 | begin 1672 | s := TStringBuilder.Create(); 1673 | try 1674 | line := lines; 1675 | while (line <> nil) do 1676 | begin 1677 | if (not line.isEmpty) then 1678 | begin 1679 | // s.append(line.value.substring(line.leading, line.value.length - line.trailing)); PSTfix 1680 | s.Append( Copy(line.value, line.leading + 1, Length(line.value) - line.trailing)); 1681 | if (line.trailing >= 2) then 1682 | s.append('
'); 1683 | end; 1684 | if (line.next <> nil) then 1685 | s.append(#10); 1686 | line := line.next; 1687 | end; 1688 | recursiveEmitLine(out_, s.ToString(), 0, mtNONE); 1689 | finally 1690 | s.Free; 1691 | end; 1692 | end; 1693 | 1694 | procedure TEmitter.emitRawLines(out_: TStringBuilder; lines: TLine); 1695 | var 1696 | s: String; 1697 | line: TLine; 1698 | temp: TStringBuilder; 1699 | position, t: integer; 1700 | begin 1701 | line := lines; 1702 | if (FConfig.safeMode) then 1703 | begin 1704 | temp := TStringBuilder.Create(); 1705 | try 1706 | while (line <> nil) do 1707 | begin 1708 | if (not line.isEmpty) then 1709 | temp.append(line.value); 1710 | temp.append(#10); 1711 | line := line.next; 1712 | end; 1713 | s := temp.ToString(); 1714 | position := 0; 1715 | while position < length(s) do 1716 | begin 1717 | if (s[1 + position] = '<') then 1718 | begin 1719 | temp.Clear; 1720 | t := TUtils.readXML(temp, s, position, FConfig.safeMode); 1721 | if (t <> -1) then 1722 | begin 1723 | out_.append(temp); 1724 | position := t; 1725 | end 1726 | else 1727 | out_.append(s[1 + position]); 1728 | end 1729 | else 1730 | out_.append(s[1 + position]); 1731 | inc(position); 1732 | end 1733 | finally 1734 | temp.Free; 1735 | end; 1736 | end 1737 | else 1738 | begin 1739 | while (line <> nil) do 1740 | begin 1741 | if (not line.isEmpty) then 1742 | out_.append(line.value); 1743 | out_.append(#10); 1744 | line := line.next; 1745 | end; 1746 | end; 1747 | end; 1748 | 1749 | procedure TEmitter.emitCodeLines(out_: TStringBuilder; lines: TLine; meta: String; removeIndent: boolean); 1750 | var 1751 | line: TLine; 1752 | list: TStringList; 1753 | i, sp: integer; 1754 | c: char; 1755 | begin 1756 | line := lines; 1757 | if (FConfig.codeBlockEmitter <> nil) then 1758 | begin 1759 | list := TStringList.Create; 1760 | try 1761 | while (line <> nil) do 1762 | begin 1763 | if (line.isEmpty) then 1764 | list.add('') 1765 | else if removeIndent then 1766 | // list.add(line.value.substring(4)) P{STfix 1767 | list.Add( Copy(line.value, 5)) 1768 | else 1769 | list.add(line.value); 1770 | line := line.next; 1771 | end; 1772 | FConfig.codeBlockEmitter.emitBlock(out_, list, meta); 1773 | finally 1774 | list.Free 1775 | end 1776 | end 1777 | else 1778 | begin 1779 | while (line <> nil) do 1780 | begin 1781 | if (not line.isEmpty) then 1782 | begin 1783 | if removeIndent then 1784 | sp := 4 1785 | else 1786 | sp := 0; 1787 | for i := sp to Length(line.value) - 1 do 1788 | begin 1789 | c := line.value[1 + i]; 1790 | case c of 1791 | '&': 1792 | out_.append('&'); 1793 | '<': 1794 | out_.append('<'); 1795 | '>': 1796 | out_.append('>'); 1797 | else 1798 | out_.append(c); 1799 | end; 1800 | end; 1801 | end; 1802 | out_.append(#10); 1803 | line := line.next; 1804 | end; 1805 | end; 1806 | end; 1807 | 1808 | { TMarkdownReader } 1809 | 1810 | constructor TMarkdownReader.Create(source: String); 1811 | begin 1812 | inherited Create; 1813 | FValue := source; 1814 | FCursor := 0; 1815 | end; 1816 | 1817 | function TMarkdownReader.read: char; 1818 | begin 1819 | inc(FCursor); 1820 | if FCursor > Length(FValue) then 1821 | result := #0 1822 | else 1823 | result := FValue[FCursor]; 1824 | end; 1825 | 1826 | { TUtils } 1827 | 1828 | class function TUtils.skipSpaces(s: String; start: integer): integer; 1829 | var 1830 | position: integer; 1831 | begin 1832 | position := start; 1833 | while (position < Length(s)) and ((s[1 + position] = ' ') or (s[1 + position] = #10)) do 1834 | inc(position); 1835 | if position < Length(s) then 1836 | result := position 1837 | else 1838 | result := -1; 1839 | end; 1840 | 1841 | class function TUtils.escape(out_: TStringBuilder; ch: char; position: integer): integer; 1842 | begin 1843 | if CharInSet(ch, ['\', '[', ']', '(', ')', '{', '}', '#', '"', '''', '.', '>', '<', '*', '+', '-', '_', '!', '`', '^']) then 1844 | begin 1845 | out_.append(ch); 1846 | result := position + 1; 1847 | end 1848 | else 1849 | begin 1850 | out_.append('\'); 1851 | result := position; 1852 | end; 1853 | end; 1854 | 1855 | class function TUtils.readUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; 1856 | var 1857 | position: integer; 1858 | ch: char; 1859 | begin 1860 | position := start; 1861 | while (position < Length(s)) do 1862 | begin 1863 | ch := s[1 + position]; 1864 | if (ch = '\') and (position + 1 < Length(s)) then 1865 | position := escape(out_, s[1 + position + 1], position) 1866 | else 1867 | begin 1868 | if CharInSet(ch, cend) then 1869 | break 1870 | else 1871 | out_.append(ch); 1872 | end; 1873 | inc(position); 1874 | end; 1875 | if position = Length(s) then 1876 | result := -1 1877 | else 1878 | result := position; 1879 | end; 1880 | 1881 | class function TUtils.readUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer; 1882 | var 1883 | position: integer; 1884 | ch: char; 1885 | begin 1886 | position := start; 1887 | while (position < Length(s)) do 1888 | begin 1889 | ch := s[1 + position]; 1890 | if (ch = '\') and (position + 1 < Length(s)) then 1891 | position := escape(out_, s[1 + position + 1], position) 1892 | else 1893 | begin 1894 | if (ch = cend) then 1895 | break; 1896 | out_.append(ch); 1897 | end; 1898 | inc(position); 1899 | end; 1900 | if position = Length(s) then 1901 | result := -1 1902 | else 1903 | result := position; 1904 | end; 1905 | 1906 | class function TUtils.readMdLink(out_: TStringBuilder; s: String; start: integer): integer; 1907 | var 1908 | position, counter: integer; 1909 | ch: char; 1910 | endReached: boolean; 1911 | begin 1912 | position := start; 1913 | counter := 1; 1914 | while (position < Length(s)) do 1915 | begin 1916 | ch := s[1 + position]; 1917 | if (ch = '\') and (position + 1 < Length(s)) then 1918 | position := escape(out_, s[1 + position + 1], position) 1919 | else 1920 | begin 1921 | endReached := false; 1922 | case ch of 1923 | '(': 1924 | inc(counter); 1925 | ' ': 1926 | if (counter = 1) then 1927 | endReached := true; 1928 | ')': 1929 | begin 1930 | dec(counter); 1931 | if (counter = 0) then 1932 | endReached := true; 1933 | end; 1934 | end; 1935 | if (endReached) then 1936 | break; 1937 | out_.append(ch); 1938 | end; 1939 | inc(position); 1940 | end; 1941 | if position = Length(s) then 1942 | result := -1 1943 | else 1944 | result := position; 1945 | end; 1946 | 1947 | class function TUtils.readMdLinkId(out_: TStringBuilder; s: String; start: integer): integer; 1948 | var 1949 | position, counter: integer; 1950 | ch: char; 1951 | endReached: boolean; 1952 | begin 1953 | position := start; 1954 | counter := 1; 1955 | while (position < Length(s)) do 1956 | begin 1957 | ch := s[1 + position]; 1958 | endReached := false; 1959 | case ch of 1960 | #10: 1961 | out_.append(' '); 1962 | '[': 1963 | begin 1964 | inc(counter); 1965 | out_.append(ch); 1966 | end; 1967 | ']': 1968 | begin 1969 | dec(counter); 1970 | if (counter = 0) then 1971 | endReached := true 1972 | else 1973 | out_.append(ch); 1974 | end; 1975 | else 1976 | out_.append(ch); 1977 | end; 1978 | if (endReached) then 1979 | break; 1980 | inc(position); 1981 | end; 1982 | if position = Length(s) then 1983 | result := -1 1984 | else 1985 | result := position; 1986 | end; 1987 | 1988 | class function TUtils.readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; 1989 | var 1990 | position: integer; 1991 | ch: char; 1992 | begin 1993 | position := start; 1994 | while (position < Length(s)) do 1995 | begin 1996 | ch := s[1 + position]; 1997 | if CharInSet(ch, cend) then 1998 | break; 1999 | out_.append(ch); 2000 | inc(position); 2001 | end; 2002 | if position = Length(s) then 2003 | result := -1 2004 | else 2005 | result := position; 2006 | end; 2007 | 2008 | class function TUtils.readRawUntil(out_: TStringBuilder; s: String; start: integer; cend: char): integer; 2009 | var 2010 | position: integer; 2011 | ch: char; 2012 | begin 2013 | position := start; 2014 | while (position < Length(s)) do 2015 | begin 2016 | ch := s[1 + position]; 2017 | if (ch = cend) then 2018 | break; 2019 | out_.append(ch); 2020 | inc(position); 2021 | end; 2022 | if position = Length(s) then 2023 | result := -1 2024 | else 2025 | result := position; 2026 | end; 2027 | 2028 | class function TUtils.readXMLUntil(out_: TStringBuilder; s: String; start: integer; cend: TSysCharSet): integer; 2029 | var 2030 | position : integer; 2031 | ch, stringChar: char; 2032 | inString: boolean; 2033 | begin 2034 | position := start; 2035 | inString := false; 2036 | stringChar := #0; 2037 | while (position < Length(s)) do 2038 | begin 2039 | ch := s[1 + position]; 2040 | if (inString) then 2041 | begin 2042 | if (ch = '\') then 2043 | begin 2044 | out_.append(ch); 2045 | inc(position); 2046 | if (position < Length(s)) then 2047 | begin 2048 | out_.append(ch); 2049 | inc(position); 2050 | end; 2051 | continue; 2052 | end; 2053 | if (ch = stringChar) then 2054 | begin 2055 | inString := false; 2056 | out_.append(ch); 2057 | inc(position); 2058 | continue; 2059 | end; 2060 | end; 2061 | if CharInSet(ch, ['"', '''']) then 2062 | begin 2063 | inString := true; 2064 | stringChar := ch; 2065 | end; 2066 | if (not inString) then 2067 | begin 2068 | if CharInSet(ch, cend) then 2069 | break; 2070 | end; 2071 | out_.append(ch); 2072 | inc(position); 2073 | end; 2074 | if position = Length(s) then 2075 | result := -1 2076 | else 2077 | result := position; 2078 | end; 2079 | 2080 | class procedure TUtils.appendCode(out_: TStringBuilder; s: String; start: integer; e: integer); 2081 | var 2082 | i: integer; 2083 | c: char; 2084 | begin 2085 | for i := start to e - 1 do 2086 | begin 2087 | c := s[1 + i]; 2088 | case c of 2089 | '&': 2090 | out_.append('&'); 2091 | '<': 2092 | out_.append('<'); 2093 | '>': 2094 | out_.append('>'); 2095 | else 2096 | out_.append(c); 2097 | end; 2098 | end; 2099 | end; 2100 | 2101 | class procedure TUtils.appendValue(out_: TStringBuilder; s: String; start: integer; e: integer); 2102 | var 2103 | i: integer; 2104 | c: char; 2105 | begin 2106 | for i := start to e - 1 do 2107 | begin 2108 | c := s[1 + i]; 2109 | case c of 2110 | '&': 2111 | out_.append('&'); 2112 | '<': 2113 | out_.append('<'); 2114 | '>': 2115 | out_.append('>'); 2116 | '"': 2117 | out_.append('"'); 2118 | '''': 2119 | out_.append('''); 2120 | else 2121 | out_.append(c); 2122 | end; 2123 | end; 2124 | end; 2125 | 2126 | class procedure TUtils.appendDecEntity(out_: TStringBuilder; value: char); 2127 | begin 2128 | out_.append('&#'); 2129 | out_.append(IntToStr(ord(value))); 2130 | out_.append(';'); 2131 | end; 2132 | 2133 | class procedure TUtils.appendHexEntity(out_: TStringBuilder; value: char); 2134 | begin 2135 | out_.append('&#x'); 2136 | out_.append(IntToHex(ord(value), 2)); 2137 | out_.append(';'); 2138 | end; 2139 | 2140 | class procedure TUtils.appendMailto(out_: TStringBuilder; s: String; start: integer; e: integer); 2141 | var 2142 | i: integer; 2143 | c: char; 2144 | begin 2145 | for i := start to e - 1 do 2146 | begin 2147 | c := s[1 + i]; 2148 | if CharInSet(c, ['a'..'z','A'..'Z','0'..'9','&', '<', '>', '"', '''', '@']) then 2149 | if random(2)=0 then appendHexEntity(out_, c) else appendDecEntity(out_, c) 2150 | else 2151 | out_.append(c); 2152 | end; 2153 | end; 2154 | 2155 | class procedure TUtils.getXMLTag(out_: TStringBuilder; bin: TStringBuilder); 2156 | var 2157 | position: integer; 2158 | begin 2159 | position := 1; 2160 | if (bin[1] = '/') then 2161 | inc(position); 2162 | while (bin[position].isLetterOrDigit) do 2163 | begin 2164 | out_.append(bin[position]); 2165 | inc(position) 2166 | end; 2167 | end; 2168 | 2169 | class procedure TUtils.getXMLTag(out_: TStringBuilder; s: String); 2170 | var 2171 | position: integer; 2172 | begin 2173 | position := 1; 2174 | if (s[1 + 1] = '/') then 2175 | inc(position); 2176 | while (s[1 + position].isLetterOrDigit) do 2177 | begin 2178 | out_.append(s[1 + position]); 2179 | inc(position) 2180 | end; 2181 | end; 2182 | 2183 | class function TUtils.readXML(out_: TStringBuilder; s: String; start: integer; safeMode: boolean): integer; 2184 | var 2185 | position: integer; 2186 | isCloseTag: boolean; 2187 | temp: TStringBuilder; 2188 | tag: String; 2189 | begin 2190 | if (length(s)<1 + start +1 ) then exit(-1); 2191 | if (s[1 + start + 1] = '/') then 2192 | begin 2193 | isCloseTag := true; 2194 | position := start + 2; 2195 | end 2196 | else if (s[1 + start + 1] = '!') then 2197 | begin 2198 | out_.append('']); 2212 | if (position = -1) then 2213 | exit(-1); 2214 | // tag := temp.ToString().trim().ToLower; PSTFix 2215 | tag := LowerCase( Trim( temp.ToString)); 2216 | if (THTML.isUnsafeHtmlElement(tag)) then 2217 | out_.append('<') 2218 | else 2219 | out_.append('<'); 2220 | if (isCloseTag) then 2221 | out_.append('/'); 2222 | out_.append(temp); 2223 | finally 2224 | temp.Free; 2225 | end; 2226 | end 2227 | else 2228 | begin 2229 | out_.append('<'); 2230 | if (isCloseTag) then 2231 | out_.append('/'); 2232 | position := readXMLUntil(out_, s, position, [' ', '/', '>']); 2233 | end; 2234 | if (position = -1) then 2235 | exit(-1); 2236 | position := readXMLUntil(out_, s, position, ['/', '>']); 2237 | if (position = -1) then 2238 | exit(-1); 2239 | 2240 | if (s[1 + position] = '/') then 2241 | begin 2242 | out_.append(' /'); 2243 | position := readXMLUntil(out_, s, position + 1, ['>']); 2244 | if (position = -1) then 2245 | exit(-1); 2246 | end; 2247 | 2248 | if (s[1 + position] = '>') then 2249 | begin 2250 | out_.append('>'); 2251 | exit(position); 2252 | end; 2253 | result := -1; 2254 | end; 2255 | 2256 | class procedure TUtils.codeEncode(out_: TStringBuilder; value: String; offset: integer); 2257 | var 2258 | i: integer; 2259 | c: char; 2260 | begin 2261 | for i := offset to Length(value) - 1 do 2262 | begin 2263 | c := value[1 + i]; 2264 | case c of 2265 | '&': 2266 | out_.append('&'); 2267 | '<': 2268 | out_.append('<'); 2269 | '>': 2270 | out_.append('>'); 2271 | '+': 2272 | out_.append('%2b'); 2273 | else 2274 | out_.append(c); 2275 | end; 2276 | end; 2277 | end; 2278 | 2279 | class function TUtils.getMetaFromFence(fenceLine: String): String; 2280 | var 2281 | i: integer; 2282 | c: char; 2283 | begin 2284 | for i := 0 to Length(fenceLine) - 1 do 2285 | begin 2286 | c := fenceLine[1 + i]; 2287 | if (not c.isWhitespace) and (c <> '`') and (c <> '~') then 2288 | // exit(fenceLine.substring(i).trim()); PSTfix 2289 | Exit( Trim( Copy(fenceLine, i+1))); 2290 | end; 2291 | result := ''; 2292 | end; 2293 | 2294 | class function TUtils.encodeURL(url: String): String; 2295 | var 2296 | x: integer; 2297 | sBuff: string; 2298 | const 2299 | SafeMask = ['A'..'Z', '0'..'9', 'a'..'z', '*', '@', '.', '_', '-']; 2300 | begin 2301 | sBuff := ''; 2302 | for x := 1 to Length(url) do 2303 | begin 2304 | if CharInSet(url[x], SafeMask) then 2305 | begin 2306 | sBuff := sBuff + url[x]; 2307 | end 2308 | else if url[x] = ' ' then 2309 | begin 2310 | sBuff := sBuff + '+'; //Append space 2311 | end 2312 | else 2313 | begin 2314 | sBuff := sBuff + '%' + IntToHex(Ord(url[x]),2); 2315 | end; 2316 | end; 2317 | Result := sBuff; 2318 | end; 2319 | 2320 | { THTML } 2321 | 2322 | class function THTML.isHtmlBlockElement(s: String): boolean; 2323 | var 2324 | ht: THTMLElement; 2325 | begin 2326 | ht := THTMLElement(StringToEnum(TypeInfo(THTMLElement), 'he' + s, ord(heNONE))); 2327 | result := ht in BLOCK_ELEMENTS; 2328 | end; 2329 | 2330 | class function THTML.isLinkPrefix(s: String): boolean; 2331 | begin 2332 | result := StringsContains(LINK_PREFIXES, s); 2333 | end; 2334 | 2335 | class function THTML.isEntity(s: String): boolean; 2336 | begin 2337 | result := StringsContains(ENTITY_NAMES, s); 2338 | end; 2339 | 2340 | class function THTML.isUnsafeHtmlElement(s: String): boolean; 2341 | var 2342 | ht: THTMLElement; 2343 | begin 2344 | ht := THTMLElement(StringToEnum(TypeInfo(THTMLElement), s, ord(heNONE))); 2345 | result := ht in UNSAFE_ELEMENTS; 2346 | end; 2347 | 2348 | { TLine } 2349 | 2350 | procedure TLine.Init(); 2351 | begin 2352 | FLeading := 0; 2353 | while (leading < Length(value)) and (value[1 + leading] = ' ') do 2354 | inc(FLeading); 2355 | 2356 | if (leading = Length(value)) then 2357 | setEmpty() 2358 | else 2359 | begin 2360 | isEmpty := false; 2361 | trailing := 0; 2362 | while (value[1 + Length(value) - trailing - 1] = ' ') do 2363 | inc(FTrailing); 2364 | end; 2365 | end; 2366 | 2367 | procedure TLine.InitLeading(); 2368 | begin 2369 | FLeading := 0; 2370 | while (leading < Length(value)) and (value[1 + leading] = ' ') do 2371 | inc(FLeading); 2372 | if (leading = Length(value)) then 2373 | setEmpty(); 2374 | end; 2375 | 2376 | // TODO use Util#skipSpaces 2377 | function TLine.skipSpaces(): boolean; 2378 | begin 2379 | while (position < Length(value)) and (value[1 + position] = ' ') do 2380 | inc(FPosition); 2381 | result := position < Length(value); 2382 | end; 2383 | 2384 | // TODO use Util#readUntil 2385 | function TLine.readUntil(chend: TSysCharSet): String; 2386 | var 2387 | sb: TStringBuilder; 2388 | p: integer; 2389 | ch, c: char; 2390 | begin 2391 | sb := TStringBuilder.Create(); 2392 | try 2393 | p := self.position; 2394 | while (p < Length(value)) do 2395 | begin 2396 | ch := value[1 + p]; 2397 | if (ch = '\') and (p + 1 < Length(value)) then 2398 | begin 2399 | c := value[1 + p + 1]; 2400 | if CharInSet(c, ['\', '[', ']', '(', ')', '{', '}', '#', '"', '''', '.', '>', '*', '+', '-', '_', '!', '`', '~']) then 2401 | begin 2402 | sb.append(c); 2403 | inc(FPosition); 2404 | end 2405 | else 2406 | begin 2407 | sb.append(ch); 2408 | break; 2409 | end; 2410 | end 2411 | else if CharInSet(ch, chend) then 2412 | break 2413 | else 2414 | sb.append(ch); 2415 | inc(p); 2416 | end; 2417 | 2418 | if (p < Length(value)) then 2419 | ch := value[1 + p] 2420 | else 2421 | ch := #10; 2422 | if CharInSet(ch, chend) then 2423 | begin 2424 | self.position := p; 2425 | result := sb.ToString(); 2426 | end 2427 | else 2428 | result := ''; 2429 | finally 2430 | sb.Free; 2431 | end; 2432 | end; 2433 | 2434 | procedure TLine.setEmpty(); 2435 | begin 2436 | value := ''; 2437 | leading := 0; 2438 | trailing := 0; 2439 | isEmpty := true; 2440 | if (previous <> nil) then 2441 | previous.nextEmpty := true; 2442 | if (next <> nil) then 2443 | next.prevEmpty := true; 2444 | end; 2445 | 2446 | function TLine.countChars(ch: char): integer; 2447 | var 2448 | count, i: integer; 2449 | c: char; 2450 | begin 2451 | count := 0; 2452 | for i := 0 to Length(value) - 1 do 2453 | begin 2454 | c := value[1 + i]; 2455 | if (c = ' ') then 2456 | continue; 2457 | if (c = ch) then 2458 | begin 2459 | inc(count); 2460 | continue; 2461 | end; 2462 | count := 0; 2463 | break; 2464 | end; 2465 | result := count; 2466 | end; 2467 | 2468 | function TLine.countCharsStart(ch: char; allowSpaces: boolean): integer; 2469 | var 2470 | count, i: integer; 2471 | c: char; 2472 | begin 2473 | count := 0; 2474 | for i := 0 to Length(value) - 1 do 2475 | begin 2476 | c := value[1 + i]; 2477 | if (c = ' ') and (allowSpaces) then 2478 | begin 2479 | continue; 2480 | end; 2481 | if (c = ch) then 2482 | inc(count) 2483 | else 2484 | break; 2485 | end; 2486 | result := count; 2487 | end; 2488 | 2489 | function TLine.getLineType(config: TConfiguration): TLineType; 2490 | var 2491 | i: integer; 2492 | begin 2493 | if (isEmpty) then 2494 | exit(ltEMPTY); 2495 | 2496 | if (leading > 3) then 2497 | exit(ltCODE); 2498 | 2499 | if (value[1 + leading] = '#') then 2500 | begin 2501 | if config.isDialect([mdCommonMark]) then 2502 | begin 2503 | i:=1 + leading + 1; 2504 | while (i 2) then 2516 | begin 2517 | if (value[1 + leading] = '`') and (countCharsStart('`', config.allowSpacesInFencedDelimiters) >= 3) then 2518 | exit(ltFENCED_CODE); 2519 | if (value[1 + leading] = '~') and (countCharsStart('~', config.allowSpacesInFencedDelimiters) >= 3) then 2520 | exit(ltFENCED_CODE); 2521 | end; 2522 | end; 2523 | 2524 | if (Length(value) - leading - trailing > 2) and ((value[1 + leading] = '*') or (value[1 + leading] = '-') or (value[1 + leading] = '_')) then 2525 | begin 2526 | if (countChars(value[1 + leading]) >= 3) then 2527 | exit(ltHR); 2528 | end; 2529 | 2530 | if (Length(value) - leading >= 2) and (value[1 + leading + 1] = ' ') then 2531 | begin 2532 | if CharInSet(value[1 + leading], ['*', '-', '+']) then 2533 | exit(ltULIST); 2534 | end; 2535 | 2536 | if (Length(value) - leading >= 3) and (value[1 + leading].isDigit) then 2537 | begin 2538 | i := leading + 1; 2539 | while (i < Length(value)) and (value[1 + i].isDigit) do 2540 | inc(i); 2541 | if (i + 1 < Length(value)) and (value[1 + i] = '.') and (value[1 + i + 1] = ' ') then 2542 | exit(ltOLIST); 2543 | if (i + 1 < Length(value)) and (value[1 + i] = ')') and (value[1 + i + 1] = ' ') then 2544 | exit(ltBLIST); 2545 | end; 2546 | 2547 | if (value[1 + leading] = '<') then 2548 | begin 2549 | if (checkHTML()) then 2550 | exit(ltXML); 2551 | end; 2552 | 2553 | if (next <> nil) and (not next.isEmpty) then 2554 | begin 2555 | i:=1; 2556 | if config.isDialect([mdCommonMark]) then 2557 | while (length(next.value)>i) and (next.value[i] <> '-') and (next.value[i] <> '=') do 2558 | inc(i); 2559 | if (i<5) and (next.value[i] = '-') and (next.countChars('-') > 0) then 2560 | exit(ltHEADLINE2); 2561 | if (i<5) and (next.value[i] = '=') and (next.countChars('=') > 0) then 2562 | exit(ltHEADLINE1); 2563 | end; 2564 | 2565 | if config.isDialect([mdCommonMark]) and (next <>nil) and (not next.isEmpty) then 2566 | begin 2567 | if (TTable.hasFormatChars(next)>0) and (TTable.ColCount(self)=TTable.Cols) then exit(ltTABLE); 2568 | end; 2569 | 2570 | exit(ltOTHER); 2571 | end; 2572 | 2573 | function TLine.readXMLComment(firstLine: TLine; start: integer): integer; 2574 | var 2575 | line: TLine; 2576 | p: integer; 2577 | begin 2578 | line := firstLine; 2579 | if (start + 3 < Length(line.value)) then 2580 | begin 2581 | if (line.value[1 + 2] = '-') and (line.value[1 + 3] = '-') then 2582 | begin 2583 | p := start + 4; 2584 | while (line <> nil) do 2585 | begin 2586 | while (p < Length(line.value)) and (line.value[1 + p] <> '-') do 2587 | inc(p); 2588 | if (p = Length(line.value)) then 2589 | begin 2590 | line := line.next; 2591 | p := 0; 2592 | end 2593 | else 2594 | begin 2595 | if (p + 2 < Length(line.value)) then 2596 | begin 2597 | if (line.value[1 + p + 1] = '-') and (line.value[1 + p + 2] = '>') then 2598 | begin 2599 | xmlEndLine := line; 2600 | exit(p + 3); 2601 | end; 2602 | end; 2603 | inc(p); 2604 | end; 2605 | end; 2606 | end; 2607 | end; 2608 | exit(-1); 2609 | end; 2610 | 2611 | // FIXME ... hack - It is OK NOW 2612 | function TLine.stripID(): String; 2613 | var 2614 | p, start: integer; 2615 | found: boolean; 2616 | id: String; 2617 | begin 2618 | if (isEmpty or (value[1 + Length(value) - trailing - 1] <> '}')) then 2619 | exit(''); 2620 | 2621 | p := leading; 2622 | found := false; 2623 | start := 0; 2624 | while (p < Length(value)) and (not found) do 2625 | begin 2626 | case value[1 + p] of 2627 | '\': 2628 | begin 2629 | if (p + 1 < Length(value)) then 2630 | begin 2631 | if (value[1 + p + 1]) = '{' then 2632 | begin 2633 | inc(p); 2634 | break; 2635 | end; 2636 | end; 2637 | inc(p); 2638 | break; 2639 | end; 2640 | '{': 2641 | begin 2642 | found := true; 2643 | break; 2644 | end 2645 | else 2646 | begin 2647 | inc(p); 2648 | end; 2649 | end; 2650 | end; 2651 | 2652 | if (found) then 2653 | begin 2654 | found := false; 2655 | if (p + 1 < Length(value)) and (value[1 + p + 1] = '#') then 2656 | begin 2657 | start := p + 2; 2658 | p := start; 2659 | while (p < Length(value)) and (not found) do 2660 | begin 2661 | case (value[1 + p]) of 2662 | '\': 2663 | begin 2664 | if (p + 1 < Length(value)) then 2665 | begin 2666 | if (value[1 + p + 1]) = '}' then 2667 | begin 2668 | inc(p); 2669 | break; 2670 | end; 2671 | end; 2672 | inc(p); 2673 | break; 2674 | end; 2675 | '}': 2676 | begin 2677 | found := true; 2678 | break; 2679 | end; 2680 | else 2681 | begin 2682 | inc(p); 2683 | end; 2684 | end; 2685 | end; 2686 | end; 2687 | end; 2688 | 2689 | if (found) then 2690 | begin 2691 | id := Trim( Copy(value, start + 1, p-start)); 2692 | if (leading <> 0) then 2693 | begin 2694 | value := Copy(value, 1, leading) + Trim( Copy( value, leading + 1, start -2)); 2695 | end 2696 | else 2697 | begin 2698 | value := Trim( Copy(value, leading +1, start -2)); 2699 | end; 2700 | trailing := 0; 2701 | if (Length(id) > 0) then 2702 | exit(id) 2703 | else 2704 | exit(''); 2705 | end; 2706 | exit(''); 2707 | end; 2708 | 2709 | function TLine.checkHTML: boolean; 2710 | var 2711 | tags: TStringList; 2712 | temp: TStringBuilder; 2713 | element, tag: String; 2714 | line: TLine; 2715 | newPos: integer; 2716 | begin 2717 | result := false; 2718 | tags := TStringList.Create(); 2719 | temp := TStringBuilder.Create(); 2720 | try 2721 | position := leading; 2722 | if (value.length >= 1 + leading + 1) and (value[1 + leading + 1] = '!') then 2723 | begin 2724 | if (readXMLComment(self, leading) > 0) then 2725 | begin 2726 | exit(true); 2727 | end; 2728 | end; 2729 | position := TUtils.readXML(temp, value, leading, false); 2730 | if (position > -1) then 2731 | begin 2732 | element := temp.ToString(); 2733 | temp.Clear; 2734 | TUtils.getXMLTag(temp, element); 2735 | tag := LowerCase(temp.ToString()); 2736 | if (not THTML.isHtmlBlockElement(tag)) then 2737 | exit(false); 2738 | // if (tag.equals('hr') or element.endsWith('/>')) then PSTFix 2739 | if (tag = 'hr') or AnsiEndsText('/>', element) then 2740 | 2741 | begin 2742 | xmlEndLine := self; 2743 | exit(true); 2744 | end; 2745 | tags.add(tag); 2746 | 2747 | line := self; 2748 | while (line <> nil) do 2749 | begin 2750 | while (position < Length(line.value)) and (line.value[1 + position] <> '<') do 2751 | inc(FPosition); 2752 | if (position >= Length(line.value)) then 2753 | begin 2754 | line := line.next; 2755 | position := 0; 2756 | end 2757 | else 2758 | begin 2759 | temp.Clear; 2760 | newPos := TUtils.readXML(temp, line.value, position, false); 2761 | if (newPos > 0) then 2762 | begin 2763 | element := temp.ToString(); 2764 | temp.Clear; 2765 | TUtils.getXMLTag(temp, element); 2766 | tag := LowerCase(temp.ToString()); 2767 | if (THTML.isHtmlBlockElement(tag)) and (tag <> 'hr') and (not AnsiEndsText('/>', element)) then 2768 | begin 2769 | if (element[1 + 1] = '/') then 2770 | begin 2771 | if (tags[tags.Count - 1] <> tag) then 2772 | exit(false); 2773 | tags.Delete(tags.count - 1); 2774 | end 2775 | else 2776 | tags.add(tag); 2777 | end; 2778 | if (tags.count = 0) then 2779 | begin 2780 | xmlEndLine := line; 2781 | break; 2782 | end; 2783 | position := newPos; 2784 | end 2785 | else 2786 | begin 2787 | inc(FPosition); 2788 | end; 2789 | end; 2790 | end; 2791 | result := tags.count = 0; 2792 | end; 2793 | finally 2794 | temp.Free; 2795 | tags.Free; 2796 | end; 2797 | end; 2798 | 2799 | { TLinkRef } 2800 | 2801 | constructor TLinkRef.Create(link, title: String; isAbbrev: boolean); 2802 | begin 2803 | inherited Create; 2804 | FLink := link; 2805 | FTitle := title; 2806 | FIsAbbrev := isAbbrev; 2807 | end; 2808 | 2809 | { TBlock } 2810 | 2811 | constructor TBlock.Create; 2812 | begin 2813 | inherited; 2814 | end; 2815 | 2816 | destructor TBlock.Destroy; 2817 | begin 2818 | FLines.Free; 2819 | FBlocks.Free; 2820 | FNext.free; 2821 | inherited; 2822 | end; 2823 | 2824 | procedure TBlock.AppendLine(line: TLine); 2825 | begin 2826 | if (self.lineTail = nil) then 2827 | begin 2828 | self.FLines := line; 2829 | self.FLineTail := line; 2830 | end 2831 | else 2832 | begin 2833 | self.lineTail.nextEmpty := line.isEmpty; 2834 | line.prevEmpty := self.lineTail.isEmpty; 2835 | line.previous := self.lineTail; 2836 | self.lineTail.next := line; 2837 | self.FLineTail := line; 2838 | end; 2839 | 2840 | end; 2841 | 2842 | procedure TBlock.expandListParagraphs; 2843 | var 2844 | outer: TBlock; 2845 | inner: TBlock; 2846 | hasParagraph: boolean; 2847 | begin 2848 | if (self.type_ <> btORDERED_LIST) and (self.type_ <> btUNORDERED_LIST) then 2849 | exit; 2850 | 2851 | outer := self.blocks; 2852 | hasParagraph := false; 2853 | while (outer <> nil) and (not hasParagraph) do 2854 | begin 2855 | if (outer.type_ = btLIST_ITEM) then 2856 | begin 2857 | inner := outer.blocks; 2858 | while (inner <> nil) and (not hasParagraph) do 2859 | begin 2860 | if (inner.type_ = btPARAGRAPH) then 2861 | begin 2862 | hasParagraph := true; 2863 | end; 2864 | inner := inner.next; 2865 | end; 2866 | end; 2867 | outer := outer.next; 2868 | end; 2869 | 2870 | if (hasParagraph) then 2871 | begin 2872 | outer := self.blocks; 2873 | while (outer <> nil) do 2874 | begin 2875 | if (outer.type_ = btLIST_ITEM) then 2876 | begin 2877 | inner := outer.blocks; 2878 | while (inner <> nil) do 2879 | begin 2880 | if (inner.type_ = btNONE) then 2881 | begin 2882 | inner.type_ := btPARAGRAPH; 2883 | end; 2884 | inner := inner.next; 2885 | end; 2886 | end; 2887 | outer := outer.next; 2888 | end; 2889 | end; 2890 | end; 2891 | 2892 | function TBlock.hasLines: boolean; 2893 | begin 2894 | result := lines <> nil; 2895 | end; 2896 | 2897 | procedure TBlock.removeLine(line: TLine); 2898 | begin 2899 | if (line.previous = nil) then 2900 | begin 2901 | self.FLines := line.next; 2902 | end 2903 | else 2904 | begin 2905 | line.previous.next := line.next; 2906 | end; 2907 | 2908 | if (line.next = nil) then 2909 | begin 2910 | self.FLineTail := line.previous; 2911 | end 2912 | else 2913 | begin 2914 | line.next.previous := line.previous; 2915 | end; 2916 | line.previous := nil; 2917 | 2918 | line.next := nil; 2919 | line.free; 2920 | end; 2921 | 2922 | procedure TBlock.removeBlockQuotePrefix; 2923 | var 2924 | line: TLine; 2925 | rem: integer; 2926 | begin 2927 | line := self.lines; 2928 | while (line <> nil) do 2929 | begin 2930 | if (not line.isEmpty) then 2931 | begin 2932 | if (line.value[1 + line.leading] = '>') then 2933 | begin 2934 | rem := line.leading + 1; 2935 | if (line.leading + 1 < Length(line.value)) and (line.value[1 + line.leading + 1] = ' ') then 2936 | begin 2937 | inc(rem); 2938 | end; 2939 | line.value := Copy(line.value, rem+1); 2940 | line.InitLeading(); 2941 | end; 2942 | end; 2943 | line := line.next; 2944 | end; 2945 | end; 2946 | 2947 | function TBlock.removeLeadingEmptyLines: boolean; 2948 | var 2949 | wasEmpty: boolean; 2950 | line: TLine; 2951 | begin 2952 | wasEmpty := false; 2953 | line := self.lines; 2954 | while (line <> nil) and (line.isEmpty) do 2955 | begin 2956 | self.removeLine(line); 2957 | line := self.lines; 2958 | wasEmpty := true; 2959 | end; 2960 | result := wasEmpty; 2961 | end; 2962 | 2963 | procedure TBlock.removeTrailingEmptyLines; 2964 | var 2965 | line: TLine; 2966 | begin 2967 | line := self.lineTail; 2968 | while (line <> nil) and (line.isEmpty) do 2969 | begin 2970 | self.removeLine(line); 2971 | line := self.lineTail; 2972 | end; 2973 | end; 2974 | 2975 | procedure TBlock.removeListIndent(config: TConfiguration); 2976 | var 2977 | line: TLine; 2978 | begin 2979 | line := self.lines; 2980 | while (line <> nil) do 2981 | begin 2982 | if (not line.isEmpty) then 2983 | begin 2984 | case (line.getLineType(config)) of 2985 | ltULIST: 2986 | line.value := Copy(line.value, line.leading +3); 2987 | ltOLIST: 2988 | line.value := Copy(line.value, pos('.', line.value) + 2); 2989 | ltBLIST: 2990 | line.value := Copy(line.value, pos(')', line.value) + 2); 2991 | else 2992 | // line.value := line.value.substring(Math.min(line.leading, 4)); pstfix 2993 | line.value := Copy(line.value, System.Math.Min(line.leading + 1, 5)); 2994 | end; 2995 | line.InitLeading(); 2996 | end; 2997 | line := line.next; 2998 | end; 2999 | 3000 | end; 3001 | 3002 | procedure TBlock.removeSurroundingEmptyLines; 3003 | begin 3004 | if (self.lines <> nil) then 3005 | begin 3006 | self.removeTrailingEmptyLines(); 3007 | self.removeLeadingEmptyLines(); 3008 | end; 3009 | 3010 | end; 3011 | 3012 | function TBlock.split(line: TLine): TBlock; 3013 | var 3014 | block: TBlock; 3015 | begin 3016 | block := TBlock.Create(); 3017 | block.FLines := self.lines; 3018 | block.FLineTail := line; 3019 | self.FLines := line.next; 3020 | line.next := nil; 3021 | if (self.lines = nil) then 3022 | begin 3023 | self.FLineTail := nil; 3024 | end 3025 | else 3026 | begin 3027 | self.lines.previous := nil; 3028 | end; 3029 | 3030 | if (self.blocks = nil) then 3031 | begin 3032 | self.FBlocks := block; 3033 | self.FBlockTail := block; 3034 | end 3035 | else 3036 | begin 3037 | self.blockTail.next := block; 3038 | self.FBlockTail := block; 3039 | end; 3040 | result := block; 3041 | end; 3042 | 3043 | procedure TBlock.transfromHeadline; 3044 | var 3045 | level, start, end_: integer; 3046 | line: TLine; 3047 | begin 3048 | if (self.hlDepth > 0) then 3049 | begin 3050 | exit; 3051 | end; 3052 | level := 0; 3053 | line := self.lines; 3054 | if (line.isEmpty) then 3055 | begin 3056 | exit; 3057 | end; 3058 | start := line.leading; 3059 | while (start < Length(line.value)) and (line.value[1 + start] = '#') do 3060 | begin 3061 | inc(level); 3062 | inc(start); 3063 | end; 3064 | while (start < Length(line.value)) and (line.value[1 + start] = ' ') do 3065 | begin 3066 | inc(start); 3067 | end; 3068 | if (start >= Length(line.value)) then 3069 | begin 3070 | line.setEmpty(); 3071 | end 3072 | else 3073 | begin 3074 | end_ := Length(line.value) - line.trailing - 1; 3075 | while (line.value[1 + end_] = '#') do 3076 | begin 3077 | dec(end_); 3078 | end; 3079 | while (line.value[1 + end_] = ' ') do 3080 | begin 3081 | dec(end_); 3082 | end; 3083 | if ((1 + end_) ' ') then end_:=Length(line.value); 3084 | line.value := Copy(line.value, start+1, end_-start+1); 3085 | line.leading := 0; 3086 | line.trailing := 0; 3087 | end; 3088 | self.hlDepth := System.Math.min(level, 6); 3089 | end; 3090 | 3091 | end. 3092 | --------------------------------------------------------------------------------