Rewrite asciiUpper2Lower as an extension (#132)

- Switch to an extension method which will work nicer with `?.` instead
  of needing to handle a null argument and has nicer looking usage.
- Rename to `toAsciiLowerCase` for consistency with the similar method
  in the SDK.
- Use `Iterable.map` to avoid needing to create a fixed size list which
  will not work well with the null safety migration.

This method is an internal detail and this is not breaking.
diff --git a/.travis.yml b/.travis.yml
index 3f5bcfb..497fe68 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@
 
 dart:
   - dev
-  - 2.3.0
+  - 2.8.1
 
 dart_task:
   - test: -p vm
@@ -15,7 +15,7 @@
     - dart: dev
       dart_task:
         dartanalyzer: --fatal-warnings --fatal-infos .
-    - dart: 2.3.0
+    - dart: 2.8.1
       dart_task:
         dartanalyzer: --fatal-warnings .
 
diff --git a/lib/parser.dart b/lib/parser.dart
index 5cfd53d..ac3d304 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -260,8 +260,7 @@
   bool isHTMLIntegrationPoint(Element element) {
     if (element.localName == 'annotation-xml' &&
         element.namespaceUri == Namespaces.mathml) {
-      var enc = element.attributes['encoding'];
-      if (enc != null) enc = asciiUpper2Lower(enc);
+      final enc = element.attributes['encoding']?.toAsciiLowerCase();
       return enc == 'text/html' || enc == 'application/xhtml+xml';
     } else {
       return htmlIntegrationPointElements
@@ -683,7 +682,7 @@
   @override
   Token processDoctype(DoctypeToken token) {
     final name = token.name;
-    var publicId = token.publicId;
+    var publicId = token.publicId?.toAsciiLowerCase();
     final systemId = token.systemId;
     final correct = token.correct;
 
@@ -697,10 +696,6 @@
 
     tree.insertDoctype(token);
 
-    if (publicId != '') {
-      publicId = asciiUpper2Lower(publicId);
-    }
-
     if (!correct ||
         token.name != 'html' ||
         startsWithAny(publicId, const [
@@ -1783,7 +1778,7 @@
   void startTagInput(StartTagToken token) {
     final savedFramesetOK = parser.framesetOK;
     startTagVoidFormatting(token);
-    if (asciiUpper2Lower(token.data['type']) == 'hidden') {
+    if (token.data['type']?.toAsciiLowerCase() == 'hidden') {
       //input type=hidden doesn't change framesetOK
       parser.framesetOK = savedFramesetOK;
     }
@@ -2486,7 +2481,7 @@
   }
 
   void startTagInput(StartTagToken token) {
-    if (asciiUpper2Lower(token.data['type']) == 'hidden') {
+    if (token.data['type']?.toAsciiLowerCase() == 'hidden') {
       parser.parseError(token.span, 'unexpected-hidden-input-in-table');
       tree.insertElement(token);
       // XXX associate with form
@@ -3621,13 +3616,13 @@
   Token processEndTag(EndTagToken token) {
     var nodeIndex = tree.openElements.length - 1;
     var node = tree.openElements.last;
-    if (asciiUpper2Lower(node.localName) != token.name) {
+    if (node.localName?.toAsciiLowerCase() != token.name) {
       parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
     }
 
     Token newToken;
     while (true) {
-      if (asciiUpper2Lower(node.localName) == token.name) {
+      if (node.localName?.toAsciiLowerCase() == token.name) {
         //XXX this isn't in the spec but it seems necessary
         if (parser.phase == parser._inTableTextPhase) {
           final inTableText = parser.phase as InTableTextPhase;
diff --git a/lib/src/constants.dart b/lib/src/constants.dart
index b09fc48..e1fad5a 100644
--- a/lib/src/constants.dart
+++ b/lib/src/constants.dart
@@ -482,19 +482,15 @@
   return false;
 }
 
-// Note: based on the original Python code, I assume we only want to convert
-// ASCII chars to.toLowerCase() case, unlike Dart's toLowerCase function.
-String asciiUpper2Lower(String text) {
-  if (text == null) return null;
-  final result = List<int>(text.length);
-  for (var i = 0; i < text.length; i++) {
-    var c = text.codeUnitAt(i);
-    if (c >= UPPER_A && c <= UPPER_Z) {
-      c += LOWER_A - UPPER_A;
-    }
-    result[i] = c;
-  }
-  return String.fromCharCodes(result);
+extension AsciiUpperToLower on String {
+  /// Converts ASCII characters to lowercase.
+  ///
+  /// Unlike [String.toLowerCase] does not touch non-ASCII characters.
+  String toAsciiLowerCase() =>
+      String.fromCharCodes(codeUnits.map(_asciiToLower));
+
+  static int _asciiToLower(int c) =>
+      (c >= UPPER_A && c <= UPPER_Z) ? c + LOWER_A - UPPER_A : c;
 }
 
 // Heading elements need to be ordered
diff --git a/lib/src/tokenizer.dart b/lib/src/tokenizer.dart
index 7af9648..2737a7e 100644
--- a/lib/src/tokenizer.dart
+++ b/lib/src/tokenizer.dart
@@ -370,7 +370,7 @@
     // Add token to the queue to be yielded
     if (token is TagToken) {
       if (lowercaseElementName) {
-        token.name = asciiUpper2Lower(token.name);
+        token.name = token.name?.toAsciiLowerCase();
       }
       if (token is EndTagToken) {
         if (_attributes != null) {
@@ -1074,7 +1074,7 @@
       // to attributes, but we do want to report the parse error in time.
       var attrName = _attributeName.toString();
       if (lowercaseAttrName) {
-        attrName = asciiUpper2Lower(attrName);
+        attrName = attrName.toAsciiLowerCase();
       }
       _attributes.last.name = attrName;
       _attributeNames ??= {};
@@ -1515,10 +1515,10 @@
   bool doctypeNameState() {
     final data = stream.char();
     if (isWhitespace(data)) {
-      currentDoctypeToken.name = asciiUpper2Lower(currentDoctypeToken.name);
+      currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
       state = afterDoctypeNameState;
     } else if (data == '>') {
-      currentDoctypeToken.name = asciiUpper2Lower(currentDoctypeToken.name);
+      currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
       _addToken(currentToken);
       state = dataState;
     } else if (data == '\u0000') {
@@ -1528,7 +1528,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype-name'));
       currentDoctypeToken.correct = false;
-      currentDoctypeToken.name = asciiUpper2Lower(currentDoctypeToken.name);
+      currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
       _addToken(currentToken);
       state = dataState;
     } else {
diff --git a/pubspec.yaml b/pubspec.yaml
index 2576b05..a8fcab3 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -5,7 +5,7 @@
 homepage: https://github.com/dart-lang/html
 
 environment:
-  sdk: '>=2.3.0 <3.0.0'
+  sdk: '>=2.8.0 <3.0.0'
 
 dependencies:
   csslib: '>=0.13.2 <0.17.0'