From ab4b402962a57999e5294bd39600f53d19a76012 Mon Sep 17 00:00:00 2001 From: Frans Bouma Date: Tue, 16 Feb 2016 11:46:08 +0100 Subject: [PATCH] Changes: Moved DocNet code to its own folder Added MarkdownDeep parser code plus tests Migrated DocNet code to use MarkdownDeep. Not complete yet (the h2 headers aren't collected yet). --- Docnet.sln | 14 +- Themes/Default/Destination/css/theme.css | 6 + src/{ => DocNet}/App.config | 0 src/{ => DocNet}/CliInput.cs | 0 src/{ => DocNet}/Config.cs | 0 src/{ => DocNet}/Docnet.csproj | 6 + src/{ => DocNet}/Engine.cs | 0 src/{ => DocNet}/INavigationElement.cs | 0 src/{ => DocNet}/MarkdownSharp.cs | 27 +- src/{ => DocNet}/NavigatedPath.cs | 0 src/{ => DocNet}/NavigationElement.cs | 0 src/{ => DocNet}/NavigationLevel.cs | 0 src/{ => DocNet}/Program.cs | 0 src/{ => DocNet}/Properties/AssemblyInfo.cs | 0 src/{ => DocNet}/SearchIndexEntry.cs | 0 src/{ => DocNet}/SimpleNavigationElement.cs | 0 src/{ => DocNet}/Utils.cs | 15 +- src/{ => DocNet}/packages.config | 0 src/MarkdownDeep/Abbreviation.cs | 32 + src/MarkdownDeep/Block.cs | 500 ++++++ src/MarkdownDeep/BlockProcessor.cs | 1565 +++++++++++++++++ src/MarkdownDeep/FootnoteReference.cs | 32 + src/MarkdownDeep/HtmlTag.cs | 351 ++++ src/MarkdownDeep/LinkDefinition.cs | 344 ++++ src/MarkdownDeep/LinkInfo.cs | 34 + src/MarkdownDeep/MardownDeep.cs | 1014 +++++++++++ src/MarkdownDeep/MarkdownDeep.csproj | 120 ++ src/MarkdownDeep/Properties/AssemblyInfo.cs | 37 + src/MarkdownDeep/SpanFormatter.cs | 1171 ++++++++++++ src/MarkdownDeep/StringScanner.cs | 541 ++++++ src/MarkdownDeep/TableSpec.cs | 219 +++ src/MarkdownDeep/Token.cs | 93 + src/MarkdownDeep/Utils.cs | 495 ++++++ src/MarkdownDeepTests/AutoHeaderIDTests.cs | 73 + src/MarkdownDeepTests/AutoLinkTests.cs | 44 + src/MarkdownDeepTests/BlockLevelTests.cs | 26 + src/MarkdownDeepTests/BlockProcessorTests.cs | 163 ++ src/MarkdownDeepTests/CodeSpanTests.cs | 59 + src/MarkdownDeepTests/EmphasisTests.cs | 240 +++ src/MarkdownDeepTests/EscapeCharacterTests.cs | 63 + src/MarkdownDeepTests/ExtraMode.cs | 26 + src/MarkdownDeepTests/GithubMode.cs | 26 + src/MarkdownDeepTests/HtmlTagTests.cs | 144 ++ src/MarkdownDeepTests/LinkAndImgTests.cs | 159 ++ src/MarkdownDeepTests/LinkDefinitionTests.cs | 108 ++ .../MarkdownDeepTests.csproj | 473 +++++ src/MarkdownDeepTests/MoreTestFiles.cs | 68 + .../Properties/AssemblyInfo.cs | 39 + src/MarkdownDeepTests/SafeModeTests.cs | 26 + src/MarkdownDeepTests/SpanLevelTests.cs | 26 + .../SpecialCharacterTests.cs | 81 + src/MarkdownDeepTests/StringScannerTests.cs | 43 + src/MarkdownDeepTests/TableSpecTests.cs | 85 + src/MarkdownDeepTests/Utils.cs | 145 ++ src/MarkdownDeepTests/XssAttackTests.cs | 136 ++ src/MarkdownDeepTests/app.config | 13 + src/MarkdownDeepTests/packages.config | 4 + .../testfiles/blocktests/AtxHeadings.html | 38 + .../testfiles/blocktests/AtxHeadings.txt | 39 + .../testfiles/blocktests/CodeBlocks.html | 12 + .../testfiles/blocktests/CodeBlocks.txt | 14 + .../blocktests/ComplexListItems.html | 27 + .../testfiles/blocktests/ComplexListItems.txt | 25 + .../blocktests/HardWrappedListItems.html | 19 + .../blocktests/HardWrappedListItems.txt | 15 + .../blocktests/HardWrappedParagraph.html | 4 + .../blocktests/HardWrappedParagraph.txt | 3 + .../HardWrappedParagraphInListItem.html | 10 + .../HardWrappedParagraphInListItem.txt | 8 + .../HardWrappedParagraphWithListLikeLine.html | 3 + .../HardWrappedParagraphWithListLikeLine.txt | 3 + .../blocktests/HtmlAttributeWithoutValue.html | 1 + .../blocktests/HtmlAttributeWithoutValue.txt | 1 + .../testfiles/blocktests/HtmlBlock.html | 18 + .../testfiles/blocktests/HtmlBlock.txt | 18 + .../testfiles/blocktests/HtmlComments.html | 10 + .../testfiles/blocktests/HtmlComments.txt | 9 + .../testfiles/blocktests/InsTypes.html | 8 + .../testfiles/blocktests/InsTypes.txt | 7 + .../blocktests/MultipleParagraphs.html | 2 + .../blocktests/MultipleParagraphs.txt | 3 + .../testfiles/blocktests/NestedListItems.html | 6 + .../testfiles/blocktests/NestedListItems.txt | 4 + .../testfiles/blocktests/ParagraphBreaks.html | 10 + .../testfiles/blocktests/ParagraphBreaks.txt | 8 + .../blocktests/PartiallyIndentedLists.html | 53 + .../blocktests/PartiallyIndentedLists.txt | 29 + .../testfiles/blocktests/QuoteBlocks.html | 34 + .../testfiles/blocktests/QuoteBlocks.txt | 36 + .../blocktests/QuoteBlocksNested.html | 16 + .../blocktests/QuoteBlocksNested.txt | 10 + .../testfiles/blocktests/SetExtHeadings.html | 5 + .../testfiles/blocktests/SetExtHeadings.txt | 1 + .../blocktests/SimpleOrderedList.html | 6 + .../blocktests/SimpleOrderedList.txt | 3 + .../testfiles/blocktests/SimpleParagraph.html | 1 + .../testfiles/blocktests/SimpleParagraph.txt | 1 + .../blocktests/SimpleUnorderedList.html | 5 + .../blocktests/SimpleUnorderedList.txt | 3 + .../extramode/Abbreviations(ExtraMode).html | 7 + .../extramode/Abbreviations(ExtraMode).txt | 17 + .../BackslashEscapes(ExtraMode).html | 22 + .../extramode/BackslashEscapes(ExtraMode).txt | 22 + .../extramode/DefinitionLists(ExtraMode).html | 57 + .../extramode/DefinitionLists(ExtraMode).txt | 54 + .../extramode/Emphasis(ExtraMode).html | 12 + .../extramode/Emphasis(ExtraMode).txt | 17 + .../FencedCodeBlocks(ExtraMode).html | 26 + .../extramode/FencedCodeBlocks(ExtraMode).txt | 28 + .../FencedCodeBlocksAlt(ExtraMode).html | 30 + .../FencedCodeBlocksAlt(ExtraMode).txt | 37 + .../extramode/Footnotes(ExtraMode).html | 30 + .../extramode/Footnotes(ExtraMode).txt | 27 + .../HeaderIDs(ExtraMode)(AutoHeadingIDs).html | 24 + .../HeaderIDs(ExtraMode)(AutoHeadingIDs).txt | 24 + .../extramode/HeaderIDs(ExtraMode).html | 13 + .../extramode/HeaderIDs(ExtraMode).txt | 14 + .../extramode/Issue12(ExtraMode).html | 8 + .../extramode/Issue12(ExtraMode).txt | 7 + .../extramode/Issue26(ExtraMode).html | 26 + .../extramode/Issue26(ExtraMode).txt | 9 + .../extramode/Issue30(ExtraMode).html | 1 + .../extramode/Issue30(ExtraMode).txt | 1 + .../extramode/MarkdownInHtml(ExtraMode).html | 19 + .../extramode/MarkdownInHtml(ExtraMode).txt | 19 + ...DeepNested(ExtraMode)(MarkdownInHtml).html | 16 + ...-DeepNested(ExtraMode)(MarkdownInHtml).txt | 16 + .../MarkdownInHtml-DeepNested(ExtraMode).html | 16 + .../MarkdownInHtml-DeepNested(ExtraMode).txt | 16 + .../MarkdownInHtml-Nested(ExtraMode).html | 16 + .../MarkdownInHtml-Nested(ExtraMode).txt | 17 + .../extramode/TableAlignment(ExtraMode).html | 22 + .../extramode/TableAlignment(ExtraMode).txt | 7 + .../extramode/TableFormatting(ExtraMode).html | 24 + .../extramode/TableFormatting(ExtraMode).txt | 9 + .../extramode/Tables(ExtraMode).html | 26 + .../testfiles/extramode/Tables(ExtraMode).txt | 9 + .../FencedCodeBlocksAlt(GitHubMode).html | 48 + .../FencedCodeBlocksAlt(GitHubMode).txt | 57 + .../testfiles/mdtest01/code-inside-list.html | 18 + .../testfiles/mdtest01/code-inside-list.text | 15 + .../testfiles/mdtest01/line-endings-cr.html | 40 + .../testfiles/mdtest01/line-endings-cr.text | 1 + .../testfiles/mdtest01/line-endings-crlf.html | 40 + .../testfiles/mdtest01/line-endings-crlf.text | 36 + .../testfiles/mdtest01/line-endings-lf.html | 40 + .../testfiles/mdtest01/line-endings-lf.text | 36 + .../testfiles/mdtest01/markdown-readme.html | 317 ++++ .../testfiles/mdtest01/markdown-readme.text | 341 ++++ .../mdtest11/Amps_and_angle_encoding.html | 17 + .../mdtest11/Amps_and_angle_encoding.text | 21 + .../testfiles/mdtest11/Auto_links.html | 18 + .../testfiles/mdtest11/Auto_links.text | 13 + .../testfiles/mdtest11/Backslash_escapes.html | 118 ++ .../testfiles/mdtest11/Backslash_escapes.text | 120 ++ .../Blockquotes_with_code_blocks.html | 15 + .../Blockquotes_with_code_blocks.text | 11 + .../testfiles/mdtest11/Code_Blocks.html | 18 + .../testfiles/mdtest11/Code_Blocks.text | 14 + .../testfiles/mdtest11/Code_Spans.html | 5 + .../testfiles/mdtest11/Code_Spans.text | 5 + ...apped_paragraphs_with_list_like_lines.html | 8 + ...apped_paragraphs_with_list_like_lines.text | 8 + .../testfiles/mdtest11/Horizontal_rules.html | 71 + .../testfiles/mdtest11/Horizontal_rules.text | 67 + .../testfiles/mdtest11/Images.html | 21 + .../testfiles/mdtest11/Images.text | 26 + .../mdtest11/Inline_HTML_Advanced.html | 30 + .../mdtest11/Inline_HTML_Advanced.text | 30 + .../mdtest11/Inline_HTML_Simple.html | 72 + .../mdtest11/Inline_HTML_Simple.text | 69 + .../mdtest11/Inline_HTML_comments.html | 13 + .../mdtest11/Inline_HTML_comments.text | 13 + .../mdtest11/Links_inline_style.html | 25 + .../mdtest11/Links_inline_style.text | 27 + .../mdtest11/Links_reference_style.html | 52 + .../mdtest11/Links_reference_style.text | 71 + .../mdtest11/Links_shortcut_references.html | 9 + .../mdtest11/Links_shortcut_references.text | 20 + .../mdtest11/Literal_quotes_in_titles.html | 3 + .../mdtest11/Literal_quotes_in_titles.text | 7 + .../Markdown_Documentation_Basics.html | 314 ++++ .../Markdown_Documentation_Basics.text | 306 ++++ .../Markdown_Documentation_Syntax.html | 942 ++++++++++ .../Markdown_Documentation_Syntax.text | 888 ++++++++++ .../mdtest11/Nested_blockquotes.html | 9 + .../mdtest11/Nested_blockquotes.text | 5 + .../mdtest11/Ordered_and_unordered_lists.html | 148 ++ .../mdtest11/Ordered_and_unordered_lists.text | 131 ++ .../mdtest11/Strong_and_em_together.html | 7 + .../mdtest11/Strong_and_em_together.text | 7 + .../testfiles/mdtest11/Tabs.html | 25 + .../testfiles/mdtest11/Tabs.text | 21 + .../testfiles/mdtest11/Tidyness.html | 8 + .../testfiles/mdtest11/Tidyness.text | 5 + .../pandoc/failure-to-escape-less-than.html | 1 + .../pandoc/failure-to-escape-less-than.text | 1 + .../pandoc/indented-code-in-list-item.html | 23 + .../pandoc/indented-code-in-list-item.text | 15 + .../testfiles/pandoc/nested-divs.html | 5 + .../testfiles/pandoc/nested-divs.text | 5 + .../testfiles/pandoc/nested-emphasis.html | 1 + .../testfiles/pandoc/nested-emphasis.text | 1 + .../unordered-list-and-horizontal-rules.html | 7 + .../unordered-list-and-horizontal-rules.text | 5 + ...ordered-list-followed-by-ordered-list.html | 9 + ...ordered-list-followed-by-ordered-list.text | 7 + .../pandoc/unpredictable-sublists.html | 20 + .../pandoc/unpredictable-sublists.text | 15 + .../phpmarkdown/Backslash escapes.html | 1 + .../phpmarkdown/Backslash escapes.text | 1 + .../testfiles/phpmarkdown/Code Spans.html | 6 + .../testfiles/phpmarkdown/Code Spans.text | 6 + .../Code block in a list item.html | 10 + .../Code block in a list item.text | 8 + .../phpmarkdown/Email auto links.html | 3 + .../phpmarkdown/Email auto links.text | 3 + .../testfiles/phpmarkdown/Emphasis.html | 72 + .../testfiles/phpmarkdown/Emphasis.text | 69 + .../testfiles/phpmarkdown/Headers.html | 39 + .../testfiles/phpmarkdown/Headers.text | 9 + .../phpmarkdown/Horizontal Rules.html | 30 + .../phpmarkdown/Horizontal Rules.text | 29 + .../phpmarkdown/Inline HTML (Simple).html | 15 + .../phpmarkdown/Inline HTML (Simple).text | 15 + .../phpmarkdown/Inline HTML (Span).html | 4 + .../phpmarkdown/Inline HTML (Span).text | 4 + .../phpmarkdown/Inline HTML comments.html | 9 + .../phpmarkdown/Inline HTML comments.text | 9 + .../testfiles/phpmarkdown/Ins & del.html | 17 + .../testfiles/phpmarkdown/Ins & del.text | 17 + .../phpmarkdown/Links, inline style.html | 1 + .../phpmarkdown/Links, inline style.text | 1 + .../testfiles/phpmarkdown/MD5 Hashes.html | 11 + .../testfiles/phpmarkdown/MD5 Hashes.text | 11 + .../testfiles/phpmarkdown/Nesting.html | 11 + .../testfiles/phpmarkdown/Nesting.text | 11 + .../phpmarkdown/PHP-Specific Bugs.html | 17 + .../phpmarkdown/PHP-Specific Bugs.text | 22 + .../testfiles/phpmarkdown/Parens in URL.html | 11 + .../testfiles/phpmarkdown/Parens in URL.text | 14 + .../testfiles/phpmarkdown/Tight blocks.html | 21 + .../testfiles/phpmarkdown/Tight blocks.text | 1 + .../testfiles/safemode/Basic(SafeMode).html | 16 + .../testfiles/safemode/Basic(SafeMode).txt | 23 + .../testfiles/spantests/BackslashEscapes.html | 61 + .../testfiles/spantests/BackslashEscapes.txt | 73 + .../testfiles/spantests/Emphasis.html | 19 + .../testfiles/spantests/Emphasis.txt | 16 + .../testfiles/spantests/EscapesInUrls.html | 2 + .../testfiles/spantests/EscapesInUrls.txt | 1 + .../ExplicitReferenceLinkWithTitle.html | 4 + .../ExplicitReferenceLinkWithTitle.txt | 6 + .../ExplicitReferenceLinkWithoutTitle.html | 1 + .../ExplicitReferenceLinkWithoutTitle.txt | 3 + .../spantests/FormattingInLinkText.html | 4 + .../spantests/FormattingInLinkText.txt | 3 + .../testfiles/spantests/HtmlEncodeLinks.html | 2 + .../testfiles/spantests/HtmlEncodeLinks.txt | 6 + .../ImplicitReferenceLinkWithTitle.html | 1 + .../ImplicitReferenceLinkWithTitle.txt | 3 + .../ImplicitReferenceLinkWithoutTitle.html | 1 + .../ImplicitReferenceLinkWithoutTitle.txt | 3 + .../spantests/InlineLinkWithTitle.html | 1 + .../spantests/InlineLinkWithTitle.txt | 1 + .../LinkTitlesWithEmbeddedQuotes.html | 1 + .../LinkTitlesWithEmbeddedQuotes.txt | 1 + .../testfiles/spantests/LinkedImage.html | 1 + .../testfiles/spantests/LinkedImage.txt | 1 + .../ReferenceLinkWithIDOnNextLine.html | 2 + .../ReferenceLinkWithIDOnNextLine.txt | 4 + .../testfiles/xsstests/non_attacks.txt | Bin 0 -> 1286 bytes .../testfiles/xsstests/xss_attacks.txt | Bin 0 -> 36928 bytes 273 files changed, 15845 insertions(+), 19 deletions(-) rename src/{ => DocNet}/App.config (100%) rename src/{ => DocNet}/CliInput.cs (100%) rename src/{ => DocNet}/Config.cs (100%) rename src/{ => DocNet}/Docnet.csproj (93%) rename src/{ => DocNet}/Engine.cs (100%) rename src/{ => DocNet}/INavigationElement.cs (100%) rename src/{ => DocNet}/MarkdownSharp.cs (99%) rename src/{ => DocNet}/NavigatedPath.cs (100%) rename src/{ => DocNet}/NavigationElement.cs (100%) rename src/{ => DocNet}/NavigationLevel.cs (100%) rename src/{ => DocNet}/Program.cs (100%) rename src/{ => DocNet}/Properties/AssemblyInfo.cs (100%) rename src/{ => DocNet}/SearchIndexEntry.cs (100%) rename src/{ => DocNet}/SimpleNavigationElement.cs (100%) rename src/{ => DocNet}/Utils.cs (94%) rename src/{ => DocNet}/packages.config (100%) create mode 100644 src/MarkdownDeep/Abbreviation.cs create mode 100644 src/MarkdownDeep/Block.cs create mode 100644 src/MarkdownDeep/BlockProcessor.cs create mode 100644 src/MarkdownDeep/FootnoteReference.cs create mode 100644 src/MarkdownDeep/HtmlTag.cs create mode 100644 src/MarkdownDeep/LinkDefinition.cs create mode 100644 src/MarkdownDeep/LinkInfo.cs create mode 100644 src/MarkdownDeep/MardownDeep.cs create mode 100644 src/MarkdownDeep/MarkdownDeep.csproj create mode 100644 src/MarkdownDeep/Properties/AssemblyInfo.cs create mode 100644 src/MarkdownDeep/SpanFormatter.cs create mode 100644 src/MarkdownDeep/StringScanner.cs create mode 100644 src/MarkdownDeep/TableSpec.cs create mode 100644 src/MarkdownDeep/Token.cs create mode 100644 src/MarkdownDeep/Utils.cs create mode 100644 src/MarkdownDeepTests/AutoHeaderIDTests.cs create mode 100644 src/MarkdownDeepTests/AutoLinkTests.cs create mode 100644 src/MarkdownDeepTests/BlockLevelTests.cs create mode 100644 src/MarkdownDeepTests/BlockProcessorTests.cs create mode 100644 src/MarkdownDeepTests/CodeSpanTests.cs create mode 100644 src/MarkdownDeepTests/EmphasisTests.cs create mode 100644 src/MarkdownDeepTests/EscapeCharacterTests.cs create mode 100644 src/MarkdownDeepTests/ExtraMode.cs create mode 100644 src/MarkdownDeepTests/GithubMode.cs create mode 100644 src/MarkdownDeepTests/HtmlTagTests.cs create mode 100644 src/MarkdownDeepTests/LinkAndImgTests.cs create mode 100644 src/MarkdownDeepTests/LinkDefinitionTests.cs create mode 100644 src/MarkdownDeepTests/MarkdownDeepTests.csproj create mode 100644 src/MarkdownDeepTests/MoreTestFiles.cs create mode 100644 src/MarkdownDeepTests/Properties/AssemblyInfo.cs create mode 100644 src/MarkdownDeepTests/SafeModeTests.cs create mode 100644 src/MarkdownDeepTests/SpanLevelTests.cs create mode 100644 src/MarkdownDeepTests/SpecialCharacterTests.cs create mode 100644 src/MarkdownDeepTests/StringScannerTests.cs create mode 100644 src/MarkdownDeepTests/TableSpecTests.cs create mode 100644 src/MarkdownDeepTests/Utils.cs create mode 100644 src/MarkdownDeepTests/XssAttackTests.cs create mode 100644 src/MarkdownDeepTests/app.config create mode 100644 src/MarkdownDeepTests/packages.config create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/AtxHeadings.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/AtxHeadings.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/CodeBlocks.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/CodeBlocks.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/ComplexListItems.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/ComplexListItems.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedListItems.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedListItems.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedParagraph.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedParagraph.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedParagraphInListItem.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedParagraphInListItem.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedParagraphWithListLikeLine.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HardWrappedParagraphWithListLikeLine.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HtmlAttributeWithoutValue.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HtmlAttributeWithoutValue.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HtmlBlock.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HtmlBlock.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HtmlComments.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/HtmlComments.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/InsTypes.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/InsTypes.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/MultipleParagraphs.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/MultipleParagraphs.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/NestedListItems.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/NestedListItems.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/ParagraphBreaks.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/ParagraphBreaks.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/PartiallyIndentedLists.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/PartiallyIndentedLists.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/QuoteBlocks.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/QuoteBlocks.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/QuoteBlocksNested.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/QuoteBlocksNested.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SetExtHeadings.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SetExtHeadings.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SimpleOrderedList.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SimpleOrderedList.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SimpleParagraph.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SimpleParagraph.txt create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SimpleUnorderedList.html create mode 100644 src/MarkdownDeepTests/testfiles/blocktests/SimpleUnorderedList.txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Abbreviations(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Abbreviations(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/BackslashEscapes(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/BackslashEscapes(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/DefinitionLists(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/DefinitionLists(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Emphasis(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Emphasis(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/FencedCodeBlocks(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/FencedCodeBlocks(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/FencedCodeBlocksAlt(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/FencedCodeBlocksAlt(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Footnotes(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Footnotes(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/HeaderIDs(ExtraMode)(AutoHeadingIDs).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/HeaderIDs(ExtraMode)(AutoHeadingIDs).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/HeaderIDs(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/HeaderIDs(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Issue12(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Issue12(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Issue26(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Issue26(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Issue30(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Issue30(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml-DeepNested(ExtraMode)(MarkdownInHtml).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml-DeepNested(ExtraMode)(MarkdownInHtml).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml-DeepNested(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml-DeepNested(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml-Nested(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/MarkdownInHtml-Nested(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/TableAlignment(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/TableAlignment(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/TableFormatting(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/TableFormatting(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Tables(ExtraMode).html create mode 100644 src/MarkdownDeepTests/testfiles/extramode/Tables(ExtraMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/githubmode/FencedCodeBlocksAlt(GitHubMode).html create mode 100644 src/MarkdownDeepTests/testfiles/githubmode/FencedCodeBlocksAlt(GitHubMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/code-inside-list.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/code-inside-list.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/line-endings-cr.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/line-endings-cr.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/line-endings-crlf.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/line-endings-crlf.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/line-endings-lf.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/line-endings-lf.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/markdown-readme.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest01/markdown-readme.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Amps_and_angle_encoding.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Amps_and_angle_encoding.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Auto_links.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Auto_links.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Backslash_escapes.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Backslash_escapes.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Blockquotes_with_code_blocks.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Blockquotes_with_code_blocks.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Code_Blocks.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Code_Blocks.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Code_Spans.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Code_Spans.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Hard_wrapped_paragraphs_with_list_like_lines.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Hard_wrapped_paragraphs_with_list_like_lines.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Horizontal_rules.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Horizontal_rules.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Images.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Images.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Inline_HTML_Advanced.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Inline_HTML_Advanced.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Inline_HTML_Simple.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Inline_HTML_Simple.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Inline_HTML_comments.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Inline_HTML_comments.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Links_inline_style.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Links_inline_style.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Links_reference_style.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Links_reference_style.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Links_shortcut_references.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Links_shortcut_references.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Literal_quotes_in_titles.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Literal_quotes_in_titles.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Markdown_Documentation_Basics.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Markdown_Documentation_Basics.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Markdown_Documentation_Syntax.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Markdown_Documentation_Syntax.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Nested_blockquotes.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Nested_blockquotes.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Ordered_and_unordered_lists.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Ordered_and_unordered_lists.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Strong_and_em_together.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Strong_and_em_together.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Tabs.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Tabs.text create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Tidyness.html create mode 100644 src/MarkdownDeepTests/testfiles/mdtest11/Tidyness.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/failure-to-escape-less-than.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/failure-to-escape-less-than.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/indented-code-in-list-item.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/indented-code-in-list-item.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/nested-divs.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/nested-divs.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/nested-emphasis.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/nested-emphasis.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/unordered-list-and-horizontal-rules.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/unordered-list-and-horizontal-rules.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/unordered-list-followed-by-ordered-list.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/unordered-list-followed-by-ordered-list.text create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/unpredictable-sublists.html create mode 100644 src/MarkdownDeepTests/testfiles/pandoc/unpredictable-sublists.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Backslash escapes.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Backslash escapes.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Code Spans.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Code Spans.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Code block in a list item.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Code block in a list item.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Email auto links.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Email auto links.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Emphasis.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Emphasis.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Headers.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Headers.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Horizontal Rules.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Horizontal Rules.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Inline HTML (Simple).html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Inline HTML (Simple).text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Inline HTML (Span).html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Inline HTML (Span).text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Inline HTML comments.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Inline HTML comments.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Ins & del.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Ins & del.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Links, inline style.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Links, inline style.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/MD5 Hashes.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/MD5 Hashes.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Nesting.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Nesting.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/PHP-Specific Bugs.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/PHP-Specific Bugs.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Parens in URL.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Parens in URL.text create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Tight blocks.html create mode 100644 src/MarkdownDeepTests/testfiles/phpmarkdown/Tight blocks.text create mode 100644 src/MarkdownDeepTests/testfiles/safemode/Basic(SafeMode).html create mode 100644 src/MarkdownDeepTests/testfiles/safemode/Basic(SafeMode).txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/BackslashEscapes.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/BackslashEscapes.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/Emphasis.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/Emphasis.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/EscapesInUrls.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/EscapesInUrls.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ExplicitReferenceLinkWithTitle.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ExplicitReferenceLinkWithTitle.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ExplicitReferenceLinkWithoutTitle.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ExplicitReferenceLinkWithoutTitle.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/FormattingInLinkText.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/FormattingInLinkText.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/HtmlEncodeLinks.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/HtmlEncodeLinks.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ImplicitReferenceLinkWithTitle.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ImplicitReferenceLinkWithTitle.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ImplicitReferenceLinkWithoutTitle.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ImplicitReferenceLinkWithoutTitle.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/InlineLinkWithTitle.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/InlineLinkWithTitle.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/LinkTitlesWithEmbeddedQuotes.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/LinkTitlesWithEmbeddedQuotes.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/LinkedImage.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/LinkedImage.txt create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ReferenceLinkWithIDOnNextLine.html create mode 100644 src/MarkdownDeepTests/testfiles/spantests/ReferenceLinkWithIDOnNextLine.txt create mode 100644 src/MarkdownDeepTests/testfiles/xsstests/non_attacks.txt create mode 100644 src/MarkdownDeepTests/testfiles/xsstests/xss_attacks.txt diff --git a/Docnet.sln b/Docnet.sln index 4d15fb3..8b375af 100644 --- a/Docnet.sln +++ b/Docnet.sln @@ -3,7 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docnet", "src\Docnet.csproj", "{48CA9947-AF13-459E-9D59-FC451B5C19D7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docnet", "src\DocNet\Docnet.csproj", "{48CA9947-AF13-459E-9D59-FC451B5C19D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeep", "src\MarkdownDeep\MarkdownDeep.csproj", "{1569ED47-C7C9-4261-B6F4-7445BD0F2C95}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeepTests", "src\MarkdownDeepTests\MarkdownDeepTests.csproj", "{CD1F5BFF-0118-4994-86A2-92658A36CE1B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +19,14 @@ Global {48CA9947-AF13-459E-9D59-FC451B5C19D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {48CA9947-AF13-459E-9D59-FC451B5C19D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {48CA9947-AF13-459E-9D59-FC451B5C19D7}.Release|Any CPU.Build.0 = Release|Any CPU + {1569ED47-C7C9-4261-B6F4-7445BD0F2C95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1569ED47-C7C9-4261-B6F4-7445BD0F2C95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1569ED47-C7C9-4261-B6F4-7445BD0F2C95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1569ED47-C7C9-4261-B6F4-7445BD0F2C95}.Release|Any CPU.Build.0 = Release|Any CPU + {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Themes/Default/Destination/css/theme.css b/Themes/Default/Destination/css/theme.css index 5f9e7b9..35bb121 100644 --- a/Themes/Default/Destination/css/theme.css +++ b/Themes/Default/Destination/css/theme.css @@ -253,18 +253,22 @@ h1, h2, h3, h4, h5, h6 { h1 { font-size: 220%; + margin-bottom:25px; } h2 { font-size: 190%; + margin-bottom:18px; } h3 { font-size: 155%; + margin-bottom:15px; } h4 { font-size: 130%; + margin-bottom: 13px; } h4.searchresulttitle { @@ -273,10 +277,12 @@ h4.searchresulttitle { h5 { font-size: 115%; + margin-bottom: 10px; } h6 { font-size: 105%; + margin-bottom: 7px; } hr { diff --git a/src/App.config b/src/DocNet/App.config similarity index 100% rename from src/App.config rename to src/DocNet/App.config diff --git a/src/CliInput.cs b/src/DocNet/CliInput.cs similarity index 100% rename from src/CliInput.cs rename to src/DocNet/CliInput.cs diff --git a/src/Config.cs b/src/DocNet/Config.cs similarity index 100% rename from src/Config.cs rename to src/DocNet/Config.cs diff --git a/src/Docnet.csproj b/src/DocNet/Docnet.csproj similarity index 93% rename from src/Docnet.csproj rename to src/DocNet/Docnet.csproj index 951ac3e..0a61731 100644 --- a/src/Docnet.csproj +++ b/src/DocNet/Docnet.csproj @@ -67,6 +67,12 @@ + + + {1569ed47-c7c9-4261-b6f4-7445bd0f2c95} + MarkdownDeep + + ) + ContentAsSpan = 0x0008, // When markdown=1 treat content as span, not block + }; + + public class HtmlTag + { + public HtmlTag(string name) + { + m_name = name; + } + + // Get the tag name eg: "div" + public string name + { + get { return m_name; } + } + + // Get a dictionary of attribute values (no decoding done) + public Dictionary attributes + { + get { return m_attributes; } + } + + // Is this tag closed eg;
+ public bool closed + { + get { return m_closed; } + set { m_closed = value; } + } + + // Is this a closing tag eg: + public bool closing + { + get { return m_closing; } + } + + string m_name; + Dictionary m_attributes = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + bool m_closed; + bool m_closing; + HtmlTagFlags m_flags = 0; + + public HtmlTagFlags Flags + { + get + { + if (m_flags == 0) + { + if (!m_tag_flags.TryGetValue(name.ToLower(), out m_flags)) + { + m_flags |= HtmlTagFlags.Inline; + } + } + + return m_flags; + } + } + + static string[] m_allowed_tags = new string [] { + "b","blockquote","code","dd","dt","dl","del","em","h1","h2","h3","h4","h5","h6","i","kbd","li","ol","ul", + "p", "pre", "s", "sub", "sup", "strong", "strike", "img", "a" + }; + + static Dictionary m_allowed_attributes = new Dictionary() { + { "a", new string[] { "href", "title", "class" } }, + { "img", new string[] { "src", "width", "height", "alt", "title", "class" } }, + }; + + static Dictionary m_tag_flags = new Dictionary() { + { "p", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan }, + { "div", HtmlTagFlags.Block }, + { "h1", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan }, + { "h2", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan}, + { "h3", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan}, + { "h4", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan}, + { "h5", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan}, + { "h6", HtmlTagFlags.Block | HtmlTagFlags.ContentAsSpan}, + { "blockquote", HtmlTagFlags.Block }, + { "pre", HtmlTagFlags.Block }, + { "table", HtmlTagFlags.Block }, + { "dl", HtmlTagFlags.Block }, + { "ol", HtmlTagFlags.Block }, + { "ul", HtmlTagFlags.Block }, + { "form", HtmlTagFlags.Block }, + { "fieldset", HtmlTagFlags.Block }, + { "iframe", HtmlTagFlags.Block }, + { "script", HtmlTagFlags.Block | HtmlTagFlags.Inline }, + { "noscript", HtmlTagFlags.Block | HtmlTagFlags.Inline }, + { "math", HtmlTagFlags.Block | HtmlTagFlags.Inline }, + { "ins", HtmlTagFlags.Block | HtmlTagFlags.Inline }, + { "del", HtmlTagFlags.Block | HtmlTagFlags.Inline }, + { "img", HtmlTagFlags.Block | HtmlTagFlags.Inline }, + { "li", HtmlTagFlags.ContentAsSpan}, + { "dd", HtmlTagFlags.ContentAsSpan}, + { "dt", HtmlTagFlags.ContentAsSpan}, + { "td", HtmlTagFlags.ContentAsSpan}, + { "th", HtmlTagFlags.ContentAsSpan}, + { "legend", HtmlTagFlags.ContentAsSpan}, + { "address", HtmlTagFlags.ContentAsSpan}, + { "hr", HtmlTagFlags.Block | HtmlTagFlags.NoClosing}, + { "!", HtmlTagFlags.Block | HtmlTagFlags.NoClosing}, + { "head", HtmlTagFlags.Block }, + }; + + // Check if this tag is safe + public bool IsSafe() + { + string name_lower=m_name.ToLowerInvariant(); + + // Check if tag is in whitelist + if (!Utils.IsInList(name_lower, m_allowed_tags)) + return false; + + // Find allowed attributes + string[] allowed_attributes; + if (!m_allowed_attributes.TryGetValue(name_lower, out allowed_attributes)) + { + // No allowed attributes, check we don't have any + return m_attributes.Count == 0; + } + + // Check all are allowed + foreach (var i in m_attributes) + { + if (!Utils.IsInList(i.Key.ToLowerInvariant(), allowed_attributes)) + return false; + } + + // Check href attribute is ok + string href; + if (m_attributes.TryGetValue("href", out href)) + { + if (!Utils.IsSafeUrl(href)) + return false; + } + + string src; + if (m_attributes.TryGetValue("src", out src)) + { + if (!Utils.IsSafeUrl(src)) + return false; + } + + + // Passed all white list checks, allow it + return true; + } + + // Render opening tag (eg: + public void RenderOpening(StringBuilder dest) + { + dest.Append("<"); + dest.Append(name); + foreach (var i in m_attributes) + { + dest.Append(" "); + dest.Append(i.Key); + dest.Append("=\""); + dest.Append(i.Value); + dest.Append("\""); + } + + if (m_closed) + dest.Append(" />"); + else + dest.Append(">"); + } + + // Render closing tag (eg: ) + public void RenderClosing(StringBuilder dest) + { + dest.Append(""); + } + + + public static HtmlTag Parse(string str, ref int pos) + { + StringScanner sp = new StringScanner(str, pos); + var ret = Parse(sp); + + if (ret!=null) + { + pos = sp.position; + return ret; + } + + return null; + } + + public static HtmlTag Parse(StringScanner p) + { + // Save position + int savepos = p.position; + + // Parse it + var ret = ParseHelper(p); + if (ret!=null) + return ret; + + // Rewind if failed + p.position = savepos; + return null; + } + + private static HtmlTag ParseHelper(StringScanner p) + { + // Does it look like a tag? + if (p.current != '<') + return null; + + // Skip '<' + p.SkipForward(1); + + // Is it a comment? + if (p.SkipString("!--")) + { + p.Mark(); + + if (p.Find("-->")) + { + var t = new HtmlTag("!"); + t.m_attributes.Add("content", p.Extract()); + t.m_closed = true; + p.SkipForward(3); + return t; + } + } + + // Is it a closing tag eg: + bool bClosing = p.SkipChar('/'); + + // Get the tag name + string tagName=null; + if (!p.SkipIdentifier(ref tagName)) + return null; + + // Probably a tag, create the HtmlTag object now + HtmlTag tag = new HtmlTag(tagName); + tag.m_closing = bClosing; + + + // If it's a closing tag, no attributes + if (bClosing) + { + if (p.current != '>') + return null; + + p.SkipForward(1); + return tag; + } + + + while (!p.eof) + { + // Skip whitespace + p.SkipWhitespace(); + + // Check for closed tag eg:
+ if (p.SkipString("/>")) + { + tag.m_closed=true; + return tag; + } + + // End of tag? + if (p.SkipChar('>')) + { + return tag; + } + + // attribute name + string attributeName = null; + if (!p.SkipIdentifier(ref attributeName)) + return null; + + // Skip whitespace + p.SkipWhitespace(); + + // Skip equal sign + if (p.SkipChar('=')) + { + // Skip whitespace + p.SkipWhitespace(); + + // Optional quotes + if (p.SkipChar('\"')) + { + // Scan the value + p.Mark(); + if (!p.Find('\"')) + return null; + + // Store the value + tag.m_attributes.Add(attributeName, p.Extract()); + + // Skip closing quote + p.SkipForward(1); + } + else + { + // Scan the value + p.Mark(); + while (!p.eof && !char.IsWhiteSpace(p.current) && p.current != '>' && p.current != '/') + p.SkipForward(1); + + if (!p.eof) + { + // Store the value + tag.m_attributes.Add(attributeName, p.Extract()); + } + } + } + else + { + tag.m_attributes.Add(attributeName, ""); + } + } + + return null; + } + + } +} diff --git a/src/MarkdownDeep/LinkDefinition.cs b/src/MarkdownDeep/LinkDefinition.cs new file mode 100644 index 0000000..b60d51e --- /dev/null +++ b/src/MarkdownDeep/LinkDefinition.cs @@ -0,0 +1,344 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + public class LinkDefinition + { + public LinkDefinition(string id) + { + this.id= id; + } + + public LinkDefinition(string id, string url) + { + this.id = id; + this.url = url; + } + + public LinkDefinition(string id, string url, string title) + { + this.id = id; + this.url = url; + this.title = title; + } + + public string id + { + get; + set; + } + + public string url + { + get; + set; + } + + public string title + { + get; + set; + } + + + internal void RenderLink(Markdown m, StringBuilder b, string link_text) + { + if (url.StartsWith("mailto:")) + { + b.Append("'); + Utils.HtmlRandomize(b, link_text); + b.Append(""); + } + else + { + HtmlTag tag = new HtmlTag("a"); + + // encode url + StringBuilder sb = m.GetStringBuilder(); + Utils.SmartHtmlEncodeAmpsAndAngles(sb, url); + tag.attributes["href"] = sb.ToString(); + + // encode title + if (!String.IsNullOrEmpty(title )) + { + sb.Length = 0; + Utils.SmartHtmlEncodeAmpsAndAngles(sb, title); + tag.attributes["title"] = sb.ToString(); + } + + // Do user processing + m.OnPrepareLink(tag); + + // Render the opening tag + tag.RenderOpening(b); + + b.Append(link_text); // Link text already escaped by SpanFormatter + b.Append(""); + } + } + + internal void RenderImg(Markdown m, StringBuilder b, string alt_text) + { + HtmlTag tag = new HtmlTag("img"); + + // encode url + StringBuilder sb = m.GetStringBuilder(); + Utils.SmartHtmlEncodeAmpsAndAngles(sb, url); + tag.attributes["src"] = sb.ToString(); + + // encode alt text + if (!String.IsNullOrEmpty(alt_text)) + { + sb.Length = 0; + Utils.SmartHtmlEncodeAmpsAndAngles(sb, alt_text); + tag.attributes["alt"] = sb.ToString(); + } + + // encode title + if (!String.IsNullOrEmpty(title)) + { + sb.Length = 0; + Utils.SmartHtmlEncodeAmpsAndAngles(sb, title); + tag.attributes["title"] = sb.ToString(); + } + + tag.closed = true; + + m.OnPrepareImage(tag, m.RenderingTitledImage); + + tag.RenderOpening(b); + } + + + // Parse a link definition from a string (used by test cases) + internal static LinkDefinition ParseLinkDefinition(string str, bool ExtraMode) + { + StringScanner p = new StringScanner(str); + return ParseLinkDefinitionInternal(p, ExtraMode); + } + + // Parse a link definition + internal static LinkDefinition ParseLinkDefinition(StringScanner p, bool ExtraMode) + { + int savepos=p.position; + var l = ParseLinkDefinitionInternal(p, ExtraMode); + if (l==null) + p.position = savepos; + return l; + + } + + internal static LinkDefinition ParseLinkDefinitionInternal(StringScanner p, bool ExtraMode) + { + // Skip leading white space + p.SkipWhitespace(); + + // Must start with an opening square bracket + if (!p.SkipChar('[')) + return null; + + // Extract the id + p.Mark(); + if (!p.Find(']')) + return null; + string id = p.Extract(); + if (id.Length == 0) + return null; + if (!p.SkipString("]:")) + return null; + + // Parse the url and title + var link=ParseLinkTarget(p, id, ExtraMode); + + // and trailing whitespace + p.SkipLinespace(); + + // Trailing crap, not a valid link reference... + if (!p.eol) + return null; + + return link; + } + + // Parse just the link target + // For reference link definition, this is the bit after "[id]: thisbit" + // For inline link, this is the bit in the parens: [link text](thisbit) + internal static LinkDefinition ParseLinkTarget(StringScanner p, string id, bool ExtraMode) + { + // Skip whitespace + p.SkipWhitespace(); + + // End of string? + if (p.eol) + return null; + + // Create the link definition + var r = new LinkDefinition(id); + + // Is the url enclosed in angle brackets + if (p.SkipChar('<')) + { + // Extract the url + p.Mark(); + + // Find end of the url + while (p.current != '>') + { + if (p.eof) + return null; + p.SkipEscapableChar(ExtraMode); + } + + string url = p.Extract(); + if (!p.SkipChar('>')) + return null; + + // Unescape it + r.url = Utils.UnescapeString(url.Trim(), ExtraMode); + + // Skip whitespace + p.SkipWhitespace(); + } + else + { + // Find end of the url + p.Mark(); + int paren_depth = 1; + while (!p.eol) + { + char ch=p.current; + if (char.IsWhiteSpace(ch)) + break; + if (id == null) + { + if (ch == '(') + paren_depth++; + else if (ch == ')') + { + paren_depth--; + if (paren_depth==0) + break; + } + } + + p.SkipEscapableChar(ExtraMode); + } + + r.url = Utils.UnescapeString(p.Extract().Trim(), ExtraMode); + } + + p.SkipLinespace(); + + // End of inline target + if (p.DoesMatch(')')) + return r; + + bool bOnNewLine = p.eol; + int posLineEnd = p.position; + if (p.eol) + { + p.SkipEol(); + p.SkipLinespace(); + } + + // Work out what the title is delimited with + char delim; + switch (p.current) + { + case '\'': + case '\"': + delim = p.current; + break; + + case '(': + delim = ')'; + break; + + default: + if (bOnNewLine) + { + p.position = posLineEnd; + return r; + } + else + return null; + } + + // Skip the opening title delimiter + p.SkipForward(1); + + // Find the end of the title + p.Mark(); + while (true) + { + if (p.eol) + return null; + + if (p.current == delim) + { + + if (delim != ')') + { + int savepos = p.position; + + // Check for embedded quotes in title + + // Skip the quote and any trailing whitespace + p.SkipForward(1); + p.SkipLinespace(); + + // Next we expect either the end of the line for a link definition + // or the close bracket for an inline link + if ((id == null && p.current != ')') || + (id != null && !p.eol)) + { + continue; + } + + p.position = savepos; + } + + // End of title + break; + } + + p.SkipEscapableChar(ExtraMode); + } + + // Store the title + r.title = Utils.UnescapeString(p.Extract(), ExtraMode); + + // Skip closing quote + p.SkipForward(1); + + // Done! + return r; + } + } +} diff --git a/src/MarkdownDeep/LinkInfo.cs b/src/MarkdownDeep/LinkInfo.cs new file mode 100644 index 0000000..a2acae5 --- /dev/null +++ b/src/MarkdownDeep/LinkInfo.cs @@ -0,0 +1,34 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + internal class LinkInfo + { + public LinkInfo(LinkDefinition def, string link_text) + { + this.def = def; + this.link_text = link_text; + } + + public LinkDefinition def; + public string link_text; + } + +} diff --git a/src/MarkdownDeep/MardownDeep.cs b/src/MarkdownDeep/MardownDeep.cs new file mode 100644 index 0000000..8de41d3 --- /dev/null +++ b/src/MarkdownDeep/MardownDeep.cs @@ -0,0 +1,1014 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + + public class ImageInfo + { + public string url; + public bool titled_image; + public int width; + public int height; + } + + + public class Markdown + { + // Constructor + public Markdown() + { + HtmlClassFootnotes = "footnotes"; + m_StringBuilder = new StringBuilder(); + m_StringBuilderFinal = new StringBuilder(); + m_StringScanner = new StringScanner(); + m_SpanFormatter = new SpanFormatter(this); + m_LinkDefinitions = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + m_Footnotes = new Dictionary(); + m_UsedFootnotes = new List(); + m_UsedHeaderIDs = new Dictionary(); + } + + internal List ProcessBlocks(string str) + { + // Reset the list of link definitions + m_LinkDefinitions.Clear(); + m_Footnotes.Clear(); + m_UsedFootnotes.Clear(); + m_UsedHeaderIDs.Clear(); + m_AbbreviationMap = null; + m_AbbreviationList = null; + + // Process blocks + return new BlockProcessor(this, MarkdownInHtml).Process(str); + } + public string Transform(string str) + { + Dictionary defs; + return Transform(str, out defs); + } + + // Transform a string + public string Transform(string str, out Dictionary definitions) + { + // Build blocks + var blocks = ProcessBlocks(str); + + // Sort abbreviations by length, longest to shortest + if (m_AbbreviationMap != null) + { + m_AbbreviationList = new List(); + m_AbbreviationList.AddRange(m_AbbreviationMap.Values); + m_AbbreviationList.Sort( + delegate(Abbreviation a, Abbreviation b) + { + return b.Abbr.Length - a.Abbr.Length; + } + ); + } + + // Setup string builder + StringBuilder sb = m_StringBuilderFinal; + sb.Length = 0; + + if (SummaryLength != 0) + { + // Render all blocks + for (int i = 0; i < blocks.Count; i++) + { + var b = blocks[i]; + b.RenderPlain(this, sb); + + if (SummaryLength>0 && sb.Length > SummaryLength) + break; + } + + } + else + { + int iSection = -1; + + // Leading section (ie: plain text before first heading) + if (blocks.Count > 0 && !IsSectionHeader(blocks[0])) + { + iSection = 0; + OnSectionHeader(sb, 0); + OnSectionHeadingSuffix(sb, 0); + } + + // Render all blocks + for (int i = 0; i < blocks.Count; i++) + { + var b = blocks[i]; + + // New section? + if (IsSectionHeader(b)) + { + // Finish the previous section + if (iSection >= 0) + { + OnSectionFooter(sb, iSection); + } + + // Work out next section index + iSection = iSection < 0 ? 1 : iSection + 1; + + // Section header + OnSectionHeader(sb, iSection); + + // Section Heading + b.Render(this, sb); + + // Section Heading suffix + OnSectionHeadingSuffix(sb, iSection); + } + else + { + // Regular section + b.Render(this, sb); + } + } + + // Finish final section + if (blocks.Count > 0) + OnSectionFooter(sb, iSection); + + // Render footnotes + if (m_UsedFootnotes.Count > 0) + { + sb.Append("\n
\n"); + sb.Append("
\n"); + sb.Append("
    \n"); + for (int i = 0; i < m_UsedFootnotes.Count; i++) + { + var fn = m_UsedFootnotes[i]; + + sb.Append("
  1. \n"); + + + // We need to get the return link appended to the last paragraph + // in the footnote + string strReturnLink = string.Format("", (string)fn.data); + + // Get the last child of the footnote + var child = fn.children[fn.children.Count - 1]; + if (child.blockType == BlockType.p) + { + child.blockType = BlockType.p_footnote; + child.data = strReturnLink; + } + else + { + child = CreateBlock(); + child.contentLen = 0; + child.blockType = BlockType.p_footnote; + child.data = strReturnLink; + fn.children.Add(child); + } + + + fn.Render(this, sb); + + sb.Append("
  2. \n"); + } + sb.Append("
\n"); + sb.Append("
\n"); + } + } + + definitions = m_LinkDefinitions; + + // Done + return sb.ToString(); + } + + public int SummaryLength + { + get; + set; + } + + + /// + /// Set to true to enable GitHub style codeblocks, which enables GitHub style codeblocks: + /// ```cs + /// code + /// ``` + /// will result in specifying the specified name after the first ``` as the class in the code element. Which can then be used with highlight.js. + /// + public bool GitHubCodeBlocks { get; set; } + + // Set to true to only allow whitelisted safe html tags + public bool SafeMode + { + get; + set; + } + + // Set to true to enable ExtraMode, which enables the same set of + // features as implemented by PHP Markdown Extra. + // - Markdown in html (eg:
or
) + // - Header ID attributes + // - Fenced code blocks + // - Definition lists + // - Footnotes + // - Abbreviations + // - Simple tables + public bool ExtraMode + { + get; + set; + } + + // When set, all html block level elements automatically support + // markdown syntax within them. + // (Similar to Pandoc's handling of markdown in html) + public bool MarkdownInHtml + { + get; + set; + } + + // When set, all headings will have an auto generated ID attribute + // based on the heading text (uses the same algorithm as Pandoc) + public bool AutoHeadingIDs + { + get; + set; + } + + // When set, all non-qualified urls (links and images) will + // be qualified using this location as the base. + // Useful when rendering RSS feeds that require fully qualified urls. + public string UrlBaseLocation + { + get; + set; + } + + // When set, all non-qualified urls (links and images) begining with a slash + // will qualified by prefixing with this string. + // Useful when rendering RSS feeds that require fully qualified urls. + public string UrlRootLocation + { + get; + set; + } + + // When true, all fully qualified urls will be give `target="_blank"' attribute + // causing them to appear in a separate browser window/tab + // ie: relative links open in same window, qualified links open externally + public bool NewWindowForExternalLinks + { + get; + set; + } + + // When true, all urls (qualified or not) will get target="_blank" attribute + // (useful for preview mode on posts) + public bool NewWindowForLocalLinks + { + get; + set; + } + + // When set, will try to determine the width/height for local images by searching + // for an appropriately named file relative to the specified location + // Local file system location of the document root. Used to locate image + // files that start with slash. + // Typical value: c:\inetpub\www\wwwroot + public string DocumentRoot + { + get; + set; + } + + // Local file system location of the current document. Used to locate relative + // path images for image size. + // Typical value: c:\inetpub\www\wwwroot\subfolder + public string DocumentLocation + { + get; + set; + } + + // Limit the width of images (0 for no limit) + public int MaxImageWidth + { + get; + set; + } + + // Set rel="nofollow" on all links + public bool NoFollowLinks + { + get; + set; + } + + /// + /// Add the NoFollow attribute to all external links. + /// + public bool NoFollowExternalLinks + { + get; + set; + } + + + + public Func QualifyUrl; + + // Override to qualify non-local image and link urls + public virtual string OnQualifyUrl(string url) + { + if (QualifyUrl != null) + { + var q = QualifyUrl(url); + if (q != null) + return q; + } + + // Is the url a fragment? + if (url.StartsWith("#")) + return url; + + // Is the url already fully qualified? + if (Utils.IsUrlFullyQualified(url)) + return url; + + if (url.StartsWith("/")) + { + if (!string.IsNullOrEmpty(UrlRootLocation)) + { + return UrlRootLocation + url; + } + + // Quit if we don't have a base location + if (String.IsNullOrEmpty(UrlBaseLocation)) + return url; + + // Need to find domain root + int pos = UrlBaseLocation.IndexOf("://"); + if (pos == -1) + pos = 0; + else + pos += 3; + + // Find the first slash after the protocol separator + pos = UrlBaseLocation.IndexOf('/', pos); + + // Get the domain name + string strDomain=pos<0 ? UrlBaseLocation : UrlBaseLocation.Substring(0, pos); + + // Join em + return strDomain + url; + } + else + { + // Quit if we don't have a base location + if (String.IsNullOrEmpty(UrlBaseLocation)) + return url; + + if (!UrlBaseLocation.EndsWith("/")) + return UrlBaseLocation + "/" + url; + else + return UrlBaseLocation + url; + } + } + + public Func GetImageSize; + + // Override to supply the size of an image + public virtual bool OnGetImageSize(string url, bool TitledImage, out int width, out int height) + { + if (GetImageSize != null) + { + var info = new ImageInfo() { url = url, titled_image=TitledImage }; + if (GetImageSize(info)) + { + width = info.width; + height = info.height; + return true; + } + } + + width = 0; + height = 0; + + if (Utils.IsUrlFullyQualified(url)) + return false; + + // Work out base location + string str = url.StartsWith("/") ? DocumentRoot : DocumentLocation; + if (String.IsNullOrEmpty(str)) + return false; + + // Work out file location + if (str.EndsWith("/") || str.EndsWith("\\")) + { + str=str.Substring(0, str.Length-1); + } + + if (url.StartsWith("/")) + { + url=url.Substring(1); + } + + str=str + "\\" + url.Replace("/", "\\"); + + + // + + //Create an image object from the uploaded file + try + { + var img = System.Drawing.Image.FromFile(str); + width=img.Width; + height=img.Height; + + if (MaxImageWidth != 0 && width>MaxImageWidth) + { + height=(int)((double)height * (double)MaxImageWidth / (double)width); + width=MaxImageWidth; + } + + return true; + } + catch (Exception) + { + return false; + } + } + + + public Func PrepareLink; + + // Override to modify the attributes of a link + public virtual void OnPrepareLink(HtmlTag tag) + { + if (PrepareLink != null) + { + if (PrepareLink(tag)) + return; + } + + string url = tag.attributes["href"]; + + // No follow? + if (NoFollowLinks) + { + tag.attributes["rel"] = "nofollow"; + } + + // No follow external links only + if (NoFollowExternalLinks) + { + if (Utils.IsUrlFullyQualified(url)) + tag.attributes["rel"] = "nofollow"; + } + + + + // New window? + if ( (NewWindowForExternalLinks && Utils.IsUrlFullyQualified(url)) || + (NewWindowForLocalLinks && !Utils.IsUrlFullyQualified(url)) ) + { + tag.attributes["target"] = "_blank"; + } + + // Qualify url + tag.attributes["href"] = OnQualifyUrl(url); + } + + public Func PrepareImage; + + internal bool RenderingTitledImage = false; + + // Override to modify the attributes of an image + public virtual void OnPrepareImage(HtmlTag tag, bool TitledImage) + { + if (PrepareImage != null) + { + if (PrepareImage(tag, TitledImage)) + return; + } + + // Try to determine width and height + int width, height; + if (OnGetImageSize(tag.attributes["src"], TitledImage, out width, out height)) + { + tag.attributes["width"] = width.ToString(); + tag.attributes["height"] = height.ToString(); + } + + // Now qualify the url + tag.attributes["src"] = OnQualifyUrl(tag.attributes["src"]); + } + + // Set the html class for the footnotes div + // (defaults to "footnotes") + // btw fyi: you can use css to disable the footnotes horizontal rule. eg: + // div.footnotes hr { display:none } + public string HtmlClassFootnotes + { + get; + set; + } + + // Callback to format a code block (ie: apply syntax highlighting) + // string FormatCodeBlock(code) + // Code = code block content (ie: the code to format) + // Return the formatted code, including
 and  tags
+		public Func FormatCodeBlock;
+
+		// when set to true, will remove head blocks and make content available
+		// as HeadBlockContent
+		public bool ExtractHeadBlocks
+		{
+			get;
+			set;
+		}
+
+		// Retrieve extracted head block content
+		public string HeadBlockContent
+		{
+			get;
+			internal set;
+		}
+
+		// Treats "===" as a user section break
+		public bool UserBreaks
+		{
+			get;
+			set;
+		}
+
+		// Set the classname for titled images
+		// A titled image is defined as a paragraph that contains an image and nothing else.
+		// If not set (the default), this features is disabled, otherwise the output is:
+		// 
+		// 
+ // + //

Alt text goes here

+ //
+ // + // Use CSS to style the figure and the caption + public string HtmlClassTitledImages + { + // TODO: + get; + set; + } + + // Set a format string to be rendered before headings + // {0} = section number + // (useful for rendering links that can lead to a page that edits that section) + // (eg: "" + public string SectionHeader + { + get; + set; + } + + // Set a format string to be rendered after each section heading + public string SectionHeadingSuffix + { + get; + set; + } + + // Set a format string to be rendered after the section content (ie: before + // the next section heading, or at the end of the document). + public string SectionFooter + { + get; + set; + } + + public virtual void OnSectionHeader(StringBuilder dest, int Index) + { + if (SectionHeader != null) + { + dest.AppendFormat(SectionHeader, Index); + } + } + + public virtual void OnSectionHeadingSuffix(StringBuilder dest, int Index) + { + if (SectionHeadingSuffix != null) + { + dest.AppendFormat(SectionHeadingSuffix, Index); + } + } + + public virtual void OnSectionFooter(StringBuilder dest, int Index) + { + if (SectionFooter!=null) + { + dest.AppendFormat(SectionFooter, Index); + } + } + + bool IsSectionHeader(Block b) + { + return b.blockType >= BlockType.h1 && b.blockType <= BlockType.h3; + } + + + + // Split the markdown into sections, one section for each + // top level heading + public static List SplitUserSections(string markdown) + { + // Build blocks + var md = new MarkdownDeep.Markdown(); + md.UserBreaks = true; + + // Process blocks + var blocks = md.ProcessBlocks(markdown); + + // Create sections + var Sections = new List(); + int iPrevSectionOffset = 0; + for (int i = 0; i < blocks.Count; i++) + { + var b = blocks[i]; + if (b.blockType==BlockType.user_break) + { + // Get the offset of the section + int iSectionOffset = b.lineStart; + + // Add section + Sections.Add(markdown.Substring(iPrevSectionOffset, iSectionOffset - iPrevSectionOffset).Trim()); + + // Next section starts on next line + if (i + 1 < blocks.Count) + { + iPrevSectionOffset = blocks[i + 1].lineStart; + if (iPrevSectionOffset==0) + iPrevSectionOffset = blocks[i + 1].contentStart; + } + else + iPrevSectionOffset = markdown.Length; + } + } + + // Add the last section + if (markdown.Length > iPrevSectionOffset) + { + Sections.Add(markdown.Substring(iPrevSectionOffset).Trim()); + } + + return Sections; + } + + // Join previously split sections back into one document + public static string JoinUserSections(List sections) + { + var sb = new StringBuilder(); + for (int i = 0; i < sections.Count; i++) + { + if (i > 0) + { + // For subsequent sections, need to make sure we + // have a line break after the previous section. + string strPrev = sections[sections.Count - 1]; + if (strPrev.Length > 0 && !strPrev.EndsWith("\n") && !strPrev.EndsWith("\r")) + sb.Append("\n"); + + sb.Append("\n===\n\n"); + } + + sb.Append(sections[i]); + } + + return sb.ToString(); + } + + // Split the markdown into sections, one section for each + // top level heading + public static List SplitSections(string markdown) + { + // Build blocks + var md = new MarkdownDeep.Markdown(); + + // Process blocks + var blocks = md.ProcessBlocks(markdown); + + // Create sections + var Sections = new List(); + int iPrevSectionOffset = 0; + for (int i = 0; i < blocks.Count; i++) + { + var b = blocks[i]; + if (md.IsSectionHeader(b)) + { + // Get the offset of the section + int iSectionOffset = b.lineStart; + + // Add section + Sections.Add(markdown.Substring(iPrevSectionOffset, iSectionOffset - iPrevSectionOffset)); + + iPrevSectionOffset = iSectionOffset; + } + } + + // Add the last section + if (markdown.Length > iPrevSectionOffset) + { + Sections.Add(markdown.Substring(iPrevSectionOffset)); + } + + return Sections; + } + + // Join previously split sections back into one document + public static string JoinSections(List sections) + { + var sb = new StringBuilder(); + for (int i = 0; i < sections.Count; i++) + { + if (i > 0) + { + // For subsequent sections, need to make sure we + // have a line break after the previous section. + string strPrev = sections[sections.Count - 1]; + if (strPrev.Length>0 && !strPrev.EndsWith("\n") && !strPrev.EndsWith("\r")) + sb.Append("\n"); + } + + sb.Append(sections[i]); + } + + return sb.ToString(); + } + + // Add a link definition + internal void AddLinkDefinition(LinkDefinition link) + { + // Store it + m_LinkDefinitions[link.id]=link; + } + + internal void AddFootnote(Block footnote) + { + m_Footnotes[(string)footnote.data] = footnote; + } + + // Look up a footnote, claim it and return it's index (or -1 if not found) + internal int ClaimFootnote(string id) + { + Block footnote; + if (m_Footnotes.TryGetValue(id, out footnote)) + { + // Move the foot note to the used footnote list + m_UsedFootnotes.Add(footnote); + m_Footnotes.Remove(id); + + // Return it's display index + return m_UsedFootnotes.Count-1; + } + else + return -1; + } + + // Get a link definition + public LinkDefinition GetLinkDefinition(string id) + { + LinkDefinition link; + if (m_LinkDefinitions.TryGetValue(id, out link)) + return link; + else + return null; + } + + internal void AddAbbreviation(string abbr, string title) + { + if (m_AbbreviationMap == null) + { + // First time + m_AbbreviationMap = new Dictionary(); + } + else if (m_AbbreviationMap.ContainsKey(abbr)) + { + // Remove previous + m_AbbreviationMap.Remove(abbr); + } + + // Store abbreviation + m_AbbreviationMap.Add(abbr, new Abbreviation(abbr, title)); + + } + + internal List GetAbbreviations() + { + return m_AbbreviationList; + } + + // HtmlEncode a range in a string to a specified string builder + internal void HtmlEncode(StringBuilder dest, string str, int start, int len) + { + m_StringScanner.Reset(str, start, len); + var p = m_StringScanner; + while (!p.eof) + { + char ch = p.current; + switch (ch) + { + case '&': + dest.Append("&"); + break; + + case '<': + dest.Append("<"); + break; + + case '>': + dest.Append(">"); + break; + + case '\"': + dest.Append("""); + break; + + default: + dest.Append(ch); + break; + } + p.SkipForward(1); + } + } + + + // HtmlEncode a string, also converting tabs to spaces (used by CodeBlocks) + internal void HtmlEncodeAndConvertTabsToSpaces(StringBuilder dest, string str, int start, int len) + { + m_StringScanner.Reset(str, start, len); + var p = m_StringScanner; + int pos = 0; + while (!p.eof) + { + char ch = p.current; + switch (ch) + { + case '\t': + dest.Append(' '); + pos++; + while ((pos % 4) != 0) + { + dest.Append(' '); + pos++; + } + pos--; // Compensate for the pos++ below + break; + + case '\r': + case '\n': + dest.Append('\n'); + pos = 0; + p.SkipEol(); + continue; + + case '&': + dest.Append("&"); + break; + + case '<': + dest.Append("<"); + break; + + case '>': + dest.Append(">"); + break; + + case '\"': + dest.Append("""); + break; + + default: + dest.Append(ch); + break; + } + p.SkipForward(1); + pos++; + } + } + + internal string MakeUniqueHeaderID(string strHeaderText) + { + return MakeUniqueHeaderID(strHeaderText, 0, strHeaderText.Length); + + } + + internal string MakeUniqueHeaderID(string strHeaderText, int startOffset, int length) + { + if (!AutoHeadingIDs) + return null; + + // Extract a pandoc style cleaned header id from the header text + string strBase=m_SpanFormatter.MakeID(strHeaderText, startOffset, length); + + // If nothing left, use "section" + if (String.IsNullOrEmpty(strBase)) + strBase = "section"; + + // Make sure it's unique by append -n counter + string strWithSuffix=strBase; + int counter=1; + while (m_UsedHeaderIDs.ContainsKey(strWithSuffix)) + { + strWithSuffix = strBase + "-" + counter.ToString(); + counter++; + } + + // Store it + m_UsedHeaderIDs.Add(strWithSuffix, true); + + // Return it + return strWithSuffix; + } + + + /* + * Get this markdown processors string builder. + * + * We re-use the same string builder whenever we can for performance. + * We just reset the length before starting to / use it again, which + * hopefully should keep the memory around for next time. + * + * Note, care should be taken when using this string builder to not + * call out to another function that also uses it. + */ + internal StringBuilder GetStringBuilder() + { + m_StringBuilder.Length = 0; + return m_StringBuilder; + } + + + internal SpanFormatter SpanFormatter + { + get + { + return m_SpanFormatter; + } + } + + #region Block Pooling + + // We cache and re-use blocks for performance + + Stack m_SpareBlocks=new Stack(); + + internal Block CreateBlock() + { + if (m_SpareBlocks.Count!=0) + return m_SpareBlocks.Pop(); + else + return new Block(); + } + + internal void FreeBlock(Block b) + { + m_SpareBlocks.Push(b); + } + + #endregion + + // Attributes + StringBuilder m_StringBuilder; + StringBuilder m_StringBuilderFinal; + StringScanner m_StringScanner; + SpanFormatter m_SpanFormatter; + Dictionary m_LinkDefinitions; + Dictionary m_Footnotes; + List m_UsedFootnotes; + Dictionary m_UsedHeaderIDs; + Dictionary m_AbbreviationMap; + List m_AbbreviationList; + + + } + +} diff --git a/src/MarkdownDeep/MarkdownDeep.csproj b/src/MarkdownDeep/MarkdownDeep.csproj new file mode 100644 index 0000000..aca5ccb --- /dev/null +++ b/src/MarkdownDeep/MarkdownDeep.csproj @@ -0,0 +1,120 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {1569ED47-C7C9-4261-B6F4-7445BD0F2C95} + Library + Properties + MarkdownDeep + MarkdownDeep + v4.6.1 + 512 + + + + + 3.5 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + + + + 3.5 + + + + + 3.5 + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + + + + \ No newline at end of file diff --git a/src/MarkdownDeep/Properties/AssemblyInfo.cs b/src/MarkdownDeep/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2526759 --- /dev/null +++ b/src/MarkdownDeep/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MarkdownDeep")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Topten Software")] +[assembly: AssemblyProduct("MarkdownDeep")] +[assembly: AssemblyCopyright("Copyright © 2010-2016 Topten Software")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("dddc6d83-efc4-4387-90e2-eabe9f5998dd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.6.0.0")] +[assembly: AssemblyFileVersion("1.6.0")] +[assembly: InternalsVisibleTo("MarkdownDeepTests")] diff --git a/src/MarkdownDeep/SpanFormatter.cs b/src/MarkdownDeep/SpanFormatter.cs new file mode 100644 index 0000000..292c7cb --- /dev/null +++ b/src/MarkdownDeep/SpanFormatter.cs @@ -0,0 +1,1171 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + internal class SpanFormatter : StringScanner + { + // Constructor + // A reference to the owning markdown object is passed incase + // we need to check for formatting options + public SpanFormatter(Markdown m) + { + m_Markdown = m; + } + + + internal void FormatParagraph(StringBuilder dest, string str, int start, int len) + { + // Parse the string into a list of tokens + Tokenize(str, start, len); + + // Titled image? + if (m_Tokens.Count == 1 && m_Markdown.HtmlClassTitledImages != null && m_Tokens[0].type == TokenType.img) + { + // Grab the link info + LinkInfo li = (LinkInfo)m_Tokens[0].data; + + // Render the div opening + dest.Append("
\n"); + + // Render the img + m_Markdown.RenderingTitledImage = true; + Render(dest, str); + m_Markdown.RenderingTitledImage = false; + dest.Append("\n"); + + // Render the title + if (!String.IsNullOrEmpty(li.def.title)) + { + dest.Append("

"); + Utils.SmartHtmlEncodeAmpsAndAngles(dest, li.def.title); + dest.Append("

\n"); + } + + dest.Append("
\n"); + } + else + { + // Render the paragraph + dest.Append("

"); + Render(dest, str); + dest.Append("

\n"); + } + } + + internal void Format(StringBuilder dest, string str) + { + Format(dest, str, 0, str.Length); + } + + // Format a range in an input string and write it to the destination string builder. + internal void Format(StringBuilder dest, string str, int start, int len) + { + // Parse the string into a list of tokens + Tokenize(str, start, len); + + // Render all tokens + Render(dest, str); + } + + internal void FormatPlain(StringBuilder dest, string str, int start, int len) + { + // Parse the string into a list of tokens + Tokenize(str, start, len); + + // Render all tokens + RenderPlain(dest, str); + } + + // Format a string and return it as a new string + // (used in formatting the text of links) + internal string Format(string str) + { + StringBuilder dest = new StringBuilder(); + Format(dest, str, 0, str.Length); + return dest.ToString(); + } + + internal string MakeID(string str) + { + return MakeID(str, 0, str.Length); + } + + internal string MakeID(string str, int start, int len) + { + // Parse the string into a list of tokens + Tokenize(str, start, len); + + StringBuilder sb = new StringBuilder(); + + foreach (var t in m_Tokens) + { + switch (t.type) + { + case TokenType.Text: + sb.Append(str, t.startOffset, t.length); + break; + + case TokenType.link: + LinkInfo li = (LinkInfo)t.data; + sb.Append(li.link_text); + break; + } + + FreeToken(t); + } + + // Now clean it using the same rules as pandoc + base.Reset(sb.ToString()); + + // Skip everything up to the first letter + while (!eof) + { + if (Char.IsLetter(current)) + break; + SkipForward(1); + } + + // Process all characters + sb.Length = 0; + while (!eof) + { + char ch = current; + if (char.IsLetterOrDigit(ch) || ch=='_' || ch=='-' || ch=='.') + sb.Append(Char.ToLower(ch)); + else if (ch == ' ') + sb.Append("-"); + else if (IsLineEnd(ch)) + { + sb.Append("-"); + SkipEol(); + continue; + } + + SkipForward(1); + } + + return sb.ToString(); + } + + // Render a list of tokens to a destinatino string builder. + private void Render(StringBuilder sb, string str) + { + foreach (Token t in m_Tokens) + { + switch (t.type) + { + case TokenType.Text: + // Append encoded text + m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); + break; + + case TokenType.HtmlTag: + // Append html as is + Utils.SmartHtmlEncodeAmps(sb, str, t.startOffset, t.length); + break; + + case TokenType.Html: + case TokenType.opening_mark: + case TokenType.closing_mark: + case TokenType.internal_mark: + // Append html as is + sb.Append(str, t.startOffset, t.length); + break; + + case TokenType.br: + sb.Append("
\n"); + break; + + case TokenType.open_em: + sb.Append(""); + break; + + case TokenType.close_em: + sb.Append(""); + break; + + case TokenType.open_strong: + sb.Append(""); + break; + + case TokenType.close_strong: + sb.Append(""); + break; + + case TokenType.code_span: + sb.Append(""); + m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); + sb.Append(""); + break; + + case TokenType.link: + { + LinkInfo li = (LinkInfo)t.data; + var sf = new SpanFormatter(m_Markdown); + sf.DisableLinks = true; + + li.def.RenderLink(m_Markdown, sb, sf.Format(li.link_text)); + break; + } + + case TokenType.img: + { + LinkInfo li = (LinkInfo)t.data; + li.def.RenderImg(m_Markdown, sb, li.link_text); + break; + } + + case TokenType.footnote: + { + FootnoteReference r=(FootnoteReference)t.data; + sb.Append("
"); + sb.Append(r.index + 1); + sb.Append(""); + break; + } + + case TokenType.abbreviation: + { + Abbreviation a = (Abbreviation)t.data; + sb.Append(""); + m_Markdown.HtmlEncode(sb, a.Abbr, 0, a.Abbr.Length); + sb.Append(""); + break; + } + } + + FreeToken(t); + } + } + + // Render a list of tokens to a destinatino string builder. + private void RenderPlain(StringBuilder sb, string str) + { + foreach (Token t in m_Tokens) + { + switch (t.type) + { + case TokenType.Text: + sb.Append(str, t.startOffset, t.length); + break; + + case TokenType.HtmlTag: + break; + + case TokenType.Html: + case TokenType.opening_mark: + case TokenType.closing_mark: + case TokenType.internal_mark: + break; + + case TokenType.br: + break; + + case TokenType.open_em: + case TokenType.close_em: + case TokenType.open_strong: + case TokenType.close_strong: + break; + + case TokenType.code_span: + sb.Append(str, t.startOffset, t.length); + break; + + case TokenType.link: + { + LinkInfo li = (LinkInfo)t.data; + sb.Append(li.link_text); + break; + } + + case TokenType.img: + { + LinkInfo li = (LinkInfo)t.data; + sb.Append(li.link_text); + break; + } + + case TokenType.footnote: + case TokenType.abbreviation: + break; + } + + FreeToken(t); + } + } + + // Scan the input string, creating tokens for anything special + public void Tokenize(string str, int start, int len) + { + // Prepare + base.Reset(str, start, len); + m_Tokens.Clear(); + + List emphasis_marks = null; + + List Abbreviations=m_Markdown.GetAbbreviations(); + bool ExtraMode = m_Markdown.ExtraMode; + + // Scan string + int start_text_token = position; + while (!eof) + { + int end_text_token=position; + + // Work out token + Token token = null; + switch (current) + { + case '*': + case '_': + + // Create emphasis mark + token = CreateEmphasisMark(); + + if (token != null) + { + // Store marks in a separate list the we'll resolve later + switch (token.type) + { + case TokenType.internal_mark: + case TokenType.opening_mark: + case TokenType.closing_mark: + if (emphasis_marks == null) + { + emphasis_marks = new List(); + } + emphasis_marks.Add(token); + break; + } + } + break; + + case '`': + token = ProcessCodeSpan(); + break; + + case '[': + case '!': + { + // Process link reference + int linkpos = position; + token = ProcessLinkOrImageOrFootnote(); + + // Rewind if invalid syntax + // (the '[' or '!' will be treated as a regular character and processed below) + if (token == null) + position = linkpos; + break; + } + + case '<': + { + // Is it a valid html tag? + int save = position; + HtmlTag tag = HtmlTag.Parse(this); + if (tag != null) + { + if (!m_Markdown.SafeMode || tag.IsSafe()) + { + // Yes, create a token for it + token = CreateToken(TokenType.HtmlTag, save, position - save); + } + else + { + // No, rewrite and encode it + position = save; + } + } + else + { + // No, rewind and check if it's a valid autolink eg: + position = save; + token = ProcessAutoLink(); + + if (token == null) + position = save; + } + break; + } + + case '&': + { + // Is it a valid html entity + int save=position; + string unused=null; + if (SkipHtmlEntity(ref unused)) + { + // Yes, create a token for it + token = CreateToken(TokenType.Html, save, position - save); + } + + break; + } + + case ' ': + { + // Check for double space at end of a line + if (CharAtOffset(1)==' ' && IsLineEnd(CharAtOffset(2))) + { + // Yes, skip it + SkipForward(2); + + // Don't put br's at the end of a paragraph + if (!eof) + { + SkipEol(); + token = CreateToken(TokenType.br, end_text_token, 0); + } + } + break; + } + + case '\\': + { + // Special handling for escaping + /* + if (CharAtOffset(1) == '<') + { + // Is it an autolink? + int savepos = position; + SkipForward(1); + bool AutoLink = ProcessAutoLink() != null; + position = savepos; + + if (AutoLink) + { + token = CreateToken(TokenType.Text, position + 1, 1); + SkipForward(2); + } + } + else + */ + { + // Check followed by an escapable character + if (Utils.IsEscapableChar(CharAtOffset(1), ExtraMode)) + { + token = CreateToken(TokenType.Text, position + 1, 1); + SkipForward(2); + } + } + break; + } + } + + // Look for abbreviations. + if (token == null && Abbreviations!=null && !Char.IsLetterOrDigit(CharAtOffset(-1))) + { + var savepos = position; + foreach (var abbr in Abbreviations) + { + if (SkipString(abbr.Abbr) && !Char.IsLetterOrDigit(current)) + { + token = CreateToken(TokenType.abbreviation, abbr); + break; + } + + position = savepos; + } + + } + + // If token found, append any preceeding text and the new token to the token list + if (token!=null) + { + // Create a token for everything up to the special character + if (end_text_token > start_text_token) + { + m_Tokens.Add(CreateToken(TokenType.Text, start_text_token, end_text_token-start_text_token)); + } + + // Add the new token + m_Tokens.Add(token); + + // Remember where the next text token starts + start_text_token=position; + } + else + { + // Skip a single character and keep looking + SkipForward(1); + } + } + + // Append a token for any trailing text after the last token. + if (position > start_text_token) + { + m_Tokens.Add(CreateToken(TokenType.Text, start_text_token, position-start_text_token)); + } + + // Do we need to resolve and emphasis marks? + if (emphasis_marks != null) + { + ResolveEmphasisMarks(m_Tokens, emphasis_marks); + } + + // Done! + return; + } + + static bool IsEmphasisChar(char ch) + { + return ch == '_' || ch == '*'; + } + + /* + * Resolving emphasis tokens is a two part process + * + * 1. Find all valid sequences of * and _ and create `mark` tokens for them + * this is done by CreateEmphasisMarks during the initial character scan + * done by Tokenize + * + * 2. Looks at all these emphasis marks and tries to pair them up + * to make the actual and tokens + * + * Any unresolved emphasis marks are rendered unaltered as * or _ + */ + + // Create emphasis mark for sequences of '*' and '_' (part 1) + public Token CreateEmphasisMark() + { + // Capture current state + char ch = current; + char altch = ch == '*' ? '_' : '*'; + int savepos = position; + + // Check for a consecutive sequence of just '_' and '*' + if (bof || char.IsWhiteSpace(CharAtOffset(-1))) + { + while (IsEmphasisChar(current)) + SkipForward(1); + + if (eof || char.IsWhiteSpace(current)) + { + return new Token(TokenType.Html, savepos, position - savepos); + } + + // Rewind + position = savepos; + } + + // Scan backwards and see if we have space before + while (IsEmphasisChar(CharAtOffset(-1))) + SkipForward(-1); + bool bSpaceBefore = bof || char.IsWhiteSpace(CharAtOffset(-1)); + position = savepos; + + // Count how many matching emphasis characters + while (current == ch) + { + SkipForward(1); + } + int count=position-savepos; + + // Scan forwards and see if we have space after + while (IsEmphasisChar(CharAtOffset(1))) + SkipForward(1); + bool bSpaceAfter = eof || char.IsWhiteSpace(current); + position = savepos + count; + + // This should have been stopped by check above + System.Diagnostics.Debug.Assert(!bSpaceBefore || !bSpaceAfter); + + if (bSpaceBefore) + { + return CreateToken(TokenType.opening_mark, savepos, position - savepos); + } + + if (bSpaceAfter) + { + return CreateToken(TokenType.closing_mark, savepos, position - savepos); + } + + if (m_Markdown.ExtraMode && ch == '_' && (Char.IsLetterOrDigit(current))) + return null; + + return CreateToken(TokenType.internal_mark, savepos, position - savepos); + } + + // Split mark token + public Token SplitMarkToken(List tokens, List marks, Token token, int position) + { + // Create the new rhs token + Token tokenRhs = CreateToken(token.type, token.startOffset + position, token.length - position); + + // Adjust down the length of this token + token.length = position; + + // Insert the new token into each of the parent collections + marks.Insert(marks.IndexOf(token) + 1, tokenRhs); + tokens.Insert(tokens.IndexOf(token) + 1, tokenRhs); + + // Return the new token + return tokenRhs; + } + + // Resolve emphasis marks (part 2) + public void ResolveEmphasisMarks(List tokens, List marks) + { + bool bContinue = true; + while (bContinue) + { + bContinue = false; + for (int i = 0; i < marks.Count; i++) + { + // Get the next opening or internal mark + Token opening_mark = marks[i]; + if (opening_mark.type != TokenType.opening_mark && opening_mark.type != TokenType.internal_mark) + continue; + + // Look for a matching closing mark + for (int j = i + 1; j < marks.Count; j++) + { + // Get the next closing or internal mark + Token closing_mark = marks[j]; + if (closing_mark.type != TokenType.closing_mark && closing_mark.type != TokenType.internal_mark) + break; + + // Ignore if different type (ie: `*` vs `_`) + if (input[opening_mark.startOffset] != input[closing_mark.startOffset]) + continue; + + // strong or em? + int style = Math.Min(opening_mark.length, closing_mark.length); + + // Triple or more on both ends? + if (style >= 3) + { + style = (style % 2)==1 ? 1 : 2; + } + + // Split the opening mark, keeping the RHS + if (opening_mark.length > style) + { + opening_mark = SplitMarkToken(tokens, marks, opening_mark, opening_mark.length - style); + i--; + } + + // Split the closing mark, keeping the LHS + if (closing_mark.length > style) + { + SplitMarkToken(tokens, marks, closing_mark, style); + } + + // Connect them + opening_mark.type = style == 1 ? TokenType.open_em : TokenType.open_strong; + closing_mark.type = style == 1 ? TokenType.close_em : TokenType.close_strong; + + // Remove the matched marks + marks.Remove(opening_mark); + marks.Remove(closing_mark); + bContinue = true; + + break; + } + } + } + } + + // Resolve emphasis marks (part 2) + public void ResolveEmphasisMarks_classic(List tokens, List marks) + { + // First pass, do + for (int i = 0; i < marks.Count; i++) + { + // Get the next opening or internal mark + Token opening_mark=marks[i]; + if (opening_mark.type!=TokenType.opening_mark && opening_mark.type!=TokenType.internal_mark) + continue; + if (opening_mark.length < 2) + continue; + + // Look for a matching closing mark + for (int j = i + 1; j < marks.Count; j++) + { + // Get the next closing or internal mark + Token closing_mark = marks[j]; + if (closing_mark.type != TokenType.closing_mark && closing_mark.type!=TokenType.internal_mark) + continue; + + // Ignore if different type (ie: `*` vs `_`) + if (input[opening_mark.startOffset] != input[closing_mark.startOffset]) + continue; + + // Must be at least two + if (closing_mark.length < 2) + continue; + + // Split the opening mark, keeping the LHS + if (opening_mark.length > 2) + { + SplitMarkToken(tokens, marks, opening_mark, 2); + } + + // Split the closing mark, keeping the RHS + if (closing_mark.length > 2) + { + closing_mark=SplitMarkToken(tokens, marks, closing_mark, closing_mark.length-2); + } + + // Connect them + opening_mark.type = TokenType.open_strong; + closing_mark.type = TokenType.close_strong; + + // Continue after the closing mark + i = marks.IndexOf(closing_mark); + break; + } + } + + // Second pass, do + for (int i = 0; i < marks.Count; i++) + { + // Get the next opening or internal mark + Token opening_mark = marks[i]; + if (opening_mark.type != TokenType.opening_mark && opening_mark.type != TokenType.internal_mark) + continue; + + // Look for a matching closing mark + for (int j = i + 1; j < marks.Count; j++) + { + // Get the next closing or internal mark + Token closing_mark = marks[j]; + if (closing_mark.type != TokenType.closing_mark && closing_mark.type != TokenType.internal_mark) + continue; + + // Ignore if different type (ie: `*` vs `_`) + if (input[opening_mark.startOffset] != input[closing_mark.startOffset]) + continue; + + // Split the opening mark, keeping the LHS + if (opening_mark.length > 1) + { + SplitMarkToken(tokens, marks, opening_mark, 1); + } + + // Split the closing mark, keeping the RHS + if (closing_mark.length > 1) + { + closing_mark = SplitMarkToken(tokens, marks, closing_mark, closing_mark.length - 1); + } + + // Connect them + opening_mark.type = TokenType.open_em; + closing_mark.type = TokenType.close_em; + + // Continue after the closing mark + i = marks.IndexOf(closing_mark); + break; + } + } + } + + // Process '*', '**' or '_', '__' + // This is horrible and probably much better done through regex, but I'm stubborn. + // For normal cases this routine works as expected. For unusual cases (eg: overlapped + // strong and emphasis blocks), the behaviour is probably not the same as the original + // markdown scanner. + /* + public Token ProcessEmphasisOld(ref Token prev_single, ref Token prev_double) + { + // Check whitespace before/after + bool bSpaceBefore = !bof && IsLineSpace(CharAtOffset(-1)); + bool bSpaceAfter = IsLineSpace(CharAtOffset(1)); + + // Ignore if surrounded by whitespace + if (bSpaceBefore && bSpaceAfter) + { + return null; + } + + // Save the current character and skip it + char ch = current; + Skip(1); + + // Do we have a previous matching single star? + if (!bSpaceBefore && prev_single != null) + { + // Yes, match them... + prev_single.type = TokenType.open_em; + prev_single = null; + return CreateToken(TokenType.close_em, position - 1, 1); + } + + // Is this a double star/under + if (current == ch) + { + // Skip second character + Skip(1); + + // Space after? + bSpaceAfter = IsLineSpace(current); + + // Space both sides? + if (bSpaceBefore && bSpaceAfter) + { + // Ignore it + return CreateToken(TokenType.Text, position - 2, 2); + } + + // Do we have a previous matching double + if (!bSpaceBefore && prev_double != null) + { + // Yes, match them + prev_double.type = TokenType.open_strong; + prev_double = null; + return CreateToken(TokenType.close_strong, position - 2, 2); + } + + if (!bSpaceAfter) + { + // Opening double star + prev_double = CreateToken(TokenType.Text, position - 2, 2); + return prev_double; + } + + // Ignore it + return CreateToken(TokenType.Text, position - 2, 2); + } + + // If there's a space before, we can open em + if (!bSpaceAfter) + { + // Opening single star + prev_single = CreateToken(TokenType.Text, position - 1, 1); + return prev_single; + } + + // Ignore + Skip(-1); + return null; + } + */ + + // Process auto links eg: + Token ProcessAutoLink() + { + if (DisableLinks) + return null; + + // Skip the angle bracket and remember the start + SkipForward(1); + Mark(); + + bool ExtraMode = m_Markdown.ExtraMode; + + // Allow anything up to the closing angle, watch for escapable characters + while (!eof) + { + char ch = current; + + // No whitespace allowed + if (char.IsWhiteSpace(ch)) + break; + + // End found? + if (ch == '>') + { + string url = Utils.UnescapeString(Extract(), ExtraMode); + + LinkInfo li = null; + if (Utils.IsEmailAddress(url)) + { + string link_text; + if (url.StartsWith("mailto:")) + { + link_text = url.Substring(7); + } + else + { + link_text = url; + url = "mailto:" + url; + } + + li = new LinkInfo(new LinkDefinition("auto", url, null), link_text); + } + else if (Utils.IsWebAddress(url)) + { + li=new LinkInfo(new LinkDefinition("auto", url, null), url); + } + + if (li!=null) + { + SkipForward(1); + return CreateToken(TokenType.link, li); + } + + return null; + } + + this.SkipEscapableChar(ExtraMode); + } + + // Didn't work + return null; + } + + // Process [link] and ![image] directives + Token ProcessLinkOrImageOrFootnote() + { + // Link or image? + TokenType token_type = SkipChar('!') ? TokenType.img : TokenType.link; + + // Opening '[' + if (!SkipChar('[')) + return null; + + // Is it a foonote? + var savepos=position; + if (m_Markdown.ExtraMode && token_type==TokenType.link && SkipChar('^')) + { + SkipLinespace(); + + // Parse it + string id; + if (SkipFootnoteID(out id) && SkipChar(']')) + { + // Look it up and create footnote reference token + int footnote_index = m_Markdown.ClaimFootnote(id); + if (footnote_index >= 0) + { + // Yes it's a footnote + return CreateToken(TokenType.footnote, new FootnoteReference(footnote_index, id)); + } + } + + // Rewind + position = savepos; + } + + if (DisableLinks && token_type==TokenType.link) + return null; + + bool ExtraMode = m_Markdown.ExtraMode; + + // Find the closing square bracket, allowing for nesting, watching for + // escapable characters + Mark(); + int depth = 1; + while (!eof) + { + char ch = current; + if (ch == '[') + { + depth++; + } + else if (ch == ']') + { + depth--; + if (depth == 0) + break; + } + + this.SkipEscapableChar(ExtraMode); + } + + // Quit if end + if (eof) + return null; + + // Get the link text and unescape it + string link_text = Utils.UnescapeString(Extract(), ExtraMode); + + // The closing ']' + SkipForward(1); + + // Save position in case we need to rewind + savepos = position; + + // Inline links must follow immediately + if (SkipChar('(')) + { + // Extract the url and title + var link_def = LinkDefinition.ParseLinkTarget(this, null, m_Markdown.ExtraMode); + if (link_def==null) + return null; + + // Closing ')' + SkipWhitespace(); + if (!SkipChar(')')) + return null; + + // Create the token + return CreateToken(token_type, new LinkInfo(link_def, link_text)); + } + + // Optional space or tab + if (!SkipChar(' ')) + SkipChar('\t'); + + // If there's line end, we're allow it and as must line space as we want + // before the link id. + if (eol) + { + SkipEol(); + SkipLinespace(); + } + + // Reference link? + string link_id = null; + if (current == '[') + { + // Skip the opening '[' + SkipForward(1); + + // Find the start/end of the id + Mark(); + if (!Find(']')) + return null; + + // Extract the id + link_id = Extract(); + + // Skip closing ']' + SkipForward(1); + } + else + { + // Rewind to just after the closing ']' + position = savepos; + } + + // Link id not specified? + if (string.IsNullOrEmpty(link_id)) + { + // Use the link text (implicit reference link) + link_id = Utils.NormalizeLineEnds(link_text); + + // If the link text has carriage returns, normalize + // to spaces + if (!object.ReferenceEquals(link_id, link_text)) + { + while (link_id.Contains(" \n")) + link_id = link_id.Replace(" \n", "\n"); + link_id = link_id.Replace("\n", " "); + } + } + + // Find the link definition abort if not defined + var def = m_Markdown.GetLinkDefinition(link_id); + if (def == null) + return null; + + // Create a token + return CreateToken(token_type, new LinkInfo(def, link_text)); + } + + // Process a ``` code span ``` + Token ProcessCodeSpan() + { + int start = position; + + // Count leading ticks + int tickcount = 0; + while (SkipChar('`')) + { + tickcount++; + } + + // Skip optional leading space... + SkipWhitespace(); + + // End? + if (eof) + return CreateToken(TokenType.Text, start, position - start); + + int startofcode = position; + + // Find closing ticks + if (!Find(Substring(start, tickcount))) + return CreateToken(TokenType.Text, start, position - start); + + // Save end position before backing up over trailing whitespace + int endpos = position + tickcount; + while (char.IsWhiteSpace(CharAtOffset(-1))) + SkipForward(-1); + + // Create the token, move back to the end and we're done + var ret = CreateToken(TokenType.code_span, startofcode, position - startofcode); + position = endpos; + return ret; + } + + + #region Token Pooling + + // CreateToken - create or re-use a token object + internal Token CreateToken(TokenType type, int startOffset, int length) + { + if (m_SpareTokens.Count != 0) + { + var t = m_SpareTokens.Pop(); + t.type = type; + t.startOffset = startOffset; + t.length = length; + t.data = null; + return t; + } + else + return new Token(type, startOffset, length); + } + + // CreateToken - create or re-use a token object + internal Token CreateToken(TokenType type, object data) + { + if (m_SpareTokens.Count != 0) + { + var t = m_SpareTokens.Pop(); + t.type = type; + t.data = data; + return t; + } + else + return new Token(type, data); + } + + // FreeToken - return a token to the spare token pool + internal void FreeToken(Token token) + { + token.data = null; + m_SpareTokens.Push(token); + } + + Stack m_SpareTokens = new Stack(); + + #endregion + + Markdown m_Markdown; + internal bool DisableLinks; + List m_Tokens=new List(); + } +} diff --git a/src/MarkdownDeep/StringScanner.cs b/src/MarkdownDeep/StringScanner.cs new file mode 100644 index 0000000..7d3be3b --- /dev/null +++ b/src/MarkdownDeep/StringScanner.cs @@ -0,0 +1,541 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + /* + * StringScanner is a simple class to help scan through an input string. + * + * Maintains a current position with various operations to inspect the current + * character, skip forward, check for matches, skip whitespace etc... + */ + public class StringScanner + { + // Constructor + public StringScanner() + { + } + + // Constructor + public StringScanner(string str) + { + Reset(str); + } + + // Constructor + public StringScanner(string str, int pos) + { + Reset(str, pos); + } + + // Constructor + public StringScanner(string str, int pos, int len) + { + Reset(str, pos, len); + } + + // Reset + public void Reset(string str) + { + Reset(str, 0, str!=null ? str.Length : 0); + } + + // Reset + public void Reset(string str, int pos) + { + Reset(str, pos, str!=null ? str.Length - pos : 0); + } + + // Reset + public void Reset(string str, int pos, int len) + { + if (str == null) + str = ""; + if (len < 0) + len = 0; + if (pos < 0) + pos = 0; + if (pos > str.Length) + pos = str.Length; + + this.str = str; + this.start = pos; + this.pos = pos; + this.end = pos + len; + + if (end > str.Length) + end = str.Length; + } + + // Get the entire input string + public string input + { + get + { + return str; + } + } + + // Get the character at the current position + public char current + { + get + { + if (pos < start || pos >= end) + return '\0'; + else + return str[pos]; + } + } + + // Get/set the current position + public int position + { + get + { + return pos; + } + set + { + pos = value; + } + } + + // Get the remainder of the input + // (use this in a watch window while debugging :) + public string remainder + { + get + { + return Substring(position); + } + } + + // Skip to the end of file + public void SkipToEof() + { + pos = end; + } + + + // Skip to the end of the current line + public void SkipToEol() + { + while (pos < end) + { + char ch=str[pos]; + if (ch=='\r' || ch=='\n') + break; + pos++; + } + } + + // Skip if currently at a line end + public bool SkipEol() + { + if (pos < end) + { + char ch = str[pos]; + if (ch == '\r') + { + pos++; + if (pos < end && str[pos] == '\n') + pos++; + return true; + } + + else if (ch == '\n') + { + pos++; + if (pos < end && str[pos] == '\r') + pos++; + return true; + } + } + + return false; + } + + // Skip to the next line + public void SkipToNextLine() + { + SkipToEol(); + SkipEol(); + } + + // Get the character at offset from current position + // Or, \0 if out of range + public char CharAtOffset(int offset) + { + int index = pos + offset; + + if (index < start) + return '\0'; + if (index >= end) + return '\0'; + return str[index]; + } + + // Skip a number of characters + public void SkipForward(int characters) + { + pos += characters; + } + + // Skip a character if present + public bool SkipChar(char ch) + { + if (current == ch) + { + SkipForward(1); + return true; + } + + return false; + } + + // Skip a matching string + public bool SkipString(string str) + { + if (DoesMatch(str)) + { + SkipForward(str.Length); + return true; + } + + return false; + } + + // Skip a matching string + public bool SkipStringI(string str) + { + if (DoesMatchI(str)) + { + SkipForward(str.Length); + return true; + } + + return false; + } + + // Skip any whitespace + public bool SkipWhitespace() + { + if (!char.IsWhiteSpace(current)) + return false; + SkipForward(1); + + while (char.IsWhiteSpace(current)) + SkipForward(1); + + return true; + } + + // Check if a character is space or tab + public static bool IsLineSpace(char ch) + { + return ch == ' ' || ch == '\t'; + } + + // Skip spaces and tabs + public bool SkipLinespace() + { + if (!IsLineSpace(current)) + return false; + SkipForward(1); + + while (IsLineSpace(current)) + SkipForward(1); + + return true; + } + + // Does current character match something + public bool DoesMatch(char ch) + { + return current == ch; + } + + // Does character at offset match a character + public bool DoesMatch(int offset, char ch) + { + return CharAtOffset(offset) == ch; + } + + // Does current character match any of a range of characters + public bool DoesMatchAny(char[] chars) + { + for (int i = 0; i < chars.Length; i++) + { + if (DoesMatch(chars[i])) + return true; + } + return false; + } + + // Does current character match any of a range of characters + public bool DoesMatchAny(int offset, char[] chars) + { + for (int i = 0; i < chars.Length; i++) + { + if (DoesMatch(offset, chars[i])) + return true; + } + return false; + } + + // Does current string position match a string + public bool DoesMatch(string str) + { + for (int i = 0; i < str.Length; i++) + { + if (str[i] != CharAtOffset(i)) + return false; + } + return true; + } + + // Does current string position match a string + public bool DoesMatchI(string str) + { + return string.Compare(str, Substring(position, str.Length), true) == 0; + } + + // Extract a substring + public string Substring(int start) + { + return str.Substring(start, end-start); + } + + // Extract a substring + public string Substring(int start, int len) + { + if (start + len > end) + len = end - start; + + return str.Substring(start, len); + } + + // Scan forward for a character + public bool Find(char ch) + { + if (pos >= end) + return false; + + // Find it + int index = str.IndexOf(ch, pos); + if (index < 0 || index>=end) + return false; + + // Store new position + pos = index; + return true; + } + + // Find any of a range of characters + public bool FindAny(char[] chars) + { + if (pos >= end) + return false; + + // Find it + int index = str.IndexOfAny(chars, pos); + if (index < 0 || index>=end) + return false; + + // Store new position + pos = index; + return true; + } + + // Forward scan for a string + public bool Find(string find) + { + if (pos >= end) + return false; + + int index = str.IndexOf(find, pos); + if (index < 0 || index > end-find.Length) + return false; + + pos = index; + return true; + } + + // Forward scan for a string (case insensitive) + public bool FindI(string find) + { + if (pos >= end) + return false; + + int index = str.IndexOf(find, pos, StringComparison.InvariantCultureIgnoreCase); + if (index < 0 || index >= end - find.Length) + return false; + + pos = index; + return true; + } + + // Are we at eof? + public bool eof + { + get + { + return pos >= end; + } + } + + // Are we at eol? + public bool eol + { + get + { + return IsLineEnd(current); + } + } + + // Are we at bof? + public bool bof + { + get + { + return pos == start; + } + } + + // Mark current position + public void Mark() + { + mark = pos; + } + + // Extract string from mark to current position + public string Extract() + { + if (mark >= pos) + return ""; + + return str.Substring(mark, pos - mark); + } + + // Skip an identifier + public bool SkipIdentifier(ref string identifier) + { + int savepos = position; + if (!Utils.ParseIdentifier(this.str, ref pos, ref identifier)) + return false; + if (pos >= end) + { + pos = savepos; + return false; + } + return true; + } + + public bool SkipFootnoteID(out string id) + { + int savepos = position; + + SkipLinespace(); + + Mark(); + + while (true) + { + char ch = current; + if (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == ':' || ch == '.' || ch == ' ') + SkipForward(1); + else + break; + } + + if (position > mark) + { + id = Extract().Trim(); + if (!String.IsNullOrEmpty(id)) + { + SkipLinespace(); + return true; + } + } + + position = savepos; + id = null; + return false; + } + + // Skip a Html entity (eg: &) + public bool SkipHtmlEntity(ref string entity) + { + int savepos = position; + if (!Utils.SkipHtmlEntity(this.str, ref pos, ref entity)) + return false; + if (pos > end) + { + pos = savepos; + return false; + } + return true; + } + + // Check if a character marks end of line + public static bool IsLineEnd(char ch) + { + return ch == '\r' || ch == '\n' || ch=='\0'; + } + + bool IsUrlChar(char ch) + { + switch (ch) + { + case '+': + case '&': + case '@': + case '#': + case '/': + case '%': + case '?': + case '=': + case '~': + case '_': + case '|': + case '[': + case ']': + case '(': + case ')': + case '!': + case ':': + case ',': + case '.': + case ';': + return true; + + default: + return Char.IsLetterOrDigit(ch); + } + } + + // Attributes + string str; + int start; + int pos; + int end; + int mark; + } +} diff --git a/src/MarkdownDeep/TableSpec.cs b/src/MarkdownDeep/TableSpec.cs new file mode 100644 index 0000000..774cea7 --- /dev/null +++ b/src/MarkdownDeep/TableSpec.cs @@ -0,0 +1,219 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + internal enum ColumnAlignment + { + NA, + Left, + Right, + Center, + } + internal class TableSpec + { + public TableSpec() + { + } + + public bool LeadingBar; + public bool TrailingBar; + + public List Columns=new List(); + + public List Headers; + public List> Rows=new List>(); + + public List ParseRow(StringScanner p) + { + p.SkipLinespace(); + + if (p.eol) + return null; // Blank line ends the table + + bool bAnyBars=LeadingBar; + if (LeadingBar && !p.SkipChar('|')) + { + return null; + } + + // Create the row + List row = new List(); + + // Parse all columns except the last + + while (!p.eol) + { + // Find the next vertical bar + p.Mark(); + while (!p.eol && p.current != '|') + p.SkipEscapableChar(true); + + row.Add(p.Extract().Trim()); + + bAnyBars|=p.SkipChar('|'); + } + + // Require at least one bar to continue the table + if (!bAnyBars) + return null; + + // Add missing columns + while (row.Count < Columns.Count) + { + row.Add(" "); + } + + p.SkipEol(); + return row; + } + + internal void RenderRow(Markdown m, StringBuilder b, List row, string type) + { + for (int i=0; i"); + m.SpanFormatter.Format(b, row[i]); + b.Append("\n"); + } + } + + public void Render(Markdown m, StringBuilder b) + { + b.Append("\n"); + if (Headers != null) + { + b.Append("\n\n"); + RenderRow(m, b, Headers, "th"); + b.Append("\n\n"); + } + + b.Append("\n"); + foreach (var row in Rows) + { + b.Append("\n"); + RenderRow(m, b, row, "td"); + b.Append("\n"); + } + b.Append("\n"); + + b.Append("
\n"); + } + + public static TableSpec Parse(StringScanner p) + { + // Leading line space allowed + p.SkipLinespace(); + + // Quick check for typical case + if (p.current != '|' && p.current != ':' && p.current != '-') + return null; + + // Don't create the spec until it at least looks like one + TableSpec spec = null; + + // Leading bar, looks like a table spec + if (p.SkipChar('|')) + { + spec=new TableSpec(); + spec.LeadingBar=true; + } + + + // Process all columns + while (true) + { + // Parse column spec + p.SkipLinespace(); + + // Must have something in the spec + if (p.current == '|') + return null; + + bool AlignLeft = p.SkipChar(':'); + while (p.current == '-') + p.SkipForward(1); + bool AlignRight = p.SkipChar(':'); + p.SkipLinespace(); + + // Work out column alignment + ColumnAlignment col = ColumnAlignment.NA; + if (AlignLeft && AlignRight) + col = ColumnAlignment.Center; + else if (AlignLeft) + col = ColumnAlignment.Left; + else if (AlignRight) + col = ColumnAlignment.Right; + + if (p.eol) + { + // Not a spec? + if (spec == null) + return null; + + // Add the final spec? + spec.Columns.Add(col); + return spec; + } + + // We expect a vertical bar + if (!p.SkipChar('|')) + return null; + + // Create the table spec + if (spec==null) + spec=new TableSpec(); + + // Add the column + spec.Columns.Add(col); + + // Check for trailing vertical bar + p.SkipLinespace(); + if (p.eol) + { + spec.TrailingBar = true; + return spec; + } + + // Next column + } + } + } +} diff --git a/src/MarkdownDeep/Token.cs b/src/MarkdownDeep/Token.cs new file mode 100644 index 0000000..afdf2be --- /dev/null +++ b/src/MarkdownDeep/Token.cs @@ -0,0 +1,93 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MarkdownDeep +{ + /* + * Token is used to mark out various special parts of a string being + * formatted by SpanFormatter. + * + * Strings aren't actually stored in the token - just their offset + * and length in the input string. + * + * For performance, Token's are pooled and reused. + * See SpanFormatter.CreateToken() + */ + + // TokenType - what sort of token? + internal enum TokenType + { + Text, // Plain text, should be htmlencoded + HtmlTag, // Valid html tag, write out directly but escape &s; + Html, // Valid html, write out directly + open_em, // + close_em, // + open_strong, // + close_strong, // + code_span, // + br, //
+ + link, // , data = LinkInfo + img, // , data = LinkInfo + footnote, // Footnote reference + abbreviation, // An abbreviation, data is a reference to Abbrevation instance + + // These are used during construction of and tokens + opening_mark, // opening '*' or '_' + closing_mark, // closing '*' or '_' + internal_mark, // internal '*' or '_' + } + + // Token + internal class Token + { + // Constructor + public Token(TokenType type, int startOffset, int length) + { + this.type = type; + this.startOffset = startOffset; + this.length = length; + } + + // Constructor + public Token(TokenType type, object data) + { + this.type = type; + this.data = data; + } + + public override string ToString() + { + if (true || data == null) + { + return string.Format("{0} - {1} - {2}", type.ToString(), startOffset, length); + } + else + { + return string.Format("{0} - {1} - {2} -> {3}", type.ToString(), startOffset, length, data.ToString()); + } + } + + public TokenType type; + public int startOffset; + public int length; + public object data; + } + +} diff --git a/src/MarkdownDeep/Utils.cs b/src/MarkdownDeep/Utils.cs new file mode 100644 index 0000000..2e8b820 --- /dev/null +++ b/src/MarkdownDeep/Utils.cs @@ -0,0 +1,495 @@ +// +// MarkdownDeep - http://www.toptensoftware.com/markdowndeep +// Copyright (C) 2010-2011 Topten Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in +// compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace MarkdownDeep +{ + /* + * Various utility and extension methods + */ + static class Utils + { + // Extension method. Get the last item in a list (or null if empty) + public static T Last(this List list) where T:class + { + if (list.Count > 0) + return list[list.Count - 1]; + else + return null; + } + + // Extension method. Get the first item in a list (or null if empty) + public static T First(this List list) where T : class + { + if (list.Count > 0) + return list[0]; + else + return null; + } + + // Extension method. Use a list like a stack + public static void Push(this List list, T value) where T : class + { + list.Add(value); + } + + // Extension method. Remove last item from a list + public static T Pop(this List list) where T : class + { + if (list.Count == 0) + return null; + else + { + T val = list[list.Count - 1]; + list.RemoveAt(list.Count - 1); + return val; + } + } + + + // Scan a string for a valid identifier. Identifier must start with alpha or underscore + // and can be followed by alpha, digit or underscore + // Updates `pos` to character after the identifier if matched + public static bool ParseIdentifier(string str, ref int pos, ref string identifer) + { + if (pos >= str.Length) + return false; + + // Must start with a letter or underscore + if (!char.IsLetter(str[pos]) && str[pos] != '_') + { + return false; + } + + // Find the end + int startpos = pos; + pos++; + while (pos < str.Length && (char.IsDigit(str[pos]) || char.IsLetter(str[pos]) || str[pos] == '_')) + pos++; + + // Return it + identifer = str.Substring(startpos, pos - startpos); + return true; + } + + // Skip over anything that looks like a valid html entity (eg: &, {, &#nnn) etc... + // Updates `pos` to character after the entity if matched + public static bool SkipHtmlEntity(string str, ref int pos, ref string entity) + { + if (str[pos] != '&') + return false; + + int savepos = pos; + int len = str.Length; + int i = pos+1; + + // Number entity? + bool bNumber=false; + bool bHex = false; + if (i < len && str[i] == '#') + { + bNumber = true; + i++; + + // Hex identity? + if (i < len && (str[i] == 'x' || str[i] == 'X')) + { + bHex = true; + i++; + } + } + + // Parse the content + int contentpos = i; + while (i < len) + { + char ch=str[i]; + + if (bHex) + { + if (!(char.IsDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))) + break; + } + + else if (bNumber) + { + if (!char.IsDigit(ch)) + break; + } + else if (!char.IsLetterOrDigit(ch)) + break; + + i++; + } + + // Quit if ran out of string + if (i == len) + return false; + + // Quit if nothing in the content + if (i == contentpos) + return false; + + // Quit if didn't find a semicolon + if (str[i] != ';') + return false; + + // Looks good... + pos = i + 1; + + entity = str.Substring(savepos, pos - savepos); + return true; + } + + // Randomize a string using html entities; + public static void HtmlRandomize(StringBuilder dest, string str) + { + // Deterministic random seed + int seed = 0; + foreach (char ch in str) + { + seed = unchecked(seed + ch); + } + Random r = new Random(seed); + + // Randomize + foreach (char ch in str) + { + int x = r.Next() % 100; + if (x > 90 && ch != '@') + { + dest.Append(ch); + } + else if (x > 45) + { + dest.Append("&#"); + dest.Append(((int)ch).ToString()); + dest.Append(";"); + } + else + { + dest.Append("&#x"); + dest.Append(((int)ch).ToString("x")); + dest.Append(";"); + } + + } + } + + // Like HtmlEncode, but don't escape &'s that look like html entities + public static void SmartHtmlEncodeAmpsAndAngles(StringBuilder dest, string str) + { + if (str == null) + return; + + for (int i=0; i': + dest.Append(">"); + break; + + case '\"': + dest.Append("""); + break; + + default: + dest.Append(str[i]); + break; + } + } + } + + + // Like HtmlEncode, but only escape &'s that don't look like html entities + public static void SmartHtmlEncodeAmps(StringBuilder dest, string str, int startOffset, int len) + { + int end = startOffset + len; + for (int i = startOffset; i < end; i++) + { + switch (str[i]) + { + case '&': + int start = i; + string unused = null; + if (SkipHtmlEntity(str, ref i, ref unused)) + { + dest.Append(str, start, i - start); + i--; + } + else + { + dest.Append("&"); + } + break; + + default: + dest.Append(str[i]); + break; + } + } + } + + // Check if a string is in an array of strings + public static bool IsInList(string str, string[] list) + { + foreach (var t in list) + { + if (string.Compare(t, str) == 0) + return true; + } + return false; + } + + // Check if a url is "safe" (we require urls start with valid protocol) + // Definitely don't allow "javascript:" or any of it's encodings. + public static bool IsSafeUrl(string url) + { + if (!url.StartsWith("http://") && !url.StartsWith("https://") && !url.StartsWith("ftp://")) + return false; + + return true; + } + + // Check if a character is escapable in markdown + public static bool IsEscapableChar(char ch, bool ExtraMode) + { + switch (ch) + { + case '\\': + case '`': + case '*': + case '_': + case '{': + case '}': + case '[': + case ']': + case '(': + case ')': + case '>': // Not in markdown documentation, but is in markdown.pl + case '#': + case '+': + case '-': + case '.': + case '!': + return true; + + case ':': + case '|': + case '=': // Added for escaping Setext H1 + case '<': + return ExtraMode; + } + + return false; + } + + // Extension method. Skip an escapable character, or one normal character + public static void SkipEscapableChar(this StringScanner p, bool ExtraMode) + { + if (p.current == '\\' && IsEscapableChar(p.CharAtOffset(1), ExtraMode)) + { + p.SkipForward(2); + } + else + { + p.SkipForward(1); + } + } + + + // Remove the markdown escapes from a string + public static string UnescapeString(string str, bool ExtraMode) + { + if (str == null || str.IndexOf('\\')==-1) + return str; + + var b = new StringBuilder(); + for (int i = 0; i < str.Length; i++) + { + if (str[i] == '\\' && i+1 url is email, web address or neither. + * + * They are not intended as validating checks. + * + * (use of Regex for more correct test unnecessarily + * slowed down some test documents by up to 300%.) + */ + + // Check if a string looks like an email address + public static bool IsEmailAddress(string str) + { + int posAt = str.IndexOf('@'); + if (posAt < 0) + return false; + + int posLastDot = str.LastIndexOf('.'); + if (posLastDot < posAt) + return false; + + return true; + } + + // Check if a string looks like a url + public static bool IsWebAddress(string str) + { + return str.StartsWith("http://") || + str.StartsWith("https://") || + str.StartsWith("ftp://") || + str.StartsWith("file://"); + } + + // Check if a string is a valid HTML ID identifier + internal static bool IsValidHtmlID(string str) + { + if (String.IsNullOrEmpty(str)) + return false; + + // Must start with a letter + if (!Char.IsLetter(str[0])) + return false; + + // Check the rest + for (int i = 0; i < str.Length; i++) + { + char ch = str[i]; + if (Char.IsLetterOrDigit(ch) || ch == '_' || ch == '-' || ch == ':' || ch == '.') + continue; + + return false; + } + + // OK + return true; + } + + // Strip the trailing HTML ID from a header string + // ie: ## header text ## {#} + // ^start ^out end ^end + // + // Returns null if no header id + public static string StripHtmlID(string str, int start, ref int end) + { + // Skip trailing whitespace + int pos = end - 1; + while (pos >= start && Char.IsWhiteSpace(str[pos])) + { + pos--; + } + + // Skip closing '{' + if (pos < start || str[pos] != '}') + return null; + + int endId = pos; + pos--; + + // Find the opening '{' + while (pos >= start && str[pos] != '{') + pos--; + + // Check for the # + if (pos < start || str[pos + 1] != '#') + return null; + + // Extract and check the ID + int startId = pos + 2; + string strID = str.Substring(startId, endId - startId); + if (!IsValidHtmlID(strID)) + return null; + + // Skip any preceeding whitespace + while (pos > start && Char.IsWhiteSpace(str[pos - 1])) + pos--; + + // Done! + end = pos; + return strID; + } + + public static bool IsUrlFullyQualified(string url) + { + return url.Contains("://") || url.StartsWith("mailto:"); + } + + } +} diff --git a/src/MarkdownDeepTests/AutoHeaderIDTests.cs b/src/MarkdownDeepTests/AutoHeaderIDTests.cs new file mode 100644 index 0000000..80de172 --- /dev/null +++ b/src/MarkdownDeepTests/AutoHeaderIDTests.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; + +namespace MarkdownDeepTests +{ + [TestFixture] + public class AutoHeaderIDTests + { + [SetUp] + public void SetUp() + { + m = new Markdown(); + m.AutoHeadingIDs = true; + m.ExtraMode = true; + } + + Markdown m; + + /* Tests for pandoc style header ID generation */ + /* Tests are based on the examples in the pandoc documentation */ + + [Test] + public void Simple() + { + Assert.AreEqual(@"header-identifiers-in-html", + m.MakeUniqueHeaderID(@"Header identifiers in HTML")); + } + + [Test] + public void WithPunctuation() + { + Assert.AreEqual(@"dogs--in-my-house", + m.MakeUniqueHeaderID(@"Dogs?--in *my* house?")); + } + + [Test] + public void WithLinks() + { + Assert.AreEqual(@"html-s5-rtf", + m.MakeUniqueHeaderID(@"[HTML](#html), [S5](#S5), [RTF](#rtf)")); + } + + [Test] + public void WithLeadingNumbers() + { + Assert.AreEqual(@"applications", + m.MakeUniqueHeaderID(@"3. Applications")); + } + + [Test] + public void RevertToSection() + { + Assert.AreEqual(@"section", + m.MakeUniqueHeaderID(@"!!!")); + } + + [Test] + public void Duplicates() + { + Assert.AreEqual(@"heading", + m.MakeUniqueHeaderID(@"heading")); + Assert.AreEqual(@"heading-1", + m.MakeUniqueHeaderID(@"heading")); + Assert.AreEqual(@"heading-2", + m.MakeUniqueHeaderID(@"heading")); + } + + } +} \ No newline at end of file diff --git a/src/MarkdownDeepTests/AutoLinkTests.cs b/src/MarkdownDeepTests/AutoLinkTests.cs new file mode 100644 index 0000000..6ca40a3 --- /dev/null +++ b/src/MarkdownDeepTests/AutoLinkTests.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MarkdownDeep; +using NUnit.Framework; + +namespace MarkdownDeepTests +{ + [TestFixture] + class AutoLinkTests + { + [SetUp] + public void SetUp() + { + m = new Markdown(); + s = new SpanFormatter(m); + } + + [Test] + public void http() + { + Assert.AreEqual("pre http://url.com post", + s.Format("pre post")); + } + + [Test] + public void https() + { + Assert.AreEqual("pre https://url.com post", + s.Format("pre post")); + } + + [Test] + public void ftp() + { + Assert.AreEqual("pre ftp://url.com post", + s.Format("pre post")); + } + + Markdown m; + SpanFormatter s; + } +} diff --git a/src/MarkdownDeepTests/BlockLevelTests.cs b/src/MarkdownDeepTests/BlockLevelTests.cs new file mode 100644 index 0000000..d5a875f --- /dev/null +++ b/src/MarkdownDeepTests/BlockLevelTests.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; +using System.Reflection; + +namespace MarkdownDeepTests +{ + [TestFixture] + class BlockLevelTests + { + public static IEnumerable GetTests() + { + return Utils.GetTests("blocktests"); + } + + + [Test, TestCaseSource("GetTests")] + public void Test(string resourceName) + { + Utils.RunResourceTest(resourceName); + } + } +} diff --git a/src/MarkdownDeepTests/BlockProcessorTests.cs b/src/MarkdownDeepTests/BlockProcessorTests.cs new file mode 100644 index 0000000..917733f --- /dev/null +++ b/src/MarkdownDeepTests/BlockProcessorTests.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; + +namespace MarkdownDeepTests +{ + [TestFixture] + class BlockProcessorTests + { + [SetUp] + public void Setup() + { + p = new BlockProcessor(new Markdown(), false); + } + + [Test] + public void SingleLineParagraph() + { + var b = p.Process("paragraph"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.p, b[0].blockType); + Assert.AreEqual("paragraph", b[0].Content); + } + + [Test] + public void MultilineParagraph() + { + var b = p.Process("l1\nl2\n\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.p, b[0].blockType); + Assert.AreEqual("l1\nl2", b[0].Content); + } + + [Test] + public void SetExtH1() + { + var b = p.Process("heading\n===\n\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.h1, b[0].blockType); + Assert.AreEqual("heading", b[0].Content); + } + + [Test] + public void SetExtH2() + { + var b = p.Process("heading\n---\n\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.h2, b[0].blockType); + Assert.AreEqual("heading", b[0].Content); + } + + [Test] + public void SetExtHeadingInParagraph() + { + var b = p.Process("p1\nheading\n---\np2\n"); + Assert.AreEqual(3, b.Count); + + Assert.AreEqual(BlockType.p, b[0].blockType); + Assert.AreEqual("p1", b[0].Content); + + Assert.AreEqual(BlockType.h2, b[1].blockType); + Assert.AreEqual("heading", b[1].Content); + + Assert.AreEqual(BlockType.p, b[2].blockType); + Assert.AreEqual("p2", b[2].Content); + } + + [Test] + public void AtxHeaders() + { + var b = p.Process("#heading#\nparagraph\n"); + Assert.AreEqual(2, b.Count); + + Assert.AreEqual(BlockType.h1, b[0].blockType); + Assert.AreEqual("heading", b[0].Content); + + Assert.AreEqual(BlockType.p, b[1].blockType); + Assert.AreEqual("paragraph", b[1].Content); + } + + [Test] + public void AtxHeadingInParagraph() + { + var b = p.Process("p1\n## heading ##\np2\n"); + + Assert.AreEqual(3, b.Count); + + Assert.AreEqual(BlockType.p, b[0].blockType); + Assert.AreEqual("p1", b[0].Content); + + Assert.AreEqual(BlockType.h2, b[1].blockType); + Assert.AreEqual("heading", b[1].Content); + + Assert.AreEqual(BlockType.p, b[2].blockType); + Assert.AreEqual("p2", b[2].Content); + } + + [Test] + public void CodeBlock() + { + var b = p.Process("\tcode1\n\t\tcode2\n\tcode3\nparagraph"); + Assert.AreEqual(2, b.Count); + + Block cb = b[0] as Block; + Assert.AreEqual("code1\n\tcode2\ncode3\n", cb.Content); + + Assert.AreEqual(BlockType.p, b[1].blockType); + Assert.AreEqual("paragraph", b[1].Content); + } + + [Test] + public void HtmlBlock() + { + var b = p.Process("
\n
\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.html, b[0].blockType); + Assert.AreEqual("
\n
\n", b[0].Content); + } + + [Test] + public void HtmlCommentBlock() + { + var b = p.Process("\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.html, b[0].blockType); + Assert.AreEqual("\n", b[0].Content); + } + + [Test] + public void HorizontalRules() + { + var b = p.Process("---\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.hr, b[0].blockType); + + b = p.Process("___\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.hr, b[0].blockType); + + b = p.Process("***\n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.hr, b[0].blockType); + + b = p.Process(" - - - \n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.hr, b[0].blockType); + + b = p.Process(" _ _ _ \n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.hr, b[0].blockType); + + b = p.Process(" * * * \n"); + Assert.AreEqual(1, b.Count); + Assert.AreEqual(BlockType.hr, b[0].blockType); + } + + + BlockProcessor p; + } +} diff --git a/src/MarkdownDeepTests/CodeSpanTests.cs b/src/MarkdownDeepTests/CodeSpanTests.cs new file mode 100644 index 0000000..881593a --- /dev/null +++ b/src/MarkdownDeepTests/CodeSpanTests.cs @@ -0,0 +1,59 @@ + using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; + +namespace MarkdownDeepTests +{ + [TestFixture] + public class CodeSpanTests + { + [SetUp] + public void SetUp() + { + f = new SpanFormatter(new Markdown()); + } + + SpanFormatter f; + + [Test] + public void SingleTick() + { + Assert.AreEqual("pre code span post", + f.Format("pre `code span` post")); + } + + [Test] + public void SingleTickWithSpaces() + { + Assert.AreEqual("pre code span post", + f.Format("pre ` code span ` post")); + } + + [Test] + public void MultiTick() + { + Assert.AreEqual("pre code span post", + f.Format("pre ````code span```` post")); + } + + [Test] + public void MultiTickWithEmbeddedTicks() + { + Assert.AreEqual("pre `code span` post", + f.Format("pre ```` `code span` ```` post")); + } + + [Test] + public void ContentEncoded() + { + Assert.AreEqual("pre <div> post", + f.Format("pre ````
```` post")); + Assert.AreEqual("pre &amp; post", + f.Format("pre ```` & ```` post")); + } + + } +} diff --git a/src/MarkdownDeepTests/EmphasisTests.cs b/src/MarkdownDeepTests/EmphasisTests.cs new file mode 100644 index 0000000..75400ae --- /dev/null +++ b/src/MarkdownDeepTests/EmphasisTests.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; + +namespace MarkdownDeepTests +{ + [TestFixture] + public class EmphasisTests + { + [SetUp] + public void SetUp() + { + f = new SpanFormatter(new Markdown()); + } + + SpanFormatter f; + + [Test] + public void PlainText() + { + Assert.AreEqual("This is plain text", + f.Format("This is plain text")); + } + + + [Test] + public void em_simple() + { + Assert.AreEqual("This is em text", + f.Format("This is *em* text")); + Assert.AreEqual("This is em text", + f.Format("This is _em_ text")); + } + + [Test] + public void strong_simple() + { + Assert.AreEqual("This is strong text", + f.Format("This is **strong** text")); + Assert.AreEqual("This is strong text", + f.Format("This is __strong__ text")); + } + + [Test] + public void em_strong_lead_tail() + { + Assert.AreEqual("strong", + f.Format("__strong__")); + Assert.AreEqual("strong", + f.Format("**strong**")); + Assert.AreEqual("em", + f.Format("_em_")); + Assert.AreEqual("em", + f.Format("*em*")); + } + + + [Test] + public void strongem() + { + Assert.AreEqual("strongem", + f.Format("***strongem***")); + Assert.AreEqual("strongem", + f.Format("___strongem___")); + } + + [Test] + public void no_strongem_if_spaces() + { + Assert.AreEqual("pre * notem *", + f.Format("pre * notem *")); + Assert.AreEqual("pre ** notstrong **", + f.Format("pre ** notstrong **")); + Assert.AreEqual("pre *Apples *Bananas *Oranges", + f.Format("pre *Apples *Bananas *Oranges")); + } + + [Test] + public void em_in_word() + { + Assert.AreEqual("unfriggingbelievable", + f.Format("un*frigging*believable")); + } + + [Test] + public void strong_in_word() + { + Assert.AreEqual("unfriggingbelievable", + f.Format("un**frigging**believable")); + } + + [Test] + public void combined_1() + { + Assert.AreEqual("test test", + f.Format("***test test***")); + } + + [Test] + public void combined_2() + { + Assert.AreEqual("test test", + f.Format("___test test___")); + } + + + [Test] + public void combined_3() + { + Assert.AreEqual("test test", + f.Format("*test **test***")); + } + + + [Test] + public void combined_4() + { + Assert.AreEqual("test test", + f.Format("**test *test***")); + } + + + [Test] + public void combined_5() + { + Assert.AreEqual("test test", + f.Format("***test* test**")); + } + + + [Test] + public void combined_6() + { + Assert.AreEqual("test test", + f.Format("***test** test*")); + } + + + [Test] + public void combined_7() + { + Assert.AreEqual("test test", + f.Format("***test* test**")); + } + + + [Test] + public void combined_8() + { + Assert.AreEqual("test test", + f.Format("**test *test***")); + } + + + [Test] + public void combined_9() + { + Assert.AreEqual("test test", + f.Format("*test **test***")); + } + + + [Test] + public void combined_10() + { + Assert.AreEqual("test test", + f.Format("_test __test___")); + } + + + [Test] + public void combined_11() + { + Assert.AreEqual("test test", + f.Format("__test _test___")); + } + + + [Test] + public void combined_12() + { + Assert.AreEqual("test test", + f.Format("___test_ test__")); + } + + + [Test] + public void combined_13() + { + Assert.AreEqual("test test", + f.Format("___test__ test_")); + } + + + [Test] + public void combined_14() + { + Assert.AreEqual("test test", + f.Format("___test_ test__")); + } + + + [Test] + public void combined_15() + { + Assert.AreEqual("test test", + f.Format("__test _test___")); + } + + + [Test] + public void combined_16() + { + Assert.AreEqual("test test", + f.Format("_test __test___")); + } + + [Test] + public void combined_17() + { + var fExtra = new SpanFormatter(new Markdown() { ExtraMode = true }); + Assert.AreEqual("Bold Italic", + fExtra.Format("__Bold__ _Italic_")); + } + + [Test] + public void combined_18() + { + var fExtra = new SpanFormatter(new Markdown() { ExtraMode = true }); + Assert.AreEqual("Emphasis, trailing", + fExtra.Format("_Emphasis_, trailing")); + } + + + + } +} diff --git a/src/MarkdownDeepTests/EscapeCharacterTests.cs b/src/MarkdownDeepTests/EscapeCharacterTests.cs new file mode 100644 index 0000000..18e5834 --- /dev/null +++ b/src/MarkdownDeepTests/EscapeCharacterTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; + +namespace MarkdownDeepTests +{ + [TestFixture] + public class EscapeCharacterTests + { + [SetUp] + public void SetUp() + { + f = new SpanFormatter(new Markdown()); + } + + SpanFormatter f; + + [Test] + public void AllEscapeCharacters() + { + Assert.AreEqual(@"pre \ ` * _ { } [ ] ( ) # + - . ! post", + f.Format(@"pre \\ \` \* \_ \{ \} \[ \] \( \) \# \+ \- \. \! post")); + } + + [Test] + public void SomeNonEscapableCharacters() + { + Assert.AreEqual( @"pre \q \% \? post", + f.Format(@"pre \q \% \? post")); + } + + [Test] + public void BackslashWithTwoDashes() + { + Assert.AreEqual(@"backslash with \-- two dashes", + f.Format(@"backslash with \\-- two dashes")); + } + + [Test] + public void BackslashWithGT() + { + Assert.AreEqual(@"backslash with \> greater", + f.Format(@"backslash with \\> greater")); + } + + [Test] + public void EscapeNotALink() + { + Assert.AreEqual(@"\[test](not a link)", + f.Format(@"\\\[test](not a link)")); + } + + [Test] + public void NoEmphasis() + { + Assert.AreEqual(@"\*no emphasis*", + f.Format(@"\\\*no emphasis*")); + } + } +} \ No newline at end of file diff --git a/src/MarkdownDeepTests/ExtraMode.cs b/src/MarkdownDeepTests/ExtraMode.cs new file mode 100644 index 0000000..6595301 --- /dev/null +++ b/src/MarkdownDeepTests/ExtraMode.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; +using System.Reflection; + +namespace MarkdownDeepTests +{ + [TestFixture] + class ExtraModeTests + { + public static IEnumerable GetTests() + { + return Utils.GetTests("extramode"); + } + + + [Test, TestCaseSource("GetTests")] + public void Test(string resourceName) + { + Utils.RunResourceTest(resourceName); + } + } +} diff --git a/src/MarkdownDeepTests/GithubMode.cs b/src/MarkdownDeepTests/GithubMode.cs new file mode 100644 index 0000000..0afd56c --- /dev/null +++ b/src/MarkdownDeepTests/GithubMode.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; +using System.Reflection; + +namespace MarkdownDeepTests +{ + [TestFixture] + class GithubModeTests + { + public static IEnumerable GetTests() + { + return Utils.GetTests("githubmode"); + } + + + [Test, TestCaseSource("GetTests")] + public void Test(string resourceName) + { + Utils.RunResourceTest(resourceName); + } + } +} diff --git a/src/MarkdownDeepTests/HtmlTagTests.cs b/src/MarkdownDeepTests/HtmlTagTests.cs new file mode 100644 index 0000000..ac62758 --- /dev/null +++ b/src/MarkdownDeepTests/HtmlTagTests.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using MarkdownDeep; + +namespace MarkdownDeepTests +{ + [TestFixture] + class HtmlTagTests + { + [SetUp] + public void SetUp() + { + m_pos = 0; + } + + [Test] + public void Unquoted() + { + string str = @"
"; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "div"); + Assert.AreEqual(tag.closing, false); + Assert.AreEqual(tag.closed, false); + Assert.AreEqual(tag.attributes.Count, 2); + Assert.AreEqual(tag.attributes["x"], "1"); + Assert.AreEqual(tag.attributes["y"], "2"); + Assert.AreEqual(m_pos, str.Length); + + } + + [Test] + public void Quoted() + { + string str = @"
"; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "div"); + Assert.AreEqual(tag.closing, false); + Assert.AreEqual(tag.closed, false); + Assert.AreEqual(tag.attributes.Count, 2); + Assert.AreEqual(tag.attributes["x"], "1"); + Assert.AreEqual(tag.attributes["y"], "2"); + Assert.AreEqual(m_pos, str.Length); + + } + + [Test] + public void Empty() + { + string str = @"
"; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "div"); + Assert.AreEqual(tag.closing, false); + Assert.AreEqual(tag.closed, false); + Assert.AreEqual(tag.attributes.Count, 0); + Assert.AreEqual(m_pos, str.Length); + + } + + [Test] + public void Closed() + { + string str = @"
"; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "div"); + Assert.AreEqual(tag.closing, false); + Assert.AreEqual(tag.closed, true); + Assert.AreEqual(tag.attributes.Count, 0); + Assert.AreEqual(m_pos, str.Length); + + } + + [Test] + public void ClosedWithAttribs() + { + string str = @"
"; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "div"); + Assert.AreEqual(tag.closing, false); + Assert.AreEqual(tag.closed, true); + Assert.AreEqual(tag.attributes.Count, 2); + Assert.AreEqual(tag.attributes["x"], "1"); + Assert.AreEqual(tag.attributes["y"], "2"); + Assert.AreEqual(m_pos, str.Length); + + } + + [Test] + public void Closing() + { + string str = @"
"; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "div"); + Assert.AreEqual(tag.closing, true); + Assert.AreEqual(tag.closed, false); + Assert.AreEqual(tag.attributes.Count, 0); + Assert.AreEqual(m_pos, str.Length); + + } + + [Test] + public void Comment() + { + string str = @""; + HtmlTag tag = HtmlTag.Parse(str, ref m_pos); + + Assert.AreEqual(tag.name, "!"); + Assert.AreEqual(tag.closing, false); + Assert.AreEqual(tag.closed, true); + Assert.AreEqual(tag.attributes.Count, 1); + Assert.AreEqual(tag.attributes["content"], " comment "); + Assert.AreEqual(m_pos, str.Length); + } + + [Test] + public void NonValuedAttribute() + { + string str = @"