diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index 945c5bc0d62..b0f6437711f 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -62,41 +62,34 @@
  * @see \Drupal\Component\Render\PlainTextOutput::renderFromHtml()
  * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
  */
-class Attribute implements \ArrayAccess, \IteratorAggregate, MarkupInterface {
+class Attribute extends \ArrayObject implements MarkupInterface {

   /**
-   * Stores the attribute data.
+   * Regular Expression that matches valid attribute names.
    *
-   * @var \Drupal\Core\Template\AttributeValueBase[]
+   * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
    */
-  protected $storage = array();
+  const VALID_NAME_PATTERN = '/^([\w_:][\w\d\-_:\.]*)*$/';

   /**
-   * Constructs a \Drupal\Core\Template\Attribute object.
+   * Constructs a Drupal\Core\Template\Attribute object
    *
-   * @param array $attributes
-   *   An associative array of key-value pairs to be converted to attributes.
+   * @param array $attributes;
    */
-  public function __construct($attributes = array()) {
-    foreach ($attributes as $name => $value) {
-      $this->offsetSet($name, $value);
-    }
-  }
+  public function __construct(array $attributes = []) {
+    // Don't apply the input array blindly - force it to go through the filters
+    // in offsetSet()
+    parent::__construct([]);

-  /**
-   * {@inheritdoc}
-   */
-  public function offsetGet($name) {
-    if (isset($this->storage[$name])) {
-      return $this->storage[$name];
+    foreach ($attributes as $key => $value) {
+      $this[$key] = $value;
     }
   }
-
   /**
    * {@inheritdoc}
    */
   public function offsetSet($name, $value) {
-    $this->storage[$name] = $this->createAttributeValue($name, $value);
+    parent::offsetSet($name, $this->createAttributeValue($name, $value));
   }

   /**
@@ -107,31 +100,43 @@ public function offsetSet($name, $value) {
    * @param mixed $value
    *   The attribute value.
    *
-   * @return \Drupal\Core\Template\AttributeValueBase
-   *   An AttributeValueBase representation of the attribute's value.
+   * @return \Drupal\Core\Template\AttributeValueInterface|null
+   *   An AttributeValueInterface representation of the attribute's value.
    */
   protected function createAttributeValue($name, $value) {
-    // If the value is already an AttributeValueBase object,
+    // This assertions will be reactivated a method for doing so without
+    // breaking contrib modules that might be misusing Attribute is devised.
+    // Asserts that $name is legal pursuant to W3C standard.
+    // assert(preg_match(self::VALID_NAME_PATTERN, $name), 'invalidAttributeName');
+
+    // If the value is already an object with the AttributeValueInterface
     // return a new instance of the same class, but with the new name.
-    if ($value instanceof AttributeValueBase) {
+    if ($value instanceof AttributeValueInterface) {
       $class = get_class($value);
       return new $class($name, $value->value());
     }
-    // An array value or 'class' attribute name are forced to always be an
-    // AttributeArray value for consistency.
-    if ($name == 'class' && !is_array($value)) {
-      // Cast the value to string in case it implements MarkupInterface.
-      $value = [(string) $value];
+    // Some attributes have known W3C patterns concerning their values.
+    // These get their own classes.
+    if ($name === 'class') {
+      $value = new AttributeCssClass($value);
     }
-    if (is_array($value)) {
-      // Cast the value to an array if the value was passed in as a string.
-      // @todo Decide to fix all the broken instances of class as a string
-      // in core or cast them.
-      $value = new AttributeArray($name, $value);
+    elseif ($name === 'id') {
+      $value = new AttributeId($value);
     }
-    elseif (is_bool($value)) {
+    // The names below are the fields which are boolean according to W3C
+    // specification. They will therefore be cast to Boolean.
+    elseif (in_array($name, [
+      'hidden', 'multiple', 'novalidate', 'readonly', 'required',
+      'reversed', 'spellcheck', 'checked', 'defer', 'disabled', 'draggable',
+      'dropzone', 'autoplay', 'selected']) || is_bool($value)) {
       $value = new AttributeBoolean($name, $value);
     }
+    elseif (is_array($value) || $value instanceof \Traversable) {
+      $value = new AttributeArray($name, $value);
+    }
+    elseif (!is_object($value) || method_exists($value, '__toString')) {
+      $value = new AttributeString($name, $value);
+    }
     // As a development aid, we allow the value to be a safe string object.
     elseif ($value instanceof MarkupInterface) {
       // Attributes are not supposed to display HTML markup, so we just convert
@@ -139,24 +144,16 @@ protected function createAttributeValue($name, $value) {
       $value = PlainTextOutput::renderFromHtml($value);
       $value = new AttributeString($name, $value);
     }
-    elseif (!is_object($value)) {
-      $value = new AttributeString($name, $value);
+    else {
+      // We shouldn't be here.
+      // Assertion commented out until we can figure out how not to break
+      // contrib.
+      // assert(false);
+      // But if assertions are turned off set to NULL
+      $value = NULL;
     }
-    return $value;
-  }

-  /**
-   * {@inheritdoc}
-   */
-  public function offsetUnset($name) {
-    unset($this->storage[$name]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function offsetExists($name) {
-    return isset($this->storage[$name]);
+    return $value;
   }

   /**
@@ -168,25 +165,12 @@ public function offsetExists($name) {
    * @return $this
    */
   public function addClass() {
-    $args = func_get_args();
-    if ($args) {
-      $classes = array();
-      foreach ($args as $arg) {
-        // Merge the values passed in from the classes array.
-        // The argument is cast to an array to support comma separated single
-        // values or one or more array arguments.
-        $classes = array_merge($classes, (array) $arg);
-      }
+    if (!isset($this['class'])) {
+      $this['class'] = '';
+    }

-      // Merge if there are values, just add them otherwise.
-      if (isset($this->storage['class']) && $this->storage['class'] instanceof AttributeArray) {
-        // Merge the values passed in from the class value array.
-        $classes = array_merge($this->storage['class']->value(), $classes);
-        $this->storage['class']->exchangeArray($classes);
-      }
-      else {
-        $this->offsetSet('class', $classes);
-      }
+    foreach (func_get_args() as $arg) {
+      $this['class'][] = $arg;
     }

     return $this;
@@ -203,8 +187,7 @@ public function addClass() {
    * @return $this
    */
   public function setAttribute($attribute, $value) {
-    $this->offsetSet($attribute, $value);
-
+    $this[$attribute] = $value;
     return $this;
   }

@@ -217,16 +200,15 @@ public function setAttribute($attribute, $value) {
    * @return $this
    */
   public function removeAttribute() {
-    $args = func_get_args();
-    foreach ($args as $arg) {
+    foreach (func_get_args() as $arg) {
       // Support arrays or multiple arguments.
       if (is_array($arg)) {
         foreach ($arg as $value) {
-          unset($this->storage[$value]);
+          unset($this[$value]);
         }
       }
       else {
-        unset($this->storage[$arg]);
+        unset($this[$arg]);
       }
     }

@@ -243,20 +225,10 @@ public function removeAttribute() {
    */
   public function removeClass() {
     // With no class attribute, there is no need to remove.
-    if (isset($this->storage['class']) && $this->storage['class'] instanceof AttributeArray) {
-      $args = func_get_args();
-      $classes = array();
-      foreach ($args as $arg) {
-        // Merge the values passed in from the classes array.
-        // The argument is cast to an array to support comma separated single
-        // values or one or more array arguments.
-        $classes = array_merge($classes, (array) $arg);
+    if (isset($this['class'])) {
+      foreach (func_get_args() as $arg) {
+        unset($this['class'][$arg]);
       }
-
-      // Remove the values passed in from the value array. Use array_values() to
-      // ensure that the array index remains sequential.
-      $classes = array_values(array_diff($this->storage['class']->value(), $classes));
-      $this->storage['class']->exchangeArray($classes);
     }
     return $this;
   }
@@ -271,12 +243,7 @@ public function removeClass() {
    *   Returns TRUE if the class exists, or FALSE otherwise.
    */
   public function hasClass($class) {
-    if (isset($this->storage['class']) && $this->storage['class'] instanceof AttributeArray) {
-      return in_array($class, $this->storage['class']->value());
-    }
-    else {
-      return FALSE;
-    }
+    return isset($this['class']) && $this['class'][$class];
   }

   /**
@@ -284,13 +251,16 @@ public function hasClass($class) {
    */
   public function __toString() {
     $return = '';
-    /** @var \Drupal\Core\Template\AttributeValueBase $value */
-    foreach ($this->storage as $name => $value) {
-      $rendered = $value->render();
-      if ($rendered) {
-        $return .= ' ' . $rendered;
+
+    foreach ($this as $name => $value) {
+      if ($value !== NULL) {
+        $rendered = $value->render();
+        if ($rendered) {
+          $return .= ' ' . $rendered;
+        }
       }
     }
+
     return $return;
   }

@@ -302,7 +272,7 @@ public function __toString() {
    */
   public function toArray() {
     $return = [];
-    foreach ($this->storage as $name => $value) {
+    foreach ($this as $name => $value) {
       $return[$name] = $value->value();
     }

@@ -313,23 +283,22 @@ public function toArray() {
    * Implements the magic __clone() method.
    */
   public function __clone() {
-    foreach ($this->storage as $name => $value) {
-      $this->storage[$name] = clone $value;
+    $clone = $this->getArrayCopy();
+    foreach ($clone as $name => &$value) {
+      if ($value !== NULL) {
+        $value = clone $value;
+      }
     }
+    $this->exchangeArray($clone);
   }

   /**
-   * {@inheritdoc}
-   */
-  public function getIterator() {
-    return new \ArrayIterator($this->storage);
-  }
-
-  /**
-   * Returns the whole array.
+   * Since making this a child of ArrayObject this becomes a BC wrapper.
+   *
+   * @deprecated use ::getArrayCopy() directly.
    */
   public function storage() {
-    return $this->storage;
+    return $this->getArrayCopy();
   }

   /**
diff --git a/core/lib/Drupal/Core/Template/AttributeArray.php b/core/lib/Drupal/Core/Template/AttributeArray.php
index ce7186e46f0..3c25b4a26c5 100644
--- a/core/lib/Drupal/Core/Template/AttributeArray.php
+++ b/core/lib/Drupal/Core/Template/AttributeArray.php
@@ -22,80 +22,68 @@
  *
  * @see \Drupal\Core\Template\Attribute
  */
-class AttributeArray extends AttributeValueBase implements \ArrayAccess, \IteratorAggregate {
+class AttributeArray extends \ArrayObject implements AttributeValueInterface {

   /**
-   * Ensures empty array as a result of array_filter will not print '$name=""'.
-   *
-   * @see \Drupal\Core\Template\AttributeArray::__toString()
-   * @see \Drupal\Core\Template\AttributeValueBase::render()
+   * Delimiter for the impolsion of the array.
    */
-  const RENDER_EMPTY_ATTRIBUTE = FALSE;
+  const DELIMITER = ' ';

   /**
-   * {@inheritdoc}
+   * Attribute Name
    */
-  public function offsetGet($offset) {
-    return $this->value[$offset];
-  }
+  protected $name;

   /**
-   * {@inheritdoc}
+   * Constructs a Drupal\Core\Template\AttributeArray object
+   *
+   * @param string $name
+   *   Name of the attribute.
+   * @param array $array
+   *   Array to store.
    */
-  public function offsetSet($offset, $value) {
-    if (isset($offset)) {
-      $this->value[$offset] = $value;
-    }
-    else {
-      $this->value[] = $value;
+  public function __construct($name, $array = []) {
+    // Don't apply the input array blindly - force it to go through whatever
+    // filtration is setup in offsetSet()
+    parent::__construct([]);
+    $this->name = $name;
+
+    foreach ($array as $key => $value) {
+      $this[$key] = $value;
     }
   }

   /**
    * {@inheritdoc}
    */
-  public function offsetUnset($offset) {
-    unset($this->value[$offset]);
+  public function value() {
+    return $this->getArrayCopy();
   }

   /**
    * {@inheritdoc}
    */
-  public function offsetExists($offset) {
-    return isset($this->value[$offset]);
-  }
-
-  /**
-   * Implements the magic __toString() method.
-   */
-  public function __toString() {
-    // Filter out any empty values before printing.
-    $this->value = array_unique(array_filter($this->value));
-    return Html::escape(implode(' ', $this->value));
+  public function offsetSet($offset, $value) {
+    $value = trim(strval($value));
+    if ($value !== '') {
+      parent::offsetSet($offset, $value);
+    }
   }

   /**
    * {@inheritdoc}
    */
-  public function getIterator() {
-    return new \ArrayIterator($this->value);
+  public function render() {
+    if (count($this) > 0) {
+      return $this->name . '="' . strval($this) . '"';
+    }
   }

   /**
-   * Exchange the array for another one.
-   *
-   * @see ArrayObject::exchangeArray
-   *
-   * @param array $input
-   *   The array input to replace the internal value.
-   *
-   * @return array
-   *   The old array value.
+   * Implements the magic __toString() method.
    */
-  public function exchangeArray($input) {
-    $old = $this->value;
-    $this->value = $input;
-    return $old;
+  public function __toString() {
+    return Html::escape(implode(self::DELIMITER, $this->getArrayCopy()));
   }

 }
diff --git a/core/lib/Drupal/Core/Template/AttributeBoolean.php b/core/lib/Drupal/Core/Template/AttributeBoolean.php
index cc948202da7..ba1209cfbda 100644
--- a/core/lib/Drupal/Core/Template/AttributeBoolean.php
+++ b/core/lib/Drupal/Core/Template/AttributeBoolean.php
@@ -24,7 +24,31 @@
  *
  * @see \Drupal\Core\Template\Attribute
  */
-class AttributeBoolean extends AttributeValueBase {
+class AttributeBoolean implements AttributeValueInterface {
+  /**
+   * Attribute Name
+   */
+  protected $name;
+
+  /**
+   * Attribute Value.
+   */
+  protected $value;
+
+  /**
+   * Constructs a Drupal\Core\Template\AttributeBoolean.
+   */
+  public function __construct($name, $value) {
+    $this->name = $name;
+    $this->value = (boolean) $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function value() {
+    return $this->value;
+  }

   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Template/AttributeCssClass.php b/core/lib/Drupal/Core/Template/AttributeCssClass.php
new file mode 100644
index 00000000000..dd5c49a807d
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/AttributeCssClass.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace Drupal\Core\Template;
+
+/**
+ * Drupal CSS Class Attribute handler.
+ */
+class AttributeCssClass extends AttributeArray {
+
+  /**
+   * Regular Expression matching valid CSS selector.
+   *
+   * This is also used by AttributeId.
+   *
+   * @see https://www.w3.org/TR/css3-selectors/
+   */
+  const VALID_SELECTOR = '/^((?!\d)(?!\-\-)(?!\-\d)([\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]+))$/u';
+
+  /**
+   * Delimiter for the implosion of the array.
+   *
+   * Repeated in case the parent delimiter is changed.
+   */
+  const DELIMITER = ' ';
+
+  /**
+   * Constructs a \Drupal\Core\Template\AttributeNamedClass object.
+   */
+  public function __construct($value = []) {
+    if (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) {
+      $value = explode(static::DELIMITER, $value);
+    }
+    elseif (empty($value)) {
+      $value = [];
+    }
+
+    parent::__construct('class', $value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    // Note: We never actually use the offset argument here. Since CSS is case
+    // insensitive the offset is strtolower($value).  If the class is passed
+    // with different casings the last one passed wins. This might leave backend
+    // developers scratching their heads but the alternative is worse for the
+    // front end developers.  As for BC breaking, CSS's behavior makes it
+    // highly unlikely any site would be doing this.
+
+    if (is_array($value)) {
+      foreach ($value as $v) {
+        $this[] = $v;
+      }
+    }
+    elseif (strpos($value, self::DELIMITER) !== FALSE) {
+      $value = explode(self::DELIMITER, $value);
+      foreach ($value as $v) {
+        $this[] = $v;
+      }
+    }
+    elseif (!empty($value)) {
+      // If an attempt is made to assign a dupe with a different case, warn.
+      //assert('!(isset($this[$value]) && $this[$value] !== $value)', 'cssCaseInsensitive');
+      // Now before accepting assert valid.
+      //assert(preg_match(self::VALUE_CLASS_NAME, $value), 'invalidIdentifier');
+
+      parent::offsetSet(strtolower($value), $value);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetGet($offset) {
+    return parent::offsetGet(strtolower($offset));
+  }
+
+  /**
+   * Overrides ArrayAccess::offsetUnset().
+   *
+   * Accepts a space delimited set of classes to unset as well as
+   * normal argument.
+   */
+  public function offsetUnset($offset) {
+    if (is_array($offset)) {
+      foreach ($offset as $o) {
+        unset($this[$o]);
+      }
+    }
+    else {
+      $offset = strtolower($offset);
+      if (strpos($offset, self::DELIMITER) !== 0) {
+        $offset = explode(self::DELIMITER, $offset);
+        foreach ($offset as $o) {
+          if (isset($this[$o])) {
+            parent::offsetUnset($o);
+          }
+        }
+      }
+      else {
+        if (isset($this[$offset])) {
+          parent::offsetUnset($offset);
+        }
+      }
+    }
+  }
+
+  /**
+   * Overrides AttributeArray::offsetExists().
+   *
+   * In addition to the normal search, this also checks a space delimited
+   * list of classes and returns true only if all match.
+   */
+  public function offsetExists($offset) {
+    assert('is_string($offset)', 'notString');
+    $offset = strtolower($offset);
+    if (strpos($offset, self::DELIMITER) !== 0) {
+      $offset = explode(self::DELIMITER, $offset);
+
+      foreach ($offset as $o) {
+        if ($o !== '' && !parent::offsetExists($o)) {
+          return FALSE;
+        }
+      }
+      return TRUE;
+    }
+    else {
+      return parent::offsetExists($offset);
+    }
+  }
+
+  /**
+   * Return the CSS classes stored as an array.
+   *
+   * We'll use array_values here because the existing tests do not expect string
+   * keys for the css classes and this preserves that behavior in case there's
+   * a module out there expecting that behavior.
+   */
+  public function value() {
+    return array_values($this->getArrayCopy());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Template/AttributeId.php b/core/lib/Drupal/Core/Template/AttributeId.php
new file mode 100644
index 00000000000..bc230e5261b
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/AttributeId.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\Core\Template;
+
+/**
+ * Attributes named ID manager
+ *
+ * ID's conform to the limits of classes in their pattern, but further they
+ * are required to be unique across the document. This class is largely
+ * disabled at the moment since the assertion that fires its validation is
+ * turned off.
+ */
+class AttributeId extends AttributeString {
+  /**
+   * The id's issued in this document are global across all objects.
+   */
+  protected static $issued = [];
+
+  /**
+   * The key of this object's ID within the hive.
+   */
+  protected $key = '';
+
+  /**
+   * {@inheritdoc}
+   *
+   * Add an assertion call, otherwise same as parent.
+   */
+  public function __construct($value) {
+    //assert('static::validate($value)', 'ID Validation Failure');
+    parent::__construct('id', $value);
+  }
+
+  /**
+   * Nothing here is in use when assertions are turned off - these are just
+   * the tests ID's should conform to.
+   */
+  protected static function validate( $value ) {
+    // If there isn't an id there's nothing to validate.
+    if (!$value) {
+      return TRUE;
+    }
+
+    // CSS identifiers are case insensitive. We'll respect the user's chosen
+    // casing on display but internally we'll store the id's for comparison by
+    // all lower case.
+    $key = strtolower($value);
+
+    // ID's are unique across the whole document, not just a given element's
+    // attribute list, which is why we're using a static here.
+    if (isset(static::$issued[$key])) {
+      return FALSE;
+    }
+
+    // Test for valid CSS identifier pattern.
+    if (!preg_match(AttributeCssClass::VALID_SELECTOR, $value)) {
+      return FALSE;
+    }
+
+    // Good to go. Register the key for later use to remove this id from the
+    // list of those issued when necessary, then register this id as in use.
+    $this->key = $key;
+    static::$issued[$key] = $key;
+    return TRUE;
+  }
+
+  /**
+   * As this instance is destroyed unregister ourself from the list of
+   * unused ID's.  This is a pointless operation when assertions when
+   * assertions are off, but also harmless.
+   */
+  public function __destruct() {
+    unset(static::$issued[$this->value]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Template/AttributeString.php b/core/lib/Drupal/Core/Template/AttributeString.php
index d4416bbae1c..71e36d6a783 100644
--- a/core/lib/Drupal/Core/Template/AttributeString.php
+++ b/core/lib/Drupal/Core/Template/AttributeString.php
@@ -19,13 +19,57 @@
  *
  * @see \Drupal\Core\Template\Attribute
  */
-class AttributeString extends AttributeValueBase {
+class AttributeString implements AttributeValueInterface {

   /**
-   * Implements the magic __toString() method.
+   * Attribute Name
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * Attribute Value
+   *
+   * @var string
+   */
+  protected $value;
+
+  /**
+   * Escaped Value
+   *
+   * @var string
+   */
+  protected $escapedValue;
+
+  /**
+   * Constructs a \Drupal\Core\Template\AttributeString object.
+   */
+  public function __construct($name, $value) {
+    $this->name = $name;
+    $this->value = trim((string) $value);
+    $this->escapedValue = Html::escape($this->value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    return "{$this->name}=\"{$this->escapedValue}\"";
+  }
+
+  /**
+   * Returns the raw value.
+   */
+  public function value() {
+    return $this->value;
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function __toString() {
-    return Html::escape($this->value);
+    return $this->escapedValue;
   }

 }
diff --git a/core/lib/Drupal/Core/Template/AttributeValueBase.php b/core/lib/Drupal/Core/Template/AttributeValueBase.php
deleted file mode 100644
index a0280e61064..00000000000
--- a/core/lib/Drupal/Core/Template/AttributeValueBase.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-namespace Drupal\Core\Template;
-use Drupal\Component\Utility\Html;
-
-/**
- * Defines the base class for an attribute type.
- *
- * @see \Drupal\Core\Template\Attribute
- */
-abstract class AttributeValueBase {
-
-  /**
-   * Renders '$name=""' if $value is an empty string.
-   *
-   * @see \Drupal\Core\Template\AttributeValueBase::render()
-   */
-  const RENDER_EMPTY_ATTRIBUTE = TRUE;
-
-  /**
-   * The value itself.
-   *
-   * @var mixed
-   */
-  protected $value;
-
-  /**
-   * The name of the value.
-   *
-   * @var mixed
-   */
-  protected $name;
-
-  /**
-   * Constructs a \Drupal\Core\Template\AttributeValueBase object.
-   */
-  public function __construct($name, $value) {
-    $this->name = $name;
-    $this->value = $value;
-  }
-
-  /**
-   * Returns a string representation of the attribute.
-   *
-   * While __toString only returns the value in a string form, render()
-   * contains the name of the attribute as well.
-   *
-   * @return string
-   *   The string representation of the attribute.
-   */
-  public function render() {
-    $value = (string) $this;
-    if (isset($this->value) && static::RENDER_EMPTY_ATTRIBUTE || !empty($value)) {
-      return Html::escape($this->name) . '="' . $value . '"';
-    }
-  }
-
-  /**
-   * Returns the raw value.
-   */
-  public function value() {
-    return $this->value;
-  }
-
-  /**
-   * Implements the magic __toString() method.
-   */
-  abstract function __toString();
-
-}
diff --git a/core/lib/Drupal/Core/Template/AttributeValueInterface.php b/core/lib/Drupal/Core/Template/AttributeValueInterface.php
new file mode 100644
index 00000000000..e6cf64a97e6
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/AttributeValueInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\Core\Template;
+
+/**
+ * Attribute managing classes conform to this interface.
+ */
+interface AttributeValueInterface {
+
+  /**
+   * Returns a string representation of the attribute.
+   *
+   * While __toString only returns the value in a string form, render()
+   * contains the name of the attribute as well.
+   *
+   * @return string
+   *   The string representation of the attribute.
+   */
+  public function render();
+
+  /**
+   * Returns the raw value.
+   */
+  public function value();
+
+  /**
+   * Return the string value of the stored attribute.
+   */
+  public function __toString();
+
+}
--
2.11.0.windows.3


From 9c8154760c74238b6a77a65149a76aaf47e6950e Mon Sep 17 00:00:00 2001
From: "LEXUCG\\mmorris" <mmorris@lexingtonky.gov>
Date: Tue, 7 Feb 2017 19:33:29 -0500
Subject: [PATCH 2/3] Two most core tests run clean. Let's see if the others
 do.

---
 core/lib/Drupal/Core/Template/Attribute.php        |  49 ++++++---
 core/lib/Drupal/Core/Template/AttributeArray.php   |  38 +++++--
 core/lib/Drupal/Core/Template/AttributeString.php  |  14 +--
 .../Drupal/Tests/Core/Template/AttributeTest.php   | 115 +++++++++++++++++++--
 4 files changed, 171 insertions(+), 45 deletions(-)

diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index b0f6437711f..f7e7a963ced 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -91,6 +91,28 @@ public function __construct(array $attributes = []) {
   public function offsetSet($name, $value) {
     parent::offsetSet($name, $this->createAttributeValue($name, $value));
   }
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($name) {
+    /*
+     * For historical reasons we set the value to NULL but do not wipe out
+     * the key.
+     */
+    parent::offsetSet($name, NULL);
+  }
+
+  /**
+   * Clear an attribute.
+   *
+   * This function exists because we need a way to truly unset attributes.
+   */
+  public function clear() {
+    foreach (func_get_args() as $name) {
+      parent::offsetUnset($name);
+    }
+    return $this;
+  }

   /**
    * Creates the different types of attribute values.
@@ -113,7 +135,13 @@ protected function createAttributeValue($name, $value) {
     // return a new instance of the same class, but with the new name.
     if ($value instanceof AttributeValueInterface) {
       $class = get_class($value);
-      return new $class($name, $value->value());
+
+      if (in_array($name, ['id','class'])) {
+        return new $class($value->value());
+      }
+      else {
+        return new $class($name, $value->value());
+      }
     }
     // Some attributes have known W3C patterns concerning their values.
     // These get their own classes.
@@ -131,12 +159,6 @@ protected function createAttributeValue($name, $value) {
       'dropzone', 'autoplay', 'selected']) || is_bool($value)) {
       $value = new AttributeBoolean($name, $value);
     }
-    elseif (is_array($value) || $value instanceof \Traversable) {
-      $value = new AttributeArray($name, $value);
-    }
-    elseif (!is_object($value) || method_exists($value, '__toString')) {
-      $value = new AttributeString($name, $value);
-    }
     // As a development aid, we allow the value to be a safe string object.
     elseif ($value instanceof MarkupInterface) {
       // Attributes are not supposed to display HTML markup, so we just convert
@@ -144,15 +166,14 @@ protected function createAttributeValue($name, $value) {
       $value = PlainTextOutput::renderFromHtml($value);
       $value = new AttributeString($name, $value);
     }
-    else {
-      // We shouldn't be here.
-      // Assertion commented out until we can figure out how not to break
-      // contrib.
-      // assert(false);
-      // But if assertions are turned off set to NULL
-      $value = NULL;
+    elseif (is_array($value) || $value instanceof \Traversable) {
+      $value = new AttributeArray($name, $value);
+    }
+    elseif ($value !== NULL) {
+      $value = new AttributeString($name, $value);
     }

+
     return $value;
   }

diff --git a/core/lib/Drupal/Core/Template/AttributeArray.php b/core/lib/Drupal/Core/Template/AttributeArray.php
index 3c25b4a26c5..7996176abb2 100644
--- a/core/lib/Drupal/Core/Template/AttributeArray.php
+++ b/core/lib/Drupal/Core/Template/AttributeArray.php
@@ -63,27 +63,43 @@ public function value() {
   /**
    * {@inheritdoc}
    */
-  public function offsetSet($offset, $value) {
-    $value = trim(strval($value));
-    if ($value !== '') {
-      parent::offsetSet($offset, $value);
+  public function render() {
+    /*
+     * This is inconsistent to the way string attributes are handled as
+     * detailed in issue #2850632
+     */
+    if (count($this) > 0) {
+      $safe = strval($this);
+      return Html::escape($this->name) . '="' . strval($this) . '"';
     }
   }

   /**
-   * {@inheritdoc}
+   * Implements the magic __toString() method.
    */
-  public function render() {
+  public function __toString() {
     if (count($this) > 0) {
-      return $this->name . '="' . strval($this) . '"';
+      return implode(self::DELIMITER, $this->getSafeCopy());
+    }
+    else {
+      return '';
     }
   }

   /**
-   * Implements the magic __toString() method.
+   * Returns HTML safe version of the stored array.
+   *
+   * Empty and duplicate values will be removed.
    */
-  public function __toString() {
-    return Html::escape(implode(self::DELIMITER, $this->getArrayCopy()));
-  }
+  protected function getSafeCopy() {
+    $array = $this->getArrayCopy();
+    $out = [];

+    foreach ($array as $value) {
+      if (!empty($value) && !isset($out[$value])) {
+        $out[$value] = HTML::escape($value);
+      }
+    }
+    return $out;
+  }
 }
diff --git a/core/lib/Drupal/Core/Template/AttributeString.php b/core/lib/Drupal/Core/Template/AttributeString.php
index 71e36d6a783..2fe726960bd 100644
--- a/core/lib/Drupal/Core/Template/AttributeString.php
+++ b/core/lib/Drupal/Core/Template/AttributeString.php
@@ -36,26 +36,18 @@ class AttributeString implements AttributeValueInterface {
   protected $value;

   /**
-   * Escaped Value
-   *
-   * @var string
-   */
-  protected $escapedValue;
-
-  /**
    * Constructs a \Drupal\Core\Template\AttributeString object.
    */
   public function __construct($name, $value) {
     $this->name = $name;
-    $this->value = trim((string) $value);
-    $this->escapedValue = Html::escape($this->value);
+    $this->value = $value;
   }

   /**
    * {@inheritdoc}
    */
   public function render() {
-    return "{$this->name}=\"{$this->escapedValue}\"";
+    return Html::escape($this->name) . '="' . Html::escape($this->value) . '"';
   }

   /**
@@ -69,7 +61,7 @@ public function value() {
    * {@inheritdoc}
    */
   public function __toString() {
-    return $this->escapedValue;
+    return Html::escape($this->value);
   }

 }
diff --git a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php
index 09ec9894dcc..c6d717eeddd 100644
--- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php
+++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php
@@ -6,7 +6,10 @@
 use Drupal\Core\Render\Markup;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Template\AttributeArray;
+use Drupal\Core\Template\AttributeCssClass;
+use Drupal\Core\Template\AttributeId;
 use Drupal\Core\Template\AttributeString;
+use Drupal\Core\Template\AttributeValueInterface;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Component\Render\MarkupInterface;

@@ -22,7 +25,7 @@ class AttributeTest extends UnitTestCase {
   public function testConstructor() {
     $attribute = new Attribute(array('class' => array('example-class')));
     $this->assertTrue(isset($attribute['class']));
-    $this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']);
+    $this->assertEquals(new AttributeCssClass(array('example-class')), $attribute['class']);

     // Test adding boolean attributes through the constructor.
     $attribute = new Attribute(['selected' => TRUE, 'checked' => FALSE]);
@@ -32,14 +35,14 @@ public function testConstructor() {
     // Test that non-array values with name "class" are cast to array.
     $attribute = new Attribute(array('class' => 'example-class'));
     $this->assertTrue(isset($attribute['class']));
-    $this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']);
+    $this->assertEquals(new AttributeCssClass(array('example-class')), $attribute['class']);

     // Test that safe string objects work correctly.
     $safe_string = $this->prophesize(MarkupInterface::class);
     $safe_string->__toString()->willReturn('example-class');
     $attribute = new Attribute(array('class' => $safe_string->reveal()));
     $this->assertTrue(isset($attribute['class']));
-    $this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']);
+    $this->assertEquals(new AttributeCssClass(array('example-class')), $attribute['class']);
   }

   /**
@@ -50,7 +53,7 @@ public function testSet() {
     $attribute['class'] = array('example-class');

     $this->assertTrue(isset($attribute['class']));
-    $this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']);
+    $this->assertEquals(new AttributeCssClass(array('example-class')), $attribute['class']);
   }

   /**
@@ -60,7 +63,7 @@ public function testAdd() {
     $attribute = new Attribute(array('class' => array('example-class')));

     $attribute['class'][] = 'other-class';
-    $this->assertEquals(new AttributeArray('class', array('example-class', 'other-class')), $attribute['class']);
+    $this->assertEquals(new AttributeCssClass(array('example-class', 'other-class')), $attribute['class']);
   }

   /**
@@ -155,8 +158,8 @@ public function testAddClasses() {
     foreach (array(NULL, FALSE, '', []) as $value) {
       // Single value.
       $attribute->addClass($value);
-      $this->assertEmpty((string) $attribute);

+      $this->assertEmpty((string) $attribute);
       // Multiple values.
       $attribute->addClass($value, $value);
       $this->assertEmpty((string) $attribute);
@@ -323,11 +326,11 @@ public function testIterate() {
     foreach ($attribute as $key => $value) {
       if ($counter == 0) {
         $this->assertEquals('class', $key);
-        $this->assertEquals(new AttributeArray('class', array('example-class')), $value);
+        $this->assertEquals(new AttributeCssClass(array('example-class')), $value);
       }
       if ($counter == 1) {
         $this->assertEquals('id', $key);
-        $this->assertEquals(new AttributeString('id', 'example-id'), $value);
+        $this->assertEquals(new AttributeId('example-id'), $value);
       }
       $counter++;
     }
@@ -448,7 +451,101 @@ protected function getXPathResultCount($query, $html) {
   public function testStorage() {
     $attribute = new Attribute(array('class' => array('example-class')));

-    $this->assertEquals(array('class' => new AttributeArray('class', array('example-class'))), $attribute->storage());
+    $this->assertEquals(array('class' => new AttributeCssClass(array('example-class'))), $attribute->storage());
+  }
+
+  /**
+   * Tests AttributeArray helper class
+   */
+  public function testAttributeArray() {
+
+    foreach ([
+      ['Apple', 'Pear', 'Orange', 'Peach'],
+      new \ArrayObject(['Apple', 'Pear', 'Orange', 'Peach'])
+      ] as $a) {
+
+      $attribute = new Attribute(['test' => $a]);
+
+      if ($a instanceof \ArrayObject) {
+        $a = $a->getArrayCopy();
+      }
+      $this->assertEquals($a, $attribute['test']->value());
+      $this->assertTrue($attribute['test'] instanceof AttributeArray);
+    }
+
+    // We can iterate.
+    $i = 0;
+    foreach ($attribute['test'] as $a) {
+      $i++;
+    }
+    $this->assertEquals(4, $i);
+
+    // We can remove, then count.
+    unset($attribute['test'][3]);
+    $this->assertEquals(3, count($attribute['test']));
+  }
+
+  /**
+   * Tests AttributeCssClass helper class
+   */
+  public function testAttributeCssClass() {
+    // Simple string name of class.
+    $attribute = new Attribute(['class' => 'test']);
+    $this->assertTrue(isset($attribute['class']));
+
+    // Instance of AttributeCssClass.
+    $attribute_value = new AttributeCssClass('test');
+    $this->assertEquals($attribute_value, $attribute['class']);
+
+    // Space delimited strings will become arrays.
+    $attribute = new Attribute(['class' => 'Apple Pear Orange Peach']);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange', 'Peach']);
+
+    // Redundants will be stripped.
+    $attribute = new Attribute(['class' => 'Apple Pear Orange Pear']);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange']);
+
+    // Since CSS is case insensitive, the AttributeCssClass stores classes in a
+    // case insensitive manner. This means different cases of the same class
+    // name will be treated as dupes. Last case arrangement used wins.
+    $attribute = new Attribute(['class' => 'Apple pEAR Orange Pear']);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange']);
+
+    // Array lists of class will also be accepted.
+    $attribute = new Attribute(['class' => ['Apple', 'Pear', 'Orange', 'Peach']]);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange', 'Peach']);
+
+    // As will traversable objects.
+    $attribute = new Attribute(['class' => new \ArrayObject(['Apple', 'Pear', 'Orange', 'Peach'])]);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange', 'Peach']);
+
+    // Redundants will be stripped.
+    $attribute = new Attribute(['class' => ['Apple', 'Pear', 'Orange', 'Pear']]);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange']);
+
+    // Empties will be stripped.
+    $attribute = new Attribute(['class' => ['Apple', 'Pear', '', 'Orange', '', 'Peach']]);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange', 'Peach']);
+
+    // Empties and dupes will be stripped.
+    $attribute = new Attribute(['class' => ['Apple', 'Pear', '', 'Orange', '', 'Pear']]);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange']);
+
+    // We can add new.
+    $attribute['class'][] = 'Peach';
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange', 'Peach']);
+
+    // We can delete.
+    unset($attribute['class']['peach']);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear', 'Orange']);
+
+    // We can delete even if the case is wrong.
+    unset($attribute['class']['Orange']);
+    $this->assertEquals($attribute['class']->value(), ['Apple', 'Pear']);
+
+    // Above case insensitively applies to isset.
+    $this->assertTrue(isset($attribute['class']['pear']));
+    $this->assertTrue(isset($attribute['class']['Pear']));
   }

 }
--
2.11.0.windows.3


From 022eceeb80297654263a189b0f2d2ece9b9bf6fb Mon Sep 17 00:00:00 2001
From: "LEXUCG\\mmorris" <mmorris@lexingtonky.gov>
Date: Wed, 8 Feb 2017 09:30:57 -0500
Subject: [PATCH 3/3] Further simplification.

---
 core/lib/Drupal/Core/Template/Attribute.php        | 87 ++++++++++++++--------
 core/lib/Drupal/Core/Template/AttributeArray.php   | 25 +------
 core/lib/Drupal/Core/Template/AttributeBoolean.php | 67 -----------------
 .../lib/Drupal/Core/Template/AttributeCssClass.php | 11 +--
 core/lib/Drupal/Core/Template/AttributeId.php      | 38 +++++++---
 core/lib/Drupal/Core/Template/AttributeString.php  | 67 -----------------
 .../Core/Template/AttributeValueInterface.php      | 10 +--
 .../Drupal/Tests/Core/Template/AttributeTest.php   |  7 +-
 8 files changed, 95 insertions(+), 217 deletions(-)
 delete mode 100644 core/lib/Drupal/Core/Template/AttributeBoolean.php
 delete mode 100644 core/lib/Drupal/Core/Template/AttributeString.php

diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index f7e7a963ced..8ee0b4789d7 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -4,6 +4,7 @@

 use Drupal\Component\Render\PlainTextOutput;
 use Drupal\Component\Render\MarkupInterface;
+use Drupal\Component\Utility\Html;

 /**
  * Collects, sanitizes, and renders HTML attributes.
@@ -72,6 +73,28 @@ class Attribute extends \ArrayObject implements MarkupInterface {
   const VALID_NAME_PATTERN = '/^([\w_:][\w\d\-_:\.]*)*$/';

   /**
+   * Attributes known to be boolean according to W3C specification.
+   *
+   * @var array
+   */
+  protected $booleanAttributes = [
+    'hidden',
+    'multiple',
+    'novalidate',
+    'readonly',
+    'required',
+    'reversed',
+    'spellcheck',
+    'checked',
+    'defer',
+    'disabled',
+    'draggable',
+    'dropzone',
+    'autoplay',
+    'selected'
+  ];
+
+  /**
    * Constructs a Drupal\Core\Template\Attribute object
    *
    * @param array $attributes;
@@ -122,8 +145,8 @@ public function clear() {
    * @param mixed $value
    *   The attribute value.
    *
-   * @return \Drupal\Core\Template\AttributeValueInterface|null
-   *   An AttributeValueInterface representation of the attribute's value.
+   * @return mixed
+   *   The value, wrapped if neccessary by an AttributeValueInterface object.
    */
   protected function createAttributeValue($name, $value) {
     // This assertions will be reactivated a method for doing so without
@@ -132,16 +155,9 @@ protected function createAttributeValue($name, $value) {
     // assert(preg_match(self::VALID_NAME_PATTERN, $name), 'invalidAttributeName');

     // If the value is already an object with the AttributeValueInterface
-    // return a new instance of the same class, but with the new name.
+    // return it.
     if ($value instanceof AttributeValueInterface) {
-      $class = get_class($value);
-
-      if (in_array($name, ['id','class'])) {
-        return new $class($value->value());
-      }
-      else {
-        return new $class($name, $value->value());
-      }
+      $value = clone $value;
     }
     // Some attributes have known W3C patterns concerning their values.
     // These get their own classes.
@@ -153,26 +169,18 @@ protected function createAttributeValue($name, $value) {
     }
     // The names below are the fields which are boolean according to W3C
     // specification. They will therefore be cast to Boolean.
-    elseif (in_array($name, [
-      'hidden', 'multiple', 'novalidate', 'readonly', 'required',
-      'reversed', 'spellcheck', 'checked', 'defer', 'disabled', 'draggable',
-      'dropzone', 'autoplay', 'selected']) || is_bool($value)) {
-      $value = new AttributeBoolean($name, $value);
+    elseif (in_array($name, $this->booleanAttributes) || is_bool($value)) {
+      $value = (bool) $value;
     }
     // As a development aid, we allow the value to be a safe string object.
     elseif ($value instanceof MarkupInterface) {
       // Attributes are not supposed to display HTML markup, so we just convert
       // the value to plain text.
       $value = PlainTextOutput::renderFromHtml($value);
-      $value = new AttributeString($name, $value);
     }
     elseif (is_array($value) || $value instanceof \Traversable) {
-      $value = new AttributeArray($name, $value);
+      $value = new AttributeArray($value);
     }
-    elseif ($value !== NULL) {
-      $value = new AttributeString($name, $value);
-    }
-

     return $value;
   }
@@ -274,11 +282,26 @@ public function __toString() {
     $return = '';

     foreach ($this as $name => $value) {
-      if ($value !== NULL) {
-        $rendered = $value->render();
-        if ($rendered) {
-          $return .= ' ' . $rendered;
+      if ($value instanceof AttributeValueInterface) {
+        $safe = (string) $value;
+        /**
+         * There's an inconsistency in the tests here. They expect the CSS Class
+         * attribute to be missing when empty, but all other attributes are
+         * expected to return attribute=''
+         *
+         * @see https://www.drupal.org/node/2850632
+         */
+        if ($value instanceof AttributeCssClass && count($value) == 0) {
+          continue;
         }
+
+        $return .= $value ? ' ' . Html::escape($name) . '="' . $value . '"' : '';
+      }
+      elseif (is_bool($value)) {
+        $return .= $value ? ' ' . Html::escape($name) : '';
+      }
+      elseif ($value !== NULL) {
+        $return .= ' ' . Html::escape($name) . '="' . Html::escape($value) . '"';
       }
     }

@@ -292,9 +315,11 @@ public function __toString() {
    *   An associative array of attributes.
    */
   public function toArray() {
-    $return = [];
-    foreach ($this as $name => $value) {
-      $return[$name] = $value->value();
+    $return = $this->getArrayCopy();
+    foreach ($return as &$value) {
+      if ($value instanceof AttributeValueInterface) {
+        $value = $value->value();
+      }
     }

     return $return;
@@ -305,8 +330,8 @@ public function toArray() {
    */
   public function __clone() {
     $clone = $this->getArrayCopy();
-    foreach ($clone as $name => &$value) {
-      if ($value !== NULL) {
+    foreach ($clone as &$value) {
+      if (is_object($value)) {
         $value = clone $value;
       }
     }
diff --git a/core/lib/Drupal/Core/Template/AttributeArray.php b/core/lib/Drupal/Core/Template/AttributeArray.php
index 7996176abb2..2b0bfc5b6e7 100644
--- a/core/lib/Drupal/Core/Template/AttributeArray.php
+++ b/core/lib/Drupal/Core/Template/AttributeArray.php
@@ -30,11 +30,6 @@ class AttributeArray extends \ArrayObject implements AttributeValueInterface {
   const DELIMITER = ' ';

   /**
-   * Attribute Name
-   */
-  protected $name;
-
-  /**
    * Constructs a Drupal\Core\Template\AttributeArray object
    *
    * @param string $name
@@ -42,11 +37,10 @@ class AttributeArray extends \ArrayObject implements AttributeValueInterface {
    * @param array $array
    *   Array to store.
    */
-  public function __construct($name, $array = []) {
+  public function __construct($array = []) {
     // Don't apply the input array blindly - force it to go through whatever
     // filtration is setup in offsetSet()
     parent::__construct([]);
-    $this->name = $name;

     foreach ($array as $key => $value) {
       $this[$key] = $value;
@@ -61,20 +55,6 @@ public function value() {
   }

   /**
-   * {@inheritdoc}
-   */
-  public function render() {
-    /*
-     * This is inconsistent to the way string attributes are handled as
-     * detailed in issue #2850632
-     */
-    if (count($this) > 0) {
-      $safe = strval($this);
-      return Html::escape($this->name) . '="' . strval($this) . '"';
-    }
-  }
-
-  /**
    * Implements the magic __toString() method.
    */
   public function __toString() {
@@ -97,9 +77,10 @@ protected function getSafeCopy() {

     foreach ($array as $value) {
       if (!empty($value) && !isset($out[$value])) {
-        $out[$value] = HTML::escape($value);
+        $out[$value] = Html::escape($value);
       }
     }
     return $out;
   }
+
 }
diff --git a/core/lib/Drupal/Core/Template/AttributeBoolean.php b/core/lib/Drupal/Core/Template/AttributeBoolean.php
deleted file mode 100644
index ba1209cfbda..00000000000
--- a/core/lib/Drupal/Core/Template/AttributeBoolean.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace Drupal\Core\Template;
-
-use Drupal\Component\Utility\Html;
-
-/**
- * A class that defines a type of boolean HTML attribute.
- *
- * Boolean HTML attributes are not attributes with values of TRUE/FALSE.
- * They are attributes that if they exist in the tag, they are TRUE.
- * Examples include selected, disabled, checked, readonly.
- *
- * To set a boolean attribute on the Attribute class, set it to TRUE.
- * @code
- *  $attributes = new Attribute();
- *  $attributes['disabled'] = TRUE;
- *  echo '<select' . $attributes . '/>';
- *  // produces <select disabled>;
- *  $attributes['disabled'] = FALSE;
- *  echo '<select' . $attributes . '/>';
- *  // produces <select>;
- * @endcode
- *
- * @see \Drupal\Core\Template\Attribute
- */
-class AttributeBoolean implements AttributeValueInterface {
-  /**
-   * Attribute Name
-   */
-  protected $name;
-
-  /**
-   * Attribute Value.
-   */
-  protected $value;
-
-  /**
-   * Constructs a Drupal\Core\Template\AttributeBoolean.
-   */
-  public function __construct($name, $value) {
-    $this->name = $name;
-    $this->value = (boolean) $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function value() {
-    return $this->value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function render() {
-    return $this->__toString();
-  }
-
-  /**
-   * Implements the magic __toString() method.
-   */
-  public function __toString() {
-    return $this->value === FALSE ? '' : Html::escape($this->name);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Template/AttributeCssClass.php b/core/lib/Drupal/Core/Template/AttributeCssClass.php
index dd5c49a807d..cd8b9efc8b5 100644
--- a/core/lib/Drupal/Core/Template/AttributeCssClass.php
+++ b/core/lib/Drupal/Core/Template/AttributeCssClass.php
@@ -8,15 +8,6 @@
 class AttributeCssClass extends AttributeArray {

   /**
-   * Regular Expression matching valid CSS selector.
-   *
-   * This is also used by AttributeId.
-   *
-   * @see https://www.w3.org/TR/css3-selectors/
-   */
-  const VALID_SELECTOR = '/^((?!\d)(?!\-\-)(?!\-\d)([\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]+))$/u';
-
-  /**
    * Delimiter for the implosion of the array.
    *
    * Repeated in case the parent delimiter is changed.
@@ -34,7 +25,7 @@ public function __construct($value = []) {
       $value = [];
     }

-    parent::__construct('class', $value);
+    parent::__construct($value);
   }

   /**
diff --git a/core/lib/Drupal/Core/Template/AttributeId.php b/core/lib/Drupal/Core/Template/AttributeId.php
index bc230e5261b..b0ba61418dc 100644
--- a/core/lib/Drupal/Core/Template/AttributeId.php
+++ b/core/lib/Drupal/Core/Template/AttributeId.php
@@ -2,6 +2,9 @@

 namespace Drupal\Core\Template;

+use Drupal\Component\Utility\Html;
+
+
 /**
  * Attributes named ID manager
  *
@@ -10,7 +13,7 @@
  * disabled at the moment since the assertion that fires its validation is
  * turned off.
  */
-class AttributeId extends AttributeString {
+class AttributeId implements AttributeValueInterface {
   /**
    * The id's issued in this document are global across all objects.
    */
@@ -22,13 +25,30 @@ class AttributeId extends AttributeString {
   protected $key = '';

   /**
-   * {@inheritdoc}
-   *
-   * Add an assertion call, otherwise same as parent.
+   * The value of this Id
+   */
+  protected $value = '';
+
+  /**
+   * Constructs a Drupal\Core\Template\AttributeId object.
    */
   public function __construct($value) {
     //assert('static::validate($value)', 'ID Validation Failure');
-    parent::__construct('id', $value);
+    $this->value = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function value() {
+    return $this->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return Html::escape($this->value);
   }

   /**
@@ -53,7 +73,7 @@ protected static function validate( $value ) {
     }

     // Test for valid CSS identifier pattern.
-    if (!preg_match(AttributeCssClass::VALID_SELECTOR, $value)) {
+    if (!preg_match(self::VALID_SELECTOR, $value)) {
       return FALSE;
     }

@@ -66,11 +86,11 @@ protected static function validate( $value ) {

   /**
    * As this instance is destroyed unregister ourself from the list of
-   * unused ID's.  This is a pointless operation when assertions when
-   * assertions are off, but also harmless.
+   * unused ID's.  This is a pointless operation when assertions when are off,
+   * but also harmless.
    */
   public function __destruct() {
-    unset(static::$issued[$this->value]);
+    unset(static::$issued[$this->key]);
   }

 }
diff --git a/core/lib/Drupal/Core/Template/AttributeString.php b/core/lib/Drupal/Core/Template/AttributeString.php
deleted file mode 100644
index 2fe726960bd..00000000000
--- a/core/lib/Drupal/Core/Template/AttributeString.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace Drupal\Core\Template;
-
-use Drupal\Component\Utility\Html;
-
-/**
- * A class that represents most standard HTML attributes.
- *
- * To use with the Attribute class, set the key to be the attribute name
- * and the value the attribute value.
- * @code
- *  $attributes = new Attribute(array());
- *  $attributes['id'] = 'socks';
- *  $attributes['style'] = 'background-color:white';
- *  echo '<cat ' . $attributes . '>';
- *  // Produces: <cat id="socks" style="background-color:white">.
- * @endcode
- *
- * @see \Drupal\Core\Template\Attribute
- */
-class AttributeString implements AttributeValueInterface {
-
-  /**
-   * Attribute Name
-   *
-   * @var string
-   */
-  protected $name;
-
-  /**
-   * Attribute Value
-   *
-   * @var string
-   */
-  protected $value;
-
-  /**
-   * Constructs a \Drupal\Core\Template\AttributeString object.
-   */
-  public function __construct($name, $value) {
-    $this->name = $name;
-    $this->value = $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function render() {
-    return Html::escape($this->name) . '="' . Html::escape($this->value) . '"';
-  }
-
-  /**
-   * Returns the raw value.
-   */
-  public function value() {
-    return $this->value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __toString() {
-    return Html::escape($this->value);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Template/AttributeValueInterface.php b/core/lib/Drupal/Core/Template/AttributeValueInterface.php
index e6cf64a97e6..ee9cb99996d 100644
--- a/core/lib/Drupal/Core/Template/AttributeValueInterface.php
+++ b/core/lib/Drupal/Core/Template/AttributeValueInterface.php
@@ -8,15 +8,11 @@
 interface AttributeValueInterface {

   /**
-   * Returns a string representation of the attribute.
+   * Regular Expression matching valid CSS selector.
    *
-   * While __toString only returns the value in a string form, render()
-   * contains the name of the attribute as well.
-   *
-   * @return string
-   *   The string representation of the attribute.
+   * @see https://www.w3.org/TR/css3-selectors/
    */
-  public function render();
+  const VALID_SELECTOR = '/^((?!\d)(?!\-\-)(?!\-\d)([\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]+))$/u';

   /**
    * Returns the raw value.
diff --git a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php
index c6d717eeddd..7adb6cf73cd 100644
--- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php
+++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php
@@ -8,7 +8,6 @@
 use Drupal\Core\Template\AttributeArray;
 use Drupal\Core\Template\AttributeCssClass;
 use Drupal\Core\Template\AttributeId;
-use Drupal\Core\Template\AttributeString;
 use Drupal\Core\Template\AttributeValueInterface;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Component\Render\MarkupInterface;
@@ -29,8 +28,8 @@ public function testConstructor() {

     // Test adding boolean attributes through the constructor.
     $attribute = new Attribute(['selected' => TRUE, 'checked' => FALSE]);
-    $this->assertTrue($attribute['selected']->value());
-    $this->assertFalse($attribute['checked']->value());
+    $this->assertTrue($attribute['selected']);
+    $this->assertFalse($attribute['checked']);

     // Test that non-array values with name "class" are cast to array.
     $attribute = new Attribute(array('class' => 'example-class'));
@@ -100,7 +99,7 @@ public function testSetAttribute() {
     // Test adding boolean attributes.
     $attribute = new Attribute();
     $attribute['checked'] = TRUE;
-    $this->assertTrue($attribute['checked']->value());
+    $this->assertTrue($attribute['checked']);
   }

   /**
