diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e9d84..14a160f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.6 + +- **FIXED**: Fixed escaping of special characters. [#6] + ## 0.0.5 - **FIXED**: Fixed parsing url such as `[text](https://domain.com/path(with)brackets)`. diff --git a/lib/src/parser.dart b/lib/src/parser.dart index 0addd8e..ad97fa3 100644 --- a/lib/src/parser.dart +++ b/lib/src/parser.dart @@ -268,6 +268,24 @@ final Uint8List _kind = Uint8List(2048) ..[124] = 1 // | - spoiler (double) ..[126] = 1; // ~ - strikethrough (double) +/// Markdown provides backslash escapes for the following characters: +final Uint8List _escapedChars = Uint8List(126) + ..[33] = 1 // ! Exclamation mark + ..[35] = 1 // # Hash mark + ..[40] = 1 // ( Left parenthesis + ..[41] = 1 // ) Right parenthesis + ..[42] = 1 // * Asterisk + ..[43] = 1 // + Plus sign + ..[45] = 1 // - Minus sign (hyphen) + ..[46] = 1 // . Period + ..[91] = 1 // [ Left square bracket + ..[92] = 1 // \ Backslash + ..[93] = 1 // ] Right square bracket + ..[95] = 1 // _ Underscore + ..[96] = 1 // ` Backtick + ..[123] = 1 // { Left curly brace + ..[125] = 1; // } Right curly brace + List _parseInlineSpans(String text) { if (text.isEmpty) return const []; @@ -399,7 +417,7 @@ List _parseInlineSpans(String text) { final spanLength = end - start - excluded.length; if (spanLength > 0) { // If the span has any valid text - final bytes = Uint8List(spanLength); + final bytes = Uint16List(spanLength); var j = 0; // Index for the new bytes array for (var i = start; i < end; i++) { if (excluded.contains(i)) continue; // Skip excluded indices @@ -435,13 +453,17 @@ List _parseInlineSpans(String text) { for (var i = 0; i < length; i++) { final ch = codes[i]; - // Check for escaped characters if (ch == esc /* \ */ && i != length - 1) { - hasExcluded = true; // We have an escaped character - excluded.add(i); // Exclude this character as it is escaped - i++; // skip next char - continue; + final nextChar = codes[i + 1]; + // Check if the next character is an escaped character + if (_escapedChars.length > nextChar && _escapedChars[nextChar] == 1) { + hasExcluded = true; // We have an escaped character + excluded.add(i); // Exclude this character as it is escaped + i++; // skip next char + + continue; + } } // If this character is part of a link or image, skip it diff --git a/pubspec.yaml b/pubspec.yaml index e7c17b6..0aaf0b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,7 +8,7 @@ description: > Markdown library written in Dart. It can parse and display Markdown. -version: 0.0.5 +version: 0.0.6 homepage: https://github.com/DoctorinaAI/md repository: https://github.com/DoctorinaAI/md diff --git a/test/parser/parser_test.dart b/test/parser/parser_test.dart index d4bba9d..2938687 100644 --- a/test/parser/parser_test.dart +++ b/test/parser/parser_test.dart @@ -369,6 +369,39 @@ void main() => group('Parse', () { ), ); }); + + group('Parse escaped characters', () { + const escapedCharacterTests = { + r'\\': r'\', // Backslash + r'\`': '`', // Backtick + r'\*': '*', // Asterisk + r'\_': '_', // Underscore + r'\{': '{', // Left curly brace + r'\}': '}', // Right curly brace + r'\[': '[', // Left square bracket + r'\]': ']', // Right square bracket + r'\(': '(', // Left parenthesis + r'\)': ')', // Right parenthesis + r'\#': '#', // Hash mark + r'\+': '+', // Plus sign + r'\-': '-', // Minus sign (hyphen) + r'\.': '.', // Period + r'\!': '!', // Exclamation mark + }; + + for (final entry in escapedCharacterTests.entries) { + test('should correctly unescape "${entry.key}"', () { + final markdown = markdownDecoder.convert(entry.key); + expect(markdown.text, entry.value); + }); + } + + test('should not escape non-special characters', () { + const input = r'\no esc\aped strin\g'; + final markdown = markdownDecoder.convert(input); + expect(markdown.text, input); + }); + }); }); const String _testSample = r'''