Fix HTML escape issues (#484)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c6672f..38d261b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
character references.
* Add a new syntax `SoftLineBreakSyntax` to remove the single space before the
line ending.
+* Add a new syntax `EscapeHtmlSyntax` to encode (`"`), (`<`), (`>`) and (`&`).
* Add an option `caseSensitive` to `TextSyntax`.
## 6.0.1
diff --git a/benchmark/output.html b/benchmark/output.html
index 16689fd..e3831bd 100644
--- a/benchmark/output.html
+++ b/benchmark/output.html
@@ -7,58 +7,58 @@
sample of real-world markdown:</p>
<p>Tests are specified using the top-level <a href="https://pub.dev/documentation/test_core/latest/test_core/test.html"><code>test()</code></a> function, and test
assertions are made using <a href="https://pub.dev/documentation/test_api/latest/test_api/expect.html"><code>expect()</code></a>:</p>
-<pre><code class="language-dart">import "package:test/test.dart";
+<pre><code class="language-dart">import "package:test/test.dart";
void main() {
- test("String.split() splits the string on the delimiter", () {
- var string = "foo,bar,baz";
- expect(string.split(","), equals(["foo", "bar", "baz"]));
+ test("String.split() splits the string on the delimiter", () {
+ var string = "foo,bar,baz";
+ expect(string.split(","), equals(["foo", "bar", "baz"]));
});
- test("String.trim() removes surrounding whitespace", () {
- var string = " foo ";
- expect(string.trim(), equals("foo"));
+ test("String.trim() removes surrounding whitespace", () {
+ var string = " foo ";
+ expect(string.trim(), equals("foo"));
});
}
</code></pre>
<p>Tests can be grouped together using the [<code>group()</code>] function. Each group's
description is added to the beginning of its test's descriptions.</p>
-<pre><code class="language-dart">import "package:test/test.dart";
+<pre><code class="language-dart">import "package:test/test.dart";
void main() {
- group("String", () {
- test(".split() splits the string on the delimiter", () {
- var string = "foo,bar,baz";
- expect(string.split(","), equals(["foo", "bar", "baz"]));
+ group("String", () {
+ test(".split() splits the string on the delimiter", () {
+ var string = "foo,bar,baz";
+ expect(string.split(","), equals(["foo", "bar", "baz"]));
});
- test(".trim() removes surrounding whitespace", () {
- var string = " foo ";
- expect(string.trim(), equals("foo"));
+ test(".trim() removes surrounding whitespace", () {
+ var string = " foo ";
+ expect(string.trim(), equals("foo"));
});
});
- group("int", () {
- test(".remainder() returns the remainder of division", () {
+ group("int", () {
+ test(".remainder() returns the remainder of division", () {
expect(11.remainder(3), equals(2));
});
- test(".toRadixString() returns a hex string", () {
- expect(11.toRadixString(16), equals("b"));
+ test(".toRadixString() returns a hex string", () {
+ expect(11.toRadixString(16), equals("b"));
});
});
}
</code></pre>
<p>Any matchers from the <a href="https://pub.dev/documentation/matcher/latest/matcher/matcher-library.html"><code>matcher</code></a> package can be used with <code>expect()</code>
to do complex validations:</p>
-<pre><code class="language-dart">import "package:test/test.dart";
+<pre><code class="language-dart">import "package:test/test.dart";
void main() {
- test(".split() splits the string on the delimiter", () {
- expect("foo,bar,baz", allOf([
- contains("foo"),
- isNot(startsWith("bar")),
- endsWith("baz")
+ test(".split() splits the string on the delimiter", () {
+ expect("foo,bar,baz", allOf([
+ contains("foo"),
+ isNot(startsWith("bar")),
+ endsWith("baz")
]));
});
}
@@ -66,9 +66,9 @@
<h2>Running Tests</h2>
<p>A single test file can be run just using <code>pub run test:test path/to/test.dart</code>
(on Dart 1.10, this can be shortened to <code>pub run test path/to/test.dart</code>).</p>
-<p><img src="https://raw.githubusercontent.com/dart-lang/test/master/image/test1.gif" alt="Single file being run via pub run"" /></p>
+<p><img src="https://raw.githubusercontent.com/dart-lang/test/master/image/test1.gif" alt="Single file being run via pub run"" /></p>
<p>Many tests can be run at a time using <code>pub run test:test path/to/dir</code>.</p>
-<p><img src="https://raw.githubusercontent.com/dart-lang/test/master/image/test2.gif" alt="Directory being run via "pub run"." /></p>
+<p><img src="https://raw.githubusercontent.com/dart-lang/test/master/image/test2.gif" alt="Directory being run via "pub run"." /></p>
<p>It's also possible to run a test on the Dart VM only by invoking it using <code>dart path/to/test.dart</code>, but this doesn't load the full test runner and will be
missing some features.</p>
<p>The test runner considers any file that ends with <code>_test.dart</code> to be a test
@@ -78,7 +78,7 @@
well by passing <code>pub run test:test -p chrome path/to/test.dart</code>.
<code>test</code> will take care of starting the browser and loading the tests, and all
the results will be reported on the command line just like for VM tests. In
-fact, you can even run tests on both platforms with a single command: <code>pub run test:test -p "chrome,vm" path/to/test.dart</code>.</p>
+fact, you can even run tests on both platforms with a single command: <code>pub run test:test -p "chrome,vm" path/to/test.dart</code>.</p>
<h3>Restricting Tests to Certain Platforms</h3>
<p>Some test files only make sense to run on particular platforms. They may use
<code>dart:html</code> or <code>dart:io</code>, they might test Windows' particular filesystem
@@ -86,17 +86,17 @@
<a href="https://pub.dev/documentation/test_api/latest/test_api/TestOn-class.html"><code>@TestOn</code></a> annotation makes it easy to declare exactly which platforms
a test file should run on. Just put it at the top of your file, before any
<code>library</code> or <code>import</code> declarations:</p>
-<pre><code class="language-dart">@TestOn("vm")
+<pre><code class="language-dart">@TestOn("vm")
-import "dart:io";
+import "dart:io";
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
// ...
}
</code></pre>
-<p>The string you pass to <code>@TestOn</code> is what's called a "platform selector", and it
+<p>The string you pass to <code>@TestOn</code> is what's called a "platform selector", and it
specifies exactly which platforms a test can run on. It can be as simple as the
name of a platform, or a more complex Dart-like boolean expression involving
these platform names.</p>
@@ -171,16 +171,16 @@
</li>
</ul>
<p>For example, if you wanted to run a test on every browser but Chrome, you would
-write <code>@TestOn("browser && !chrome")</code>.</p>
+write <code>@TestOn("browser && !chrome")</code>.</p>
<h2>Asynchronous Tests</h2>
<p>Tests written with <code>async</code>/<code>await</code> will work automatically. The test runner
won't consider the test finished until the returned <code>Future</code> completes.</p>
-<pre><code class="language-dart">import "dart:async";
+<pre><code class="language-dart">import "dart:async";
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
- test("new Future.value() returns the value", () async {
+ test("new Future.value() returns the value", () async {
var value = await new Future.value(10);
expect(value, equals(10));
});
@@ -190,12 +190,12 @@
asynchrony. The <a href="https://pub.dev/documentation/test_api/latest/test_api/completion.html"><code>completion()</code></a> matcher can be used to test
<code>Futures</code>; it ensures that the test doesn't finish until the <code>Future</code> completes,
and runs a matcher against that <code>Future</code>'s value.</p>
-<pre><code class="language-dart">import "dart:async";
+<pre><code class="language-dart">import "dart:async";
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
- test("new Future.value() returns the value", () {
+ test("new Future.value() returns the value", () {
expect(new Future.value(10), completion(equals(10)));
});
}
@@ -203,14 +203,14 @@
<p>The <a href="https://pub.dev/documentation/test_api/latest/test_api/throwsA.html"><code>throwsA()</code></a> matcher and the various <code>throwsExceptionType</code>
matchers work with both synchronous callbacks and asynchronous <code>Future</code>s. They
ensure that a particular type of exception is thrown:</p>
-<pre><code class="language-dart">import "dart:async";
+<pre><code class="language-dart">import "dart:async";
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
- test("new Future.error() throws the error", () {
- expect(new Future.error("oh no"), throwsA(equals("oh no")));
- expect(new Future.error(new StateError("bad state")), throwsStateError);
+ test("new Future.error() throws the error", () {
+ expect(new Future.error("oh no"), throwsA(equals("oh no")));
+ expect(new Future.error(new StateError("bad state")), throwsStateError);
});
}
</code></pre>
@@ -219,12 +219,12 @@
times, and will cause the test to fail if it's called too often; second, it
keeps the test from finishing until the function is called the requisite number
of times.</p>
-<pre><code class="language-dart">import "dart:async";
+<pre><code class="language-dart">import "dart:async";
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
- test("Stream.fromIterable() emits the values in the iterable", () {
+ test("Stream.fromIterable() emits the values in the iterable", () {
var stream = new Stream.fromIterable([1, 2, 3]);
stream.listen(expectAsync((number) {
@@ -242,11 +242,11 @@
<p>They must have the same name as the test, with <code>.dart</code> replaced by <code>.html</code>.</p>
</li>
<li>
-<p>They must contain a <code>link</code> tag with <code>rel="x-dart-test"</code> and an <code>href</code>
+<p>They must contain a <code>link</code> tag with <code>rel="x-dart-test"</code> and an <code>href</code>
attribute pointing to the test script.</p>
</li>
<li>
-<p>They must contain <code><script src="packages/test/dart.js"></script></code>.</p>
+<p>They must contain <code><script src="packages/test/dart.js"></script></code>.</p>
</li>
</ul>
<p>For example, if you had a test called <code>custom_html_test.dart</code>, you might write
@@ -256,8 +256,8 @@
<html>
<head>
<title>Custom HTML Test</title>
- <link rel="x-dart-test" href="custom_html_test.dart">
- <script src="packages/test/dart.js"></script>
+ <link rel="x-dart-test" href="custom_html_test.dart">
+ <script src="packages/test/dart.js"></script>
</head>
<body>
// ...
@@ -267,15 +267,15 @@
<h2>Configuring Tests</h2>
<h3>Skipping Tests</h3>
<p>If a test, group, or entire suite isn't working yet and you just want it to stop
-complaining, you can mark it as "skipped". The test or tests won't be run, and,
+complaining, you can mark it as "skipped". The test or tests won't be run, and,
if you supply a reason why, that reason will be printed. In general, skipping
tests indicates that they should run but is temporarily not working. If they're
is fundamentally incompatible with a platform, <a href="https://pub.dev/documentation/test_api/latest/test_api/TestOn-class.html"><code>@TestOn</code>/<code>testOn</code></a>
should be used instead.</p>
<p>To skip a test suite, put a <code>@Skip</code> annotation at the top of the file:</p>
-<pre><code class="language-dart">@Skip("currently failing (see issue 1234)")
+<pre><code class="language-dart">@Skip("currently failing (see issue 1234)")
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
// ...
@@ -285,16 +285,16 @@
include it, but it's a good idea to document why the test isn't running.</p>
<p>Groups and individual tests can be skipped by passing the <code>skip</code> parameter. This
can be either <code>true</code> or a String describing why the test is skipped. For example:</p>
-<pre><code class="language-dart">import "package:test/test.dart";
+<pre><code class="language-dart">import "package:test/test.dart";
void main() {
- group("complicated algorithm tests", () {
+ group("complicated algorithm tests", () {
// ...
- }, skip: "the algorithm isn't quite right");
+ }, skip: "the algorithm isn't quite right");
- test("error-checking test", () {
+ test("error-checking test", () {
// ...
- }, skip: "TODO: add error-checking.");
+ }, skip: "TODO: add error-checking.");
}
</code></pre>
<h3>Timeouts</h3>
@@ -303,7 +303,7 @@
for a test suite, put a <code>@Timeout</code> annotation at the top of the file:</p>
<pre><code class="language-dart">@Timeout(const Duration(seconds: 45))
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
// ...
@@ -314,20 +314,20 @@
set the timeout to one and a half times as long as the default—45 seconds.</p>
<p>Timeouts can be set for tests and groups using the <code>timeout</code> parameter. This
parameter takes a <code>Timeout</code> object just like the annotation. For example:</p>
-<pre><code class="language-dart">import "package:test/test.dart";
+<pre><code class="language-dart">import "package:test/test.dart";
void main() {
- group("slow tests", () {
+ group("slow tests", () {
// ...
- test("even slower test", () {
+ test("even slower test", () {
// ...
}, timeout: new Timeout.factor(2))
}, timeout: new Timeout(new Duration(minutes: 1)));
}
</code></pre>
<p>Nested timeouts apply in order from outermost to innermost. That means that
-"even slower test" will take two minutes to time out, since it multiplies the
+"even slower test" will take two minutes to time out, since it multiplies the
group's timeout by 2.</p>
<h3>Platform-Specific Configuration</h3>
<p>Sometimes a test may need to be configured differently for different platforms.
@@ -337,16 +337,16 @@
and <code>group()</code>. For example:</p>
<pre><code class="language-dart">@OnPlatform(const {
// Give Windows some extra wiggle-room before timing out.
- "windows": const Timeout.factor(2)
+ "windows": const Timeout.factor(2)
})
-import "package:test/test.dart";
+import "package:test/test.dart";
void main() {
- test("do a thing", () {
+ test("do a thing", () {
// ...
}, onPlatform: {
- "safari": new Skip("Safari is currently broken (see #1234)")
+ "safari": new Skip("Safari is currently broken (see #1234)")
});
}
</code></pre>
diff --git a/lib/markdown.dart b/lib/markdown.dart
index 62d7576..3e435af 100644
--- a/lib/markdown.dart
+++ b/lib/markdown.dart
@@ -72,6 +72,7 @@
export 'src/inline_syntaxes/email_autolink_syntax.dart';
export 'src/inline_syntaxes/emoji_syntax.dart';
export 'src/inline_syntaxes/emphasis_syntax.dart';
+export 'src/inline_syntaxes/escape_html_syntax.dart';
export 'src/inline_syntaxes/escape_syntax.dart';
export 'src/inline_syntaxes/image_syntax.dart';
export 'src/inline_syntaxes/inline_html_syntax.dart';
diff --git a/lib/src/inline_parser.dart b/lib/src/inline_parser.dart
index c9ebbb6..d69af2b 100644
--- a/lib/src/inline_parser.dart
+++ b/lib/src/inline_parser.dart
@@ -11,6 +11,7 @@
import 'inline_syntaxes/delimiter_syntax.dart';
import 'inline_syntaxes/email_autolink_syntax.dart';
import 'inline_syntaxes/emphasis_syntax.dart';
+import 'inline_syntaxes/escape_html_syntax.dart';
import 'inline_syntaxes/escape_syntax.dart';
import 'inline_syntaxes/image_syntax.dart';
import 'inline_syntaxes/inline_syntax.dart';
@@ -27,8 +28,6 @@
EmailAutolinkSyntax(),
AutolinkSyntax(),
LineBreakSyntax(),
- // Allow any punctuation to be escaped.
- EscapeSyntax(),
// "*" surrounded by spaces is left alone.
TextSyntax(r' \* ', startCharacter: $space),
// "_" surrounded by spaces is left alone.
@@ -47,12 +46,6 @@
// Leave already-encoded HTML entities alone. Ensures we don't turn
// "&" into "&amp;"
TextSyntax('&[#a-zA-Z0-9]*;', startCharacter: $ampersand),
- // Encode "&".
- TextSyntax('&', sub: '&', startCharacter: $ampersand),
- // Encode "<".
- TextSyntax('<', sub: '<', startCharacter: $lt),
- // Encode ">".
- TextSyntax('>', sub: '>', startCharacter: $gt),
]);
/// The string of Markdown being parsed.
@@ -95,6 +88,7 @@
if (document.withDefaultInlineSyntaxes) {
// Custom link resolvers go after the generic text syntax.
syntaxes.addAll([
+ EscapeSyntax(),
DecodeHtmlSyntax(),
LinkSyntax(linkResolver: document.linkResolver),
ImageSyntax(linkResolver: document.imageLinkResolver)
@@ -104,6 +98,10 @@
}
if (encodeHtml) {
+ syntaxes.add(EscapeHtmlSyntax());
+ }
+
+ if (encodeHtml) {
syntaxes.addAll(_htmlSyntaxes);
}
}
diff --git a/lib/src/inline_syntaxes/autolink_syntax.dart b/lib/src/inline_syntaxes/autolink_syntax.dart
index 05eb41c..073c1cc 100644
--- a/lib/src/inline_syntaxes/autolink_syntax.dart
+++ b/lib/src/inline_syntaxes/autolink_syntax.dart
@@ -16,7 +16,10 @@
final url = match[1]!;
final text = parser.encodeHtml ? escapeHtml(url) : url;
final anchor = Element.text('a', text);
- anchor.attributes['href'] = Uri.encodeFull(url);
+
+ final destination = normalizeLinkDestination(url);
+ anchor.attributes['href'] =
+ parser.encodeHtml ? escapeHtml(destination) : destination;
parser.addNode(anchor);
return true;
diff --git a/lib/src/inline_syntaxes/decode_html_syntax.dart b/lib/src/inline_syntaxes/decode_html_syntax.dart
index 348a6b2..1af15d5 100644
--- a/lib/src/inline_syntaxes/decode_html_syntax.dart
+++ b/lib/src/inline_syntaxes/decode_html_syntax.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import '../assets/html_entities.dart';
import '../ast.dart';
import '../charcode.dart';
import '../inline_parser.dart';
@@ -27,9 +28,7 @@
return false;
}
- // TODO(Zhiguang): Enable HTML entity decoding when working on HTML escape
- // issues.
- if (match[1] != null) {
+ if (match[1] != null && htmlEntitiesMap[match.match] == null) {
return false;
}
@@ -40,7 +39,11 @@
@override
bool onMatch(InlineParser parser, Match match) {
- final decodedText = decodeHtmlCharacterFromMatch(match);
+ var decodedText = decodeHtmlCharacterFromMatch(match);
+
+ if (parser.encodeHtml) {
+ decodedText = escapeHtml(decodedText);
+ }
parser.addNode(Text(decodedText));
return true;
diff --git a/lib/src/inline_syntaxes/escape_html_syntax.dart b/lib/src/inline_syntaxes/escape_html_syntax.dart
new file mode 100644
index 0000000..565f870
--- /dev/null
+++ b/lib/src/inline_syntaxes/escape_html_syntax.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../ast.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Encodes (`"`), (`<`), (`>`) and (`&`).
+class EscapeHtmlSyntax extends InlineSyntax {
+ EscapeHtmlSyntax() : super('["<>&]');
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final text = escapeHtml(match[0]!);
+ parser.addNode(Text(text));
+
+ return true;
+ }
+}
diff --git a/lib/src/inline_syntaxes/escape_syntax.dart b/lib/src/inline_syntaxes/escape_syntax.dart
index 48a6426..30e6ea4 100644
--- a/lib/src/inline_syntaxes/escape_syntax.dart
+++ b/lib/src/inline_syntaxes/escape_syntax.dart
@@ -5,34 +5,29 @@
import '../ast.dart';
import '../charcode.dart';
import '../inline_parser.dart';
+import '../patterns.dart';
import '../util.dart';
import 'inline_syntax.dart';
-/// Escape punctuation preceded by a backslash.
+/// Escape ASCII punctuation preceded by a backslash.
+///
+/// Backslashes before other characters are treated as literal backslashes.
+// See https://spec.commonmark.org/0.30/#backslash-escapes.
class EscapeSyntax extends InlineSyntax {
- EscapeSyntax() : super(r'''\\[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~]''');
+ EscapeSyntax()
+ : super('\\\\([$asciiPunctuationEscaped])', startCharacter: $backslash);
@override
bool onMatch(InlineParser parser, Match match) {
final chars = match.match;
- final char = chars.codeUnitAt(1);
- // Insert the substitution. Why these three charactes are replaced with
- // their equivalent HTML entity referenced appears to be missing from the
- // CommonMark spec, but is very present in all of the examples.
- // https://talk.commonmark.org/t/entity-ification-of-quotes-and-brackets-missing-from-spec/3207
- if (parser.encodeHtml) {
- if (char == $double_quote) {
- parser.addNode(Text('"'));
- } else if (char == $lt) {
- parser.addNode(Text('<'));
- } else if (char == $gt) {
- parser.addNode(Text('>'));
- } else {
- parser.addNode(Text(chars[1]));
- }
+
+ if ('&"<>'.contains(match[1]!) && parser.encodeHtml) {
+ final text = escapeHtml(match[1]!);
+ parser.addNode(Text(text));
+ return true;
} else {
parser.addNode(Text(chars[1]));
+ return true;
}
- return true;
}
}
diff --git a/lib/src/inline_syntaxes/image_syntax.dart b/lib/src/inline_syntaxes/image_syntax.dart
index 3c13679..cb47ae0 100644
--- a/lib/src/inline_syntaxes/image_syntax.dart
+++ b/lib/src/inline_syntaxes/image_syntax.dart
@@ -27,8 +27,7 @@
element.attributes['src'] = destination;
element.attributes['alt'] = children.map((node) => node.textContent).join();
if (title != null && title.isNotEmpty) {
- element.attributes['title'] =
- escapeAttribute(title.replaceAll('&', '&'));
+ element.attributes['title'] = normalizeLinkTitle(title);
}
return element;
}
diff --git a/lib/src/inline_syntaxes/link_syntax.dart b/lib/src/inline_syntaxes/link_syntax.dart
index 0546b93..bc47b49 100644
--- a/lib/src/inline_syntaxes/link_syntax.dart
+++ b/lib/src/inline_syntaxes/link_syntax.dart
@@ -152,9 +152,13 @@
}) {
final children = getChildren();
final element = Element('a', children);
- element.attributes['href'] = escapeAttribute(destination);
+ element.attributes['href'] = normalizeLinkDestination(
+ escapePunctuation(destination),
+ );
if (title != null && title.isNotEmpty) {
- element.attributes['title'] = escapeAttribute(title);
+ element.attributes['title'] = normalizeLinkTitle(
+ escapePunctuation(title),
+ );
}
return element;
}
diff --git a/lib/src/patterns.dart b/lib/src/patterns.dart
index a99cc5e..269689f 100644
--- a/lib/src/patterns.dart
+++ b/lib/src/patterns.dart
@@ -202,9 +202,13 @@
caseSensitive: false);
/// ASCII punctuation characters.
-// see https://spec.commonmark.org/0.30/#unicode-whitespace-character.
+// See https://spec.commonmark.org/0.30/#unicode-whitespace-character.
const asciiPunctuationCharacters = r'''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~''';
+/// ASCII punctuation characters with some characters escaped, in order to be
+// used in the RegExp character set.
+const asciiPunctuationEscaped = r'''!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~''';
+
/// A pattern to match HTML entity references and numeric character references.
// https://spec.commonmark.org/0.30/#entity-and-numeric-character-references
final htmlCharactersPattern = RegExp(
diff --git a/lib/src/util.dart b/lib/src/util.dart
index a4861e2..02e26ab 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -9,81 +9,20 @@
import 'charcode.dart';
import 'patterns.dart';
-String escapeHtml(String html) =>
- const HtmlEscape(HtmlEscapeMode.element).convert(html);
-
-String escapeHtmlAttribute(String text) =>
- const HtmlEscape(HtmlEscapeMode.attribute).convert(text);
-
-/// Escapes the contents of [value], so that it may be used as an HTML
-/// attribute.
-///
-/// Based on http://spec.commonmark.org/0.28/#backslash-escapes.
-String escapeAttribute(String value) {
- final result = StringBuffer();
- int ch;
- for (var i = 0; i < value.codeUnits.length; i++) {
- ch = value.codeUnitAt(i);
- if (ch == $backslash) {
- i++;
- if (i == value.codeUnits.length) {
- result.writeCharCode(ch);
- break;
- }
- ch = value.codeUnitAt(i);
- switch (ch) {
- case $quote:
- result.write('"');
- break;
- case $exclamation:
- case $hash:
- case $dollar:
- case $percent:
- case $ampersand:
- case $apostrophe:
- case $lparen:
- case $rparen:
- case $asterisk:
- case $plus:
- case $comma:
- case $dash:
- case $dot:
- case $slash:
- case $colon:
- case $semicolon:
- case $lt:
- case $equal:
- case $gt:
- case $question:
- case $at:
- case $lbracket:
- case $backslash:
- case $rbracket:
- case $caret:
- case $underscore:
- case $backquote:
- case $lbrace:
- case $bar:
- case $rbrace:
- case $tilde:
- result.writeCharCode(ch);
- break;
- default:
- result.write('%5C');
- result.writeCharCode(ch);
- }
- } else if (ch == $quote) {
- result.write('%22');
- } else {
- result.writeCharCode(ch);
- }
- }
- return result.toString();
-}
-
/// One or more whitespace, for compressing.
final _oneOrMoreWhitespacePattern = RegExp('[ \n\r\t]+');
+/// Escapes (`'`), (`"`), (`<`), (`>`) and (`&`) characters.
+String escapeHtml(String html) => const HtmlEscape(HtmlEscapeMode(
+ escapeApos: true,
+ escapeLtGt: true,
+ escapeQuot: true,
+ )).convert(html);
+
+/// Escapes (`"`), (`<`) and (`>`) characters.
+String escapeHtmlAttribute(String text) =>
+ const HtmlEscape(HtmlEscapeMode.attribute).convert(text);
+
/// "Normalizes" a link label, according to the [CommonMark spec].
///
/// [CommonMark spec] https://spec.commonmark.org/0.30/#link-label
@@ -98,6 +37,34 @@
return text;
}
+/// Normalizes a link destination, including the process of HTML characters
+/// decoding and percent encoding.
+// See the description of these examples:
+// https://spec.commonmark.org/0.30/#example-501
+// https://spec.commonmark.org/0.30/#example-502
+String normalizeLinkDestination(String destination) {
+ // Decode first, because the destination might have been partly encoded.
+ // For example https://spec.commonmark.org/0.30/#example-502.
+ // With this function, `foo%20bä` will be parsed in the following steps:
+ // 1. foo bä
+ // 2. foo bä
+ // 3. foo%20b%C3%A4
+ try {
+ destination = Uri.decodeFull(destination);
+ } catch (_) {}
+ return Uri.encodeFull(decodeHtmlCharacters(destination));
+}
+
+/// Normalizes a link title, including the process of HTML characters decoding
+/// and HTML characters escaping.
+// See the description of these examples:
+// https://spec.commonmark.org/0.30/#example-505
+// https://spec.commonmark.org/0.30/#example-506
+// https://spec.commonmark.org/0.30/#example-507
+// https://spec.commonmark.org/0.30/#example-508
+String normalizeLinkTitle(String title) =>
+ escapeHtmlAttribute(decodeHtmlCharacters(title));
+
/// Decodes HTML entity and numeric character references, for example decode
/// `#` to `#`.
String decodeHtmlCharacters(String input) =>
diff --git a/test/common_mark/autolinks.unit b/test/common_mark/autolinks.unit
index 5547760..8b5b887 100644
--- a/test/common_mark/autolinks.unit
+++ b/test/common_mark/autolinks.unit
@@ -5,7 +5,7 @@
>>> Autolinks - 594
<http://foo.bar.baz/test?q=hello&id=22&boolean>
<<<
-<p><a href="http://foo.bar.baz/test?q=hello&id=22&boolean">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p>
+<p><a href="http://foo.bar.baz/test?q=hello&id=22&boolean">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p>
>>> Autolinks - 595
<irc://foo.bar:2233/baz>
<<<
diff --git a/test/common_mark/backslash_escapes.unit b/test/common_mark/backslash_escapes.unit
index 7042aa8..2c499ed 100644
--- a/test/common_mark/backslash_escapes.unit
+++ b/test/common_mark/backslash_escapes.unit
@@ -1,7 +1,7 @@
>>> Backslash escapes - 12
\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
<<<
-<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p>
+<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p>
>>> Backslash escapes - 13
\ \A\a\ \3\φ\«
<<<
@@ -18,14 +18,14 @@
\ö not a character entity
<<<
<p>*not emphasized*
-<br/> not a tag
+<br/> not a tag
[not a link](/foo)
`not code`
1. not a list
* not a list
# not a heading
-[foo]: /url "not a reference"
-ö not a character entity</p>
+[foo]: /url "not a reference"
+&ouml; not a character entity</p>
>>> Backslash escapes - 15
\\*emphasis*
<<<
diff --git a/test/common_mark/code_spans.unit b/test/common_mark/code_spans.unit
index 6864bf7..4c664ab 100644
--- a/test/common_mark/code_spans.unit
+++ b/test/common_mark/code_spans.unit
@@ -70,7 +70,7 @@
>>> Code spans - 343
`<a href="`">`
<<<
-<p><code><a href="</code>">`</p>
+<p><code><a href="</code>">`</p>
>>> Code spans - 344
<a href="`">`
<<<
diff --git a/test/common_mark/emphasis_and_strong_emphasis.unit b/test/common_mark/emphasis_and_strong_emphasis.unit
index 67155bf..514b651 100644
--- a/test/common_mark/emphasis_and_strong_emphasis.unit
+++ b/test/common_mark/emphasis_and_strong_emphasis.unit
@@ -9,7 +9,7 @@
>>> Emphasis and strong emphasis - 352
a*"foo"*
<<<
-<p>a*"foo"*</p>
+<p>a*"foo"*</p>
>>> Emphasis and strong emphasis - 353
* a *
<<<
@@ -33,7 +33,7 @@
>>> Emphasis and strong emphasis - 358
a_"foo"_
<<<
-<p>a_"foo"_</p>
+<p>a_"foo"_</p>
>>> Emphasis and strong emphasis - 359
foo_bar_
<<<
@@ -49,7 +49,7 @@
>>> Emphasis and strong emphasis - 362
aa_"bb"_cc
<<<
-<p>aa_"bb"_cc</p>
+<p>aa_"bb"_cc</p>
>>> Emphasis and strong emphasis - 363
foo-_(bar)_
<<<
@@ -119,7 +119,7 @@
>>> Emphasis and strong emphasis - 379
a**"foo"**
<<<
-<p>a**"foo"**</p>
+<p>a**"foo"**</p>
>>> Emphasis and strong emphasis - 380
foo**bar**
<<<
@@ -141,7 +141,7 @@
>>> Emphasis and strong emphasis - 384
a__"foo"__
<<<
-<p>a__"foo"__</p>
+<p>a__"foo"__</p>
>>> Emphasis and strong emphasis - 385
foo__bar__
<<<
@@ -183,7 +183,7 @@
>>> Emphasis and strong emphasis - 394
**foo "*bar*" foo**
<<<
-<p><strong>foo "<em>bar</em>" foo</strong></p>
+<p><strong>foo "<em>bar</em>" foo</strong></p>
>>> Emphasis and strong emphasis - 395
**foo**bar
<<<
diff --git a/test/common_mark/entity_and_numeric_character_references.unit b/test/common_mark/entity_and_numeric_character_references.unit
index da96678..2859435 100644
--- a/test/common_mark/entity_and_numeric_character_references.unit
+++ b/test/common_mark/entity_and_numeric_character_references.unit
@@ -3,9 +3,9 @@
¾ ℋ ⅆ
∲ ≧̸
<<<
-<p> & © Æ Ď
-¾ ℋ ⅆ
-∲ ≧̸</p>
+<p>& © Æ Ď
+¾ ℋ ⅆ
+∲ ≧̸</p>
>>> Entity and numeric character references - 26
# Ӓ Ϡ �
<<<
@@ -13,17 +13,17 @@
>>> Entity and numeric character references - 27
" ആ ಫ
<<<
-<p>" ആ ಫ</p>
+<p>" ആ ಫ</p>
>>> Entity and numeric character references - 28
  &x; &#; &#x;
�
&#abcdef0;
&ThisIsNotDefined; &hi?;
<<<
-<p>&nbsp &x; &#; &#x;
-�
-&#abcdef0;
-&ThisIsNotDefined; &hi?;</p>
+<p>&nbsp &x; &#; &#x;
+&#87654321;
+&#abcdef0;
+&ThisIsNotDefined; &hi?;</p>
>>> Entity and numeric character references - 29
©
<<<
@@ -31,7 +31,7 @@
>>> Entity and numeric character references - 30
&MadeUpEntity;
<<<
-<p>&MadeUpEntity;</p>
+<p>&MadeUpEntity;</p>
>>> Entity and numeric character references - 31
<a href="öö.html">
<<<
@@ -39,13 +39,13 @@
>>> Entity and numeric character references - 32
[foo](/föö "föö")
<<<
-<p><a href="/föö" title="föö">foo</a></p>
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
>>> Entity and numeric character references - 33
[foo]
[foo]: /föö "föö"
<<<
-<p><a href="/föö" title="föö">foo</a></p>
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
>>> Entity and numeric character references - 34
``` föö
foo
diff --git a/test/common_mark/images.unit b/test/common_mark/images.unit
index f1cf602..158ca40 100644
--- a/test/common_mark/images.unit
+++ b/test/common_mark/images.unit
@@ -100,7 +100,7 @@
[[foo]]: /url "title"
<<<
<p>![[foo]]</p>
-<p>[[foo]]: /url "title"</p>
+<p>[[foo]]: /url "title"</p>
>>> Images - 590
![Foo]
diff --git a/test/common_mark/link_reference_definitions.unit b/test/common_mark/link_reference_definitions.unit
index 46bbcf1..8d9ae8f 100644
--- a/test/common_mark/link_reference_definitions.unit
+++ b/test/common_mark/link_reference_definitions.unit
@@ -72,7 +72,7 @@
[foo]
<<<
-<p><a href="<>">foo</a></p>
+<p><a href="%3C%3E">foo</a></p>
>>> Link reference definitions - 201
[foo]: <bar>(baz)
@@ -84,7 +84,7 @@
[foo]
<<<
-<p>[foo]: /url\bar*baz "foo"bar\baz"</p>
+<p>[foo]: /url\bar*baz "foo"bar\baz"</p>
<p>[foo]</p>
>>> Link reference definitions - 203
[foo]
@@ -110,7 +110,7 @@
[αγω]
<<<
-<p><a href="/φου">αγω</a></p>
+<p><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
>>> Link reference definitions - 207
[foo]: /url
<<<
@@ -125,18 +125,18 @@
>>> Link reference definitions - 209
[foo]: /url "title" ok
<<<
-<p>[foo]: /url "title" ok</p>
+<p>[foo]: /url "title" ok</p>
>>> Link reference definitions - 210
[foo]: /url
"title" ok
<<<
-<p>"title" ok</p>
+<p>"title" ok</p>
>>> Link reference definitions - 211
[foo]: /url "title"
[foo]
<<<
-<pre><code>[foo]: /url "title"
+<pre><code>[foo]: /url "title"
</code></pre>
<p>[foo]</p>
>>> Link reference definitions - 212
diff --git a/test/common_mark/links.unit b/test/common_mark/links.unit
index 5e99f65..4015350 100644
--- a/test/common_mark/links.unit
+++ b/test/common_mark/links.unit
@@ -99,7 +99,7 @@
>>> Links - 502
[link](foo%20bä)
<<<
-<p><a href="foo%20bä">link</a></p>
+<p><a href="foo%20b%C3%A4">link</a></p>
>>> Links - 503
[link]("title")
<<<
@@ -115,19 +115,19 @@
>>> Links - 505
[link](/url "title \""")
<<<
-<p><a href="/url" title="title %22"">link</a></p>
+<p><a href="/url" title="title """>link</a></p>
>>> Links - 506
[link](/url "title")
<<<
-<p><a href="/url %22title%22">link</a></p>
+<p><a href="/url%C2%A0%22title%22">link</a></p>
>>> Links - 507
[link](/url "title "and" title")
<<<
-<p>[link](/url "title "and" title")</p>
+<p>[link](/url "title "and" title")</p>
>>> Links - 508
[link](/url 'title "and" title')
<<<
-<p><a href="/url" title="title %22and%22 title">link</a></p>
+<p><a href="/url" title="title "and" title">link</a></p>
>>> Links - 509
[link]( /uri
"title" )
diff --git a/test/common_mark/raw_html.unit b/test/common_mark/raw_html.unit
index cde09c4..64315b0 100644
--- a/test/common_mark/raw_html.unit
+++ b/test/common_mark/raw_html.unit
@@ -29,11 +29,11 @@
>>> Raw HTML - 618
<a h*#ref="hi">
<<<
-<p><a h*#ref="hi"></p>
+<p><a h*#ref="hi"></p>
>>> Raw HTML - 619
<a href="hi'> <a href=hi'>
<<<
-<p><a href="hi'> <a href=hi'></p>
+<p><a href="hi'> <a href=hi'></p>
>>> Raw HTML - 620
< a><
foo><bar/ >
@@ -43,7 +43,7 @@
<p>< a><
foo><bar/ >
<foo bar=baz
-bim!bop /></p>
+bim!bop /></p>
>>> Raw HTML - 621
<a href='bar'title=title>
<<<
@@ -55,7 +55,7 @@
>>> Raw HTML - 623
</a href="foo">
<<<
-<p></a href="foo"></p>
+<p></a href="foo"></p>
>>> Raw HTML - 624
foo <!-- this is a
comment - with hyphen -->
@@ -96,4 +96,4 @@
>>> Raw HTML - 632
<a href="\"">
<<<
-<p><a href="""></p>
+<p><a href="""></p>
diff --git a/test/common_mark/setext_headings.unit b/test/common_mark/setext_headings.unit
index 8fae410..dca5f93 100644
--- a/test/common_mark/setext_headings.unit
+++ b/test/common_mark/setext_headings.unit
@@ -99,8 +99,8 @@
<<<
<h2>`Foo</h2>
<p>`</p>
-<h2><a title="a lot</h2>
-<p>of dashes"/></p>
+<h2><a title="a lot</h2>
+<p>of dashes"/></p>
>>> Setext headings - 92
> Foo
---
diff --git a/test/extensions/inline_html.unit b/test/extensions/inline_html.unit
index d02d1fd..1540118 100644
--- a/test/extensions/inline_html.unit
+++ b/test/extensions/inline_html.unit
@@ -14,4 +14,4 @@
Text <a href="_foo_">And "_foo_"</a>.
<<<
-<p>Text <a href="_foo_">And "<em>foo</em>"</a>.</p>
+<p>Text <a href="_foo_">And "<em>foo</em>"</a>.</p>
diff --git a/test/gfm/autolinks.unit b/test/gfm/autolinks.unit
index 4de9122..6df5296 100644
--- a/test/gfm/autolinks.unit
+++ b/test/gfm/autolinks.unit
@@ -5,7 +5,7 @@
>>> Autolinks - 603
<http://foo.bar.baz/test?q=hello&id=22&boolean>
<<<
-<p><a href="http://foo.bar.baz/test?q=hello&id=22&boolean">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p>
+<p><a href="http://foo.bar.baz/test?q=hello&id=22&boolean">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p>
>>> Autolinks - 604
<irc://foo.bar:2233/baz>
<<<
diff --git a/test/gfm/autolinks_extension.unit b/test/gfm/autolinks_extension.unit
index 024fde1..ff69a53 100644
--- a/test/gfm/autolinks_extension.unit
+++ b/test/gfm/autolinks_extension.unit
@@ -36,7 +36,7 @@
www.google.com/search?q=commonmark&hl;
<<<
<p><a href="http://www.google.com/search?q=commonmark&hl=en">www.google.com/search?q=commonmark&hl=en</a></p>
-<p><a href="http://www.google.com/search?q=commonmark">www.google.com/search?q=commonmark</a>&hl;</p>
+<p><a href="http://www.google.com/search?q=commonmark">www.google.com/search?q=commonmark</a>&hl;</p>
>>> Autolinks (extension) - 627
www.commonmark.org/he<lp
<<<
diff --git a/test/gfm/backslash_escapes.unit b/test/gfm/backslash_escapes.unit
index f5cce5c..0e9074a 100644
--- a/test/gfm/backslash_escapes.unit
+++ b/test/gfm/backslash_escapes.unit
@@ -1,7 +1,7 @@
>>> Backslash escapes - 308
\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
<<<
-<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p>
+<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p>
>>> Backslash escapes - 309
\ \A\a\ \3\φ\«
<<<
@@ -18,14 +18,14 @@
\ö not a character entity
<<<
<p>*not emphasized*
-<br/> not a tag
+<br/> not a tag
[not a link](/foo)
`not code`
1. not a list
* not a list
# not a heading
-[foo]: /url "not a reference"
-ö not a character entity</p>
+[foo]: /url "not a reference"
+&ouml; not a character entity</p>
>>> Backslash escapes - 311
\\*emphasis*
<<<
diff --git a/test/gfm/code_spans.unit b/test/gfm/code_spans.unit
index f620371..ebf1f64 100644
--- a/test/gfm/code_spans.unit
+++ b/test/gfm/code_spans.unit
@@ -70,7 +70,7 @@
>>> Code spans - 353
`<a href="`">`
<<<
-<p><code><a href="</code>">`</p>
+<p><code><a href="</code>">`</p>
>>> Code spans - 354
<a href="`">`
<<<
diff --git a/test/gfm/emphasis_and_strong_emphasis.unit b/test/gfm/emphasis_and_strong_emphasis.unit
index b4222a6..3cde638 100644
--- a/test/gfm/emphasis_and_strong_emphasis.unit
+++ b/test/gfm/emphasis_and_strong_emphasis.unit
@@ -9,7 +9,7 @@
>>> Emphasis and strong emphasis - 362
a*"foo"*
<<<
-<p>a*"foo"*</p>
+<p>a*"foo"*</p>
>>> Emphasis and strong emphasis - 363
* a *
<<<
@@ -33,7 +33,7 @@
>>> Emphasis and strong emphasis - 368
a_"foo"_
<<<
-<p>a_"foo"_</p>
+<p>a_"foo"_</p>
>>> Emphasis and strong emphasis - 369
foo_bar_
<<<
@@ -49,7 +49,7 @@
>>> Emphasis and strong emphasis - 372
aa_"bb"_cc
<<<
-<p>aa_"bb"_cc</p>
+<p>aa_"bb"_cc</p>
>>> Emphasis and strong emphasis - 373
foo-_(bar)_
<<<
@@ -119,7 +119,7 @@
>>> Emphasis and strong emphasis - 389
a**"foo"**
<<<
-<p>a**"foo"**</p>
+<p>a**"foo"**</p>
>>> Emphasis and strong emphasis - 390
foo**bar**
<<<
@@ -141,7 +141,7 @@
>>> Emphasis and strong emphasis - 394
a__"foo"__
<<<
-<p>a__"foo"__</p>
+<p>a__"foo"__</p>
>>> Emphasis and strong emphasis - 395
foo__bar__
<<<
@@ -183,7 +183,7 @@
>>> Emphasis and strong emphasis - 404
**foo "*bar*" foo**
<<<
-<p><strong>foo "<em>bar</em>" foo</strong></p>
+<p><strong>foo "<em>bar</em>" foo</strong></p>
>>> Emphasis and strong emphasis - 405
**foo**bar
<<<
diff --git a/test/gfm/entity_and_numeric_character_references.unit b/test/gfm/entity_and_numeric_character_references.unit
index be80572..2a6248e 100644
--- a/test/gfm/entity_and_numeric_character_references.unit
+++ b/test/gfm/entity_and_numeric_character_references.unit
@@ -3,9 +3,9 @@
¾ ℋ ⅆ
∲ ≧̸
<<<
-<p> & © Æ Ď
-¾ ℋ ⅆ
-∲ ≧̸</p>
+<p>& © Æ Ď
+¾ ℋ ⅆ
+∲ ≧̸</p>
>>> Entity and numeric character references - 322
# Ӓ Ϡ �
<<<
@@ -13,17 +13,17 @@
>>> Entity and numeric character references - 323
" ആ ಫ
<<<
-<p>" ആ ಫ</p>
+<p>" ആ ಫ</p>
>>> Entity and numeric character references - 324
  &x; &#; &#x;
�
&#abcdef0;
&ThisIsNotDefined; &hi?;
<<<
-<p>&nbsp &x; &#; &#x;
-�
-&#abcdef0;
-&ThisIsNotDefined; &hi?;</p>
+<p>&nbsp &x; &#; &#x;
+&#987654321;
+&#abcdef0;
+&ThisIsNotDefined; &hi?;</p>
>>> Entity and numeric character references - 325
©
<<<
@@ -31,7 +31,7 @@
>>> Entity and numeric character references - 326
&MadeUpEntity;
<<<
-<p>&MadeUpEntity;</p>
+<p>&MadeUpEntity;</p>
>>> Entity and numeric character references - 327
<a href="öö.html">
<<<
@@ -39,13 +39,13 @@
>>> Entity and numeric character references - 328
[foo](/föö "föö")
<<<
-<p><a href="/föö" title="föö">foo</a></p>
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
>>> Entity and numeric character references - 329
[foo]
[foo]: /föö "föö"
<<<
-<p><a href="/föö" title="föö">foo</a></p>
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
>>> Entity and numeric character references - 330
``` föö
foo
diff --git a/test/gfm/images.unit b/test/gfm/images.unit
index 838a07c..e0bfb90 100644
--- a/test/gfm/images.unit
+++ b/test/gfm/images.unit
@@ -100,7 +100,7 @@
[[foo]]: /url "title"
<<<
<p>![[foo]]</p>
-<p>[[foo]]: /url "title"</p>
+<p>[[foo]]: /url "title"</p>
>>> Images - 599
![Foo]
diff --git a/test/gfm/link_reference_definitions.unit b/test/gfm/link_reference_definitions.unit
index 16f86fb..442c431 100644
--- a/test/gfm/link_reference_definitions.unit
+++ b/test/gfm/link_reference_definitions.unit
@@ -72,7 +72,7 @@
[foo]
<<<
-<p><a href="<>">foo</a></p>
+<p><a href="%3C%3E">foo</a></p>
>>> Link reference definitions - 170
[foo]: <bar>(baz)
@@ -84,7 +84,7 @@
[foo]
<<<
-<p>[foo]: /url\bar*baz "foo"bar\baz"</p>
+<p>[foo]: /url\bar*baz "foo"bar\baz"</p>
<p>[foo]</p>
>>> Link reference definitions - 172
[foo]
@@ -110,7 +110,7 @@
[αγω]
<<<
-<p><a href="/φου">αγω</a></p>
+<p><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
>>> Link reference definitions - 176
[foo]: /url
<<<
@@ -125,18 +125,18 @@
>>> Link reference definitions - 178
[foo]: /url "title" ok
<<<
-<p>[foo]: /url "title" ok</p>
+<p>[foo]: /url "title" ok</p>
>>> Link reference definitions - 179
[foo]: /url
"title" ok
<<<
-<p>"title" ok</p>
+<p>"title" ok</p>
>>> Link reference definitions - 180
[foo]: /url "title"
[foo]
<<<
-<pre><code>[foo]: /url "title"
+<pre><code>[foo]: /url "title"
</code></pre>
<p>[foo]</p>
>>> Link reference definitions - 181
diff --git a/test/gfm/links.unit b/test/gfm/links.unit
index b2d8e7f..38a861e 100644
--- a/test/gfm/links.unit
+++ b/test/gfm/links.unit
@@ -87,7 +87,7 @@
>>> Links - 511
[link](foo%20bä)
<<<
-<p><a href="foo%20bä">link</a></p>
+<p><a href="foo%20b%C3%A4">link</a></p>
>>> Links - 512
[link]("title")
<<<
@@ -103,19 +103,19 @@
>>> Links - 514
[link](/url "title \""")
<<<
-<p><a href="/url" title="title %22"">link</a></p>
+<p><a href="/url" title="title """>link</a></p>
>>> Links - 515
[link](/url "title")
<<<
-<p><a href="/url %22title%22">link</a></p>
+<p><a href="/url%C2%A0%22title%22">link</a></p>
>>> Links - 516
[link](/url "title "and" title")
<<<
-<p>[link](/url "title "and" title")</p>
+<p>[link](/url "title "and" title")</p>
>>> Links - 517
[link](/url 'title "and" title')
<<<
-<p><a href="/url" title="title %22and%22 title">link</a></p>
+<p><a href="/url" title="title "and" title">link</a></p>
>>> Links - 518
[link]( /uri
"title" )
diff --git a/test/gfm/raw_html.unit b/test/gfm/raw_html.unit
index 1802dba..bc3a388 100644
--- a/test/gfm/raw_html.unit
+++ b/test/gfm/raw_html.unit
@@ -29,11 +29,11 @@
>>> Raw HTML - 638
<a h*#ref="hi">
<<<
-<p><a h*#ref="hi"></p>
+<p><a h*#ref="hi"></p>
>>> Raw HTML - 639
<a href="hi'> <a href=hi'>
<<<
-<p><a href="hi'> <a href=hi'></p>
+<p><a href="hi'> <a href=hi'></p>
>>> Raw HTML - 640
< a><
foo><bar/ >
@@ -43,7 +43,7 @@
<p>< a><
foo><bar/ >
<foo bar=baz
-bim!bop /></p>
+bim!bop /></p>
>>> Raw HTML - 641
<a href='bar'title=title>
<<<
@@ -55,7 +55,7 @@
>>> Raw HTML - 643
</a href="foo">
<<<
-<p></a href="foo"></p>
+<p></a href="foo"></p>
>>> Raw HTML - 644
foo <!-- this is a
comment - with hyphen -->
@@ -96,4 +96,4 @@
>>> Raw HTML - 652
<a href="\"">
<<<
-<p><a href="""></p>
+<p><a href="""></p>
diff --git a/test/gfm/setext_headings.unit b/test/gfm/setext_headings.unit
index 5909731..4306bf4 100644
--- a/test/gfm/setext_headings.unit
+++ b/test/gfm/setext_headings.unit
@@ -99,8 +99,8 @@
<<<
<h2>`Foo</h2>
<p>`</p>
-<h2><a title="a lot</h2>
-<p>of dashes"/></p>
+<h2><a title="a lot</h2>
+<p>of dashes"/></p>
>>> Setext headings - 62
> Foo
---
diff --git a/test/markdown_test.dart b/test/markdown_test.dart
index 35fde7e..58b42c6 100644
--- a/test/markdown_test.dart
+++ b/test/markdown_test.dart
@@ -74,7 +74,7 @@
validateCore('Unicode ellipsis as punctuation', '''
"Connecting dot **A** to **B.**…"
''', '''
-<p>"Connecting dot <strong>A</strong> to <strong>B.</strong>…"</p>
+<p>"Connecting dot <strong>A</strong> to <strong>B.</strong>…"</p>
''');
});
diff --git a/test/original/autolinks.unit b/test/original/autolinks.unit
index 804b65c..d3658e8 100644
--- a/test/original/autolinks.unit
+++ b/test/original/autolinks.unit
@@ -7,4 +7,4 @@
<http://foo.com/?a=1&b=2>
<<<
-<p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p>
+<p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p>
diff --git a/test/original/backslash_escapes.unit b/test/original/backslash_escapes.unit
index 05a8c99..e3446f3 100644
--- a/test/original/backslash_escapes.unit
+++ b/test/original/backslash_escapes.unit
@@ -5,7 +5,7 @@
and \~.
<<<
-<p>Punctuations like ! and " and # and $ and % and & and ' and ( and )
+<p>Punctuations like ! and " and # and $ and % and & and ' and ( and )
and * and + and , and - and . and / and : and ; and < and = and >
and ? and @ and [ and \ and ] and ^ and _ and ` and { and | and }
and ~.</p>
diff --git a/test/original/inline_links.unit b/test/original/inline_links.unit
index 4d910c8..0a40407 100644
--- a/test/original/inline_links.unit
+++ b/test/original/inline_links.unit
@@ -84,4 +84,4 @@
links [are](<http://example.com> "title\") awesome
<<<
-<p>links [are](<a href="http://example.com">http://example.com</a> "title") awesome</p>
\ No newline at end of file
+<p>links [are](<a href="http://example.com">http://example.com</a> "title") awesome</p>
\ No newline at end of file
diff --git a/tool/common_mark_stats.json b/tool/common_mark_stats.json
index 4e814d3..65181ec 100644
--- a/tool/common_mark_stats.json
+++ b/tool/common_mark_stats.json
@@ -21,7 +21,7 @@
},
"Autolinks": {
"593": "strict",
- "594": "loose",
+ "594": "strict",
"595": "strict",
"596": "strict",
"597": "strict",
@@ -41,9 +41,9 @@
"611": "strict"
},
"Backslash escapes": {
- "12": "loose",
+ "12": "strict",
"13": "strict",
- "14": "loose",
+ "14": "strict",
"15": "strict",
"16": "strict",
"17": "strict",
@@ -101,7 +101,7 @@
"340": "strict",
"341": "strict",
"342": "strict",
- "343": "loose",
+ "343": "strict",
"344": "strict",
"345": "strict",
"346": "strict",
@@ -112,17 +112,17 @@
"Emphasis and strong emphasis": {
"350": "strict",
"351": "strict",
- "352": "loose",
+ "352": "strict",
"353": "fail",
"354": "strict",
"355": "strict",
"356": "strict",
"357": "strict",
- "358": "loose",
+ "358": "strict",
"359": "strict",
"360": "strict",
"361": "strict",
- "362": "loose",
+ "362": "strict",
"363": "strict",
"364": "strict",
"365": "strict",
@@ -139,12 +139,12 @@
"376": "strict",
"377": "strict",
"378": "strict",
- "379": "loose",
+ "379": "strict",
"380": "strict",
"381": "strict",
"382": "strict",
"383": "strict",
- "384": "loose",
+ "384": "strict",
"385": "strict",
"386": "strict",
"387": "strict",
@@ -154,7 +154,7 @@
"391": "strict",
"392": "strict",
"393": "strict",
- "394": "loose",
+ "394": "strict",
"395": "strict",
"396": "strict",
"397": "strict",
@@ -245,13 +245,13 @@
"Entity and numeric character references": {
"25": "loose",
"26": "strict",
- "27": "loose",
- "28": "loose",
+ "27": "strict",
+ "28": "strict",
"29": "strict",
- "30": "loose",
+ "30": "strict",
"31": "strict",
- "32": "fail",
- "33": "fail",
+ "32": "strict",
+ "33": "strict",
"34": "strict",
"35": "strict",
"36": "strict",
@@ -374,7 +374,7 @@
"586": "strict",
"587": "strict",
"588": "strict",
- "589": "loose",
+ "589": "strict",
"590": "strict",
"591": "strict",
"592": "strict"
@@ -411,12 +411,12 @@
"203": "strict",
"204": "strict",
"205": "strict",
- "206": "fail",
+ "206": "strict",
"207": "loose",
"208": "strict",
- "209": "loose",
- "210": "loose",
- "211": "loose",
+ "209": "strict",
+ "210": "strict",
+ "211": "strict",
"212": "strict",
"213": "strict",
"214": "strict",
@@ -447,13 +447,13 @@
"499": "strict",
"500": "strict",
"501": "strict",
- "502": "fail",
+ "502": "strict",
"503": "strict",
"504": "strict",
- "505": "fail",
- "506": "fail",
- "507": "loose",
- "508": "fail",
+ "505": "strict",
+ "506": "strict",
+ "507": "strict",
+ "508": "strict",
"509": "strict",
"510": "strict",
"511": "strict",
@@ -615,12 +615,12 @@
"615": "strict",
"616": "strict",
"617": "strict",
- "618": "loose",
- "619": "loose",
- "620": "loose",
+ "618": "strict",
+ "619": "strict",
+ "620": "strict",
"621": "strict",
"622": "strict",
- "623": "loose",
+ "623": "strict",
"624": "strict",
"625": "strict",
"626": "strict",
@@ -629,7 +629,7 @@
"629": "strict",
"630": "strict",
"631": "strict",
- "632": "loose"
+ "632": "strict"
},
"Setext headings": {
"80": "strict",
@@ -643,7 +643,7 @@
"88": "strict",
"89": "strict",
"90": "strict",
- "91": "loose",
+ "91": "strict",
"92": "strict",
"93": "fail",
"94": "strict",
diff --git a/tool/common_mark_stats.txt b/tool/common_mark_stats.txt
index 4b6f751..72b98de 100644
--- a/tool/common_mark_stats.txt
+++ b/tool/common_mark_stats.txt
@@ -5,15 +5,15 @@
25 of 25 – 100.0% Block quotes
22 of 22 – 100.0% Code spans
130 of 131 – 99.2% Emphasis and strong emphasis
- 15 of 17 – 88.2% Entity and numeric character references
+ 17 of 17 – 100.0% Entity and numeric character references
29 of 29 – 100.0% Fenced code blocks
15 of 15 – 100.0% Hard line breaks
44 of 44 – 100.0% HTML blocks
21 of 22 – 95.5% Images
11 of 12 – 91.7% Indented code blocks
1 of 1 – 100.0% Inlines
- 20 of 27 – 74.1% Link reference definitions
- 85 of 90 – 94.4% Links
+ 21 of 27 – 77.8% Link reference definitions
+ 89 of 90 – 98.9% Links
45 of 48 – 93.8% List items
22 of 26 – 84.6% Lists
8 of 8 – 100.0% Paragraphs
@@ -24,5 +24,5 @@
11 of 11 – 100.0% Tabs
3 of 3 – 100.0% Textual content
19 of 19 – 100.0% Thematic breaks
- 626 of 652 – 96.0% TOTAL
- 573 of 626 – 91.5% TOTAL Strict
+ 633 of 652 – 97.1% TOTAL
+ 604 of 633 – 95.4% TOTAL Strict
diff --git a/tool/gfm_stats.json b/tool/gfm_stats.json
index ff4d34a..657aaff 100644
--- a/tool/gfm_stats.json
+++ b/tool/gfm_stats.json
@@ -21,7 +21,7 @@
},
"Autolinks": {
"602": "strict",
- "603": "loose",
+ "603": "strict",
"604": "strict",
"605": "strict",
"606": "strict",
@@ -46,7 +46,7 @@
"623": "strict",
"624": "strict",
"625": "strict",
- "626": "loose",
+ "626": "strict",
"627": "strict",
"628": "strict",
"629": "strict",
@@ -54,9 +54,9 @@
"631": "strict"
},
"Backslash escapes": {
- "308": "loose",
+ "308": "strict",
"309": "strict",
- "310": "loose",
+ "310": "strict",
"311": "strict",
"312": "strict",
"313": "strict",
@@ -114,7 +114,7 @@
"350": "strict",
"351": "strict",
"352": "strict",
- "353": "loose",
+ "353": "strict",
"354": "strict",
"355": "strict",
"356": "strict",
@@ -128,17 +128,17 @@
"Emphasis and strong emphasis": {
"360": "strict",
"361": "strict",
- "362": "loose",
+ "362": "strict",
"363": "fail",
"364": "strict",
"365": "strict",
"366": "strict",
"367": "strict",
- "368": "loose",
+ "368": "strict",
"369": "strict",
"370": "strict",
"371": "strict",
- "372": "loose",
+ "372": "strict",
"373": "strict",
"374": "strict",
"375": "strict",
@@ -155,12 +155,12 @@
"386": "strict",
"387": "strict",
"388": "strict",
- "389": "loose",
+ "389": "strict",
"390": "strict",
"391": "strict",
"392": "strict",
"393": "strict",
- "394": "loose",
+ "394": "strict",
"395": "strict",
"396": "strict",
"397": "strict",
@@ -170,7 +170,7 @@
"401": "strict",
"402": "strict",
"403": "strict",
- "404": "loose",
+ "404": "strict",
"405": "strict",
"406": "strict",
"407": "strict",
@@ -261,13 +261,13 @@
"Entity and numeric character references": {
"321": "loose",
"322": "strict",
- "323": "loose",
- "324": "loose",
+ "323": "strict",
+ "324": "strict",
"325": "strict",
- "326": "loose",
+ "326": "strict",
"327": "strict",
- "328": "fail",
- "329": "fail",
+ "328": "strict",
+ "329": "strict",
"330": "strict",
"331": "strict",
"332": "strict",
@@ -389,7 +389,7 @@
"595": "strict",
"596": "strict",
"597": "strict",
- "598": "loose",
+ "598": "strict",
"599": "strict",
"600": "strict",
"601": "strict"
@@ -426,12 +426,12 @@
"172": "strict",
"173": "strict",
"174": "strict",
- "175": "fail",
+ "175": "strict",
"176": "loose",
"177": "strict",
- "178": "loose",
- "179": "loose",
- "180": "loose",
+ "178": "strict",
+ "179": "strict",
+ "180": "strict",
"181": "strict",
"182": "strict",
"183": "strict",
@@ -460,13 +460,13 @@
"508": "strict",
"509": "strict",
"510": "strict",
- "511": "fail",
+ "511": "strict",
"512": "strict",
"513": "strict",
- "514": "fail",
- "515": "fail",
- "516": "loose",
- "517": "fail",
+ "514": "strict",
+ "515": "strict",
+ "516": "strict",
+ "517": "strict",
"518": "strict",
"519": "strict",
"520": "strict",
@@ -628,12 +628,12 @@
"635": "strict",
"636": "strict",
"637": "strict",
- "638": "loose",
- "639": "loose",
- "640": "loose",
+ "638": "strict",
+ "639": "strict",
+ "640": "strict",
"641": "strict",
"642": "strict",
- "643": "loose",
+ "643": "strict",
"644": "strict",
"645": "strict",
"646": "strict",
@@ -642,7 +642,7 @@
"649": "strict",
"650": "strict",
"651": "strict",
- "652": "loose"
+ "652": "strict"
},
"Setext headings": {
"50": "strict",
@@ -656,7 +656,7 @@
"58": "strict",
"59": "strict",
"60": "strict",
- "61": "loose",
+ "61": "strict",
"62": "strict",
"63": "fail",
"64": "strict",
diff --git a/tool/gfm_stats.txt b/tool/gfm_stats.txt
index 5cb3981..aea55ba 100644
--- a/tool/gfm_stats.txt
+++ b/tool/gfm_stats.txt
@@ -7,15 +7,15 @@
22 of 22 – 100.0% Code spans
0 of 1 – 0.0% Disallowed Raw HTML (extension)
130 of 131 – 99.2% Emphasis and strong emphasis
- 15 of 17 – 88.2% Entity and numeric character references
+ 17 of 17 – 100.0% Entity and numeric character references
29 of 29 – 100.0% Fenced code blocks
15 of 15 – 100.0% Hard line breaks
43 of 43 – 100.0% HTML blocks
21 of 22 – 95.5% Images
11 of 12 – 91.7% Indented code blocks
1 of 1 – 100.0% Inlines
- 21 of 28 – 75.0% Link reference definitions
- 82 of 87 – 94.3% Links
+ 22 of 28 – 78.6% Link reference definitions
+ 86 of 87 – 98.9% Links
45 of 48 – 93.8% List items
22 of 26 – 84.6% Lists
8 of 8 – 100.0% Paragraphs
@@ -28,5 +28,5 @@
11 of 11 – 100.0% Tabs
3 of 3 – 100.0% Textual content
19 of 19 – 100.0% Thematic breaks
- 644 of 671 – 96.0% TOTAL
- 589 of 644 – 91.5% TOTAL Strict
+ 651 of 671 – 97.0% TOTAL
+ 621 of 651 – 95.4% TOTAL Strict