Merge pull request dart-lang/markdown#84 from mehaase/commonmark

Improve dart-lang/markdowndart-lang/markdown#51.
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml
index 7740d41..dc8a73d 100644
--- a/pkgs/markdown/pubspec.yaml
+++ b/pkgs/markdown/pubspec.yaml
@@ -8,6 +8,7 @@
 dev_dependencies:
   args: '^0.13.3+1'
   collection: ^1.2.0
+  html: '^0.12.2'
   path: '^1.3.1'
   test: '^0.12.4+1'
   yaml: '^2.1.8'
diff --git a/pkgs/markdown/tool/common_mark_stats.dart b/pkgs/markdown/tool/common_mark_stats.dart
index f34ca05..87c8039 100644
--- a/pkgs/markdown/tool/common_mark_stats.dart
+++ b/pkgs/markdown/tool/common_mark_stats.dart
@@ -6,6 +6,7 @@
 import 'dart:mirrors';
 
 import 'package:collection/collection.dart';
+import 'package:html/parser.dart' show parseFragment;
 import 'package:markdown/markdown.dart';
 import 'package:path/path.dart' as p;
 
@@ -28,12 +29,20 @@
 
   sections.forEach((section, examples) {
     for (var e in examples) {
-      var output = markdownToHtml(e.markdown);
-
+      var output;
       var nestedMap =
           scores.putIfAbsent(section, () => new SplayTreeMap<int, bool>());
 
-      nestedMap[e.example] = (output == e.html);
+      try {
+        output = markdownToHtml(e.markdown);
+      } catch (exc) {
+        nestedMap[e.example] = false;
+        continue;
+      }
+
+      var expected = parseFragment(e.html);
+      var actual = parseFragment(output);
+      nestedMap[e.example] = compareHtml(expected.children, actual.children);
     }
   });
 
@@ -91,6 +100,60 @@
       '– ${pct}%  TOTAL');
 }
 
+/// Compare two DOM trees for equality.
+bool compareHtml(List<Element> expectedElements, List<Element> actualElements) {
+  if (expectedElements.length != actualElements.length) {
+    return false;
+  }
+
+  for (var childNum = 0; childNum < expectedElements.length; childNum++) {
+    var expected = expectedElements[childNum];
+    var actual = actualElements[childNum];
+
+    if (expected.runtimeType != actual.runtimeType) {
+      return false;
+    }
+
+    if (expected is Element) {
+      if (expected.localName != actual.localName) {
+        return false;
+      }
+
+      if (expected.attributes.length != actual.attributes.length) {
+        return false;
+      }
+
+      var expectedAttrKeys = expected.attributes.keys.toList();
+      expectedAttrKeys.sort();
+
+      var actualAttrKeys = actual.attributes.keys.toList();
+      actualAttrKeys.sort();
+
+      for (var attrNum = 0; attrNum < actualAttrKeys.length; attrNum++) {
+        var expectedAttrKey = expectedAttrKeys[attrNum];
+        var actualAttrKey = actualAttrKeys[attrNum];
+
+        if (expectedAttrKey != actualAttrKey) {
+          return false;
+        }
+
+        if (expected.attributes[expectedAttrKey] !=
+            actual.attributes.keys[actualAttrKey]) {
+          return false;
+        }
+      }
+    }
+
+    var childrenEqual = compareHtml(expected.children, actual.children);
+
+    if (!childrenEqual) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 Map<String, List<CommonMarkTestCase>> loadCommonMarkSections() {
   var testFile = new File(p.join(_currentDir, _commonMarkTests));
   var testsJson = testFile.readAsStringSync();