diff --git a/lib/definitions.dart b/lib/definitions.dart index 2f9942d..c696c9c 100644 --- a/lib/definitions.dart +++ b/lib/definitions.dart @@ -568,6 +568,18 @@ class Strong extends Inline { } +class Strikeout extends Inline { + Inlines contents; + + Strikeout(this.contents); + + String toString() => 'Strikeout $contents'; + + bool operator== (obj) => obj is Strikeout && + _iterableEquality.equals(contents, obj.contents); +} + + abstract class Link extends Inline { Inlines label; Target target; diff --git a/lib/html_writer.dart b/lib/html_writer.dart index 0921e0a..c5d2105 100644 --- a/lib/html_writer.dart +++ b/lib/html_writer.dart @@ -179,6 +179,8 @@ class _HtmlBuilder extends StringBuffer { writeEmph(inline, stripped: stripped); } else if (inline is Strong) { writeStrong(inline, stripped: stripped); + } else if (inline is Strikeout) { + writeStrikeout(inline, stripped: stripped); } else if (inline is Link) { writeLink(inline, stripped: stripped); } else if (inline is Image) { @@ -241,6 +243,17 @@ class _HtmlBuilder extends StringBuffer { } + void writeStrikeout(Strikeout strikeout, {bool stripped: false}) { + if (!stripped) { + write(''); + } + writeInlines(strikeout.contents, stripped: stripped); + if (!stripped) { + write(''); + } + } + + void writeSmartQuote(SmartQuote quote, {bool stripped: false}) { // TODO different quotation styles if (quote.open && quote.close) { @@ -335,6 +348,7 @@ class HtmlWriter { return builder.toString(); } + static HtmlWriter commonmark = new HtmlWriter(Options.commonmark); static HtmlWriter strict = new HtmlWriter(Options.strict); static HtmlWriter defaults = new HtmlWriter(Options.defaults); } diff --git a/lib/markdown_parser.dart b/lib/markdown_parser.dart index 17e73b0..a77e0f9 100644 --- a/lib/markdown_parser.dart +++ b/lib/markdown_parser.dart @@ -68,7 +68,21 @@ class CommonMarkParser { Map _references; - CommonMarkParser(this._options, [this._references]); + String _inlineDelimiters; + String _strSpecialChars; + + CommonMarkParser(this._options, [this._references]) { + _inlineDelimiters = "_*"; + _strSpecialChars = " *_`![]&<\\"; + if (_options.smartPunctuation) { + _inlineDelimiters += "'\""; + _strSpecialChars += "'\".-"; + } + if (_options.strikeout) { + _inlineDelimiters += "~"; + _strSpecialChars += "~"; + } + } Document parse(String s) { // TODO separate preprocess option @@ -519,8 +533,9 @@ class CommonMarkParser { static RegExp _isSpace = new RegExp(r'^\s'); static RegExp _isPunctuation = new RegExp("^[\u{2000}-\u{206F}\u{2E00}-\u{2E7F}\\\\'!\"#\\\$%&\\(\\)\\*\\+,\\-\\.\\/:;<=>\\?@\\[\\]\\^_`\\{\\|\\}~]"); + Parser get scanDelims => new Parser((String s, Position pos) { - ParseResult testRes = oneOf(_options.smartPunctuation ? "*_'\"" : "*_").lookAhead.run(s, pos); + ParseResult testRes = oneOf(_inlineDelimiters).lookAhead.run(s, pos); if (!testRes.isSuccess) { return testRes; } @@ -544,6 +559,10 @@ class CommonMarkParser { canOpen = canOpen && (!rightFlanking || _isPunctuation.hasMatch(charBefore)); canClose = canClose && (!leftFlanking || _isPunctuation.hasMatch(charAfter)); } + if (c == '~' && numDelims < 2) { + canOpen = false; + canClose = false; + } return res.copy(value: [numDelims, canOpen, canClose, c]); }); @@ -618,6 +637,17 @@ class CommonMarkParser { inlines.add(inline); count--; } + } else if (char == "~") { + if (count & 1 == 1) { + inlines.add(new Str("~")); + count--; + } + while (count > 0) { + inline = new Strikeout(inlines); + inlines = new Inlines(); + inlines.add(inline); + count -= 2; + } } else { if (count & 1 == 1) { inline = new Emph(inlines); @@ -930,7 +960,6 @@ class CommonMarkParser { }; - String get _strSpecialChars => _options.smartPunctuation ? " *_`'\".-![]&<\\" : " *_`![]&<\\"; Parser get str => (noneOf(_strSpecialChars + "\n").many1 ^ (chars) => _transformString(chars.join())) | (oneOf(_strSpecialChars) ^ (chars) => _transformString(chars)) | (char("\n").notFollowedBy(spnl) ^ (_) => [new Str("\n")]); @@ -1793,6 +1822,7 @@ class CommonMarkParser { Parser get document => (block.manyUntil(eof) ^ (res) => new Document(processParsedBlocks(res))) % "document"; + static CommonMarkParser commonmark = new CommonMarkParser(Options.commonmark); static CommonMarkParser defaults = new CommonMarkParser(Options.defaults); static CommonMarkParser strict = new CommonMarkParser(Options.strict); } diff --git a/lib/markdown_writer.dart b/lib/markdown_writer.dart index dc671d0..1e1bb15 100644 --- a/lib/markdown_writer.dart +++ b/lib/markdown_writer.dart @@ -128,6 +128,10 @@ class _NotCheckedPart extends _InlinePart { }); } + if (_options.strikeout) { + content = content.replaceAll(new RegExp("~~"), r"\~~"); + } + if (!context.isHeader) { content = content.replaceAllMapped(_notHeaderRegExp1, (Match m) => m.group(1) + r"\" + m.group(2)); content = content.replaceAllMapped(_notHeaderRegExp2, (Match m) => m.group(1) + r"\" + m.group(2)); @@ -301,6 +305,8 @@ class _InlineRenderer { } } else if (inline is SmartQuote) { writeSmartQuote(inline, context: context); + } else if (inline is Strikeout) { + writeStrikeout(inline, context: context); } else if (inline is RawInline) { write(inline.contents); } else { @@ -364,6 +370,13 @@ class _InlineRenderer { } + void writeStrikeout(Strikeout strikeout, {_EscapeContext context: _EscapeContext.empty}) { + write("~~"); + writeInlines(strikeout.contents, context: context); + write("~~"); + } + + void writeLink(Link link) { if (link is InlineLink) { write('['); @@ -708,6 +721,7 @@ class MarkdownWriter { return builder.toString(); } + static const MarkdownWriter commonmark = const MarkdownWriter(Options.commonmark); static const MarkdownWriter strict = const MarkdownWriter(Options.strict); static const MarkdownWriter defaults = const MarkdownWriter(Options.defaults); } diff --git a/lib/options.dart b/lib/options.dart index 14a7741..d120666 100644 --- a/lib/options.dart +++ b/lib/options.dart @@ -15,13 +15,17 @@ Target defaultLinkResolver(String normalizedReference, String reference) => null class Options { final bool smartPunctuation; + final bool strikeout; final LinkResolver linkResolver; const Options({ this.smartPunctuation: false, + this.strikeout: false, this.linkResolver: defaultLinkResolver }); - static const Options defaults = const Options(smartPunctuation: true); + static const Options commonmark = const Options(smartPunctuation: true); + static const Options defaults = const Options(smartPunctuation: true, + strikeout: true); static const Options strict = const Options(); } diff --git a/test/data/strikeout.txt b/test/data/strikeout.txt new file mode 100644 index 0000000..97b9673 --- /dev/null +++ b/test/data/strikeout.txt @@ -0,0 +1,37 @@ +## Strikeout tests + +. +~~Strikeout text~~ +. +

Strikeout text

+. + +. +~~__Strikeout text__~~ +. +

Strikeout text

+. + +. +Ins~~e~~ide w~~a~~ord +. +

Inseide waord

+. + +. +~\~No strikeout~~ +. +

~~No strikeout~~

+. + +. +\~~No strikeout~~ +. +

~~No strikeout~~

+. + +. +~No strikeout~ +. +

~No strikeout~

+. diff --git a/test/data/test_data.dart b/test/data/test_data.dart index 52c7ae0..21ec2bc 100644 --- a/test/data/test_data.dart +++ b/test/data/test_data.dart @@ -15,3 +15,6 @@ final Map markdownToMarkdown = _$markdownToMarkdownTests; @EmbedTests('additionalMarkdownToHtml.txt') final Map additionalMarkdownToHtml = _$additionalMarkdownToHtmlTests; + +@EmbedTests('strikeout.txt') +final Map strikeout = _$strikeoutTests; diff --git a/test/data/test_data.g.dart b/test/data/test_data.g.dart index 31eefcf..5a0b91b 100644 --- a/test/data/test_data.g.dart +++ b/test/data/test_data.g.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -// 2015-08-10T12:56:10.881Z +// 2015-08-10T14:52:17.270Z part of md_proc.test.data.test_data; @@ -4145,3 +4145,29 @@ bar ''', }; + +// ************************************************************************** +// Generator: EmbedTestsGenerator +// Target: final Map strikeout +// ************************************************************************** + +final Map _$strikeoutTests = { + r'''~~Strikeout text~~ +''': r'''

Strikeout text

+''', + r'''~~__Strikeout text__~~ +''': r'''

Strikeout text

+''', + r'''Ins~~e~~ide w~~a~~ord +''': r'''

Inseide waord

+''', + r'''~\~No strikeout~~ +''': r'''

~~No strikeout~~

+''', + r'''\~~No strikeout~~ +''': r'''

~~No strikeout~~

+''', + r'''~No strikeout~ +''': r'''

~No strikeout~

+''', +}; diff --git a/test/library_test.dart b/test/library_test.dart index 3bab148..821740e 100644 --- a/test/library_test.dart +++ b/test/library_test.dart @@ -22,6 +22,7 @@ void main() { tests("Additional", additionalMarkdownToHtml, mdToHtmlTest(Options.strict)); // Additional tests tests("SmartPunct", smartPunctuation, mdToHtmlTest(Options.defaults)); + tests("Strikeout", strikeout, mdToHtmlTest(Options.defaults)); // Markdown to markdown tests tests("md2md", markdownToMarkdown, mdToMdTest(Options.strict)); // Custom resolver diff --git a/test/service.dart b/test/service.dart index 08ef521..0e6343f 100644 --- a/test/service.dart +++ b/test/service.dart @@ -495,6 +495,20 @@ void serviceTests() { }); }); + t.group('Strikeout', () { + var strikeout = new Strikeout(new Inlines.from([new Str('Strikeout')])); + t.test('toString', () { + t.expect(strikeout.toString(), t.equals('Strikeout [Str "Strikeout"]')); + }); + t.test('==', () { + t.expect(strikeout, t.equals(new Strikeout(new Inlines.from([new Str('Strikeout')])))); + }); + t.test('!=', () { + t.expect(strikeout, t.isNot(t.equals(new Inlines.from([new Str('Emph')])))); + t.expect(strikeout, t.isNot(t.equals(null))); + }); + }); + t.group('InlineLink', () { var link = new InlineLink(new Inlines.from([new Str('Dart')]), new Target('https://www.dartlang.org/', null)); t.test('toString', () {