diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
index c0c21af70..0000eb760 100644
--- a/.phpcs.xml.dist
+++ b/.phpcs.xml.dist
@@ -7,12 +7,7 @@
-
-
-
- bin/*
-
-
+
@@ -25,14 +20,23 @@
-
+
-
-
- bin/*
-
+
+
+ bin/*
+
+
+ src/Validator/ErrorCode.php
+ src/Validator/Spec.php
+ src/Validator/Spec/*
tests/*
+
+
+
+ src/Validator/Spec/Tag/*_.php
+
diff --git a/bin/generate-validator-spec.php b/bin/generate-validator-spec.php
new file mode 100644
index 000000000..d83d44ca6
--- /dev/null
+++ b/bin/generate-validator-spec.php
@@ -0,0 +1,67 @@
+#!/usr/bin/env php
+generate(
+ json_decode($json, true),
+ 'AmpProject\Validator',
+ $destination
+ );
+ echo "\n";
+
+ echo "Done!\n";
+} catch (Exception $exception) {
+ echo 'ERROR: ' . $exception->getMessage() . "\n";
+ exit -1;
+}
diff --git a/bin/src/Validator/SpecGenerator.php b/bin/src/Validator/SpecGenerator.php
new file mode 100644
index 000000000..39672e2cd
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator.php
@@ -0,0 +1,337 @@
+setTypeResolving(false);
+
+ $fileManager = new FileManager($rootNamespace, $destination, $printer);
+
+ $fileManager->ensureDirectoriesExist();
+
+ list($file, $namespace) = $fileManager->createNewNamespacedFile();
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass('Spec')
+ ->setFinal();
+
+ $namespace->addUse("{$rootNamespace}\\Spec");
+
+ $jsonSpec = $this->adaptJsonSpec($jsonSpec);
+ $specRuleKeys = $this->collectSpecRuleKeys($jsonSpec);
+
+ $this->generateEntityClass('AttributeList', $fileManager);
+ $this->generateEntityClass('DeclarationList', $fileManager);
+ $this->generateEntityClass('DescendantTagList', $fileManager);
+ $this->generateEntityClass('CssRuleset', $fileManager);
+ $this->generateEntityClass('DocRuleset', $fileManager);
+ $this->generateEntityClass('Error', $fileManager);
+ $this->generateEntityClass('Tag', $fileManager);
+ $this->generateEntityClass('TagWithExtensionSpec', $fileManager, 'interface');
+ $this->generateEntityClass('ExtensionSpec', $fileManager, 'trait');
+ $this->generateEntityClass('IterableSection', $fileManager, 'interface');
+ $this->generateEntityClass('Iteration', $fileManager, 'trait');
+ $this->generateErrorCodeInterface($jsonSpec, $fileManager);
+ $this->generateSpecRuleInterface($specRuleKeys, $fileManager);
+
+ foreach ($jsonSpec as $section => $sectionSpec) {
+ switch ($section) {
+ case 'minValidatorRevisionRequired':
+ case 'specFileRevision':
+ $class->addProperty($section, $sectionSpec)
+ ->setPrivate()
+ ->addComment("@var int");
+
+ $class->addMethod($section)
+ ->addBody('return $this->?;', [$section])
+ ->addComment("@return int");
+ break;
+ case 'scriptSpecUrl':
+ case 'stylesSpecUrl':
+ case 'templateSpecUrl':
+ $class->addProperty($section, $sectionSpec)
+ ->setPrivate()
+ ->addComment("@var string");
+
+ $class->addMethod($section)
+ ->addBody('return $this->?;', [$section])
+ ->addComment("@return string");
+ break;
+ default:
+ $sectionClassName = $this->generateSectionClass($section, $sectionSpec, $fileManager);
+
+ $class->addProperty($section)
+ ->setPrivate()
+ ->addComment("@var Spec\\Section\\{$sectionClassName}");
+
+ $class->addMethod($section)
+ ->addBody('if ($this->? === null) {', [$section])
+ ->addBody(" \$this->? = new Spec\\Section\\{$sectionClassName}();", [$section])
+ ->addBody('}')
+ ->addBody('return $this->?;', [$section])
+ ->addComment("@return Spec\\Section\\{$sectionClassName}");
+ }
+ }
+
+ $fileManager->saveFile($file, 'Spec.php');
+ }
+
+ /**
+ * Get the class name for a given value.
+ *
+ * @param string $value Value to get the class name for.
+ * @return string Class name to use.
+ */
+ private function getClassName($value)
+ {
+ return ucfirst($value);
+ }
+
+ /**
+ * Generate an entity class.
+ *
+ * @param string $entity Entity name to generate the class for.
+ * @param FileManager $fileManager FileManager instance to use.
+ * @param string $type Optional. Type of class construct. Can be 'class', interface' or 'trait'.
+ */
+ private function generateEntityClass($entity, FileManager $fileManager, $type = 'class')
+ {
+ /** @var PhpNamespace $namespace */
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec');
+ switch ($type) {
+ case 'interface':
+ $class = ClassType::from(self::GENERATOR_NAMESPACE . "\\Template\\{$entity}");
+ $class->setInterface();
+ foreach ($class->getMethods() as $method) {
+ $method->setPublic();
+ }
+ break;
+ case 'trait':
+ $class = ClassType::withBodiesFrom(self::GENERATOR_NAMESPACE . "\\Template\\{$entity}");
+ $class->setTrait();
+ break;
+ default:
+ $class = ClassType::withBodiesFrom(self::GENERATOR_NAMESPACE . "\\Template\\{$entity}");
+ }
+ $namespace->add($class);
+ $fileManager->saveFile($file, "Spec/{$entity}.php");
+ }
+
+ /**
+ * Generate a Section class.
+ *
+ * @param string $section Key of the section to generate.
+ * @param mixed $sectionSpec Spec data of the section to be generated.
+ * @param FileManager $fileManager FileManager instance to use.
+ * @return string Section class name.
+ */
+ private function generateSectionClass($section, $sectionSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\Section');
+ $className = $this->getClassName($section);
+ $class = $namespace->addClass($className)
+ ->setFinal();
+
+ $sectionProcessorClass = self::GENERATOR_NAMESPACE . "\\Section\\{$className}";
+
+ if (class_exists($sectionProcessorClass)) {
+ /** @var Section $sectionProcessor */
+ $sectionProcessor = new $sectionProcessorClass();
+ $sectionProcessor->process($fileManager, $sectionSpec, $namespace, $class);
+ }
+
+ $fileManager->saveFile($file, "Spec/Section/{$className}.php");
+
+ return $className;
+ }
+
+ /**
+ * Generate a SpecRule interface.
+ *
+ * @param array $specRuleKeys Array of spec rule keys to create constants for.
+ * @param FileManager $fileManager FileManager instance to use.
+ */
+ private function generateSpecRuleInterface($specRuleKeys, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec');
+ $interface = $namespace->addInterface('SpecRule');
+
+ foreach ($specRuleKeys as $specRuleKey) {
+ $interface->addConstant($this->getConstantName($specRuleKey), $specRuleKey);
+ }
+
+ $fileManager->saveFile($file, 'Spec/SpecRule.php');
+ }
+
+ /**
+ * Generate the ErrorCode interface.
+ *
+ * @param array $jsonSpec JSON spec that contains the spec details.
+ * @param FileManager $fileManager FileManager instance to use.
+ */
+ private function generateErrorCodeInterface($jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile();
+ $interface = $namespace->addInterface('ErrorCode');
+
+ $errorCodes = array_unique(array_keys($jsonSpec['errors']));
+
+ sort($errorCodes);
+
+ foreach ($errorCodes as $errorCode) {
+ $interface->addConstant($this->getConstantName($errorCode), $errorCode);
+ }
+
+ $fileManager->saveFile($file, 'ErrorCode.php');
+ }
+
+ /**
+ * Adapt JSON spec data.
+ *
+ * @param array $jsonSpec JSON spec data to adapt.
+ * @return array Adapted JSON spec data.
+ */
+ private function adaptJsonSpec($jsonSpec)
+ {
+ $jsonSpec['cssRulesets'] = $jsonSpec['css'];
+ unset($jsonSpec['css']);
+
+ $jsonSpec['docRulesets'] = $jsonSpec['doc'];
+ unset($jsonSpec['doc']);
+
+ $jsonSpec['attributeLists'] = $jsonSpec['attrLists'];
+ unset($jsonSpec['attrLists']);
+
+ $jsonSpec['declarationLists'] = $jsonSpec['declarationList'];
+ unset($jsonSpec['declarationList']);
+
+ $jsonSpec['descendantTagLists'] = $jsonSpec['descendantTagList'];
+ unset($jsonSpec['descendantTagList']);
+
+ $errorArray = [
+ 'format' => $jsonSpec['errorFormats'],
+ 'specificity' => $jsonSpec['errorSpecificity'],
+ ];
+
+ $jsonSpec['errors'] = [];
+
+ foreach ($errorArray as $key => $errors) {
+ foreach ($errors as $error) {
+ $jsonSpec['errors'][$error['code']][$key] = $error[$key];
+ }
+ }
+
+ unset($jsonSpec['errorFormats'], $jsonSpec['errorSpecificity']);
+
+ return $jsonSpec;
+ }
+
+ /**
+ * Collect all spec rule keys.
+ *
+ * @param array $jsonSpec JSON spec data.
+ * @return array Array of spec rule keys.
+ */
+ private function collectSpecRuleKeys($jsonSpec)
+ {
+ $specRuleKeys = [];
+ foreach ($jsonSpec as $sectionKey => $sectionData) {
+ switch ($sectionKey) {
+ case 'attributeLists':
+ $attributeLists = array_column($sectionData, 'attrs');
+ foreach ($attributeLists as $attributeList) {
+ foreach ($attributeList as $attributeEntry) {
+ foreach (array_keys($attributeEntry) as $specRuleKey) {
+ $specRuleKeys[$specRuleKey] = $specRuleKey;
+
+ $this->collectSpecRuleKeysFromSubset($specRuleKeys, $attributeEntry);
+ }
+ }
+ }
+ break;
+ case 'cssRulesets':
+ case 'docRulesets':
+ case 'errors':
+ foreach ($sectionData as $ruleset) {
+ foreach (array_keys($ruleset) as $specRuleKey) {
+ $specRuleKeys[$specRuleKey] = $specRuleKey;
+ }
+ }
+ break;
+ case 'declarationLists':
+ $declarationLists = array_column($sectionData, 'declaration');
+ foreach ($declarationLists as $declarationList) {
+ foreach ($declarationList as $declarationEntry) {
+ foreach (array_keys($declarationEntry) as $specRuleKey) {
+ $specRuleKeys[$specRuleKey] = $specRuleKey;
+ }
+ }
+ }
+ break;
+ case 'tags':
+ foreach ($sectionData as $ruleset) {
+ foreach (array_keys($ruleset) as $specRuleKey) {
+ $specRuleKeys[$specRuleKey] = $specRuleKey;
+ }
+
+ $this->collectSpecRuleKeysFromSubset($specRuleKeys, $ruleset);
+ }
+ break;
+ default:
+ }
+ }
+
+ ksort($specRuleKeys);
+
+ return $specRuleKeys;
+ }
+
+ /**
+ * Collect all spec rule keys from a spec subset.
+ *
+ * @param array $specRuleKeys Array of collected spec rule keys.
+ * @param array $subset Subset to collect additional keys from.
+ */
+ private function collectSpecRuleKeysFromSubset(&$specRuleKeys, $subset)
+ {
+ if (!is_array($subset)) {
+ return;
+ }
+
+ foreach ($subset as $key => $value) {
+ if (is_string($key)) {
+ $specRuleKeys[$key] = $key;
+ }
+ if (is_array($value)) {
+ $this->collectSpecRuleKeysFromSubset($specRuleKeys, $value);
+ }
+ }
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/ClassNames.php b/bin/src/Validator/SpecGenerator/ClassNames.php
new file mode 100644
index 000000000..9fab93770
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/ClassNames.php
@@ -0,0 +1,28 @@
+', '.', '_', '/', '*', ':', '+', '$'],
+ ' ',
+ $id
+ );
+ $className = preg_replace('/\s+/', ' ', trim($className));
+ $className = str_replace(' ', '', ucwords(strtolower($className)));
+
+ $className = (new ReservedKeywords())->maybeAddSuffix($className);
+
+ return $className;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/ConstantNames.php b/bin/src/Validator/SpecGenerator/ConstantNames.php
new file mode 100644
index 000000000..cc3b56b10
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/ConstantNames.php
@@ -0,0 +1,142 @@
+prefixLeadingDigits(
+ str_replace(
+ [':', '-', '!', '+'],
+ '_',
+ preg_replace('/([a-z])([A-Z])/', '$1_$2', $value)
+ )
+ )
+ );
+ }
+
+ /**
+ * Get the tag constant using the correct interface constants.
+ *
+ * @param string $constantName Constant name to build the correct tag constant for.
+ * @return string Tag constant.
+ */
+ private function getTagConstant($constantName)
+ {
+ if (strpos($constantName, 'AMP_') === 0) {
+ $interface = 'Extension';
+ $constantName = $this->prefixLeadingDigits(
+ str_replace('AMP_', '', $constantName)
+ );
+ } elseif (strpos($constantName, 'I_AMPHTML_') === 0) {
+ $interface = 'Internal';
+ $constantName = $this->prefixLeadingDigits(
+ str_replace('I_AMPHTML_', '', $constantName)
+ );
+ } else {
+ $interface = 'Element';
+ }
+
+ $constantName = (new ReservedKeywords())->maybeAddSuffix($constantName);
+
+ return "{$interface}::{$constantName}";
+ }
+
+ /**
+ * Prefix leading digits with an underscore, as constants cannot start with a digit.
+ *
+ * @param string $constant Constant to prefix the digits for.
+ * @return string Prefixed constant.
+ */
+ private function prefixLeadingDigits($constant)
+ {
+ return preg_replace('/^(\d)/', '_$1', $constant);
+ }
+
+ /**
+ * Get the AMP HTML format constant.
+ *
+ * @param string $format Format to get the constant for.
+ * @return string Format constant.
+ */
+ private function getFormatConstant($format)
+ {
+ if (!in_array($format, Amp::FORMATS)) {
+ return $format;
+ }
+
+ return "Format::{$format}";
+ }
+
+ /**
+ * Get the layout constant.
+ *
+ * @param string $layout Layout to get the constant for.
+ * @return string Layout constant.
+ */
+ private function getLayoutConstant($layout)
+ {
+ return "Layout::{$layout}";
+ }
+
+ /**
+ * Get the at rule constant.
+ *
+ * @param string $atRule At rule to get the constant for.
+ * @return string At rule constant.
+ */
+ private function getAtRuleConstant($atRule)
+ {
+ return "AtRule::{$atRule}";
+ }
+
+ /**
+ * Get the protocol constant.
+ *
+ * @param string $protocol Protocol to get the constant for.
+ * @return string Protocol constant.
+ */
+ private function getProtocolConstant($protocol)
+ {
+ return "Protocol::{$protocol}";
+ }
+
+ /**
+ * Get the attribute constant.
+ *
+ * @param string $attribute Attribute to get the constant for.
+ * @return string Attribute constant.
+ */
+ private function getAttributeConstant($attribute)
+ {
+ if (strpos($attribute, '[') === 0) {
+ return $attribute;
+ }
+
+ $attribute = (new ReservedKeywords())->maybeAddSuffix($attribute);
+
+ return "Attribute::{$attribute}";
+ }
+
+ /**
+ * Get the error code constant.
+ *
+ * @param string $errorCode Error code to get the constant for.
+ * @return string Error code constant.
+ */
+ private function getErrorCodeConstant($errorCode)
+ {
+ return "ErrorCode::{$errorCode}";
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Dumper.php b/bin/src/Validator/SpecGenerator/Dumper.php
new file mode 100644
index 000000000..be5319fa9
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Dumper.php
@@ -0,0 +1,379 @@
+ $parentKeys Optional. Array of parent keys.
+ * @return string Dump of the provided variable.
+ */
+ public function dump($value, $level, $parentKeys = [])
+ {
+ if ($this->dumper === null) {
+ $this->dumper = new NetteDumper();
+ }
+
+ $extraIndentation = str_pad('', $level * 4, ' ');
+
+ if (is_array($value)) {
+ if (count($value) === 0) {
+ return "[]";
+ }
+
+ $line = '';
+ foreach ($value as $subKey => $subValue) {
+ $line .= "{$extraIndentation} ";
+
+ if (is_string($subKey)) {
+ $line .= "{$this->dumpWithKey($subKey, $subValue, $level + 1, $parentKeys)},\n";
+ } else {
+ $line .= "{$this->dump($subValue, $level + 1, $parentKeys)},\n";
+ }
+ }
+
+ return "[\n" . $line . "{$extraIndentation}]";
+ }
+
+ return "{$this->getValueString($value)}";
+ }
+
+ /**
+ * Dump a key-value pair so it can be used for code generation.
+ *
+ * @param string $key Key to dump.
+ * @param mixed $value Value to dump.
+ * @param int $level Indentation level to use.
+ * @param array $parentKeys Optional. Array of parent keys.
+ * @return string Dump of the provided variable.
+ */
+ public function dumpWithKey($key, $value, $level, $parentKeys = [])
+ {
+ $value = $this->replaceConstants($key, $value, $parentKeys);
+
+ if (is_string($key)) {
+ array_unshift($parentKeys, $key);
+ }
+
+ if (in_array($key, self::SPEC_RULE_KEYS, true)) {
+ $key = "SpecRule::{$this->getConstantName($key)}";
+ }
+
+ return "{$this->getValueString($key)} => {$this->dump($value, $level, $parentKeys)}";
+ }
+
+ /**
+ * Get the string representation of a value.
+ *
+ * This takes into account the optional callback that might have been passed in.
+ *
+ * @param mixed $value Value to get the string representation of.
+ * @return string String representation of the value.
+ */
+ private function getValueString($value)
+ {
+ if ($this->dumper === null) {
+ $this->dumper = new NetteDumper();
+ }
+
+ $valueString = $this->filterValueStrings($value);
+
+ if ($valueString === false) {
+ $valueString = $this->dumper->dump($value);
+ }
+
+ return $valueString;
+ }
+
+ /**
+ * Filtering callback to use for ensuring constants are not put between quotes.
+ *
+ * @param mixed $value Value to filter.
+ * @return string|false String to use, or false if fallback to the regular variable dumper should be used.
+ */
+ public function filterValueStrings($value)
+ {
+ if (!is_string($value)) {
+ return false;
+ }
+
+ if (
+ strpos($value, 'AtRule::') === 0
+ ||
+ strpos($value, 'Attribute::') === 0
+ ||
+ strpos($value, 'AttributeList::') === 0
+ ||
+ strpos($value, 'AttributeList\\') === 0
+ ||
+ strpos($value, 'CssRuleset::') === 0
+ ||
+ strpos($value, 'CssRuleset\\') === 0
+ ||
+ strpos($value, 'DocRuleset::') === 0
+ ||
+ strpos($value, 'DocRuleset\\') === 0
+ ||
+ strpos($value, 'DeclarationList::') === 0
+ ||
+ strpos($value, 'DeclarationList\\') === 0
+ ||
+ strpos($value, 'DescendantTagList::') === 0
+ ||
+ strpos($value, 'DescendantTagList\\') === 0
+ ||
+ strpos($value, 'Element::') === 0
+ ||
+ strpos($value, 'Error::') === 0
+ ||
+ strpos($value, 'Error\\') === 0
+ ||
+ strpos($value, 'ErrorCode::') === 0
+ ||
+ strpos($value, 'Extension::') === 0
+ ||
+ strpos($value, 'Format::') === 0
+ ||
+ strpos($value, 'Internal::') === 0
+ ||
+ strpos($value, 'Layout::') === 0
+ ||
+ strpos($value, 'Protocol::') === 0
+ ||
+ strpos($value, 'SpecRule::') === 0
+ ||
+ strpos($value, 'self::') === 0
+ ||
+ strpos($value, 'static::') === 0
+ ||
+ strpos($value, '::class') !== false
+ ||
+ strpos($value, '::ID') !== false
+ ) {
+ return $value;
+ }
+
+ return false;
+ }
+
+ private function replaceConstants($specRule, $value, $parentKeys)
+ {
+ if (!is_string($value) && !is_array($value)) {
+ return $value;
+ }
+
+ switch ($specRule) {
+ case 'alsoRequiresAttr':
+ case 'alternativeNames':
+ case 'disabledBy':
+ case 'enabledBy':
+ $attributes = [];
+ foreach ($value as $attribute) {
+ $attributes[] = $this->getAttributeConstant($this->getConstantName($attribute));
+ }
+ return $attributes;
+ case 'ampLayout':
+ if (array_key_exists('supportedLayouts', $value)) {
+ foreach ($value['supportedLayouts'] as $index => $layout) {
+ $value['supportedLayouts'][$index] = $this->getLayoutConstant($this->getConstantName($layout));
+ }
+ }
+ return $value;
+ case 'attrLists':
+ $attributeLists = [];
+ foreach ($value as $attributeList) {
+ $className = $this->getClassNameFromId($attributeList);
+ $attributeLists[] = "AttributeList\\{$className}::ID";
+ }
+ return $attributeLists;
+ case 'declarationList':
+ $declarationLists = [];
+ foreach ($value as $declarationList) {
+ $className = $this->getClassNameFromId($declarationList);
+ $declarationLists[] = "DeclarationList\\{$className}::ID";
+ }
+ return $declarationLists;
+ case 'descendantTagList':
+ $className = $this->getClassNameFromId($value);
+ return "DescendantTagList\\{$className}::ID";
+ case 'htmlFormat':
+ $formats = [];
+ foreach ($value as $format) {
+ $formats[] = $this->getFormatConstant($this->getConstantName($format));
+ }
+ return $formats;
+ case 'name':
+ if (empty($parentKeys[0])) {
+ return $value;
+ }
+ if ($parentKeys[0] === 'atRuleSpec') {
+ return $this->getAtRuleConstant($this->getConstantName($value));
+ }
+ if ($parentKeys[0] !== 'extensionSpec' && $parentKeys[0] !== 'properties') {
+ $constant = $this->getAttributeConstant($this->getConstantName($value));
+ if (strpos($value, '[') !== 0) {
+ return $constant;
+ }
+ }
+ return $value;
+ case 'protocol':
+ $protocols = [];
+ foreach ($value as $protocol) {
+ $protocols[] = $this->getProtocolConstant($this->getConstantName($protocol));
+ }
+ return $protocols;
+ case 'mandatoryAncestor':
+ case 'mandatoryAncestorSuggestedAlternative':
+ case 'mandatoryParent':
+ case 'tagName':
+ $constant = $this->getTagConstant($this->getConstantName($value));
+ if (strpos($value, '$') !== 0 && strpos($value, ' ') === false) {
+ return $constant;
+ }
+ return $value;
+ case 'requiresExtension':
+ $extensions = [];
+ foreach ($value as $extension) {
+ $extensions[] = $this->getTagConstant($this->getConstantName($extension));
+ }
+ return $extensions;
+ case 'valueCasei':
+ if (!empty($parentKeys[0]) && $parentKeys[0] === 'i-amphtml-layout') {
+ foreach ($value as $index => $layout) {
+ $constant = $this->getLayoutConstant($this->getConstantName($layout));
+ if (defined("AmpProject\\{$constant}")) {
+ $value[$index] = $constant;
+ }
+ }
+ }
+ return $value;
+ default:
+ return $value;
+ }
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/FileManager.php b/bin/src/Validator/SpecGenerator/FileManager.php
new file mode 100644
index 000000000..6ec4001d8
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/FileManager.php
@@ -0,0 +1,381 @@
+rootNamespace = $rootNamespace;
+ $this->destination = $destination;
+ $this->printer = $printer;
+ $this->dumper = new Dumper();
+ }
+
+ /**
+ * Ensure all the required subfolders exist.
+ */
+ public function ensureDirectoriesExist()
+ {
+ $folders = [
+ $this->destination,
+ "{$this->destination}/Spec",
+ "{$this->destination}/Spec/AttributeList",
+ "{$this->destination}/Spec/CssRuleset",
+ "{$this->destination}/Spec/DocRuleset",
+ "{$this->destination}/Spec/DeclarationList",
+ "{$this->destination}/Spec/DescendantTagList",
+ "{$this->destination}/Spec/Error",
+ "{$this->destination}/Spec/Section",
+ "{$this->destination}/Spec/Tag",
+ ];
+
+ foreach ($folders as $folder) {
+ if (!is_dir($folder)) {
+ mkdir($folder);
+ }
+ }
+ }
+
+ /**
+ * Create a new PHP file.
+ *
+ * This creates the standard file header.
+ *
+ * @return PhpFile New PHP file.
+ */
+ public function createNewFile()
+ {
+ $file = new PhpFile();
+
+ $file->addComment('DO NOT EDIT!');
+ $file->addComment('This file was automatically generated via bin/generate-validator-spec.php.');
+
+ return $file;
+ }
+
+ /**
+ * Create a new PHP file with a namespace.
+ *
+ * This creates the standard file header.
+ *
+ * @param string $relativeNamespace Optional. Relative namespace to use for the new file.
+ * @return array New PHP file and its namespace object.
+ */
+ public function createNewNamespacedFile($relativeNamespace = '')
+ {
+ $file = $this->createNewFile();
+
+ $namespace = empty($relativeNamespace)
+ ? $this->rootNamespace
+ : "{$this->rootNamespace}\\{$relativeNamespace}";
+
+ return [$file, $file->addNamespace($namespace)];
+ }
+
+ /**
+ * Get the root namespace to use.
+ *
+ * @return string Root namespace.
+ */
+ public function getRootNamespace()
+ {
+ return $this->rootNamespace;
+ }
+
+ /**
+ * Save a file to the filesystem.
+ *
+ * @param PhpFile $file File to save.
+ * @param string $relativeFilePath Relative file path to use.
+ */
+ public function saveFile(PhpFile $file, $relativeFilePath)
+ {
+ $file = $this->addMissingImports($file);
+
+ file_put_contents(
+ "{$this->destination}/{$relativeFilePath}",
+ $this->printer->printFile($file)
+ );
+ }
+
+ /**
+ * Add missing imports.
+ *
+ * @param PhpFile $file File to add the missing imports into.
+ * @return PhpFile Adapted file.
+ */
+ private function addMissingImports(PhpFile $file)
+ {
+ foreach ($file->getNamespaces() as $namespace) {
+ $classes = [];
+ foreach ($namespace->getClasses() as $class) {
+ foreach ($class->getConstants() as $constant) {
+ $source = $this->dumper->dump($constant->getValue(), 0);
+ $constantClasses = $this->extractClassNames($source);
+ $classes = array_merge($classes, $constantClasses);
+ }
+
+ foreach ($class->getMethods() as $method) {
+ $source = $method->getBody();
+ if ($source === null) {
+ continue;
+ }
+ $methodClasses = $this->extractClassNames($source);
+ $method->setBody($this->adaptSource($source, $methodClasses));
+ $classes = array_merge($classes, $methodClasses);
+ }
+
+ $classes = array_merge($classes, $class->getExtends());
+ $classes = array_merge($classes, $class->getImplements());
+ }
+
+ foreach (array_unique($classes) as $class) {
+ $this->maybeAddImport($namespace, $class);
+ }
+ }
+
+ return $file;
+ }
+
+ /**
+ * Adapt the source file to reduce fully qualified class names to their short name.
+ *
+ * @param string $source Source to adapt.
+ * @param array $classes Array of class names.
+ * @return string Adapted source.
+ */
+ private function adaptSource($source, $classes)
+ {
+ return str_replace(
+ $classes,
+ array_map([$this, 'getShortName'], $classes),
+ $source
+ );
+ }
+
+ /**
+ * Extract class names from a source fragment.
+ *
+ * @param string $source Source fragment to extract class names from.
+ * @return array Array of class names.
+ */
+ private function extractClassNames($source)
+ {
+ $matches = [];
+ if (!preg_match_all('/[\\\\\w]+::/', $source, $matches)) {
+ return [];
+ }
+
+ $classes = [];
+ foreach (array_unique($matches[0]) as $match) {
+ $class = rtrim($match, '::');
+
+ if (in_array($class, ['self', 'static'])) {
+ continue;
+ }
+
+ $classes[] = $class;
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Add an import in case it is missing for a given namespace.
+ *
+ * @param PhpNamespace $namespace Namespace into which to add missing imports.
+ * @param string $class Class to check whether its import is missing.
+ */
+ private function maybeAddImport(PhpNamespace $namespace, $class)
+ {
+ foreach ($namespace->getUses() as $alias => $import) {
+ if ($import === $class) {
+ return;
+ }
+
+ if ($import === ltrim($class, '\\')) {
+ return;
+ }
+
+ if ($this->getShortName($import) === $class) {
+ return;
+ }
+
+ $partials = explode('\\', $class);
+ $relativeNamespace = array_shift($partials);
+
+ if ($this->getShortName($import) === $relativeNamespace) {
+ return;
+ }
+
+ if ($alias === $this->getShortName($class)) {
+ return;
+ }
+ }
+
+ $fqcn = $this->getFullyQualifiedName($class);
+ $alias = null;
+
+ if ($fqcn === 'AmpProject\\Element') {
+ $fqcn = 'AmpProject\\Tag';
+ $alias = 'Element';
+ }
+
+ $namespace->addUse($fqcn, $alias);
+ }
+
+ /**
+ * Get the short name for a provided class name.
+ *
+ * @param string $class Class name to get the short name for.
+ * @return string Short name of the provided class name.
+ */
+ private function getShortName($class)
+ {
+ $class = ltrim($class, '\\');
+
+ if (strpos($class, 'AmpProject\\') === 0) {
+ $partials = array_filter(explode('\\', $class));
+ $class = array_pop($partials);
+ }
+
+ return $class;
+ }
+
+ /**
+ * Get the fully qualified class name for a provided class name.
+ *
+ * @param string $class Class name to turn into a FQCN.
+ * @return string Fully qualified class name.
+ */
+ private function getFullyQualifiedName($class)
+ {
+ $class = ltrim($class, '\\');
+
+ if (strpos($class, 'AmpProject\\') === 0) {
+ return $class;
+ }
+
+ if ($class === 'Element') {
+ return "AmpProject\\Element";
+ }
+
+ if (strpos($class, 'Tag\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\Tag";
+ }
+
+ if (strpos($class, 'CssSpecRule\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\CssSpecRule";
+ }
+
+ if (strpos($class, 'DocSpecRule\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\DocSpecRule";
+ }
+
+ if (strpos($class, 'Error\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\Error";
+ }
+
+ if (strpos($class, 'AttributeList\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\AttributeList";
+ }
+
+ if (strpos($class, 'DeclarationList\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\DeclarationList";
+ }
+
+ if (strpos($class, 'DescendantTagList\\') === 0) {
+ return "AmpProject\\Validator\\Spec\\DescendantTagList";
+ }
+
+ if (
+ in_array(
+ $class,
+ [
+ 'AttributeList',
+ 'CssRuleset',
+ 'DocRuleset',
+ 'DeclarationList',
+ 'DescendantTagList',
+ 'Error',
+ 'SpecRule'
+ ],
+ true
+ )
+ ) {
+ return "AmpProject\\Validator\\Spec\\{$class}";
+ }
+
+ if (
+ in_array(
+ $class,
+ [
+ 'AttributeLists',
+ 'CssRulesets',
+ 'DocRulesets',
+ 'DeclarationLists',
+ 'DescendantTagLists',
+ 'Errors',
+ 'Tags'
+ ],
+ true
+ )
+ ) {
+ return "AmpProject\\Validator\\Spec\\Section\\{$class}";
+ }
+
+ if (file_exists(self::NAMESPACE_ROOT_FOLDER . "/{$class}.php")) {
+ return "AmpProject\\{$class}";
+ }
+
+ return $class;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/ReservedKeywords.php b/bin/src/Validator/SpecGenerator/ReservedKeywords.php
new file mode 100644
index 000000000..7cd4003cb
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/ReservedKeywords.php
@@ -0,0 +1,39 @@
+
+ */
+ const PHP_KEYWORDS = [
+ 'AS',
+ 'CLASS',
+ 'DEFAULT',
+ 'FOR',
+ 'LIST',
+ 'SWITCH',
+ 'USE',
+ 'VAR',
+ ];
+
+ /**
+ * Add a suffix to a value in case it is a reserved keyword.
+ *
+ * @param string $value Value to maybe add a suffix to.
+ * @param string $suffix Optional. Suffix to add. Defaults to an underscore ('_').
+ * @return string Adapted value.
+ */
+ public function maybeAddSuffix($value, $suffix = '_')
+ {
+ if (in_array(strtoupper($value), self::PHP_KEYWORDS, true)) {
+ return "{$value}{$suffix}";
+ }
+
+ return $value;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section.php b/bin/src/Validator/SpecGenerator/Section.php
new file mode 100644
index 000000000..1f595ed64
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section.php
@@ -0,0 +1,21 @@
+addUse('AmpProject\Exception\InvalidListName');
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\AttributeList");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+
+ $this->data = $this->adaptSpec($spec);
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('attributeLists')
+ ->setValue([])
+ ->setPrivate()
+ ->addComment("Cache of instantiated AttributeList objects.\n\n@var array");
+
+ $attributeLists = [];
+ foreach ($this->data as $key => $value) {
+ $className = $this->generateAttributeListSpecificClass($key, $value, $fileManager);
+
+ $attributeLists["AttributeList\\{$className}::ID"] = "AttributeList\\{$className}::class";
+ }
+ $class->addConstant('ATTRIBUTE_LISTS', $attributeLists)
+ ->addComment("Mapping of attribute list ID to attribute list implementation.\n\n@var array");
+
+ $attributeListsTemplateClass = ClassType::withBodiesFrom(Template\AttributeLists::class);
+ foreach ($attributeListsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($attributeListsTemplateClass->getComment());
+ }
+
+ /**
+ * Adapt JSON spec data.
+ *
+ * @param array $jsonSpec JSON spec data to adapt.
+ * @return array Adapted JSON spec data.
+ */
+ protected function adaptSpec($jsonSpec)
+ {
+ $attributeList = [];
+
+ foreach ($jsonSpec as $entry) {
+ $key = $entry['name'];
+ $data = $entry['attrs'];
+
+ $attributeList[$key] = [];
+
+ foreach ($data as $datum) {
+ $name = $datum['name'];
+ unset($datum['name']);
+ $attributeList[$key][$name] = $datum;
+ }
+ }
+
+ return $attributeList;
+ }
+
+ /**
+ * Generate the AttributeList-specific class file.
+ *
+ * @param string $attributeListId ID of the attribute list to generate the class for.
+ * @param array $jsonSpec Array of spec data for the attribute list.
+ * @param FileManager $fileManager File manager instance to use.
+ * @return string Short name of the class that was generated.
+ */
+ private function generateAttributeListSpecificClass($attributeListId, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\AttributeList');
+
+ $className = $this->getClassNameFromId($attributeListId);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\AttributeList");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\AttributeList');
+
+ $class->addConstant('ID', $attributeListId)
+ ->addComment("ID of the attribute list.\n\n@var string");
+
+ $attributes = [];
+ foreach ($jsonSpec as $key => $value) {
+ $attributes[$this->getKeyString($key)] = $value;
+ }
+
+ $class->addConstant('ATTRIBUTES', $attributes)
+ ->addComment("Array of attributes.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/AttributeList/{$className}.php");
+
+ return $className;
+ }
+
+ /**
+ * Get the string to use as key for the attribute list.
+ *
+ * This automatically reuses existing constants to reduce memory consumption.
+ *
+ * @param string $attributeListId Attribute list ID to produce a key string for.
+ * @return string Key string to use.
+ */
+ private function getKeyString($attributeListId)
+ {
+ if (!is_string($attributeListId)) {
+ return $attributeListId;
+ }
+
+ return $this->getAttributeConstant($this->getConstantName($attributeListId));
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section/CssRulesets.php b/bin/src/Validator/SpecGenerator/Section/CssRulesets.php
new file mode 100644
index 000000000..7f1fbb5e1
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section/CssRulesets.php
@@ -0,0 +1,161 @@
+addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\CssRuleset");
+
+ $cssRulesetsTemplateClass = ClassType::withBodiesFrom(Template\CssRulesets::class);
+ foreach ($cssRulesetsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($cssRulesetsTemplateClass->getComment());
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('cssRulesetsCache')
+ ->setPrivate()
+ ->addComment("Cache of instantiated CssRuleset objects.\n\n@var array")
+ ->setValue([]);
+
+ $class->addProperty('iterationArray')
+ ->setPrivate()
+ ->addComment("Array used for storing the iteration index in.\n\n@var array|null");
+
+ foreach ($spec as $attributes) {
+ $cssRulesetId = $this->getNameForRuleset($attributes);
+ $cssRulesets[$cssRulesetId] = $attributes;
+ }
+
+ $cssRulesetIds = array_keys($cssRulesets);
+ natcasesort($cssRulesetIds);
+
+ $rulesets = [];
+ foreach ($cssRulesetIds as $cssRulesetId) {
+ $cssRulesetIdString = "CssRuleset\\{$this->getClassNameFromId($cssRulesetId)}::ID";
+
+ $className = $this->generateCssRulesetSpecificClass(
+ $cssRulesetId,
+ $cssRulesets[$cssRulesetId],
+ $fileManager
+ );
+
+ $rulesets["CssRuleset\\{$className}::ID"] = "CssRuleset\\{$className}::class";
+
+ if (array_key_exists('htmlFormat', $cssRulesets[$cssRulesetId])) {
+ $formats = $cssRulesets[$cssRulesetId]['htmlFormat'];
+ foreach ($formats as $format) {
+ $format = $this->getFormatConstant($this->getConstantName($format));
+ if (!array_key_exists($format, $byFormat)) {
+ $byFormat[$format] = [];
+ }
+ $byFormat[$format][] = $cssRulesetIdString;
+ }
+ }
+ }
+
+ $class->addConstant('CSS_RULESETS', $rulesets)
+ ->addComment("Mapping of CSS ruleset ID to CSS ruleset implementation.\n\n@var array");
+
+ $class->addConstant('BY_FORMAT', $byFormat)
+ ->addComment(
+ "Mapping of AMP format to array of CSS ruleset IDs.\n\n"
+ . "This is used to optimize querying by AMP format.\n\n"
+ . "@var array>"
+ );
+ }
+
+ /**
+ * Get the name for a given ruleset.
+ *
+ * @param array $ruleSet Rule set to get the name for.
+ * @return string Name to use for the rule set.
+ */
+ private function getNameForRuleSet($ruleSet)
+ {
+ static $index = 1;
+
+ if (!array_key_exists('htmlFormat', $ruleSet) || count($ruleSet['htmlFormat']) === 0) {
+ $name = "ruleset-{$index}";
+ $index++;
+
+ return $name;
+ }
+
+ $name = $ruleSet['htmlFormat'][0];
+
+ if (array_key_exists('enabledBy', $ruleSet) && count($ruleSet['enabledBy']) > 0) {
+ $name .= " ({$ruleSet['enabledBy'][0]})";
+ }
+
+ if (array_key_exists('disabledBy', $ruleSet) && count($ruleSet['disabledBy']) > 0) {
+ $name .= " (no-{$ruleSet['disabledBy'][0]})";
+ }
+
+ return $name;
+ }
+
+ /**
+ * Generate the CSS ruleset-specific class file.
+ *
+ * @param string $ruleset ID of the CSS ruleset to generate the class for.
+ * @param array $jsonSpec Array of spec data for the CSS ruleset.
+ * @param FileManager $fileManager File manager instance to use.
+ */
+ private function generateCssRulesetSpecificClass($ruleset, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\CssRuleset');
+
+ $className = $this->getClassNameFromId($ruleset);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\CssRuleset");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\CssRuleset');
+
+ $class->addConstant('ID', $ruleset)
+ ->addComment("ID of the ruleset.\n\n@var string");
+
+ $class->addConstant('SPEC', $jsonSpec)
+ ->addComment("Array of spec rules.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/CssRuleset/{$className}.php");
+
+ return $className;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section/DeclarationLists.php b/bin/src/Validator/SpecGenerator/Section/DeclarationLists.php
new file mode 100644
index 000000000..11dd3712a
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section/DeclarationLists.php
@@ -0,0 +1,144 @@
+addUse('AmpProject\Exception\InvalidListName');
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\DeclarationList");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+
+ $this->data = $this->adaptSpec($spec);
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('declarationLists')
+ ->setValue([])
+ ->setPrivate()
+ ->addComment("Cache of instantiated declaration list objects.\n\n@var array");
+
+ $declarationLists = [];
+ foreach ($this->data as $key => $value) {
+ $className = $this->generateDeclarationListSpecificClass($key, $value, $fileManager);
+
+ $declarationLists["DeclarationList\\{$className}::ID"] = "DeclarationList\\{$className}::class";
+ }
+ $class->addConstant('DECLARATION_LISTS', $declarationLists)
+ ->addComment("Mapping of declaration list ID to declaration list implementation.\n\n@var array");
+
+ $declarationListsTemplateClass = ClassType::withBodiesFrom(Template\DeclarationLists::class);
+ foreach ($declarationListsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($declarationListsTemplateClass->getComment());
+ }
+
+ /**
+ * Adapt JSON spec data.
+ *
+ * @param array $jsonSpec JSON spec data to adapt.
+ * @return array Adapted JSON spec data.
+ */
+ protected function adaptSpec($jsonSpec)
+ {
+ $declarationList = [];
+
+ foreach ($jsonSpec as $entry) {
+ $key = $entry['name'];
+ $data = $entry['declaration'];
+
+ $declarationList[$key] = [];
+
+ foreach ($data as $datum) {
+ $name = $datum['name'];
+ unset($datum['name']);
+ $declarationList[$key][$name] = $datum;
+ }
+ }
+
+ return $declarationList;
+ }
+
+ /**
+ * Generate the DeclarationList-specific class file.
+ *
+ * @param string $declarationListId ID of the declaration list to generate the class for.
+ * @param array $jsonSpec Array of spec data for the declaration list.
+ * @param FileManager $fileManager File manager instance to use.
+ * @return string Short name of the class that was generated.
+ */
+ private function generateDeclarationListSpecificClass($declarationListId, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\DeclarationList');
+
+ $className = $this->getClassNameFromId($declarationListId);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\DeclarationList");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\DeclarationList');
+
+ $class->addConstant('ID', $declarationListId)
+ ->addComment("ID of the declaration list.\n\n@var string");
+
+ $declarations = [];
+ foreach ($jsonSpec as $key => $value) {
+ $declarations[$this->getKeyString($key)] = $value;
+ }
+
+ $class->addConstant('DECLARATIONS', $declarations)
+ ->addComment("Array of declarations.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/DeclarationList/{$className}.php");
+
+ return $className;
+ }
+
+ /**
+ * Get the string to use as key for the declaration list.
+ *
+ * This automatically reuses existing constants to reduce memory consumption.
+ *
+ * @param string $declarationListId Declaration list ID to produce a key string for.
+ * @return string Key string to use.
+ */
+ private function getKeyString($declarationListId)
+ {
+ if (!is_string($declarationListId)) {
+ return $declarationListId;
+ }
+
+ return $this->getAttributeConstant($this->getConstantName($declarationListId));
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section/DescendantTagLists.php b/bin/src/Validator/SpecGenerator/Section/DescendantTagLists.php
new file mode 100644
index 000000000..28cd2febc
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section/DescendantTagLists.php
@@ -0,0 +1,120 @@
+addUse('AmpProject\Exception\InvalidListName');
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\DescendantTagList");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+
+ $this->data = $this->adaptSpec($spec);
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('descendantTagLists')
+ ->setValue([])
+ ->setPrivate()
+ ->addComment("Cache of instantiated descendant tag list objects.\n\n@var array");
+
+ $descendantTagLists = [];
+ foreach ($this->data as $key => $value) {
+ $className = $this->generateDescendantTagListSpecificClass($key, $value, $fileManager);
+
+ $descendantTagLists["DescendantTagList\\{$className}::ID"] = "DescendantTagList\\{$className}::class";
+ }
+ $class->addConstant('DESCENDANT_TAG_LISTS', $descendantTagLists)
+ ->addComment(
+ "Mapping of descendant tag list ID to descendant tag list implementation.\n\n@var array"
+ );
+
+ $descendantTagListsTemplateClass = ClassType::withBodiesFrom(Template\DescendantTagLists::class);
+ foreach ($descendantTagListsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($descendantTagListsTemplateClass->getComment());
+ }
+
+ /**
+ * Adapt JSON spec data.
+ *
+ * @param array $jsonSpec JSON spec data to adapt.
+ * @return array Adapted JSON spec data.
+ */
+ protected function adaptSpec($jsonSpec)
+ {
+ $descendantTagList = [];
+
+ foreach ($jsonSpec as $entry) {
+ $descendantTagList[$entry['name']] = $entry['tag'];
+ }
+
+ return $descendantTagList;
+ }
+
+ /**
+ * Generate the DescendantTagList-specific class file.
+ *
+ * @param string $descendantTagListId ID of the descendant tag list to generate the class for.
+ * @param array $jsonSpec Array of spec data for the descendant tag list.
+ * @param FileManager $fileManager File manager instance to use.
+ * @return string Short name of the class that was generated.
+ */
+ private function generateDescendantTagListSpecificClass($descendantTagListId, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\DescendantTagList');
+
+ $className = $this->getClassNameFromId($descendantTagListId);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\DescendantTagList");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\DescendantTagList');
+
+ $class->addConstant('ID', $descendantTagListId)
+ ->addComment("ID of the descendant tag list.\n\n@var string");
+
+ $descendantTags = [];
+ foreach ($jsonSpec as $key => $value) {
+ $descendantTags[$key] = $this->getTagConstant($this->getConstantName($value));
+ }
+
+ $class->addConstant('DESCENDANT_TAGS', $descendantTags)
+ ->addComment("Array of descendant tags.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/DescendantTagList/{$className}.php");
+
+ return $className;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section/DocRulesets.php b/bin/src/Validator/SpecGenerator/Section/DocRulesets.php
new file mode 100644
index 000000000..49ea015b3
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section/DocRulesets.php
@@ -0,0 +1,161 @@
+addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\DocRuleset");
+
+ $docRulesetsTemplateClass = ClassType::withBodiesFrom(Template\DocRulesets::class);
+ foreach ($docRulesetsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($docRulesetsTemplateClass->getComment());
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('docRulesetsCache')
+ ->setPrivate()
+ ->addComment("Cache of instantiated DocRuleset objects.\n\n@var array")
+ ->setValue([]);
+
+ $class->addProperty('iterationArray')
+ ->setPrivate()
+ ->addComment("Array used for storing the iteration index in.\n\n@var array|null");
+
+ foreach ($spec as $attributes) {
+ $docRulesetId = $this->getNameForRuleset($attributes);
+ $docRulesets[$docRulesetId] = $attributes;
+ }
+
+ $docRulesetIds = array_keys($docRulesets);
+ natcasesort($docRulesetIds);
+
+ $rulesets = [];
+ foreach ($docRulesetIds as $docRulesetId) {
+ $docRulesetIdString = "DocRuleset\\{$this->getClassNameFromId($docRulesetId)}::ID";
+
+ $className = $this->generateDocRulesetSpecificClass(
+ $docRulesetId,
+ $docRulesets[$docRulesetId],
+ $fileManager
+ );
+
+ $rulesets["DocRuleset\\{$className}::ID"] = "DocRuleset\\{$className}::class";
+
+ if (array_key_exists('htmlFormat', $docRulesets[$docRulesetId])) {
+ $formats = $docRulesets[$docRulesetId]['htmlFormat'];
+ foreach ($formats as $format) {
+ $format = $this->getFormatConstant($this->getConstantName($format));
+ if (!array_key_exists($format, $byFormat)) {
+ $byFormat[$format] = [];
+ }
+ $byFormat[$format][] = $docRulesetIdString;
+ }
+ }
+ }
+
+ $class->addConstant('DOC_RULESETS', $rulesets)
+ ->addComment("Mapping of document ruleset ID to document ruleset implementation.\n\n@var array");
+
+ $class->addConstant('BY_FORMAT', $byFormat)
+ ->addComment(
+ "Mapping of AMP format to array of document ruleset IDs.\n\n"
+ . "This is used to optimize querying by AMP format.\n\n"
+ . "@var array>"
+ );
+ }
+
+ /**
+ * Get the name for a given ruleset.
+ *
+ * @param array $ruleSet Rule set to get the name for.
+ * @return string Name to use for the rule set.
+ */
+ private function getNameForRuleSet($ruleSet)
+ {
+ static $index = 1;
+
+ if (!array_key_exists('htmlFormat', $ruleSet) || count($ruleSet['htmlFormat']) === 0) {
+ $name = "ruleset-{$index}";
+ $index++;
+
+ return $name;
+ }
+
+ $name = $ruleSet['htmlFormat'][0];
+
+ if (array_key_exists('enabledBy', $ruleSet) && count($ruleSet['enabledBy']) > 0) {
+ $name .= " ({$ruleSet['enabledBy'][0]})";
+ }
+
+ if (array_key_exists('disabledBy', $ruleSet) && count($ruleSet['disabledBy']) > 0) {
+ $name .= " (no-{$ruleSet['disabledBy'][0]})";
+ }
+
+ return $name;
+ }
+
+ /**
+ * Generate the document ruleset-specific class file.
+ *
+ * @param string $ruleset ID of the document ruleset to generate the class for.
+ * @param array $jsonSpec Array of spec data for the document ruleset.
+ * @param FileManager $fileManager File manager instance to use.
+ */
+ private function generateDocRulesetSpecificClass($ruleset, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\DocRuleset');
+
+ $className = $this->getClassNameFromId($ruleset);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\DocRuleset");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\DocRuleset');
+
+ $class->addConstant('ID', $ruleset)
+ ->addComment("ID of the ruleset.\n\n@var string");
+
+ $class->addConstant('SPEC', $jsonSpec)
+ ->addComment("Array of spec rules.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/DocRuleset/{$className}.php");
+
+ return $className;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section/Errors.php b/bin/src/Validator/SpecGenerator/Section/Errors.php
new file mode 100644
index 000000000..f8bda15fc
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section/Errors.php
@@ -0,0 +1,94 @@
+addUse('AmpProject\Exception\InvalidListName');
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Error");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('errors')
+ ->setValue([])
+ ->setPrivate()
+ ->addComment("Cache of instantiated Error objects.\n\n@var array");
+
+ $errors = [];
+ foreach ($spec as $key => $value) {
+ $className = $this->generateErrorSpecificClass($key, $value, $fileManager);
+
+ $errors["Error\\{$className}::CODE"] = "Error\\{$className}::class";
+ }
+ $class->addConstant('ERRORS', $errors)
+ ->addComment("Mapping of error code to error implementation.\n\n@var array");
+
+ $errorsTemplateClass = ClassType::withBodiesFrom(Template\Errors::class);
+ foreach ($errorsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($errorsTemplateClass->getComment());
+ }
+
+ /**
+ * Generate the Error-specific class file.
+ *
+ * @param string $errorCode Code of the error to generate the class for.
+ * @param array $jsonSpec Array of spec data for the error.
+ * @param FileManager $fileManager File manager instance to use.
+ * @return string Short name of the class that was generated.
+ */
+ private function generateErrorSpecificClass($errorCode, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\Error');
+
+ $className = $this->getClassNameFromId($errorCode);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Error");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\Error');
+
+ $class->addConstant('CODE', $errorCode)
+ ->addComment("Code of the error.\n\n@var string");
+
+ $class->addConstant('SPEC', $jsonSpec)
+ ->addComment("Array of spec data.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/Error/{$className}.php");
+
+ return $className;
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Section/Tags.php b/bin/src/Validator/SpecGenerator/Section/Tags.php
new file mode 100644
index 000000000..3ee7674c9
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Section/Tags.php
@@ -0,0 +1,271 @@
+addUse("LogicException");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Iteration");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Tag");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\TagWithExtensionSpec");
+
+ $tagsTemplateClass = ClassType::withBodiesFrom(Template\Tags::class);
+ foreach ($tagsTemplateClass->getMethods() as $method) {
+ $class->addMember($method);
+ }
+ $class->addComment($tagsTemplateClass->getComment());
+
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\IterableSection");
+ $class->addTrait(
+ "{$fileManager->getRootNamespace()}\\Spec\\Iteration",
+ ['Iteration::current as parentCurrent']
+ );
+
+ $class->addProperty('tagsCache')
+ ->setPrivate()
+ ->addComment("Cache of instantiated Tag objects.\n\n@var array")
+ ->setValue([]);
+
+ $class->addProperty('iterationArray')
+ ->setPrivate()
+ ->addComment("Array used for storing the iteration index in.\n\n@var array|null");
+
+ foreach ($spec as $attributes) {
+ $tagId = $this->getTagId($tags, $attributes);
+ $tags[$tagId] = $attributes;
+ }
+
+ $tagIds = array_keys($tags);
+ natcasesort($tagIds);
+
+ $class->addConstant('TAGS', $this->getTagsMapping($tags))
+ ->addComment("Mapping of tag ID to tag implementation.\n\n@var array");
+
+
+ foreach ($tagIds as $tagId) {
+ $tagIdString = "Tag\\{$this->getClassNameFromId($tagId)}::ID";
+
+ $this->generateTagSpecificClass($tagId, $tags[$tagId], $fileManager);
+
+ if (array_key_exists('tagName', $tags[$tagId])) {
+ $tagName = $tags[$tagId]['tagName'];
+ if (strpos($tagName, '$') !== 0) {
+ $tagName = $this->getKeyString($tagName);
+ if (array_key_exists($tagName, $byTagName)) {
+ if (!is_array($byTagName[$tagName])) {
+ $previousTagId = $byTagName[$tagName];
+ $byTagName[$tagName] = [];
+ $byTagName[$tagName][] = $previousTagId;
+ }
+ $byTagName[$tagName][] = $tagIdString;
+ } else {
+ $byTagName[$tagName] = $tagIdString;
+ }
+ }
+ }
+
+ if (array_key_exists('specName', $tags[$tagId])) {
+ // Spec name and tag ID happens to be the same at this point but could change in the future.
+ $specName = $tagIdString;
+ $bySpecName[$specName] = $tagIdString;
+ }
+
+ if (array_key_exists('htmlFormat', $tags[$tagId])) {
+ $formats = $tags[$tagId]['htmlFormat'];
+ foreach ($formats as $format) {
+ $format = $this->getFormatConstant($this->getConstantName($format));
+ if (!array_key_exists($format, $byFormat)) {
+ $byFormat[$format] = [];
+ }
+ $byFormat[$format][] = $tagIdString;
+ }
+ }
+
+ if (array_key_exists('extensionSpec', $tags[$tagId])) {
+ $extensionSpec = $tags[$tagId]['extensionSpec'];
+ $extensionName = $this->getKeyString($extensionSpec['name']);
+ $byExtensionSpec[$extensionName] = $tagIdString;
+ }
+ }
+
+ $class->addConstant('BY_TAG_NAME', $byTagName)
+ ->addComment(
+ "Mapping of tag name to tag ID or array of tag IDs.\n\n"
+ . "This is used to optimize querying by tag name.\n\n"
+ . "@var array>"
+ );
+
+ $class->addConstant('BY_SPEC_NAME', $bySpecName)
+ ->addComment(
+ "Mapping of spec name to tag ID.\n\n"
+ . "This is used to optimize querying by spec name.\n\n"
+ . "@var array"
+ );
+
+ $class->addConstant('BY_FORMAT', $byFormat)
+ ->addComment(
+ "Mapping of AMP format to array of tag IDs.\n\n"
+ . "This is used to optimize querying by AMP format.\n\n"
+ . "@var array>"
+ );
+
+ $class->addConstant('BY_EXTENSION_SPEC', $byExtensionSpec)
+ ->addComment(
+ "Mapping of extension name to tag ID.\n\n"
+ . "This is used to optimize querying by extension spec.\n\n"
+ . "@var array"
+ );
+ }
+
+ /**
+ * Get a unique tag ID.
+ *
+ * @param array $tags Array of tags that were collected.
+ * @param array $attributes Attributes array to get the tag ID for.
+ * @return string Tag ID.
+ */
+ private function getTagId($tags, $attributes)
+ {
+ if (array_key_exists('specName', $attributes)) {
+ $specName = $attributes['specName'];
+ } elseif (array_key_exists('tagName', $attributes)) {
+ $specName = $attributes['tagName'];
+ } else {
+ $specName = 'unnamed';
+ }
+
+ if ($specName === 'SCRIPT' && array_key_exists('extensionSpec', $attributes)) {
+ $specName .= " [{$attributes['extensionSpec']['name']}]";
+ }
+
+ $tagId = $specName;
+ $index = 1;
+
+ while (array_key_exists($tagId, $tags)) {
+ $index++;
+ $tagId = "{$specName} ({$index})";
+ }
+
+ return $tagId;
+ }
+
+ /**
+ * Get the string to use as key for the tag.
+ *
+ * This automatically reuses existing constant to reduce memory consumption.
+ *
+ * @param string $tagId Tag ID to produce a key string for.
+ * @return string Key string to use.
+ */
+ private function getKeyString($tagId)
+ {
+ if (!is_string($tagId)) {
+ return $tagId;
+ }
+
+ $constant = $this->getTagConstant($this->getConstantName($tagId));
+
+ $definition = "AmpProject\\{$constant}";
+ $definition = str_replace('\\Element::', '\\Tag::', $definition);
+
+ if (!defined($definition)) {
+ return $tagId;
+ }
+
+ return $constant;
+ }
+
+ /**
+ * Get the tag mappings that map tag names to tag implementations.
+ *
+ * @param array $tags Array of tags that were collected.
+ * @return array Tags mapping information.
+ */
+ private function getTagsMapping($tags)
+ {
+ $tagMappings = [];
+
+ foreach ($tags as $tagId => $attributes) {
+ unset($tagMappings[$tagId]);
+
+ $class = "Tag\\{$this->getClassNameFromId($tagId)}::class";
+ $tagId = "Tag\\{$this->getClassNameFromId($tagId)}::ID";
+
+ $tagMappings[$tagId] = $class;
+ }
+
+ return $tagMappings;
+ }
+
+ /**
+ * Generate the tag-specific class file.
+ *
+ * @param string $tagId ID of the tag to generate the class for.
+ * @param array $jsonSpec Array of spec data for the tag.
+ * @param FileManager $fileManager File manager instance to use.
+ */
+ private function generateTagSpecificClass($tagId, $jsonSpec, FileManager $fileManager)
+ {
+ list($file, $namespace) = $fileManager->createNewNamespacedFile('Spec\\Tag');
+
+ $className = $this->getClassNameFromId($tagId);
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\SpecRule");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\Tag");
+
+ /** @var ClassType $class */
+ $class = $namespace->addClass($className)
+ ->setFinal()
+ ->addExtend('AmpProject\Validator\Spec\Tag');
+
+ $class->addConstant('ID', $tagId)
+ ->addComment("ID of the tag.\n\n@var string");
+
+ if (array_key_exists('extensionSpec', $jsonSpec)) {
+ $extensionSpec = $jsonSpec['extensionSpec'];
+ $jsonSpec['extensionSpec'] = "self::EXTENSION_SPEC";
+
+ $class->addConstant('EXTENSION_SPEC', $extensionSpec)
+ ->addComment("Array of extension spec rules.\n\n@var array");
+
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\TagWithExtensionSpec");
+ $class->addImplement("{$fileManager->getRootNamespace()}\\Spec\\TagWithExtensionSpec");
+ $namespace->addUse("{$fileManager->getRootNamespace()}\\Spec\\ExtensionSpec");
+ $class->addTrait("{$fileManager->getRootNamespace()}\\Spec\\ExtensionSpec");
+ }
+
+ $class->addConstant('SPEC', $jsonSpec)
+ ->addComment("Array of spec rules.\n\n@var array");
+
+ $fileManager->saveFile($file, "Spec/Tag/{$className}.php");
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/SpecPrinter.php b/bin/src/Validator/SpecGenerator/SpecPrinter.php
new file mode 100644
index 000000000..3d316047f
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/SpecPrinter.php
@@ -0,0 +1,145 @@
+dumper = new Dumper();
+ }
+
+ public function printClass(ClassType $class, PhpNamespace $namespace = null): string
+ {
+ $class->validate();
+ $resolver = $this->resolveTypes && $namespace
+ ? [$namespace, 'unresolveUnionType']
+ : function ($s) {
+ return $s;
+ };
+
+ $traits = [];
+ foreach ($class->getTraitResolutions() as $trait => $resolutions) {
+ $traits[] = 'use ' . $resolver($trait) . ($resolutions
+ ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n"
+ : ";\n");
+ }
+
+ $consts = [];
+ foreach ($class->getConstants() as $const) {
+ $consts[] = Helpers::formatDocComment((string) $const->getComment())
+ . self::printAttributes($const->getAttributes(), $namespace)
+ . "const {$const->getName()} = "
+ . $this->dumper->dump($const->getValue(), 0) . ";\n";
+ }
+
+ $properties = [];
+ foreach ($class->getProperties() as $property) {
+ $type = $property->getType();
+ $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' '
+ . ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ')
+ . '$' . $property->getName());
+
+ $properties[] = Helpers::formatDocComment((string) $property->getComment())
+ . self::printAttributes($property->getAttributes(), $namespace)
+ . $def
+ . ($property->getValue() === null && !$property->isInitialized()
+ ? ''
+ : ' = ' . $this->dumper->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
+ . ";\n";
+ }
+
+ $methods = [];
+ foreach ($class->getMethods() as $method) {
+ $methods[] = $this->printMethod($method, $namespace);
+ }
+
+ $members = array_filter(
+ [
+ implode('', $traits),
+ $this->joinProperties($consts),
+ $this->joinProperties($properties),
+ ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
+ . implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
+ ]
+ );
+
+ return Strings::normalize(
+ Helpers::formatDocComment($class->getComment() . "\n")
+ . self::printAttributes($class->getAttributes(), $namespace)
+ . ($class->isAbstract() ? 'abstract ' : '')
+ . ($class->isFinal() ? 'final ' : '')
+ . ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '')
+ . ($class->getExtends()
+ ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' '
+ : '')
+ . ($class->getImplements()
+ ? ($class->isInterface() ? 'extends ' : 'implements ')
+ . implode(', ', array_map($resolver, $class->getImplements())) . ' '
+ : '')
+ . ($class->getName() ? "\n" : '') . "{\n"
+ . ($members ? $this->indent(implode("\n", $members)) : '')
+ . '}'
+ ) . ($class->getName() ? "\n" : '');
+ }
+
+ /**
+ * @param Closure|GlobalFunction|Method $function
+ */
+ private function printReturnType($function, ?PhpNamespace $namespace): string
+ {
+ return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable(), $namespace))
+ ? $this->returnTypeColon . $tmp
+ : '';
+ }
+
+
+ private function printAttributes(array $attrs, ?PhpNamespace $namespace, bool $inline = false): string
+ {
+ if (!$attrs) {
+ return '';
+ }
+ $items = [];
+ foreach ($attrs as $attr) {
+ $args = (new Dumper())->format('...?:', $attr->getArguments());
+ $items[] = $this->printType($attr->getName(), false, $namespace) . ($args ? "($args)" : '');
+ }
+ return $inline
+ ? '#[' . implode(', ', $items) . '] '
+ : '#[' . implode("]\n#[", $items) . "]\n";
+ }
+
+ private function joinProperties(array $props)
+ {
+ return $this->linesBetweenProperties
+ ? implode(str_repeat("\n", $this->linesBetweenProperties), $props)
+ : preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props));
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/AttributeList.php b/bin/src/Validator/SpecGenerator/Template/AttributeList.php
new file mode 100644
index 000000000..8397ee2f2
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/AttributeList.php
@@ -0,0 +1,66 @@
+
+ */
+ const ATTRIBUTES = [];
+
+ /**
+ * Get the ID of the attribute list.
+ *
+ * @return string ID of the attribute list.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given attribute is contained within the list.
+ *
+ * @param string $attribute Attribute to check for.
+ * @return bool Whether the given attribute is contained within the list.
+ */
+ public function has($attribute)
+ {
+ return array_key_exists($attribute, static::ATTRIBUTES);
+ }
+
+ /**
+ * Get a specific attribute.
+ *
+ * @param string $attribute Attribute to get.
+ * @return array Attribute data that was requested.
+ */
+ public function get($attribute)
+ {
+ if (!$this->has($attribute)) {
+ throw InvalidAttributeName::forAttribute($attribute);
+ }
+
+ return static::ATTRIBUTES[$attribute];
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/AttributeLists.php b/bin/src/Validator/SpecGenerator/Template/AttributeLists.php
new file mode 100644
index 000000000..15208e5d6
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/AttributeLists.php
@@ -0,0 +1,83 @@
+ */
+ const ATTRIBUTE_LISTS = [];
+
+ /** @var array */
+ private $attributeLists = [];
+
+ /**
+ * Get a specific attribute list.
+ *
+ * @param string $attributeListName Name of the attribute list to get.
+ * @return Spec\AttributeList Attribute list with the given attribute list name.
+ * @throws InvalidListName If an invalid attribute list name is requested.
+ */
+ public function get($attributeListName)
+ {
+ if (!array_key_exists($attributeListName, self::ATTRIBUTE_LISTS)) {
+ throw InvalidListName::forAttributeList($attributeListName);
+ }
+
+ if (array_key_exists($attributeListName, $this->attributeLists)) {
+ return $this->attributeLists[$attributeListName];
+ }
+
+ $attributeListClassName = self::ATTRIBUTE_LISTS[$attributeListName];
+
+ /** @var Spec\AttributeList $attributeList */
+ $attributeList = new $attributeListClassName();
+
+ $this->attributeLists[$attributeListName] = $attributeList;
+
+ return $attributeList;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::ATTRIBUTE_LISTS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return AttributeList Attribute list object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/CssRuleset.php b/bin/src/Validator/SpecGenerator/Template/CssRuleset.php
new file mode 100644
index 000000000..fe43626e2
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/CssRuleset.php
@@ -0,0 +1,111 @@
+ $declarationListSvg
+ * @property-read array $disabledBy
+ * @property-read array $enabledBy
+ * @property-read array $fontUrlSpec
+ * @property-read array $htmlFormat
+ * @property-read array $imageUrlSpec
+ */
+class CssRuleset
+{
+
+ /**
+ * ID of the CSS ruleset.
+ *
+ * This needs to be overridden in the extending class.
+ *
+ * @var string
+ */
+ const ID = '[css ruleset base class]';
+
+ /**
+ * Spec data of the CSS ruleset.
+ *
+ * @var array
+ */
+ const SPEC = [];
+
+ /**
+ * Get the ID of the CSS ruleset.
+ *
+ * @return string ID of the CSS ruleset.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+
+ /**
+ * Check whether a given spec rule is present.
+ *
+ * @param string $cssRulesetName Name of the spec rule to check for.
+ * @return bool Whether the given spec rule is contained in the spec.
+ */
+ public function has($cssRulesetName)
+ {
+ return array_key_exists($cssRulesetName, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $cssRulesetName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($cssRulesetName)
+ {
+ if (!$this->has($cssRulesetName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($cssRulesetName);
+ }
+
+ return static::SPEC[$cssRulesetName];
+ }
+
+ /**
+ * Magic getter to return the spec rules.
+ *
+ * @param string $cssRulesetName Name of the spec rule to return.
+ * @return mixed Value of the spec rule.
+ */
+ public function __get($cssRulesetName)
+ {
+ switch ($cssRulesetName) {
+ case SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE:
+ case SpecRule::ALLOW_IMPORTANT:
+ case SpecRule::EXPAND_VENDOR_PREFIXES:
+ case SpecRule::MAX_BYTES_IS_WARNING:
+ case SpecRule::URL_BYTES_INCLUDED:
+ return array_key_exists($cssRulesetName, static::SPEC) ? static::SPEC[$cssRulesetName] : false;
+ case SpecRule::DECLARATION_LIST_SVG:
+ case SpecRule::DISABLED_BY:
+ case SpecRule::ENABLED_BY:
+ case SpecRule::FONT_URL_SPEC:
+ case SpecRule::HTML_FORMAT:
+ case SpecRule::IMAGE_URL_SPEC:
+ return array_key_exists($cssRulesetName, static::SPEC) ? static::SPEC[$cssRulesetName] : [];
+ default:
+ if (!array_key_exists($cssRulesetName, static::SPEC)) {
+ throw InvalidSpecRuleName::forSpecRuleName($cssRulesetName);
+ }
+
+ return static::SPEC[$cssRulesetName];
+ }
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/CssRulesets.php b/bin/src/Validator/SpecGenerator/Template/CssRulesets.php
new file mode 100644
index 000000000..49b5b66b0
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/CssRulesets.php
@@ -0,0 +1,115 @@
+cssRulesetsCache)) {
+ return $this->cssRulesetsCache[$cssRulesetId];
+ }
+
+ $cssRulesetClassName = self::CSS_RULESETS[$cssRulesetId];
+
+ /** @var CssRuleset $cssRuleset */
+ $cssRuleset = new $cssRulesetClassName();
+
+ $this->cssRulesetsCache[$cssRulesetId] = $cssRuleset;
+
+ return $cssRuleset;
+ }
+
+ /**
+ * Get a collection of CSS rulesets for a given AMP HTML format name.
+ *
+ * @param string $format AMP HTML format to get the CSS rulesets for.
+ * @return array Array of CSS rulesets matching the requested AMP HTML format.
+ * @throws InvalidFormat If an invalid AMP HTML format is requested.
+ */
+ public function byFormat($format)
+ {
+ if (!array_key_exists($format, self::BY_FORMAT)) {
+ throw InvalidFormat::forFormat($format);
+ }
+
+ $cssRulesetIds = self::BY_FORMAT[$format];
+ if (!is_array($cssRulesetIds)) {
+ $cssRulesetIds = [$cssRulesetIds];
+ }
+
+ $cssRulesets = [];
+ foreach ($cssRulesetIds as $cssRulesetId) {
+ $cssRulesets[] = $this->get($cssRulesetId);
+ }
+
+ return $cssRulesets;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::CSS_RULESETS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return CssRuleset Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return CssRuleset CssRuleset object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/DeclarationList.php b/bin/src/Validator/SpecGenerator/Template/DeclarationList.php
new file mode 100644
index 000000000..8f4394b08
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/DeclarationList.php
@@ -0,0 +1,66 @@
+
+ */
+ const DECLARATIONS = [];
+
+ /**
+ * Get the ID of the declaration list.
+ *
+ * @return string ID of the declaration list.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given declaration is contained within the list.
+ *
+ * @param string $declaration Declaration to check for.
+ * @return bool Whether the given declaration is contained within the list.
+ */
+ public function has($declaration)
+ {
+ return array_key_exists($declaration, static::DECLARATIONS);
+ }
+
+ /**
+ * Get a specific declaration.
+ *
+ * @param string $declaration Declaration to get.
+ * @return array Declaration data that was requested.
+ */
+ public function get($declaration)
+ {
+ if (!$this->has($declaration)) {
+ throw InvalidDeclarationName::forDeclaration($declaration);
+ }
+
+ return static::DECLARATIONS[$declaration];
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/DeclarationLists.php b/bin/src/Validator/SpecGenerator/Template/DeclarationLists.php
new file mode 100644
index 000000000..5354d7f52
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/DeclarationLists.php
@@ -0,0 +1,83 @@
+ */
+ const DECLARATION_LISTS = [];
+
+ /** @var array */
+ private $declarationLists = [];
+
+ /**
+ * Get a specific declaration list.
+ *
+ * @param string $declarationListName Name of the declaration list to get.
+ * @return Spec\DeclarationList Declaration list with the given declaration list name.
+ * @throws InvalidListName If an invalid declaration list name is requested.
+ */
+ public function get($declarationListName)
+ {
+ if (!array_key_exists($declarationListName, self::DECLARATION_LISTS)) {
+ throw InvalidListName::forDeclarationList($declarationListName);
+ }
+
+ if (array_key_exists($declarationListName, $this->declarationLists)) {
+ return $this->declarationLists[$declarationListName];
+ }
+
+ $declarationListClassName = self::DECLARATION_LISTS[$declarationListName];
+
+ /** @var Spec\DeclarationList $declarationList */
+ $declarationList = new $declarationListClassName();
+
+ $this->declarationLists[$declarationListName] = $declarationList;
+
+ return $declarationList;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::DECLARATION_LISTS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return DeclarationList Declaration list object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/DescendantTagList.php b/bin/src/Validator/SpecGenerator/Template/DescendantTagList.php
new file mode 100644
index 000000000..11633dbd1
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/DescendantTagList.php
@@ -0,0 +1,49 @@
+
+ */
+ const DESCENDANT_TAGS = [];
+
+ /**
+ * Get the ID of the descendant tag list.
+ *
+ * @return string ID of the descendant tag list.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given descendant tag is contained within the list.
+ *
+ * @param string $descendantTag Descendant tag to check for.
+ * @return bool Whether the given descendant tag is contained within the list.
+ */
+ public function has($descendantTag)
+ {
+ return in_array($descendantTag, static::DESCENDANT_TAGS, true);
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/DescendantTagLists.php b/bin/src/Validator/SpecGenerator/Template/DescendantTagLists.php
new file mode 100644
index 000000000..0a91486ca
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/DescendantTagLists.php
@@ -0,0 +1,83 @@
+ */
+ const DESCENDANT_TAG_LISTS = [];
+
+ /** @var array */
+ private $descendantTagLists = [];
+
+ /**
+ * Get a specific descendantTag list.
+ *
+ * @param string $descendantTagListName Name of the descendant tag list to get.
+ * @return Spec\DescendantTagList Descendant tag list with the given descendant tag list name.
+ * @throws InvalidListName If an invalid descendant tag list name is requested.
+ */
+ public function get($descendantTagListName)
+ {
+ if (!array_key_exists($descendantTagListName, self::DESCENDANT_TAG_LISTS)) {
+ throw InvalidListName::forDescendantTagList($descendantTagListName);
+ }
+
+ if (array_key_exists($descendantTagListName, $this->descendantTagLists)) {
+ return $this->descendantTagLists[$descendantTagListName];
+ }
+
+ $descendantTagListClassName = self::DESCENDANT_TAG_LISTS[$descendantTagListName];
+
+ /** @var Spec\DescendantTagList $descendantTagList */
+ $descendantTagList = new $descendantTagListClassName();
+
+ $this->descendantTagLists[$descendantTagListName] = $descendantTagList;
+
+ return $descendantTagList;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::DESCENDANT_TAG_LISTS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return DescendantTagList Descendant tag list object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/DocRuleset.php b/bin/src/Validator/SpecGenerator/Template/DocRuleset.php
new file mode 100644
index 000000000..4049c3ab4
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/DocRuleset.php
@@ -0,0 +1,90 @@
+ $htmlFormat HTML format that this DocRuleset applies to.
+ */
+class DocRuleset
+{
+
+ /**
+ * ID of the document ruleset.
+ *
+ * This needs to be overridden in the extending class.
+ *
+ * @var string
+ */
+ const ID = '[document ruleset base class]';
+
+ /**
+ * Spec data of the document ruleset.
+ *
+ * @var array
+ */
+ const SPEC = [];
+
+ /**
+ * Get the ID of the document ruleset.
+ *
+ * @return string ID of the document ruleset.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+
+ /**
+ * Check whether a given spec rule is present.
+ *
+ * @param string $docRulesetName Name of the spec rule to check for.
+ * @return bool Whether the given spec rule is contained in the spec.
+ */
+ public function has($docRulesetName)
+ {
+ return array_key_exists($docRulesetName, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $docRulesetName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($docRulesetName)
+ {
+ if (!$this->has($docRulesetName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($docRulesetName);
+ }
+
+ return static::SPEC[$docRulesetName];
+ }
+
+ /**
+ * Magic getter to return the spec rules.
+ *
+ * @param string $docRulesetName Name of the spec rule to return.
+ * @return mixed Value of the spec rule.
+ */
+ public function __get($docRulesetName)
+ {
+ switch ($docRulesetName) {
+ case SpecRule::HTML_FORMAT:
+ return array_key_exists($docRulesetName, static::SPEC) ? static::SPEC[$docRulesetName] : [];
+ default:
+ if (!array_key_exists($docRulesetName, static::SPEC)) {
+ throw InvalidSpecRuleName::forSpecRuleName($docRulesetName);
+ }
+
+ return static::SPEC[$docRulesetName];
+ }
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/DocRulesets.php b/bin/src/Validator/SpecGenerator/Template/DocRulesets.php
new file mode 100644
index 000000000..9d212c44b
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/DocRulesets.php
@@ -0,0 +1,115 @@
+docRulesetsCache)) {
+ return $this->docRulesetsCache[$docRulesetId];
+ }
+
+ $docRulesetClassName = self::DOC_RULESETS[$docRulesetId];
+
+ /** @var DocRuleset $docRuleset */
+ $docRuleset = new $docRulesetClassName();
+
+ $this->docRulesetsCache[$docRulesetId] = $docRuleset;
+
+ return $docRuleset;
+ }
+
+ /**
+ * Get a collection of document rulesets for a given AMP HTML format name.
+ *
+ * @param string $format AMP HTML format to get the document rulesets for.
+ * @return array Array of document rulesets matching the requested AMP HTML format.
+ * @throws InvalidFormat If an invalid AMP HTML format is requested.
+ */
+ public function byFormat($format)
+ {
+ if (!array_key_exists($format, self::BY_FORMAT)) {
+ throw InvalidFormat::forFormat($format);
+ }
+
+ $docRulesetIds = self::BY_FORMAT[$format];
+ if (!is_array($docRulesetIds)) {
+ $docRulesetIds = [$docRulesetIds];
+ }
+
+ $docRulesets = [];
+ foreach ($docRulesetIds as $docRulesetId) {
+ $docRulesets[] = $this->get($docRulesetId);
+ }
+
+ return $docRulesets;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::DOC_RULESETS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return DocRuleset Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return DocRuleset DocRuleset object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/Error.php b/bin/src/Validator/SpecGenerator/Template/Error.php
new file mode 100644
index 000000000..7d616e282
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/Error.php
@@ -0,0 +1,66 @@
+
+ */
+ const SPEC = [];
+
+ /**
+ * Get the code of the error.
+ *
+ * @return string Code of the error.
+ */
+ public function getCode()
+ {
+ return static::CODE;
+ }
+
+ /**
+ * Check whether the error has a given spec rule.
+ *
+ * @param string $specRule Spec rule to check for.
+ * @return bool Whether the error has the given spec rule.
+ */
+ public function has($specRule)
+ {
+ return array_key_exists($specRule, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $specRuleName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($specRuleName)
+ {
+ if (!$this->has($specRuleName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($specRuleName);
+ }
+
+ return static::SPEC[$specRuleName];
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/Errors.php b/bin/src/Validator/SpecGenerator/Template/Errors.php
new file mode 100644
index 000000000..4e6aa9758
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/Errors.php
@@ -0,0 +1,83 @@
+ */
+ const ERRORS = [];
+
+ /** @var array */
+ private $errors = [];
+
+ /**
+ * Get a specific error.
+ *
+ * @param string $errorCode Code of the error to get.
+ * @return Spec\Error Error with the given error code.
+ * @throws InvalidErrorCode If an invalid error code is requested.
+ */
+ public function get($errorCode)
+ {
+ if (!array_key_exists($errorCode, self::ERRORS)) {
+ throw InvalidErrorCode::forErrorCode($errorCode);
+ }
+
+ if (array_key_exists($errorCode, $this->errors)) {
+ return $this->errors[$errorCode];
+ }
+
+ $errorClassName = self::ERRORS[$errorCode];
+
+ /** @var Spec\Error $error */
+ $error = new $errorClassName();
+
+ $this->errors[$errorCode] = $error;
+
+ return $error;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::ERRORS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return Error Error object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/ExtensionSpec.php b/bin/src/Validator/SpecGenerator/Template/ExtensionSpec.php
new file mode 100644
index 000000000..bd62f9446
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/ExtensionSpec.php
@@ -0,0 +1,78 @@
+ Array of available keys.
+ */
+ public function getAvailableKeys();
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key);
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/Iteration.php b/bin/src/Validator/SpecGenerator/Template/Iteration.php
new file mode 100644
index 000000000..79c5d0e3f
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/Iteration.php
@@ -0,0 +1,121 @@
+initIterationArray();
+
+ $key = current($this->iterationArray);
+
+ return $this->findByKey($key);
+ }
+
+ /**
+ * Move forward to next iterable object.
+ *
+ * @return void Any returned value is ignored.
+ */
+ public function next()
+ {
+ $this->initIterationArray();
+
+ next($this->iterationArray);
+ }
+
+ /**
+ * Return the ID of the current iterable object.
+ *
+ * @return string|null ID of the current iterable object, or null if out of bounds.
+ */
+ public function key()
+ {
+ $this->initIterationArray();
+
+ return key($this->iterationArray);
+ }
+
+ /**
+ * Checks if current position is valid.
+ *
+ * @return bool The return value will be casted to boolean and then evaluated.
+ * Returns true on success or false on failure.
+ */
+ public function valid()
+ {
+ $this->initIterationArray();
+
+ $key = $this->key();
+
+ return $key !== null && $key !== false;
+ }
+
+ /**
+ * Rewind the Iterator to the first iterable object.
+ *
+ * @return void Any returned value is ignored.
+ */
+ public function rewind()
+ {
+ $this->initIterationArray();
+
+ reset($this->iterationArray);
+ }
+
+ /**
+ * Initialize the iteration array.
+ */
+ private function initIterationArray()
+ {
+ if ($this->iterationArray === null) {
+ $this->iterationArray = $this->getAvailableKeys();
+ }
+ }
+
+ /**
+ * Count elements of an iterable section.
+ *
+ * @return int The custom count as an integer.
+ */
+ public function count()
+ {
+ return count($this->getAvailableKeys());
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ abstract public function getAvailableKeys();
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ abstract public function findByKey($key);
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/Tag.php b/bin/src/Validator/SpecGenerator/Template/Tag.php
new file mode 100644
index 000000000..df46c5fbf
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/Tag.php
@@ -0,0 +1,140 @@
+ $alsoRequiresTagWarning
+ * @property-read array $ampLayout
+ * @property-read array $attrLists
+ * @property-read array $attrs
+ * @property-read array $cdata
+ * @property-read array $childTags
+ * @property-read string $deprecation
+ * @property-read string $deprecationUrl
+ * @property-read string $descendantTagList
+ * @property-read string $descriptiveName
+ * @property-read array $disabledBy
+ * @property-read array $disallowedAncestor
+ * @property-read array $enabledBy
+ * @property-read array $excludes
+ * @property-read bool $explicitAttrsOnly
+ * @property-read array $extensionSpec
+ * @property-read array $htmlFormat
+ * @property-read bool $mandatory
+ * @property-read string $mandatoryAlternatives
+ * @property-read string $mandatoryAncestor
+ * @property-read string $mandatoryAncestorSuggestedAlternative
+ * @property-read bool $mandatoryLastChild
+ * @property-read string $mandatoryParent
+ * @property-read array $markDescendants
+ * @property-read string $namedId
+ * @property-read array $referencePoints
+ * @property-read array $requires
+ * @property-read array $requiresExtension
+ * @property-read array $satisfies
+ * @property-read bool $siblingsDisallowed
+ * @property-read string $specName
+ * @property-read string $specUrl
+ * @property-read string $tagName
+ * @property-read bool $unique
+ * @property-read bool $uniqueWarning
+ */
+class Tag
+{
+
+ /**
+ * ID of the tag.
+ *
+ * This needs to be overridden in the extending class.
+ *
+ * @var string
+ */
+ const ID = '[tag base class]';
+
+ /**
+ * Spec data of the tag.
+ *
+ * @var array
+ */
+ const SPEC = [];
+
+ /**
+ * Get the ID of the tag.
+ *
+ * @return string ID of the tag.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+
+ /**
+ * Check whether a given spec rule is present.
+ *
+ * @param string $specRuleName Name of the spec rule to check for.
+ * @return bool Whether the given spec rule is contained in the spec.
+ */
+ public function has($specRuleName)
+ {
+ return array_key_exists($specRuleName, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $specRuleName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($specRuleName)
+ {
+ if (!$this->has($specRuleName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($specRuleName);
+ }
+
+ return static::SPEC[$specRuleName];
+ }
+
+ /**
+ * Magic getter to return the spec rules.
+ *
+ * @param string $specRuleName Name of the spec rule to return.
+ * @return mixed Value of the spec rule.
+ */
+ public function __get($specRuleName)
+ {
+ switch ($specRuleName) {
+ case SpecRule::EXPLICIT_ATTRS_ONLY:
+ case SpecRule::MANDATORY:
+ case SpecRule::MANDATORY_LAST_CHILD:
+ case SpecRule::SIBLINGS_DISALLOWED:
+ case SpecRule::UNIQUE:
+ case SpecRule::UNIQUE_WARNING:
+ return array_key_exists($specRuleName, static::SPEC) ? static::SPEC[$specRuleName] : false;
+ case SpecRule::ALSO_REQUIRES_TAG_WARNING:
+ case SpecRule::ATTR_LISTS:
+ case SpecRule::DISABLED_BY:
+ case SpecRule::DISALLOWED_ANCESTOR:
+ case SpecRule::ENABLED_BY:
+ case SpecRule::EXCLUDES:
+ case SpecRule::HTML_FORMAT:
+ case SpecRule::REQUIRES:
+ case SpecRule::REQUIRES_EXTENSION:
+ case SpecRule::SATISFIES:
+ return array_key_exists($specRuleName, static::SPEC) ? static::SPEC[$specRuleName] : [];
+ default:
+ if (!array_key_exists($specRuleName, static::SPEC)) {
+ throw InvalidSpecRuleName::forSpecRuleName($specRuleName);
+ }
+
+ return static::SPEC[$specRuleName];
+ }
+ }
+}
diff --git a/bin/src/Validator/SpecGenerator/Template/TagWithExtensionSpec.php b/bin/src/Validator/SpecGenerator/Template/TagWithExtensionSpec.php
new file mode 100644
index 000000000..ccf2c4320
--- /dev/null
+++ b/bin/src/Validator/SpecGenerator/Template/TagWithExtensionSpec.php
@@ -0,0 +1,32 @@
+ Array of tags. Empty array if tag name not found.
+ */
+ public function byTagName($tagName)
+ {
+ $tagName = strtolower($tagName);
+
+ if (!array_key_exists($tagName, self::BY_TAG_NAME)) {
+ return [];
+ }
+
+ $tagIds = self::BY_TAG_NAME[$tagName];
+ if (!is_array($tagIds)) {
+ $tagIds = [$tagIds];
+ }
+
+ $tags = [];
+ foreach ($tagIds as $tagId) {
+ $tags[] = $this->byTagId($tagId);
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Get the tag for a given spec name.
+ *
+ * @param string $specName Spec name to get the tag for.
+ * @return Tag Tag with the given spec name.
+ * @throws InvalidSpecName If an invalid spec name is requested.
+ */
+ public function bySpecName($specName)
+ {
+ if (!array_key_exists($specName, self::BY_SPEC_NAME)) {
+ throw InvalidSpecName::forSpecName($specName);
+ }
+
+ return $this->byTagId(self::BY_SPEC_NAME[$specName]);
+ }
+
+ /**
+ * Get a collection of tags for a given AMP HTML format name.
+ *
+ * @param string $format AMP HTML format to get the tags for.
+ * @return array Array of tags matching the requested AMP HTML format.
+ * @throws InvalidFormat If an invalid AMP HTML format is requested.
+ */
+ public function byFormat($format)
+ {
+ if (!array_key_exists($format, self::BY_FORMAT)) {
+ throw InvalidFormat::forFormat($format);
+ }
+
+ $tagIds = self::BY_FORMAT[$format];
+ if (!is_array($tagIds)) {
+ $tagIds = [$tagIds];
+ }
+
+ $tags = [];
+ foreach ($tagIds as $tagId) {
+ $tags[] = $this->byTagId($tagId);
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Get the tag for a given extension spec name.
+ *
+ * @param string $extension Extension name to get the extension spec for.
+ * @return TagWithExtensionSpec Tag with the given extension spec name.
+ * @throws InvalidExtension If an invalid extension name is requested.
+ */
+ public function byExtensionSpec($extension)
+ {
+ if (!array_key_exists($extension, self::BY_EXTENSION_SPEC)) {
+ throw InvalidExtension::forExtension($extension);
+ }
+
+ $tag = $this->byTagId(self::BY_EXTENSION_SPEC[$extension]);
+
+ if (!$tag instanceof \AmpProject\Validator\Spec\TagWithExtensionSpec) {
+ throw new LogicException('Tags::byExtensionSpec returned tag without extension spec');
+ }
+
+ return $tag;
+ }
+
+ /**
+ * Get a tag by its tag ID.
+ *
+ * @param string $tagId Tag ID for which to get the tag.
+ * @return Tag Tag object.
+ */
+ public function byTagId($tagId)
+ {
+ if (array_key_exists($tagId, $this->tagsCache)) {
+ return $this->tagsCache[$tagId];
+ }
+
+ if (!array_key_exists($tagId, self::TAGS)) {
+ throw InvalidTagId::forTagId($tagId);
+ }
+
+ $tagClassName = self::TAGS[$tagId];
+ $tag = new $tagClassName();
+
+ $this->tagsCache[$tagId] = $tag;
+
+ return $tag;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::TAGS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->byTagId($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return Tag Tag object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/composer.json b/composer.json
index 32568ea26..c8c05cfca 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,7 @@
"civicrm/composer-downloads-plugin": "^2.1 || ^3.0",
"dealerdirect/phpcodesniffer-composer-installer": "0.7.1",
"php-parallel-lint/php-parallel-lint": "^1.2",
- "phpcompatibility/phpcompatibility-wp": "2.1.1",
+ "phpcompatibility/php-compatibility": "^9",
"phpunit/phpunit": "^5 || ^6 || ^7 || ^8 || ^9",
"roave/security-advisories": "dev-master",
"sirbrillig/phpcs-variable-analysis": "2.11.0",
@@ -25,7 +25,8 @@
},
"suggest": {
"ext-json": "Provides native implementation of json_encode()/json_decode().",
- "ext-mbstring": "Used by Dom\\Document to convert encoding to UTF-8 if needed."
+ "ext-mbstring": "Used by Dom\\Document to convert encoding to UTF-8 if needed.",
+ "nette/php-generator": "Needed to generate the validator spec PHP classes and interfaces."
},
"config": {
"sort-packages": true
@@ -49,18 +50,21 @@
},
"autoload-dev": {
"psr-4": {
- "AmpProject\\Tests\\": "tests/src/"
+ "AmpProject\\Tests\\": "tests/src/",
+ "AmpProject\\Tooling\\": "bin/src/"
}
},
"scripts": {
"cbf": "phpcbf",
+ "compat": "if [ -z $TEST_SKIP_PHPCOMPAT ]; then phpcs --standard=PHPCompatibility -s -p src --runtime-set testVersion 5.6; fi",
"cs": "if [ -z $TEST_SKIP_PHPCS ]; then phpcs; fi",
"lint": "if [ -z $TEST_SKIP_LINTING ]; then parallel-lint -j 10 --colors --exclude vendor .; fi",
"test": [
"@lint",
"@unit",
"@cs",
- "@analyze"
+ "@analyze",
+ "@compat"
],
"analyze": "if [ -z $TEST_SKIP_PHPSTAN ]; then phpstan --version; phpstan analyze --ansi; fi",
"unit": "if [ -z $TEST_SKIP_PHPUNIT ]; then phpunit --colors=always; fi",
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 30bc4dffb..a501c3cb3 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -9,3 +9,8 @@ parameters:
ignoreErrors:
- '#^PHPDoc tag @throws with type AmpProject\\Exception\\FailedRemoteRequest is not subtype of Throwable$#'
- '/^Parameter \#1 \$callback of function set_exception_handler expects/'
+ # @see https://github.com/phpstan/phpstan/issues/4570
+ - '#^Call to function array_key_exists\(\) with ''extensionType'' and array\(.*\) will always evaluate to false.$#'
+ -
+ message: '#^Unreachable statement - code above always terminates.$#'
+ path: src/Validator/Spec/ExtensionSpec.php
diff --git a/src/Amp.php b/src/Amp.php
index 5bcad4f7a..546843810 100644
--- a/src/Amp.php
+++ b/src/Amp.php
@@ -49,14 +49,14 @@ final class Amp
const CACHE_ROOT_URL = self::CACHE_HOST . '/';
/**
- * List of valid AMP formats.
+ * List of valid AMP HTML formats.
*
* @var string[]
*/
- const FORMATS = ['AMP', 'AMP4EMAIL', 'AMP4ADS'];
+ const FORMATS = [Format::AMP, Format::AMP4ADS, Format::AMP4EMAIL];
/**
- * List of dynamic components
+ * List of dynamic components.
*
* This list should be kept in sync with the list of dynamic components at:
*
diff --git a/src/AtRule.php b/src/AtRule.php
new file mode 100644
index 000000000..20b6199f2
--- /dev/null
+++ b/src/AtRule.php
@@ -0,0 +1,27 @@
+errors[] = $error;
}
diff --git a/src/Protocol.php b/src/Protocol.php
new file mode 100644
index 000000000..abb16d9c3
--- /dev/null
+++ b/src/Protocol.php
@@ -0,0 +1,44 @@
+tags === null) {
+ $this->tags = new Spec\Section\Tags();
+ }
+ return $this->tags;
+ }
+
+ /**
+ * @return int
+ */
+ public function minValidatorRevisionRequired()
+ {
+ return $this->minValidatorRevisionRequired;
+ }
+
+ /**
+ * @return int
+ */
+ public function specFileRevision()
+ {
+ return $this->specFileRevision;
+ }
+
+ /**
+ * @return string
+ */
+ public function templateSpecUrl()
+ {
+ return $this->templateSpecUrl;
+ }
+
+ /**
+ * @return string
+ */
+ public function stylesSpecUrl()
+ {
+ return $this->stylesSpecUrl;
+ }
+
+ /**
+ * @return string
+ */
+ public function scriptSpecUrl()
+ {
+ return $this->scriptSpecUrl;
+ }
+
+ /**
+ * @return Spec\Section\CssRulesets
+ */
+ public function cssRulesets()
+ {
+ if ($this->cssRulesets === null) {
+ $this->cssRulesets = new Spec\Section\CssRulesets();
+ }
+ return $this->cssRulesets;
+ }
+
+ /**
+ * @return Spec\Section\DocRulesets
+ */
+ public function docRulesets()
+ {
+ if ($this->docRulesets === null) {
+ $this->docRulesets = new Spec\Section\DocRulesets();
+ }
+ return $this->docRulesets;
+ }
+
+ /**
+ * @return Spec\Section\AttributeLists
+ */
+ public function attributeLists()
+ {
+ if ($this->attributeLists === null) {
+ $this->attributeLists = new Spec\Section\AttributeLists();
+ }
+ return $this->attributeLists;
+ }
+
+ /**
+ * @return Spec\Section\DeclarationLists
+ */
+ public function declarationLists()
+ {
+ if ($this->declarationLists === null) {
+ $this->declarationLists = new Spec\Section\DeclarationLists();
+ }
+ return $this->declarationLists;
+ }
+
+ /**
+ * @return Spec\Section\DescendantTagLists
+ */
+ public function descendantTagLists()
+ {
+ if ($this->descendantTagLists === null) {
+ $this->descendantTagLists = new Spec\Section\DescendantTagLists();
+ }
+ return $this->descendantTagLists;
+ }
+
+ /**
+ * @return Spec\Section\Errors
+ */
+ public function errors()
+ {
+ if ($this->errors === null) {
+ $this->errors = new Spec\Section\Errors();
+ }
+ return $this->errors;
+ }
+}
diff --git a/src/Validator/Spec/AttributeList.php b/src/Validator/Spec/AttributeList.php
new file mode 100644
index 000000000..e23a28a78
--- /dev/null
+++ b/src/Validator/Spec/AttributeList.php
@@ -0,0 +1,70 @@
+
+ */
+ const ATTRIBUTES = [];
+
+ /**
+ * Get the ID of the attribute list.
+ *
+ * @return string ID of the attribute list.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given attribute is contained within the list.
+ *
+ * @param string $attribute Attribute to check for.
+ * @return bool Whether the given attribute is contained within the list.
+ */
+ public function has($attribute)
+ {
+ return array_key_exists($attribute, static::ATTRIBUTES);
+ }
+
+ /**
+ * Get a specific attribute.
+ *
+ * @param string $attribute Attribute to get.
+ * @return array Attribute data that was requested.
+ */
+ public function get($attribute)
+ {
+ if (!$this->has($attribute)) {
+ throw InvalidAttributeName::forAttribute($attribute);
+ }
+
+ return static::ATTRIBUTES[$attribute];
+ }
+}
diff --git a/src/Validator/Spec/AttributeList/AmpAudioCommon.php b/src/Validator/Spec/AttributeList/AmpAudioCommon.php
new file mode 100644
index 000000000..6df11f0e1
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpAudioCommon.php
@@ -0,0 +1,62 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ALBUM => [],
+ Attribute::ARTIST => [],
+ Attribute::ARTWORK => [],
+ Attribute::CONTROLS => [],
+ Attribute::CONTROLSLIST => [],
+ Attribute::LOOP => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::MUTED => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::SRC => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => true,
+ ],
+ ],
+ '[ALBUM]' => [],
+ '[ARTIST]' => [],
+ '[ARTWORK]' => [],
+ '[CONTROLSLIST]' => [],
+ '[LOOP]' => [],
+ '[SRC]' => [],
+ '[TITLE]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpBaseCarouselCommon.php b/src/Validator/Spec/AttributeList/AmpBaseCarouselCommon.php
new file mode 100644
index 000000000..604304498
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpBaseCarouselCommon.php
@@ -0,0 +1,89 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ADVANCE_COUNT => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(-?\d+),\s*)*(-?\d+)',
+ ],
+ Attribute::AUTO_ADVANCE => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::AUTO_ADVANCE_COUNT => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(-?\d+),\s*)*(-?\d+)',
+ ],
+ Attribute::AUTO_ADVANCE_INTERVAL => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+),\s*)*(\d+)',
+ ],
+ Attribute::AUTO_ADVANCE_LOOPS => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+),\s*)*(\d+)',
+ ],
+ Attribute::CONTROLS => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(always|auto|never),\s*)*(always|auto|never)',
+ ],
+ Attribute::HORIZONTAL => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::LOOP => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::MIXED_LENGTH => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::ORIENTATION => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(horizontal|vertical),\s*)*(horizontal|vertical)',
+ ],
+ Attribute::SLIDE => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+),\s*)*(\d+)',
+ ],
+ Attribute::SNAP => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::SNAP_ALIGN => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(start|center),\s*)*(start|center)',
+ ],
+ Attribute::SNAP_BY => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+),\s*)*(\d+)',
+ ],
+ Attribute::VISIBLE_COUNT => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+(\.\d+)?),\s*)*(\d+(\.\d+)?)',
+ ],
+ '[ADVANCE_COUNT]' => [],
+ '[AUTO_ADVANCE]' => [],
+ '[AUTO_ADVANCE_COUNT]' => [],
+ '[AUTO_ADVANCE_INTERVAL]' => [],
+ '[AUTO_ADVANCE_LOOPS]' => [],
+ '[HORIZONTAL]' => [],
+ '[LOOP]' => [],
+ '[MIXED_LENGTH]' => [],
+ '[ORIENTATION]' => [],
+ '[SLIDE]' => [],
+ '[SNAP]' => [],
+ '[SNAP_ALIGN]' => [],
+ '[SNAP_BY]' => [],
+ '[VISIBLE_COUNT]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpCarouselCommon.php b/src/Validator/Spec/AttributeList/AmpCarouselCommon.php
new file mode 100644
index 000000000..55cdc679c
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpCarouselCommon.php
@@ -0,0 +1,68 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ARROWS => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::AUTOPLAY => [
+ SpecRule::VALUE_REGEX => '(|[0-9]+)',
+ ],
+ Attribute::CONTROLS => [],
+ Attribute::DELAY => [
+ SpecRule::VALUE_REGEX => '[0-9]+',
+ ],
+ Attribute::DOTS => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::LOOP => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::SLIDE => [
+ SpecRule::VALUE_REGEX => '[0-9]+',
+ ],
+ Attribute::TYPE => [
+ SpecRule::VALUE => [
+ 'carousel',
+ 'slides',
+ ],
+ ],
+ '[SLIDE]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpDatePickerCommonAttributes.php b/src/Validator/Spec/AttributeList/AmpDatePickerCommonAttributes.php
new file mode 100644
index 000000000..d5e9ac388
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpDatePickerCommonAttributes.php
@@ -0,0 +1,85 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ALLOW_BLOCKED_END_DATE => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::ALLOW_BLOCKED_RANGES => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::BLOCKED => [],
+ Attribute::DAY_SIZE => [
+ SpecRule::VALUE_REGEX => '[0-9]+',
+ ],
+ Attribute::FIRST_DAY_OF_WEEK => [
+ SpecRule::VALUE_REGEX => '[0-6]',
+ ],
+ Attribute::FORMAT => [],
+ Attribute::HIGHLIGHTED => [],
+ Attribute::LOCALE => [],
+ Attribute::MAX => [],
+ Attribute::MIN => [],
+ Attribute::MONTH_FORMAT => [],
+ Attribute::NUMBER_OF_MONTHS => [
+ SpecRule::VALUE_REGEX => '[0-9]+',
+ ],
+ Attribute::OPEN_AFTER_CLEAR => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::OPEN_AFTER_SELECT => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::HIDE_KEYBOARD_SHORTCUTS_PANEL => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::SRC => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ ],
+ ],
+ Attribute::WEEK_DAY_FORMAT => [],
+ '[SRC]' => [],
+ '[MAX]' => [],
+ '[MIN]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpDatePickerOverlayModeAttributes.php b/src/Validator/Spec/AttributeList/AmpDatePickerOverlayModeAttributes.php
new file mode 100644
index 000000000..683d669c2
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpDatePickerOverlayModeAttributes.php
@@ -0,0 +1,35 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::TOUCH_KEYBOARD_EDITABLE => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpDatePickerRangeTypeAttributes.php b/src/Validator/Spec/AttributeList/AmpDatePickerRangeTypeAttributes.php
new file mode 100644
index 000000000..7a2ba8e5b
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpDatePickerRangeTypeAttributes.php
@@ -0,0 +1,40 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::END_DATE => [],
+ Attribute::END_INPUT_SELECTOR => [],
+ Attribute::MAXIMUM_NIGHTS => [
+ SpecRule::VALUE_REGEX => '[0-9]+',
+ ],
+ Attribute::MINIMUM_NIGHTS => [
+ SpecRule::VALUE_REGEX => '[0-9]+',
+ ],
+ Attribute::START_DATE => [],
+ Attribute::START_INPUT_SELECTOR => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpDatePickerSingleTypeAttributes.php b/src/Validator/Spec/AttributeList/AmpDatePickerSingleTypeAttributes.php
new file mode 100644
index 000000000..f6802b4c8
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpDatePickerSingleTypeAttributes.php
@@ -0,0 +1,32 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::DATE => [],
+ Attribute::INPUT_SELECTOR => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpDatePickerStaticModeAttributes.php b/src/Validator/Spec/AttributeList/AmpDatePickerStaticModeAttributes.php
new file mode 100644
index 000000000..56d527bb2
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpDatePickerStaticModeAttributes.php
@@ -0,0 +1,35 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::FULLSCREEN => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpInputmaskCommonAttr.php b/src/Validator/Spec/AttributeList/AmpInputmaskCommonAttr.php
new file mode 100644
index 000000000..1d79167bf
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpInputmaskCommonAttr.php
@@ -0,0 +1,45 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::MASK_OUTPUT => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::MASK,
+ ],
+ ],
+ ],
+ Attribute::TYPE => [
+ SpecRule::VALUE => [
+ 'text',
+ 'tel',
+ 'search',
+ ],
+ ],
+ '[TYPE]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpLayoutAttrs.php b/src/Validator/Spec/AttributeList/AmpLayoutAttrs.php
new file mode 100644
index 000000000..e20524032
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpLayoutAttrs.php
@@ -0,0 +1,38 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::DISABLE_INLINE_WIDTH => [],
+ Attribute::HEIGHT => [],
+ Attribute::HEIGHTS => [],
+ Attribute::LAYOUT => [],
+ Attribute::SIZES => [],
+ Attribute::WIDTH => [],
+ '[HEIGHT]' => [],
+ '[WIDTH]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpMegaphoneCommon.php b/src/Validator/Spec/AttributeList/AmpMegaphoneCommon.php
new file mode 100644
index 000000000..b57c65f08
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpMegaphoneCommon.php
@@ -0,0 +1,40 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::DATA_LIGHT => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::DATA_SHARING => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpNestedMenuActions.php b/src/Validator/Spec/AttributeList/AmpNestedMenuActions.php
new file mode 100644
index 000000000..bd7c00004
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpNestedMenuActions.php
@@ -0,0 +1,36 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::AMP_NESTED_SUBMENU_CLOSE => [
+ SpecRule::MANDATORY_ONEOF => '[\'amp-nested-submenu-close\', \'amp-nested-submenu-open\']',
+ ],
+ Attribute::AMP_NESTED_SUBMENU_OPEN => [
+ SpecRule::MANDATORY_ONEOF => '[\'amp-nested-submenu-close\', \'amp-nested-submenu-open\']',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpStreamGalleryCommon.php b/src/Validator/Spec/AttributeList/AmpStreamGalleryCommon.php
new file mode 100644
index 000000000..200122cec
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpStreamGalleryCommon.php
@@ -0,0 +1,76 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::CONTROLS => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(always|auto|never),\s*)*(always|auto|never)',
+ ],
+ Attribute::EXTRA_SPACE => [
+ SpecRule::VALUE => [
+ 'between',
+ ],
+ ],
+ Attribute::LOOP => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::MIN_VISIBLE_COUNT => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+(\.\d+)?),\s*)*(\d+(\.\d+)?)',
+ ],
+ Attribute::MAX_VISIBLE_COUNT => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+(\.\d+)?),\s*)*(\d+(\.\d+)?)',
+ ],
+ Attribute::MIN_ITEM_WIDTH => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+),\s*)*(\d+)',
+ ],
+ Attribute::MAX_ITEM_WIDTH => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+),\s*)*(\d+)',
+ ],
+ Attribute::OUTSET_ARROWS => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ Attribute::PEEK => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(\d+(\.\d+)?),\s*)*(\d+(\.\d+)?)',
+ ],
+ Attribute::SLIDE_ALIGN => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(start|center),\s*)*(start|center)',
+ ],
+ Attribute::SNAP => [
+ SpecRule::VALUE_REGEX => '([^,]+\s+(true|false),\s*)*(true|false)',
+ ],
+ '[CONTROLS]' => [],
+ '[EXTRA_SPACE]' => [],
+ '[LOOP]' => [],
+ '[MIN_VISIBLE_COUNT]' => [],
+ '[MAX_VISIBLE_COUNT]' => [],
+ '[MIN_ITEM_WIDTH]' => [],
+ '[MAX_ITEM_WIDTH]' => [],
+ '[OUTSET_ARROWS]' => [],
+ '[PEEK]' => [],
+ '[SLIDE_ALIGN]' => [],
+ '[SNAP]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpVideoCommon.php b/src/Validator/Spec/AttributeList/AmpVideoCommon.php
new file mode 100644
index 000000000..28e76a83a
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpVideoCommon.php
@@ -0,0 +1,111 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ALBUM => [],
+ Attribute::ALT => [],
+ Attribute::ARTIST => [],
+ Attribute::ARTWORK => [],
+ Attribute::ATTRIBUTION => [],
+ Attribute::AUTOPLAY => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::CONTROLS => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::CONTROLSLIST => [],
+ Attribute::CROSSORIGIN => [],
+ Attribute::DISABLEREMOTEPLAYBACK => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::DOCK => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::VIDEO_DOCKING,
+ ],
+ ],
+ Attribute::LOOP => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::MUTED => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::NOAUDIO => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::OBJECT_FIT => [],
+ Attribute::OBJECT_POSITION => [],
+ Attribute::PLACEHOLDER => [],
+ Attribute::PRELOAD => [
+ SpecRule::VALUE => [
+ 'auto',
+ 'metadata',
+ 'none',
+ '',
+ ],
+ ],
+ Attribute::ROTATE_TO_FULLSCREEN => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::SRC => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => true,
+ ],
+ ],
+ '[ALBUM]' => [],
+ '[ALT]' => [],
+ '[ARTIST]' => [],
+ '[ARTWORK]' => [],
+ '[ATTRIBUTION]' => [],
+ '[CONTROLS]' => [],
+ '[CONTROLSLIST]' => [],
+ '[LOOP]' => [],
+ '[POSTER]' => [],
+ '[PRELOAD]' => [],
+ '[SRC]' => [],
+ '[TITLE]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmpVideoIframeCommon.php b/src/Validator/Spec/AttributeList/AmpVideoIframeCommon.php
new file mode 100644
index 000000000..2ab98c198
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmpVideoIframeCommon.php
@@ -0,0 +1,74 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ALBUM => [],
+ Attribute::ALT => [],
+ Attribute::ARTIST => [],
+ Attribute::ARTWORK => [],
+ Attribute::ATTRIBUTION => [],
+ Attribute::AUTOPLAY => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::DOCK => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::VIDEO_DOCKING,
+ ],
+ ],
+ Attribute::IMPLEMENTS_MEDIA_SESSION => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::IMPLEMENTS_ROTATE_TO_FULLSCREEN => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::POSTER => [],
+ Attribute::REFERRERPOLICY => [],
+ Attribute::ROTATE_TO_FULLSCREEN => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::SRC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ ],
+ ],
+ '[SRC]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmphtmlEngineAttrs.php b/src/Validator/Spec/AttributeList/AmphtmlEngineAttrs.php
new file mode 100644
index 000000000..9bc8d1bf0
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmphtmlEngineAttrs.php
@@ -0,0 +1,46 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ASYNC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::CROSSORIGIN => [
+ SpecRule::VALUE => [
+ 'anonymous',
+ ],
+ ],
+ Attribute::TYPE => [
+ SpecRule::VALUE_CASEI => [
+ 'text/javascript',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmphtmlModuleEngineAttrs.php b/src/Validator/Spec/AttributeList/AmphtmlModuleEngineAttrs.php
new file mode 100644
index 000000000..2e701db44
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmphtmlModuleEngineAttrs.php
@@ -0,0 +1,49 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ASYNC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::CROSSORIGIN => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ 'anonymous',
+ ],
+ ],
+ Attribute::TYPE => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISPATCH_KEY => 'NAME_VALUE_DISPATCH',
+ SpecRule::VALUE_CASEI => [
+ 'module',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/AmphtmlNomoduleEngineAttrs.php b/src/Validator/Spec/AttributeList/AmphtmlNomoduleEngineAttrs.php
new file mode 100644
index 000000000..8661e7265
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/AmphtmlNomoduleEngineAttrs.php
@@ -0,0 +1,52 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ASYNC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::CROSSORIGIN => [
+ SpecRule::VALUE => [
+ 'anonymous',
+ ],
+ ],
+ Attribute::NOMODULE => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::TYPE => [
+ SpecRule::VALUE_CASEI => [
+ 'text/javascript',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/CiteAttr.php b/src/Validator/Spec/AttributeList/CiteAttr.php
new file mode 100644
index 000000000..ed1a8849d
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/CiteAttr.php
@@ -0,0 +1,41 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::CITE => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTP,
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/CommonExtensionAttrs.php b/src/Validator/Spec/AttributeList/CommonExtensionAttrs.php
new file mode 100644
index 000000000..e19e874de
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/CommonExtensionAttrs.php
@@ -0,0 +1,51 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ASYNC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::CROSSORIGIN => [
+ SpecRule::VALUE => [
+ 'anonymous',
+ ],
+ ],
+ Attribute::NONCE => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::TYPE => [
+ SpecRule::VALUE_CASEI => [
+ 'text/javascript',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/CommonLinkAttrs.php b/src/Validator/Spec/AttributeList/CommonLinkAttrs.php
new file mode 100644
index 000000000..c2acfc0d1
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/CommonLinkAttrs.php
@@ -0,0 +1,42 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::CHARSET => [
+ SpecRule::VALUE_CASEI => [
+ 'utf-8',
+ ],
+ ],
+ Attribute::COLOR => [],
+ Attribute::CROSSORIGIN => [],
+ Attribute::HREFLANG => [],
+ Attribute::MEDIA => [],
+ Attribute::SIZES => [],
+ Attribute::TARGET => [],
+ Attribute::TYPE => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/ExtendedAmpGlobal.php b/src/Validator/Spec/AttributeList/ExtendedAmpGlobal.php
new file mode 100644
index 000000000..f2333d2d5
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/ExtendedAmpGlobal.php
@@ -0,0 +1,52 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::I_AMPHTML_LAYOUT => [
+ SpecRule::VALUE_CASEI => [
+ 'container',
+ 'fill',
+ 'fixed',
+ 'fixed-height',
+ 'flex-item',
+ 'fluid',
+ 'intrinsic',
+ 'nodisplay',
+ 'responsive',
+ ],
+ SpecRule::ENABLED_BY => [
+ Attribute::TRANSFORMED,
+ ],
+ ],
+ Attribute::MEDIA => [],
+ Attribute::NOLOADING => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/FormNameAttr.php b/src/Validator/Spec/AttributeList/FormNameAttr.php
new file mode 100644
index 000000000..8af33d640
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/FormNameAttr.php
@@ -0,0 +1,33 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::NAME => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_POSITION_CONTAINED_BY|DOCUMENT_POSITION_CONTAINS|DOCUMENT_POSITION_DISCONNECTED|DOCUMENT_POSITION_FOLLOWING|DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC|DOCUMENT_POSITION_PRECEDING|DOCUMENT_TYPE_NODE|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|URL|URLUnencoded|__amp_\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\S*|activeElement|addEventListener|adoptNode|alinkColor|all|anchors|append|appendChild|applets|baseURI|bgColor|body|captureEvents|caretPositionFromPoint|caretRangeFromPoint|characterSet|charset|childElementCount|childNodes|children|clear|cloneNode|close|compareDocumentPosition|compatMode|constructor|contains|contentType|cookie|createAttribute|createAttributeNS|createCDATASection|createComment|createDocumentFragment|createElement|createElementNS|createEvent|createExpression|createNSResolver|createNodeIterator|createProcessingInstruction|createRange|createTextNode|createTreeWalker|currentScript|defaultView|designMode|dir|dispatchEvent|doctype|documentElement|documentURI|domain|elementFromPoint|elementsFromPoint|embeds|enableStyleSheetsForSet|evaluate|execCommand|execCommandShowHelp|exitFullscreen|exitPictureInPicture|exitPointerLock|fgColor|firstChild|firstElementChild|focus|fonts|forms|fullscreen|fullscreenElement|fullscreenEnabled|getCSSCanvasContext|getElementById|getElementsByClassName|getElementsByName|getElementsByTagName|getElementsByTagNameNS|getOverrideStyle|getRootNode|getSelection|hasChildNodes|hasFocus|hasOwnProperty|hasStorageAccess|head|hidden|images|implementation|importNode|inputEncoding|insertBefore|isConnected|isDefaultNamespace|isEqualNode|isPrototypeOf|isSameNode|l10n|lastChild|lastElementChild|lastModified|lastStyleSheetSet|linkColor|links|location|lookupNamespaceURI|lookupPrefix|mozCancelFullScreen|mozFullScreen|mozFullScreenElement|mozFullScreenEnabled|mozSetImageElement|msCSSOMElementFloatMetrics|msCapsLockWarningOff|msElementsFromPoint|msElementsFromRect|nextSibling|nodeName|nodeType|nodeValue|normalize|onabort|onactivate|onafterscriptexecute|onanimationcancel|onanimationend|onanimationiteration|onanimationstart|onauxclick|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeinput|onbeforepaste|onbeforescriptexecute|onblur|oncancel|oncanplay|oncanplaythrough|onchange|onclick|onclose|oncontextmenu|oncopy|oncuechange|oncut|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragexit|ondragleave|ondragover|ondragstart|ondrop|ondurationchange|onemptied|onended|onerror|onfocus|onfreeze|onfullscreenchange|onfullscreenerror|ongotpointercapture|oninput|oninvalid|onkeydown|onkeypress|onkeyup|onload|onloadeddata|onloadedmetadata|onloadend|onloadstart|onlostpointercapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|onmousewheel|onmozfullscreenchange|onmozfullscreenerror|onmscontentzoom|onmsgesturechange|onmsgesturedoubletap|onmsgestureend|onmsgesturehold|onmsgesturestart|onmsgesturetap|onmsinertiastart|onmsmanipulationstatechanged|onmssitemodejumplistitemremoved|onmsthumbnailclick|onpaste|onpause|onplay|onplaying|onpointercancel|onpointerdown|onpointerenter|onpointerleave|onpointerlockchange|onpointerlockerror|onpointermove|onpointerout|onpointerover|onpointerup|onprogress|onratechange|onreadystatechange|onrejectionhandled|onreset|onresize|onresume|onscroll|onsearch|onseeked|onseeking|onselect|onselectionchange|onselectstart|onshow|onstalled|onstop|onsubmit|onsuspend|ontimeupdate|ontoggle|ontransitioncancel|ontransitionend|ontransitionrun|ontransitionstart|onunhandledrejection|onvisibilitychange|onvolumechange|onwaiting|onwebkitanimationend|onwebkitanimationiteration|onwebkitanimationstart|onwebkitfullscreenchange|onwebkitfullscreenerror|onwebkitmouseforcechanged|onwebkitmouseforcedown|onwebkitmouseforceup|onwebkitmouseforcewillbegin|onwebkittransitionend|onwheel|open|origin|ownerDocument|parentElement|parentNode|pictureInPictureElement|pictureInPictureEnabled|plugins|pointerLockElement|preferredStyleSheetSet|prepend|previousSibling|propertyIsEnumerable|queryCommandEnabled|queryCommandIndeterm|queryCommandState|queryCommandSupported|queryCommandText|queryCommandValue|querySelector|querySelectorAll|readyState|referrer|registerElement|releaseCapture|releaseEvents|removeChild|removeEventListener|replaceChild|requestStorageAccess|rootElement|scripts|scrollingElement|selectedStyleSheetSet|styleSheetSets|styleSheets|textContent|title|toLocaleString|toSource|toString|updateSettings|valueOf|visibilityState|vlinkColor|wasDiscarded|webkitCancelFullScreen|webkitCurrentFullScreenElement|webkitExitFullscreen|webkitFullScreenKeyboardInputAllowed|webkitFullscreenElement|webkitFullscreenEnabled|webkitHidden|webkitIsFullScreen|webkitVisibilityState|write|writeln|xmlEncoding|xmlStandalone|xmlVersion)(\s|$)',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/GlobalAttrs.php b/src/Validator/Spec/AttributeList/GlobalAttrs.php
new file mode 100644
index 000000000..bda3f0df2
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/GlobalAttrs.php
@@ -0,0 +1,290 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ITEMID => [],
+ Attribute::ITEMPROP => [],
+ Attribute::ITEMREF => [],
+ Attribute::ITEMSCOPE => [],
+ Attribute::ITEMTYPE => [],
+ Attribute::ABOUT => [],
+ Attribute::CONTENT => [],
+ Attribute::DATATYPE => [],
+ Attribute::INLIST => [],
+ Attribute::PREFIX => [],
+ Attribute::PROPERTY => [],
+ Attribute::REL => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(canonical|components|dns-prefetch|import|manifest|preconnect|preload|prerender|serviceworker|stylesheet|subresource)(\s|$)',
+ ],
+ Attribute::RESOURCE => [],
+ Attribute::REV => [],
+ Attribute::STYLE => [
+ SpecRule::VALUE_DOC_CSS => true,
+ ],
+ Attribute::TYPEOF => [],
+ Attribute::VOCAB => [],
+ Attribute::ACCESSKEY => [],
+ Attribute::CLASS_ => [],
+ Attribute::DIR => [],
+ Attribute::DRAGGABLE => [],
+ Attribute::HIDDEN => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::ID => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(__amp_\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\S*|\$p|\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|AMP|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|i-amphtml-\S*|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\s|$)',
+ ],
+ Attribute::LANG => [],
+ Attribute::SLOT => [],
+ Attribute::TABINDEX => [],
+ Attribute::TITLE => [],
+ Attribute::TRANSLATE => [],
+ Attribute::ARIA_ACTIVEDESCENDANT => [],
+ Attribute::ARIA_ATOMIC => [],
+ Attribute::ARIA_AUTOCOMPLETE => [],
+ Attribute::ARIA_BUSY => [],
+ Attribute::ARIA_CHECKED => [],
+ Attribute::ARIA_CONTROLS => [],
+ Attribute::ARIA_CURRENT => [],
+ Attribute::ARIA_DESCRIBEDBY => [],
+ Attribute::ARIA_DISABLED => [],
+ Attribute::ARIA_DROPEFFECT => [],
+ Attribute::ARIA_EXPANDED => [],
+ Attribute::ARIA_FLOWTO => [],
+ Attribute::ARIA_GRABBED => [],
+ Attribute::ARIA_HASPOPUP => [],
+ Attribute::ARIA_HIDDEN => [],
+ Attribute::ARIA_INVALID => [],
+ Attribute::ARIA_LABEL => [],
+ Attribute::ARIA_LABELLEDBY => [],
+ Attribute::ARIA_LEVEL => [],
+ Attribute::ARIA_LIVE => [],
+ Attribute::ARIA_MULTILINE => [],
+ Attribute::ARIA_MULTISELECTABLE => [],
+ Attribute::ARIA_ORIENTATION => [],
+ Attribute::ARIA_OWNS => [],
+ Attribute::ARIA_POSINSET => [],
+ Attribute::ARIA_PRESSED => [],
+ Attribute::ARIA_READONLY => [],
+ Attribute::ARIA_RELEVANT => [],
+ Attribute::ARIA_REQUIRED => [],
+ Attribute::ARIA_SELECTED => [],
+ Attribute::ARIA_SETSIZE => [],
+ Attribute::ARIA_SORT => [],
+ Attribute::ARIA_VALUEMAX => [],
+ Attribute::ARIA_VALUEMIN => [],
+ Attribute::ARIA_VALUENOW => [],
+ Attribute::ARIA_VALUETEXT => [],
+ Attribute::ON => [
+ SpecRule::TRIGGER => [
+ SpecRule::IF_VALUE_REGEX => 'tap:.*',
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::ROLE,
+ Attribute::TABINDEX,
+ ],
+ ],
+ ],
+ Attribute::ROLE => [],
+ Attribute::PLACEHOLDER => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::FALLBACK => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::OVERFLOW => [],
+ Attribute::AMP_ACCESS => [],
+ Attribute::AMP_ACCESS_BEHAVIOR => [],
+ Attribute::AMP_ACCESS_HIDE => [],
+ Attribute::AMP_ACCESS_ID => [],
+ Attribute::AMP_ACCESS_LOADER => [],
+ Attribute::AMP_ACCESS_LOADING => [],
+ Attribute::AMP_ACCESS_OFF => [],
+ Attribute::AMP_ACCESS_ON => [],
+ Attribute::AMP_ACCESS_SHOW => [],
+ Attribute::AMP_ACCESS_STYLE => [],
+ Attribute::AMP_ACCESS_TEMPLATE => [],
+ Attribute::I_AMP_ACCESS_ID => [],
+ Attribute::VALIDATION_FOR => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::VISIBLE_WHEN_INVALID,
+ ],
+ ],
+ ],
+ Attribute::VISIBLE_WHEN_INVALID => [
+ SpecRule::VALUE => [
+ 'badInput',
+ 'customError',
+ 'patternMismatch',
+ 'rangeOverflow',
+ 'rangeUnderflow',
+ 'stepMismatch',
+ 'tooLong',
+ 'tooShort',
+ 'typeMismatch',
+ 'valueMissing',
+ ],
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::VALIDATION_FOR,
+ ],
+ ],
+ ],
+ Attribute::AMP_FX => [
+ SpecRule::VALUE_REGEX_CASEI => '(fade-in|fade-in-scroll|float-in-bottom|float-in-top|fly-in-bottom|fly-in-left|fly-in-right|fly-in-top|parallax)(\s|fade-in|fade-in-scroll|float-in-bottom|float-in-top|fly-in-bottom|fly-in-left|fly-in-right|fly-in-top|parallax)*',
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::FX_COLLECTION,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_ACTION => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_ACTIONS => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_DECORATE => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_DIALOG => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_DISPLAY => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_LANG => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_SECTION => [
+ SpecRule::VALUE_CASEI => [
+ 'actions',
+ 'content',
+ 'content-not-granted',
+ 'loading',
+ ],
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_SERVICE => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS,
+ ],
+ ],
+ Attribute::SUBSCRIPTIONS_GOOGLE_RTC => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::SUBSCRIPTIONS_GOOGLE,
+ ],
+ ],
+ Attribute::NEXT_PAGE_HIDE => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::NEXT_PAGE,
+ ],
+ ],
+ Attribute::NEXT_PAGE_REPLACE => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::NEXT_PAGE,
+ ],
+ ],
+ '[ARIA_ACTIVEDESCENDANT]' => [],
+ '[ARIA_ATOMIC]' => [],
+ '[ARIA_AUTOCOMPLETE]' => [],
+ '[ARIA_BUSY]' => [],
+ '[ARIA_CHECKED]' => [],
+ '[ARIA_CONTROLS]' => [],
+ '[ARIA_DESCRIBEDBY]' => [],
+ '[ARIA_DISABLED]' => [],
+ '[ARIA_DROPEFFECT]' => [],
+ '[ARIA_EXPANDED]' => [],
+ '[ARIA_FLOWTO]' => [],
+ '[ARIA_GRABBED]' => [],
+ '[ARIA_HASPOPUP]' => [],
+ '[ARIA_HIDDEN]' => [],
+ '[ARIA_INVALID]' => [],
+ '[ARIA_LABEL]' => [],
+ '[ARIA_LABELLEDBY]' => [],
+ '[ARIA_LEVEL]' => [],
+ '[ARIA_LIVE]' => [],
+ '[ARIA_MULTILINE]' => [],
+ '[ARIA_MULTISELECTABLE]' => [],
+ '[ARIA_ORIENTATION]' => [],
+ '[ARIA_OWNS]' => [],
+ '[ARIA_POSINSET]' => [],
+ '[ARIA_PRESSED]' => [],
+ '[ARIA_READONLY]' => [],
+ '[ARIA_RELEVANT]' => [],
+ '[ARIA_REQUIRED]' => [],
+ '[ARIA_SELECTED]' => [],
+ '[ARIA_SETSIZE]' => [],
+ '[ARIA_SORT]' => [],
+ '[ARIA_VALUEMAX]' => [],
+ '[ARIA_VALUEMIN]' => [],
+ '[ARIA_VALUENOW]' => [],
+ '[ARIA_VALUETEXT]' => [],
+ '[CLASS]' => [],
+ '[HIDDEN]' => [],
+ '[TEXT]' => [],
+ Attribute::I_AMPHTML_BINDING => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ SpecRule::ENABLED_BY => [
+ Attribute::TRANSFORMED,
+ ],
+ ],
+ Attribute::AUTOSCROLL => [
+ SpecRule::REQUIRES_ANCESTOR => [
+ SpecRule::MARKER => [
+ 'AUTOSCROLL',
+ ],
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/InputCommonAttr.php b/src/Validator/Spec/AttributeList/InputCommonAttr.php
new file mode 100644
index 000000000..169c932df
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/InputCommonAttr.php
@@ -0,0 +1,114 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ACCEPT => [],
+ Attribute::ACCESSKEY => [],
+ Attribute::AUTOCOMPLETE => [],
+ Attribute::AUTOFOCUS => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::CHECKED => [],
+ Attribute::DISABLED => [],
+ Attribute::HEIGHT => [],
+ Attribute::INPUTMODE => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::LIST_ => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::ENTERKEYHINT => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::MAX => [],
+ Attribute::MAXLENGTH => [],
+ Attribute::MIN => [],
+ Attribute::MINLENGTH => [],
+ Attribute::MULTIPLE => [],
+ Attribute::PATTERN => [],
+ Attribute::PLACEHOLDER => [],
+ Attribute::READONLY => [],
+ Attribute::REQUIRED => [],
+ Attribute::SELECTIONDIRECTION => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ Attribute::SIZE => [],
+ Attribute::SPELLCHECK => [],
+ Attribute::STEP => [],
+ Attribute::TABINDEX => [],
+ Attribute::VALUE => [],
+ Attribute::WIDTH => [],
+ '[ACCEPT]' => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ '[ACCESSKEY]' => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ '[AUTOCOMPLETE]' => [],
+ '[CHECKED]' => [],
+ '[DISABLED]' => [],
+ '[HEIGHT]' => [],
+ '[INPUTMODE]' => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ '[MAX]' => [],
+ '[MAXLENGTH]' => [],
+ '[MIN]' => [],
+ '[MINLENGTH]' => [],
+ '[MULTIPLE]' => [],
+ '[PATTERN]' => [],
+ '[PLACEHOLDER]' => [],
+ '[READONLY]' => [],
+ '[REQUIRED]' => [],
+ '[SELECTIONDIRECTION]' => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ '[SIZE]' => [],
+ '[SPELLCHECK]' => [],
+ '[STEP]' => [],
+ '[VALUE]' => [],
+ '[WIDTH]' => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/InteractiveOptionsConfettiAttrs.php b/src/Validator/Spec/AttributeList/InteractiveOptionsConfettiAttrs.php
new file mode 100644
index 000000000..eae349de9
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/InteractiveOptionsConfettiAttrs.php
@@ -0,0 +1,34 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::OPTION_1_CONFETTI => [],
+ Attribute::OPTION_2_CONFETTI => [],
+ Attribute::OPTION_3_CONFETTI => [],
+ Attribute::OPTION_4_CONFETTI => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/InteractiveOptionsResultsCategoryAttrs.php b/src/Validator/Spec/AttributeList/InteractiveOptionsResultsCategoryAttrs.php
new file mode 100644
index 000000000..31225c8b7
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/InteractiveOptionsResultsCategoryAttrs.php
@@ -0,0 +1,60 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::OPTION_1_RESULTS_CATEGORY => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::OPTION_2_RESULTS_CATEGORY,
+ ],
+ ],
+ ],
+ Attribute::OPTION_2_RESULTS_CATEGORY => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::OPTION_1_RESULTS_CATEGORY,
+ ],
+ ],
+ ],
+ Attribute::OPTION_3_RESULTS_CATEGORY => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::OPTION_2_RESULTS_CATEGORY,
+ Attribute::OPTION_3_TEXT,
+ ],
+ ],
+ ],
+ Attribute::OPTION_4_RESULTS_CATEGORY => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::OPTION_3_RESULTS_CATEGORY,
+ Attribute::OPTION_4_TEXT,
+ ],
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/InteractiveOptionsTextAttrs.php b/src/Validator/Spec/AttributeList/InteractiveOptionsTextAttrs.php
new file mode 100644
index 000000000..bb190958a
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/InteractiveOptionsTextAttrs.php
@@ -0,0 +1,44 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::OPTION_1_TEXT => [
+ SpecRule::MANDATORY => true,
+ ],
+ Attribute::OPTION_2_TEXT => [
+ SpecRule::MANDATORY => true,
+ ],
+ Attribute::OPTION_3_TEXT => [],
+ Attribute::OPTION_4_TEXT => [
+ SpecRule::TRIGGER => [
+ SpecRule::ALSO_REQUIRES_ATTR => [
+ Attribute::OPTION_3_TEXT,
+ ],
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/InteractiveSharedConfigsAttrs.php b/src/Validator/Spec/AttributeList/InteractiveSharedConfigsAttrs.php
new file mode 100644
index 000000000..ec1a80d12
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/InteractiveSharedConfigsAttrs.php
@@ -0,0 +1,65 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ID => [
+ SpecRule::MANDATORY => true,
+ ],
+ Attribute::PROMPT_TEXT => [],
+ Attribute::ENDPOINT => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ SpecRule::ALLOW_EMPTY => false,
+ ],
+ ],
+ Attribute::THEME => [
+ SpecRule::VALUE => [
+ 'light',
+ 'dark',
+ ],
+ ],
+ Attribute::CHIP_STYLE => [
+ SpecRule::VALUE => [
+ 'shadow',
+ 'flat',
+ 'transparent',
+ ],
+ ],
+ Attribute::PROMPT_SIZE => [
+ SpecRule::VALUE => [
+ 'small',
+ 'medium',
+ 'large',
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/LightboxableElements.php b/src/Validator/Spec/AttributeList/LightboxableElements.php
new file mode 100644
index 000000000..3349ad7a3
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/LightboxableElements.php
@@ -0,0 +1,34 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::LIGHTBOX => [],
+ Attribute::LIGHTBOX_THUMBNAIL_ID => [
+ SpecRule::VALUE_REGEX_CASEI => '^[a-z][a-z\d_-]*',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/MandatoryIdAttr.php b/src/Validator/Spec/AttributeList/MandatoryIdAttr.php
new file mode 100644
index 000000000..214613d40
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/MandatoryIdAttr.php
@@ -0,0 +1,34 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ID => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(__amp_\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\S*|\$p|\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|AMP|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|i-amphtml-\S*|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\s|$)',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/MandatoryNameAttr.php b/src/Validator/Spec/AttributeList/MandatoryNameAttr.php
new file mode 100644
index 000000000..311b7cdc5
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/MandatoryNameAttr.php
@@ -0,0 +1,34 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::NAME => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(__amp_\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\S*|\$p|\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\s|$)',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/MandatorySrcAmp4email.php b/src/Validator/Spec/AttributeList/MandatorySrcAmp4email.php
new file mode 100644
index 000000000..0a5aa0af4
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/MandatorySrcAmp4email.php
@@ -0,0 +1,41 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::SRC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin|(.|\s){{|}}(.|\s)|^{{.*[^}][^}]$|^[^{][^{].*}}$|^}}|{{$|{{#|{{/|{{\^',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/MandatorySrcOrSrcset.php b/src/Validator/Spec/AttributeList/MandatorySrcOrSrcset.php
new file mode 100644
index 000000000..0a44bba2d
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/MandatorySrcOrSrcset.php
@@ -0,0 +1,45 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::SRC => [
+ SpecRule::ALTERNATIVE_NAMES => [
+ Attribute::SRCSET,
+ ],
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::DATA,
+ Protocol::HTTP,
+ Protocol::HTTPS,
+ ],
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/NameAttr.php b/src/Validator/Spec/AttributeList/NameAttr.php
new file mode 100644
index 000000000..a2445f06d
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/NameAttr.php
@@ -0,0 +1,33 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::NAME => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(__amp_\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\S*|\$p|\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\s|$)',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/NonceAttr.php b/src/Validator/Spec/AttributeList/NonceAttr.php
new file mode 100644
index 000000000..be4df6c20
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/NonceAttr.php
@@ -0,0 +1,35 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::NONCE => [
+ SpecRule::DISABLED_BY => [
+ Attribute::AMP4EMAIL,
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/OptionalSrcAmp4email.php b/src/Validator/Spec/AttributeList/OptionalSrcAmp4email.php
new file mode 100644
index 000000000..49fa21c95
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/OptionalSrcAmp4email.php
@@ -0,0 +1,40 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::SRC => [
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin|(.|\s){{|}}(.|\s)|^{{.*[^}][^}]$|^[^{][^{].*}}$|^}}|{{$|{{#|{{/|{{\^',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/PooolAccessAttrs.php b/src/Validator/Spec/AttributeList/PooolAccessAttrs.php
new file mode 100644
index 000000000..4cff9119d
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/PooolAccessAttrs.php
@@ -0,0 +1,41 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::POOOL_ACCESS_PREVIEW => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::ACCESS_POOOL,
+ ],
+ ],
+ Attribute::POOOL_ACCESS_CONTENT => [
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::ACCESS_POOOL,
+ ],
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgConditionalProcessingAttributes.php b/src/Validator/Spec/AttributeList/SvgConditionalProcessingAttributes.php
new file mode 100644
index 000000000..022782e02
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgConditionalProcessingAttributes.php
@@ -0,0 +1,33 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::REQUIREDEXTENSIONS => [],
+ Attribute::REQUIREDFEATURES => [],
+ Attribute::SYSTEMLANGUAGE => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgCoreAttributes.php b/src/Validator/Spec/AttributeList/SvgCoreAttributes.php
new file mode 100644
index 000000000..83e70130e
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgCoreAttributes.php
@@ -0,0 +1,34 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::XML_LANG => [],
+ Attribute::XML_SPACE => [],
+ Attribute::XMLNS => [],
+ Attribute::XMLNS_XLINK => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgFilterPrimitiveAttributes.php b/src/Validator/Spec/AttributeList/SvgFilterPrimitiveAttributes.php
new file mode 100644
index 000000000..51af39c2e
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgFilterPrimitiveAttributes.php
@@ -0,0 +1,35 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::HEIGHT => [],
+ Attribute::RESULT => [],
+ Attribute::WIDTH => [],
+ Attribute::X => [],
+ Attribute::Y => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgPresentationAttributes.php b/src/Validator/Spec/AttributeList/SvgPresentationAttributes.php
new file mode 100644
index 000000000..79c188898
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgPresentationAttributes.php
@@ -0,0 +1,91 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::ALIGNMENT_BASELINE => [],
+ Attribute::BASELINE_SHIFT => [],
+ Attribute::CLIP => [],
+ Attribute::CLIP_PATH => [],
+ Attribute::CLIP_RULE => [],
+ Attribute::COLOR => [],
+ Attribute::COLOR_INTERPOLATION => [],
+ Attribute::COLOR_INTERPOLATION_FILTERS => [],
+ Attribute::COLOR_PROFILE => [],
+ Attribute::COLOR_RENDERING => [],
+ Attribute::CURSOR => [],
+ Attribute::DIRECTION => [],
+ Attribute::DISPLAY => [],
+ Attribute::DOMINANT_BASELINE => [],
+ Attribute::ENABLE_BACKGROUND => [],
+ Attribute::FILL => [],
+ Attribute::FILL_OPACITY => [],
+ Attribute::FILL_RULE => [],
+ Attribute::FILTER => [],
+ Attribute::FLOOD_COLOR => [],
+ Attribute::FLOOD_OPACITY => [],
+ Attribute::FOCUSABLE => [],
+ Attribute::FONT_FAMILY => [],
+ Attribute::FONT_SIZE => [],
+ Attribute::FONT_SIZE_ADJUST => [],
+ Attribute::FONT_STRETCH => [],
+ Attribute::FONT_STYLE => [],
+ Attribute::FONT_VARIANT => [],
+ Attribute::FONT_WEIGHT => [],
+ Attribute::GLYPH_ORIENTATION_HORIZONTAL => [],
+ Attribute::GLYPH_ORIENTATION_VERTICAL => [],
+ Attribute::IMAGE_RENDERING => [],
+ Attribute::KERNING => [],
+ Attribute::LETTER_SPACING => [],
+ Attribute::LIGHTING_COLOR => [],
+ Attribute::MARKER_END => [],
+ Attribute::MARKER_MID => [],
+ Attribute::MARKER_START => [],
+ Attribute::MASK => [],
+ Attribute::OPACITY => [],
+ Attribute::OVERFLOW => [],
+ Attribute::POINTER_EVENTS => [],
+ Attribute::SHAPE_RENDERING => [],
+ Attribute::STOP_COLOR => [],
+ Attribute::STOP_OPACITY => [],
+ Attribute::STROKE => [],
+ Attribute::STROKE_DASHARRAY => [],
+ Attribute::STROKE_DASHOFFSET => [],
+ Attribute::STROKE_LINECAP => [],
+ Attribute::STROKE_LINEJOIN => [],
+ Attribute::STROKE_MITERLIMIT => [],
+ Attribute::STROKE_OPACITY => [],
+ Attribute::STROKE_WIDTH => [],
+ Attribute::TEXT_ANCHOR => [],
+ Attribute::TEXT_DECORATION => [],
+ Attribute::TEXT_RENDERING => [],
+ Attribute::UNICODE_BIDI => [],
+ Attribute::VECTOR_EFFECT => [],
+ Attribute::VISIBILITY => [],
+ Attribute::WORD_SPACING => [],
+ Attribute::WRITING_MODE => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgStyleAttr.php b/src/Validator/Spec/AttributeList/SvgStyleAttr.php
new file mode 100644
index 000000000..ff330c3f8
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgStyleAttr.php
@@ -0,0 +1,33 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::STYLE => [
+ SpecRule::VALUE_DOC_SVG_CSS => true,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgTransferFunctionAttributes.php b/src/Validator/Spec/AttributeList/SvgTransferFunctionAttributes.php
new file mode 100644
index 000000000..2e653c14b
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgTransferFunctionAttributes.php
@@ -0,0 +1,38 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::AMPLITUDE => [],
+ Attribute::EXPONENT => [],
+ Attribute::INTERCEPT => [],
+ Attribute::OFFSET => [],
+ Attribute::SLOPE => [],
+ Attribute::TABLE => [],
+ Attribute::TABLEVALUES => [],
+ Attribute::TYPE => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/SvgXlinkAttributes.php b/src/Validator/Spec/AttributeList/SvgXlinkAttributes.php
new file mode 100644
index 000000000..a338e82b0
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/SvgXlinkAttributes.php
@@ -0,0 +1,49 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::XLINK_ACTUATE => [],
+ Attribute::XLINK_ARCROLE => [],
+ Attribute::XLINK_HREF => [
+ SpecRule::ALTERNATIVE_NAMES => [
+ Attribute::HREF,
+ ],
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTP,
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_EMPTY => false,
+ ],
+ ],
+ Attribute::XLINK_ROLE => [],
+ Attribute::XLINK_SHOW => [],
+ Attribute::XLINK_TITLE => [],
+ Attribute::XLINK_TYPE => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/TrackAttrsNoSubtitles.php b/src/Validator/Spec/AttributeList/TrackAttrsNoSubtitles.php
new file mode 100644
index 000000000..6ede5e1fe
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/TrackAttrsNoSubtitles.php
@@ -0,0 +1,56 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::DEFAULT_ => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::KIND => [
+ SpecRule::VALUE => [
+ 'captions',
+ 'chapters',
+ 'descriptions',
+ 'metadata',
+ ],
+ ],
+ Attribute::LABEL => [],
+ Attribute::SRC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ ],
+ ],
+ Attribute::SRCLANG => [],
+ ];
+}
diff --git a/src/Validator/Spec/AttributeList/TrackAttrsSubtitles.php b/src/Validator/Spec/AttributeList/TrackAttrsSubtitles.php
new file mode 100644
index 000000000..8e91a1fc6
--- /dev/null
+++ b/src/Validator/Spec/AttributeList/TrackAttrsSubtitles.php
@@ -0,0 +1,56 @@
+
+ */
+ const ATTRIBUTES = [
+ Attribute::DEFAULT_ => [
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ Attribute::KIND => [
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE_CASEI => [
+ 'subtitles',
+ ],
+ ],
+ Attribute::LABEL => [],
+ Attribute::SRC => [
+ SpecRule::MANDATORY => true,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ ],
+ ],
+ Attribute::SRCLANG => [
+ SpecRule::MANDATORY => true,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/CssRuleset.php b/src/Validator/Spec/CssRuleset.php
new file mode 100644
index 000000000..4351a3372
--- /dev/null
+++ b/src/Validator/Spec/CssRuleset.php
@@ -0,0 +1,113 @@
+ $declarationListSvg
+ * @property-read array $disabledBy
+ * @property-read array $enabledBy
+ * @property-read array $fontUrlSpec
+ * @property-read array $htmlFormat
+ * @property-read array $imageUrlSpec
+ */
+class CssRuleset
+{
+ /**
+ * ID of the CSS ruleset.
+ *
+ * This needs to be overridden in the extending class.
+ *
+ * @var string
+ */
+ const ID = '[css ruleset base class]';
+
+ /**
+ * Spec data of the CSS ruleset.
+ *
+ * @var array
+ */
+ const SPEC = [];
+
+ /**
+ * Get the ID of the CSS ruleset.
+ *
+ * @return string ID of the CSS ruleset.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given spec rule is present.
+ *
+ * @param string $cssRulesetName Name of the spec rule to check for.
+ * @return bool Whether the given spec rule is contained in the spec.
+ */
+ public function has($cssRulesetName)
+ {
+ return array_key_exists($cssRulesetName, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $cssRulesetName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($cssRulesetName)
+ {
+ if (!$this->has($cssRulesetName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($cssRulesetName);
+ }
+
+ return static::SPEC[$cssRulesetName];
+ }
+
+ /**
+ * Magic getter to return the spec rules.
+ *
+ * @param string $cssRulesetName Name of the spec rule to return.
+ * @return mixed Value of the spec rule.
+ */
+ public function __get($cssRulesetName)
+ {
+ switch ($cssRulesetName) {
+ case SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE:
+ case SpecRule::ALLOW_IMPORTANT:
+ case SpecRule::EXPAND_VENDOR_PREFIXES:
+ case SpecRule::MAX_BYTES_IS_WARNING:
+ case SpecRule::URL_BYTES_INCLUDED:
+ return array_key_exists($cssRulesetName, static::SPEC) ? static::SPEC[$cssRulesetName] : false;
+ case SpecRule::DECLARATION_LIST_SVG:
+ case SpecRule::DISABLED_BY:
+ case SpecRule::ENABLED_BY:
+ case SpecRule::FONT_URL_SPEC:
+ case SpecRule::HTML_FORMAT:
+ case SpecRule::IMAGE_URL_SPEC:
+ return array_key_exists($cssRulesetName, static::SPEC) ? static::SPEC[$cssRulesetName] : [];
+ default:
+ if (!array_key_exists($cssRulesetName, static::SPEC)) {
+ throw InvalidSpecRuleName::forSpecRuleName($cssRulesetName);
+ }
+
+ return static::SPEC[$cssRulesetName];
+ }
+ }
+}
diff --git a/src/Validator/Spec/CssRuleset/Amp4ads.php b/src/Validator/Spec/CssRuleset/Amp4ads.php
new file mode 100644
index 000000000..3ee342a53
--- /dev/null
+++ b/src/Validator/Spec/CssRuleset/Amp4ads.php
@@ -0,0 +1,55 @@
+ [
+ Format::AMP4ADS,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/a4a_spec/#css',
+ SpecRule::MAX_BYTES_SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/a4a_spec/#css',
+ SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE => true,
+ SpecRule::IMAGE_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ Protocol::HTTP,
+ Protocol::DATA,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ SpecRule::FONT_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ Protocol::HTTP,
+ Protocol::DATA,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ SpecRule::ALLOW_IMPORTANT => false,
+ SpecRule::EXPAND_VENDOR_PREFIXES => true,
+ ];
+}
diff --git a/src/Validator/Spec/CssRuleset/Amp4emailDataCssStrict.php b/src/Validator/Spec/CssRuleset/Amp4emailDataCssStrict.php
new file mode 100644
index 000000000..d873eb0a3
--- /dev/null
+++ b/src/Validator/Spec/CssRuleset/Amp4emailDataCssStrict.php
@@ -0,0 +1,56 @@
+ [
+ Format::AMP4EMAIL,
+ ],
+ SpecRule::ENABLED_BY => [
+ Attribute::DATA_CSS_STRICT,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/email-spec/amp-email-css',
+ SpecRule::MAX_BYTES => 75000,
+ SpecRule::MAX_BYTES_PER_INLINE_STYLE => 1000,
+ SpecRule::URL_BYTES_INCLUDED => true,
+ SpecRule::MAX_BYTES_SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#maximum-size',
+ SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE => false,
+ SpecRule::DECLARATION_LIST => [
+ DeclarationList\EmailSpecificDeclarations::ID,
+ ],
+ SpecRule::IMAGE_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ ],
+ SpecRule::ALLOW_IMPORTANT => false,
+ SpecRule::MAX_BYTES_IS_WARNING => false,
+ SpecRule::EXPAND_VENDOR_PREFIXES => false,
+ ];
+}
diff --git a/src/Validator/Spec/CssRuleset/Amp4emailNoDataCssStrict.php b/src/Validator/Spec/CssRuleset/Amp4emailNoDataCssStrict.php
new file mode 100644
index 000000000..b4ba364ac
--- /dev/null
+++ b/src/Validator/Spec/CssRuleset/Amp4emailNoDataCssStrict.php
@@ -0,0 +1,59 @@
+ [
+ Format::AMP4EMAIL,
+ ],
+ SpecRule::DISABLED_BY => [
+ Attribute::DATA_CSS_STRICT,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/email-spec/amp-email-css',
+ SpecRule::MAX_BYTES => 75000,
+ SpecRule::MAX_BYTES_PER_INLINE_STYLE => 1000,
+ SpecRule::URL_BYTES_INCLUDED => true,
+ SpecRule::MAX_BYTES_SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#maximum-size',
+ SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE => true,
+ SpecRule::DECLARATION_LIST => [
+ DeclarationList\BasicDeclarations::ID,
+ ],
+ SpecRule::DECLARATION_LIST_SVG => [
+ 'SVG_BASIC_DECLARATIONS',
+ ],
+ SpecRule::IMAGE_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ ],
+ SpecRule::ALLOW_IMPORTANT => false,
+ SpecRule::MAX_BYTES_IS_WARNING => true,
+ SpecRule::EXPAND_VENDOR_PREFIXES => true,
+ ];
+}
diff --git a/src/Validator/Spec/CssRuleset/AmpNoTransformed.php b/src/Validator/Spec/CssRuleset/AmpNoTransformed.php
new file mode 100644
index 000000000..c580ef4f9
--- /dev/null
+++ b/src/Validator/Spec/CssRuleset/AmpNoTransformed.php
@@ -0,0 +1,62 @@
+ [
+ Format::AMP,
+ ],
+ SpecRule::DISABLED_BY => [
+ Attribute::TRANSFORMED,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#stylesheets',
+ SpecRule::MAX_BYTES => 75000,
+ SpecRule::MAX_BYTES_PER_INLINE_STYLE => 1000,
+ SpecRule::URL_BYTES_INCLUDED => true,
+ SpecRule::MAX_BYTES_SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#maximum-size',
+ SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE => true,
+ SpecRule::IMAGE_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ Protocol::HTTP,
+ Protocol::DATA,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ SpecRule::FONT_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ Protocol::HTTP,
+ Protocol::DATA,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ SpecRule::ALLOW_IMPORTANT => false,
+ SpecRule::EXPAND_VENDOR_PREFIXES => true,
+ ];
+}
diff --git a/src/Validator/Spec/CssRuleset/AmpTransformed.php b/src/Validator/Spec/CssRuleset/AmpTransformed.php
new file mode 100644
index 000000000..f187b2397
--- /dev/null
+++ b/src/Validator/Spec/CssRuleset/AmpTransformed.php
@@ -0,0 +1,62 @@
+ [
+ Format::AMP,
+ ],
+ SpecRule::ENABLED_BY => [
+ Attribute::TRANSFORMED,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#stylesheets',
+ SpecRule::MAX_BYTES => 112500,
+ SpecRule::MAX_BYTES_PER_INLINE_STYLE => 1500,
+ SpecRule::URL_BYTES_INCLUDED => false,
+ SpecRule::MAX_BYTES_SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#maximum-size',
+ SpecRule::ALLOW_ALL_DECLARATION_IN_STYLE => true,
+ SpecRule::IMAGE_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ Protocol::HTTP,
+ Protocol::DATA,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ SpecRule::FONT_URL_SPEC => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ Protocol::HTTP,
+ Protocol::DATA,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ SpecRule::ALLOW_IMPORTANT => false,
+ SpecRule::EXPAND_VENDOR_PREFIXES => true,
+ ];
+}
diff --git a/src/Validator/Spec/DeclarationList.php b/src/Validator/Spec/DeclarationList.php
new file mode 100644
index 000000000..39a5fc62c
--- /dev/null
+++ b/src/Validator/Spec/DeclarationList.php
@@ -0,0 +1,70 @@
+
+ */
+ const DECLARATIONS = [];
+
+ /**
+ * Get the ID of the declaration list.
+ *
+ * @return string ID of the declaration list.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given declaration is contained within the list.
+ *
+ * @param string $declaration Declaration to check for.
+ * @return bool Whether the given declaration is contained within the list.
+ */
+ public function has($declaration)
+ {
+ return array_key_exists($declaration, static::DECLARATIONS);
+ }
+
+ /**
+ * Get a specific declaration.
+ *
+ * @param string $declaration Declaration to get.
+ * @return array Declaration data that was requested.
+ */
+ public function get($declaration)
+ {
+ if (!$this->has($declaration)) {
+ throw InvalidDeclarationName::forDeclaration($declaration);
+ }
+
+ return static::DECLARATIONS[$declaration];
+ }
+}
diff --git a/src/Validator/Spec/DeclarationList/BasicDeclarations.php b/src/Validator/Spec/DeclarationList/BasicDeclarations.php
new file mode 100644
index 000000000..e5f02d6ab
--- /dev/null
+++ b/src/Validator/Spec/DeclarationList/BasicDeclarations.php
@@ -0,0 +1,267 @@
+
+ */
+ const DECLARATIONS = [
+ Attribute::ALIGN_CONTENT => [],
+ Attribute::ALIGN_ITEMS => [],
+ Attribute::ALIGN_SELF => [],
+ Attribute::ALL => [],
+ Attribute::ANIMATION => [],
+ Attribute::ANIMATION_DELAY => [],
+ Attribute::ANIMATION_DIRECTION => [],
+ Attribute::ANIMATION_DURATION => [],
+ Attribute::ANIMATION_FILL_MODE => [],
+ Attribute::ANIMATION_ITERATION_COUNT => [],
+ Attribute::ANIMATION_NAME => [],
+ Attribute::ANIMATION_PLAY_STATE => [],
+ Attribute::ANIMATION_TIMING_FUNCTION => [],
+ Attribute::ASPECT_RATIO => [],
+ Attribute::BACKFACE_VISIBILITY => [],
+ Attribute::BACKGROUND => [],
+ Attribute::BACKGROUND_ATTACHMENT => [],
+ Attribute::BACKGROUND_BLEND_MODE => [],
+ Attribute::BACKGROUND_CLIP => [],
+ Attribute::BACKGROUND_COLOR => [],
+ Attribute::BACKGROUND_IMAGE => [],
+ Attribute::BACKGROUND_ORIGIN => [],
+ Attribute::BACKGROUND_POSITION => [],
+ Attribute::BACKGROUND_REPEAT => [],
+ Attribute::BACKGROUND_SIZE => [],
+ Attribute::BORDER => [],
+ Attribute::BORDER_BOTTOM => [],
+ Attribute::BORDER_BOTTOM_COLOR => [],
+ Attribute::BORDER_BOTTOM_LEFT_RADIUS => [],
+ Attribute::BORDER_BOTTOM_RIGHT_RADIUS => [],
+ Attribute::BORDER_BOTTOM_STYLE => [],
+ Attribute::BORDER_BOTTOM_WIDTH => [],
+ Attribute::BORDER_COLLAPSE => [],
+ Attribute::BORDER_COLOR => [],
+ Attribute::BORDER_IMAGE => [],
+ Attribute::BORDER_IMAGE_OUTSET => [],
+ Attribute::BORDER_IMAGE_REPEAT => [],
+ Attribute::BORDER_IMAGE_SLICE => [],
+ Attribute::BORDER_IMAGE_SOURCE => [],
+ Attribute::BORDER_IMAGE_WIDTH => [],
+ Attribute::BORDER_LEFT => [],
+ Attribute::BORDER_LEFT_COLOR => [],
+ Attribute::BORDER_LEFT_STYLE => [],
+ Attribute::BORDER_LEFT_WIDTH => [],
+ Attribute::BORDER_RADIUS => [],
+ Attribute::BORDER_RIGHT => [],
+ Attribute::BORDER_RIGHT_COLOR => [],
+ Attribute::BORDER_RIGHT_STYLE => [],
+ Attribute::BORDER_RIGHT_WIDTH => [],
+ Attribute::BORDER_SPACING => [],
+ Attribute::BORDER_STYLE => [],
+ Attribute::BORDER_TOP => [],
+ Attribute::BORDER_TOP_COLOR => [],
+ Attribute::BORDER_TOP_LEFT_RADIUS => [],
+ Attribute::BORDER_TOP_RIGHT_RADIUS => [],
+ Attribute::BORDER_TOP_STYLE => [],
+ Attribute::BORDER_TOP_WIDTH => [],
+ Attribute::BORDER_WIDTH => [],
+ Attribute::BOTTOM => [],
+ Attribute::BOX_DECORATION_BREAK => [],
+ Attribute::BOX_SHADOW => [],
+ Attribute::BOX_SIZING => [],
+ Attribute::BREAK_AFTER => [],
+ Attribute::BREAK_BEFORE => [],
+ Attribute::BREAK_INSIDE => [],
+ Attribute::CAPTION_SIDE => [],
+ Attribute::CARET_COLOR => [],
+ Attribute::CLEAR => [],
+ Attribute::CLIP => [],
+ Attribute::COLOR => [],
+ Attribute::COLUMN_COUNT => [],
+ Attribute::COLUMN_FILL => [],
+ Attribute::COLUMN_GAP => [],
+ Attribute::COLUMN_RULE => [],
+ Attribute::COLUMN_RULE_COLOR => [],
+ Attribute::COLUMN_RULE_STYLE => [],
+ Attribute::COLUMN_RULE_WIDTH => [],
+ Attribute::COLUMN_SPAN => [],
+ Attribute::COLUMN_WIDTH => [],
+ Attribute::COLUMNS => [],
+ Attribute::CONTENT => [],
+ Attribute::COUNTER_INCREMENT => [],
+ Attribute::COUNTER_RESET => [],
+ Attribute::CURSOR => [],
+ Attribute::DIRECTION => [],
+ Attribute::DISPLAY => [],
+ Attribute::EMPTY_CELLS => [],
+ Attribute::FILTER => [],
+ Attribute::FLEX => [],
+ Attribute::FLEX_BASIS => [],
+ Attribute::FLEX_DIRECTION => [],
+ Attribute::FLEX_FLOW => [],
+ Attribute::FLEX_GROW => [],
+ Attribute::FLEX_SHRINK => [],
+ Attribute::FLEX_WRAP => [],
+ Attribute::FLOAT => [],
+ Attribute::FONT => [],
+ Attribute::FONT_FAMILY => [],
+ Attribute::FONT_FEATURE_SETTINGS => [],
+ Attribute::FONT_KERNING => [],
+ Attribute::FONT_LANGUAGE_OVERRIDE => [],
+ Attribute::FONT_SIZE => [],
+ Attribute::FONT_SIZE_ADJUST => [],
+ Attribute::FONT_STRETCH => [],
+ Attribute::FONT_STYLE => [],
+ Attribute::FONT_SYNTHESIS => [],
+ Attribute::FONT_VARIANT => [],
+ Attribute::FONT_VARIANT_ALTERNATES => [],
+ Attribute::FONT_VARIANT_CAPS => [],
+ Attribute::FONT_VARIANT_EAST_ASIAN => [],
+ Attribute::FONT_VARIANT_LIGATURES => [],
+ Attribute::FONT_VARIANT_NUMERIC => [],
+ Attribute::FONT_VARIANT_POSITION => [],
+ Attribute::FONT_WEIGHT => [],
+ Attribute::GRID => [],
+ Attribute::GRID_AREA => [],
+ Attribute::GRID_AUTO_COLUMNS => [],
+ Attribute::GRID_AUTO_FLOW => [],
+ Attribute::GRID_AUTO_ROWS => [],
+ Attribute::GRID_COLUMN => [],
+ Attribute::GRID_COLUMN_END => [],
+ Attribute::GRID_COLUMN_GAP => [],
+ Attribute::GRID_COLUMN_START => [],
+ Attribute::GRID_GAP => [],
+ Attribute::GRID_ROW => [],
+ Attribute::GRID_ROW_END => [],
+ Attribute::GRID_ROW_GAP => [],
+ Attribute::GRID_ROW_START => [],
+ Attribute::GRID_TEMPLATE => [],
+ Attribute::GRID_TEMPLATE_AREAS => [],
+ Attribute::GRID_TEMPLATE_COLUMNS => [],
+ Attribute::GRID_TEMPLATE_ROWS => [],
+ Attribute::HANGING_PUNCTUATION => [],
+ Attribute::HEIGHT => [],
+ Attribute::HYPHENS => [],
+ Attribute::IMAGE_RENDERING => [],
+ Attribute::ISOLATION => [],
+ Attribute::JUSTIFY_CONTENT => [],
+ Attribute::LEFT => [],
+ Attribute::LETTER_SPACING => [],
+ Attribute::LINE_BREAK => [],
+ Attribute::LINE_HEIGHT => [],
+ Attribute::LIST_STYLE => [],
+ Attribute::LIST_STYLE_IMAGE => [],
+ Attribute::LIST_STYLE_POSITION => [],
+ Attribute::LIST_STYLE_TYPE => [],
+ Attribute::MARGIN => [],
+ Attribute::MARGIN_BOTTOM => [],
+ Attribute::MARGIN_LEFT => [],
+ Attribute::MARGIN_RIGHT => [],
+ Attribute::MARGIN_TOP => [],
+ Attribute::MAX_HEIGHT => [],
+ Attribute::MAX_WIDTH => [],
+ Attribute::MIN_HEIGHT => [],
+ Attribute::MIN_WIDTH => [],
+ Attribute::MIX_BLEND_MODE => [],
+ Attribute::OBJECT_FIT => [],
+ Attribute::OBJECT_POSITION => [],
+ Attribute::OPACITY => [],
+ Attribute::ORDER => [],
+ Attribute::ORPHANS => [],
+ Attribute::OUTLINE => [],
+ Attribute::OUTLINE_COLOR => [],
+ Attribute::OUTLINE_OFFSET => [],
+ Attribute::OUTLINE_STYLE => [],
+ Attribute::OUTLINE_WIDTH => [],
+ Attribute::OVERFLOW => [],
+ Attribute::OVERFLOW_WRAP => [],
+ Attribute::OVERFLOW_X => [],
+ Attribute::OVERFLOW_Y => [],
+ Attribute::PADDING => [],
+ Attribute::PADDING_BOTTOM => [],
+ Attribute::PADDING_LEFT => [],
+ Attribute::PADDING_RIGHT => [],
+ Attribute::PADDING_TOP => [],
+ Attribute::PAGE_BREAK_AFTER => [],
+ Attribute::PAGE_BREAK_BEFORE => [],
+ Attribute::PAGE_BREAK_INSIDE => [],
+ Attribute::PERSPECTIVE => [],
+ Attribute::PERSPECTIVE_ORIGIN => [],
+ Attribute::POINTER_EVENTS => [],
+ Attribute::POSITION => [
+ SpecRule::VALUE_CASEI => [
+ 'absolute',
+ 'inherit',
+ 'initial',
+ 'relative',
+ 'static',
+ ],
+ ],
+ Attribute::QUOTES => [],
+ Attribute::RESIZE => [],
+ Attribute::RIGHT => [],
+ Attribute::TAB_SIZE => [],
+ Attribute::TABLE_LAYOUT => [],
+ Attribute::TEXT_ALIGN => [],
+ Attribute::TEXT_ALIGN_LAST => [],
+ Attribute::TEXT_COMBINE_UPRIGHT => [],
+ Attribute::TEXT_DECORATION => [],
+ Attribute::TEXT_DECORATION_COLOR => [],
+ Attribute::TEXT_DECORATION_LINE => [],
+ Attribute::TEXT_DECORATION_SKIP_INK => [],
+ Attribute::TEXT_DECORATION_STYLE => [],
+ Attribute::TEXT_FILL_COLOR => [],
+ Attribute::TEXT_INDENT => [],
+ Attribute::TEXT_JUSTIFY => [],
+ Attribute::TEXT_ORIENTATION => [],
+ Attribute::TEXT_OVERFLOW => [],
+ Attribute::TEXT_SHADOW => [],
+ Attribute::TEXT_STROKE => [],
+ Attribute::TEXT_STROKE_COLOR => [],
+ Attribute::TEXT_STROKE_WIDTH => [],
+ Attribute::TEXT_TRANSFORM => [],
+ Attribute::TEXT_UNDERLINE_POSITION => [],
+ Attribute::TOP => [],
+ Attribute::TRANSFORM => [],
+ Attribute::TRANSFORM_ORIGIN => [],
+ Attribute::TRANSFORM_STYLE => [],
+ Attribute::TRANSITION => [],
+ Attribute::TRANSITION_DELAY => [],
+ Attribute::TRANSITION_DURATION => [],
+ Attribute::TRANSITION_PROPERTY => [],
+ Attribute::TRANSITION_TIMING_FUNCTION => [],
+ Attribute::UNICODE_BIDI => [],
+ Attribute::USER_SELECT => [],
+ Attribute::VERTICAL_ALIGN => [],
+ Attribute::VISIBILITY => [],
+ Attribute::WHITE_SPACE => [],
+ Attribute::WIDOWS => [],
+ Attribute::WIDTH => [],
+ Attribute::WORD_BREAK => [],
+ Attribute::WORD_SPACING => [],
+ Attribute::WORD_WRAP => [],
+ Attribute::WRITING_MODE => [],
+ Attribute::Z_INDEX => [
+ SpecRule::VALUE_REGEX_CASEI => 'auto|initial|inherit|[-+]?[0-9]+',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/DeclarationList/EmailSpecificDeclarations.php b/src/Validator/Spec/DeclarationList/EmailSpecificDeclarations.php
new file mode 100644
index 000000000..15f3565c3
--- /dev/null
+++ b/src/Validator/Spec/DeclarationList/EmailSpecificDeclarations.php
@@ -0,0 +1,279 @@
+
+ */
+ const DECLARATIONS = [
+ Attribute::_MOZ_APPEARANCE => [],
+ Attribute::_WEBKIT_APPEARANCE => [],
+ Attribute::_WEBKIT_TAP_HIGHLIGHT_COLOR => [],
+ Attribute::ALIGN_CONTENT => [],
+ Attribute::ALIGN_ITEMS => [],
+ Attribute::ALIGN_SELF => [],
+ Attribute::APPEARANCE => [],
+ Attribute::ASPECT_RATIO => [],
+ Attribute::AZIMUTH => [],
+ Attribute::BACKGROUND => [],
+ Attribute::BACKGROUND_ATTACHMENT => [],
+ Attribute::BACKGROUND_BLEND_MODE => [],
+ Attribute::BACKGROUND_CLIP => [],
+ Attribute::BACKGROUND_COLOR => [],
+ Attribute::BACKGROUND_IMAGE => [],
+ Attribute::BACKGROUND_ORIGIN => [],
+ Attribute::BACKGROUND_POSITION => [],
+ Attribute::BACKGROUND_REPEAT => [],
+ Attribute::BACKGROUND_SIZE => [],
+ Attribute::BORDER => [],
+ Attribute::BORDER_BOTTOM => [],
+ Attribute::BORDER_BOTTOM_COLOR => [],
+ Attribute::BORDER_BOTTOM_LEFT_RADIUS => [],
+ Attribute::BORDER_BOTTOM_RIGHT_RADIUS => [],
+ Attribute::BORDER_BOTTOM_STYLE => [],
+ Attribute::BORDER_BOTTOM_WIDTH => [],
+ Attribute::BORDER_COLLAPSE => [],
+ Attribute::BORDER_COLOR => [],
+ Attribute::BORDER_LEFT => [],
+ Attribute::BORDER_LEFT_COLOR => [],
+ Attribute::BORDER_LEFT_STYLE => [],
+ Attribute::BORDER_LEFT_WIDTH => [],
+ Attribute::BORDER_RADIUS => [],
+ Attribute::BORDER_RIGHT => [],
+ Attribute::BORDER_RIGHT_COLOR => [],
+ Attribute::BORDER_RIGHT_STYLE => [],
+ Attribute::BORDER_RIGHT_WIDTH => [],
+ Attribute::BORDER_SPACING => [],
+ Attribute::BORDER_STYLE => [],
+ Attribute::BORDER_TOP => [],
+ Attribute::BORDER_TOP_COLOR => [],
+ Attribute::BORDER_TOP_LEFT_RADIUS => [],
+ Attribute::BORDER_TOP_RIGHT_RADIUS => [],
+ Attribute::BORDER_TOP_STYLE => [],
+ Attribute::BORDER_TOP_WIDTH => [],
+ Attribute::BORDER_WIDTH => [],
+ Attribute::BOTTOM => [],
+ Attribute::BOX_SHADOW => [],
+ Attribute::BOX_SIZING => [],
+ Attribute::BREAK_AFTER => [],
+ Attribute::BREAK_BEFORE => [],
+ Attribute::BREAK_INSIDE => [],
+ Attribute::CAPTION_SIDE => [],
+ Attribute::CARET_COLOR => [],
+ Attribute::CLEAR => [],
+ Attribute::COLOR => [],
+ Attribute::COLOR_ADJUST => [],
+ Attribute::COLUMN_COUNT => [],
+ Attribute::COLUMN_FILL => [],
+ Attribute::COLUMN_GAP => [],
+ Attribute::COLUMN_RULE => [],
+ Attribute::COLUMN_RULE_COLOR => [],
+ Attribute::COLUMN_RULE_STYLE => [],
+ Attribute::COLUMN_RULE_WIDTH => [],
+ Attribute::COLUMN_SPAN => [],
+ Attribute::COLUMN_WIDTH => [],
+ Attribute::COLUMNS => [],
+ Attribute::COUNTER_INCREMENT => [],
+ Attribute::COUNTER_RESET => [],
+ Attribute::CURSOR => [
+ SpecRule::VALUE_CASEI => [
+ 'initial',
+ 'pointer',
+ ],
+ ],
+ Attribute::DIRECTION => [],
+ Attribute::DISPLAY => [],
+ Attribute::ELEVATION => [],
+ Attribute::EMPTY_CELLS => [],
+ Attribute::FILTER => [
+ SpecRule::VALUE_REGEX_CASEI => '^ *((blur|brightness|contrast|drop-shadow|grayscale|hue-rotate|invert|opacity|saturate|sepia)\(([^() ]*|(rgb|rgba|hsl|hsla)\([^()]*\))( +([^() ]*|(rgb|rgba|hsl|hsla)\([^()]*\)))*\) *)*$',
+ ],
+ Attribute::FLEX => [],
+ Attribute::FLEX_BASIS => [],
+ Attribute::FLEX_DIRECTION => [],
+ Attribute::FLEX_FLOW => [],
+ Attribute::FLEX_GROW => [],
+ Attribute::FLEX_SHRINK => [],
+ Attribute::FLEX_WRAP => [],
+ Attribute::FLOAT => [],
+ Attribute::FONT => [],
+ Attribute::FONT_FAMILY => [],
+ Attribute::FONT_FEATURE_SETTINGS => [],
+ Attribute::FONT_KERNING => [],
+ Attribute::FONT_SIZE => [],
+ Attribute::FONT_SIZE_ADJUST => [],
+ Attribute::FONT_STRETCH => [],
+ Attribute::FONT_STYLE => [],
+ Attribute::FONT_SYNTHESIS => [],
+ Attribute::FONT_VARIANT => [],
+ Attribute::FONT_VARIANT_ALTERNATES => [],
+ Attribute::FONT_VARIANT_CAPS => [],
+ Attribute::FONT_VARIANT_EAST_ASIAN => [],
+ Attribute::FONT_VARIANT_LIGATURES => [],
+ Attribute::FONT_VARIANT_NUMERIC => [],
+ Attribute::FONT_VARIATION_SETTINGS => [],
+ Attribute::FONT_WEIGHT => [],
+ Attribute::GAP => [],
+ Attribute::GRID => [],
+ Attribute::GRID_AREA => [],
+ Attribute::GRID_AUTO_COLUMNS => [],
+ Attribute::GRID_AUTO_FLOW => [],
+ Attribute::GRID_AUTO_ROWS => [],
+ Attribute::GRID_COLUMN => [],
+ Attribute::GRID_COLUMN_END => [],
+ Attribute::GRID_COLUMN_START => [],
+ Attribute::GRID_ROW => [],
+ Attribute::GRID_ROW_END => [],
+ Attribute::GRID_ROW_START => [],
+ Attribute::GRID_TEMPLATE => [],
+ Attribute::GRID_TEMPLATE_AREAS => [],
+ Attribute::GRID_TEMPLATE_COLUMNS => [],
+ Attribute::GRID_TEMPLATE_ROWS => [],
+ Attribute::HEIGHT => [],
+ Attribute::HYPHENS => [],
+ Attribute::IMAGE_ORIENTATION => [],
+ Attribute::IMAGE_RESOLUTION => [],
+ Attribute::INLINE_SIZE => [],
+ Attribute::ISOLATION => [],
+ Attribute::JUSTIFY_CONTENT => [],
+ Attribute::JUSTIFY_ITEMS => [],
+ Attribute::JUSTIFY_SELF => [],
+ Attribute::LEFT => [],
+ Attribute::LETTER_SPACING => [],
+ Attribute::LINE_BREAK => [],
+ Attribute::LINE_HEIGHT => [],
+ Attribute::LIST_STYLE => [],
+ Attribute::LIST_STYLE_POSITION => [],
+ Attribute::LIST_STYLE_TYPE => [],
+ Attribute::MARGIN => [],
+ Attribute::MARGIN_BOTTOM => [],
+ Attribute::MARGIN_LEFT => [],
+ Attribute::MARGIN_RIGHT => [],
+ Attribute::MARGIN_TOP => [],
+ Attribute::MAX_HEIGHT => [],
+ Attribute::MAX_WIDTH => [],
+ Attribute::MIN_HEIGHT => [],
+ Attribute::MIN_WIDTH => [],
+ Attribute::MIX_BLEND_MODE => [],
+ Attribute::OBJECT_FIT => [],
+ Attribute::OBJECT_POSITION => [],
+ Attribute::OFFSET_DISTANCE => [],
+ Attribute::OPACITY => [],
+ Attribute::ORDER => [],
+ Attribute::OUTLINE => [],
+ Attribute::OUTLINE_COLOR => [],
+ Attribute::OUTLINE_OFFSET => [],
+ Attribute::OUTLINE_STYLE => [],
+ Attribute::OUTLINE_WIDTH => [],
+ Attribute::OVERFLOW => [],
+ Attribute::OVERFLOW_WRAP => [],
+ Attribute::OVERFLOW_X => [],
+ Attribute::OVERFLOW_Y => [],
+ Attribute::PADDING => [],
+ Attribute::PADDING_BOTTOM => [],
+ Attribute::PADDING_LEFT => [],
+ Attribute::PADDING_RIGHT => [],
+ Attribute::PADDING_TOP => [],
+ Attribute::PAUSE => [],
+ Attribute::PAUSE_AFTER => [],
+ Attribute::PAUSE_BEFORE => [],
+ Attribute::PERSPECTIVE => [],
+ Attribute::PERSPECTIVE_ORIGIN => [],
+ Attribute::PITCH => [],
+ Attribute::PITCH_RANGE => [],
+ Attribute::PLACE_ITEMS => [],
+ Attribute::POSITION => [
+ SpecRule::VALUE_CASEI => [
+ 'absolute',
+ 'inherit',
+ 'initial',
+ 'relative',
+ 'static',
+ ],
+ ],
+ Attribute::QUOTES => [],
+ Attribute::RESIZE => [],
+ Attribute::RICHNESS => [],
+ Attribute::RIGHT => [],
+ Attribute::ROW_GAP => [],
+ Attribute::SPEAK => [],
+ Attribute::SPEAK_HEADER => [],
+ Attribute::SPEAK_NUMERAL => [],
+ Attribute::SPEAK_PUNCTUATION => [],
+ Attribute::SPEECH_RATE => [],
+ Attribute::STRESS => [],
+ Attribute::TABLE_LAYOUT => [],
+ Attribute::TEXT_ALIGN => [],
+ Attribute::TEXT_ALIGN_LAST => [],
+ Attribute::TEXT_COMBINE_UPRIGHT => [],
+ Attribute::TEXT_DECORATION => [],
+ Attribute::TEXT_DECORATION_COLOR => [],
+ Attribute::TEXT_DECORATION_LINE => [],
+ Attribute::TEXT_DECORATION_SKIP => [],
+ Attribute::TEXT_DECORATION_STYLE => [],
+ Attribute::TEXT_EMPHASIS => [],
+ Attribute::TEXT_EMPHASIS_COLOR => [],
+ Attribute::TEXT_EMPHASIS_POSITION => [],
+ Attribute::TEXT_EMPHASIS_STYLE => [],
+ Attribute::TEXT_INDENT => [],
+ Attribute::TEXT_JUSTIFY => [],
+ Attribute::TEXT_ORIENTATION => [],
+ Attribute::TEXT_OVERFLOW => [],
+ Attribute::TEXT_SHADOW => [],
+ Attribute::TEXT_TRANSFORM => [],
+ Attribute::TEXT_UNDERLINE_POSITION => [],
+ Attribute::TOP => [],
+ Attribute::TRANSFORM => [],
+ Attribute::TRANSFORM_BOX => [],
+ Attribute::TRANSFORM_ORIGIN => [],
+ Attribute::TRANSFORM_STYLE => [],
+ Attribute::TRANSITION => [
+ SpecRule::VALUE_REGEX_CASEI => '^ *((initial|unset)|(((none|offset-distance|opacity|transform|visibility)( *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?(s|ms)( *(linear|(ease|ease-in|ease-out|ease-in-out|cubic-bezier\( *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?(, *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?){3} *\))|(step-start|step-end|steps\( *(|-|\+)[0-9]+(, *(jump-start|jump-end|jump-none|jump-both|start|end))? *\)))( *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?(s|ms))?)?)?)(, *((none|offset-distance|opacity|transform|visibility)( *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?(s|ms)( *(linear|(ease|ease-in|ease-out|ease-in-out|cubic-bezier\( *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?(, *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?){3} *\))|(step-start|step-end|steps\( *(|-|\+)[0-9]+(, *(jump-start|jump-end|jump-none|jump-both|start|end))? *\)))( *(|-|\+)([0-9]+|[0-9]*\.[0-9]+)(e(|-|\+)?[0-9]+)?(s|ms))?)?)?))*)) *$',
+ ],
+ Attribute::TRANSITION_DELAY => [],
+ Attribute::TRANSITION_DURATION => [],
+ Attribute::TRANSITION_PROPERTY => [
+ SpecRule::VALUE_REGEX_CASEI => '^ *(initial|unset|(none|offset-distance|opacity|transform|visibility)(, *(none|offset-distance|opacity|transform|visibility))*) *$',
+ ],
+ Attribute::TRANSITION_TIMING_FUNCTION => [],
+ Attribute::UNICODE_BIDI => [],
+ Attribute::VERTICAL_ALIGN => [],
+ Attribute::VISIBILITY => [
+ SpecRule::VALUE_CASEI => [
+ 'hidden',
+ 'initial',
+ 'visible',
+ ],
+ ],
+ Attribute::VOICE_FAMILY => [],
+ Attribute::WHITE_SPACE => [],
+ Attribute::WIDTH => [],
+ Attribute::WORD_BREAK => [],
+ Attribute::WORD_SPACING => [],
+ Attribute::WORD_WRAP => [],
+ Attribute::WRITING_MODE => [],
+ Attribute::Z_INDEX => [
+ SpecRule::VALUE_REGEX_CASEI => '([-+]?0)|([-+]?100)|([-+]?[1-9][0-9]?)',
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/DeclarationList/SvgBasicDeclarations.php b/src/Validator/Spec/DeclarationList/SvgBasicDeclarations.php
new file mode 100644
index 000000000..ee1c5e77e
--- /dev/null
+++ b/src/Validator/Spec/DeclarationList/SvgBasicDeclarations.php
@@ -0,0 +1,67 @@
+
+ */
+ const DECLARATIONS = [
+ Attribute::ALIGNMENT_BASELINE => [],
+ Attribute::BASELINE_SHIFT => [],
+ Attribute::CLIP_PATH => [],
+ Attribute::CLIP_RULE => [],
+ Attribute::COLOR_INTERPOLATION => [],
+ Attribute::COLOR_INTERPOLATION_FILTERS => [],
+ Attribute::COLOR_PROFILE => [],
+ Attribute::COLOR_RENDERING => [],
+ Attribute::DOMINANT_BASELINE => [],
+ Attribute::ENABLE_BACKGROUND => [],
+ Attribute::FILL => [],
+ Attribute::FILL_OPACITY => [],
+ Attribute::FILL_RULE => [],
+ Attribute::FLOOD_COLOR => [],
+ Attribute::FLOOD_OPACITY => [],
+ Attribute::GLYPH_ORIENTATION_HORIZONTAL => [],
+ Attribute::GLYPH_ORIENTATION_VERTICAL => [],
+ Attribute::KERNING => [],
+ Attribute::LIGHTING_COLOR => [],
+ Attribute::MARKER => [],
+ Attribute::MARKER_END => [],
+ Attribute::MARKER_MID => [],
+ Attribute::MARKER_START => [],
+ Attribute::MASK => [],
+ Attribute::SHAPE_RENDERING => [],
+ Attribute::STOP_COLOR => [],
+ Attribute::STOP_OPACITY => [],
+ Attribute::STROKE => [],
+ Attribute::STROKE_DASHARRAY => [],
+ Attribute::STROKE_DASHOFFSET => [],
+ Attribute::STROKE_LINECAP => [],
+ Attribute::STROKE_LINEJOIN => [],
+ Attribute::STROKE_MITERLIMIT => [],
+ Attribute::STROKE_OPACITY => [],
+ Attribute::STROKE_WIDTH => [],
+ Attribute::TEXT_ANCHOR => [],
+ Attribute::TEXT_RENDERING => [],
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList.php b/src/Validator/Spec/DescendantTagList.php
new file mode 100644
index 000000000..c684c6557
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList.php
@@ -0,0 +1,53 @@
+
+ */
+ const DESCENDANT_TAGS = [];
+
+ /**
+ * Get the ID of the descendant tag list.
+ *
+ * @return string ID of the descendant tag list.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given descendant tag is contained within the list.
+ *
+ * @param string $descendantTag Descendant tag to check for.
+ * @return bool Whether the given descendant tag is contained within the list.
+ */
+ public function has($descendantTag)
+ {
+ return in_array($descendantTag, static::DESCENDANT_TAGS, true);
+ }
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpMegaMenuAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpMegaMenuAllowedDescendants.php
new file mode 100644
index 000000000..26a0e9cc0
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpMegaMenuAllowedDescendants.php
@@ -0,0 +1,83 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::A,
+ Extension::AD,
+ Extension::CAROUSEL,
+ Extension::EMBED,
+ Extension::IMG,
+ Extension::LIGHTBOX,
+ Extension::LIST_,
+ Extension::VIDEO,
+ Element::B,
+ Element::BR,
+ Element::BUTTON,
+ Element::COL,
+ Element::COLGROUP,
+ Element::DIV,
+ Element::EM,
+ Element::FIELDSET,
+ Element::FORM,
+ Element::H1,
+ Element::H2,
+ Element::H3,
+ Element::H4,
+ Element::H5,
+ Element::H6,
+ Element::I,
+ Element::INPUT,
+ Element::LABEL,
+ Element::LI,
+ Element::MARK,
+ Element::NAV,
+ Element::OL,
+ Element::OPTION,
+ Element::P,
+ Element::PATH,
+ Element::SECTION,
+ Element::SELECT,
+ Element::SPAN,
+ Element::STRIKE,
+ Element::STRONG,
+ Element::SUB,
+ Element::SUP,
+ Element::SVG,
+ Element::TABLE,
+ Element::TBODY,
+ Element::TD,
+ Element::TEMPLATE,
+ Element::TH,
+ Element::TIME,
+ Element::TITLE,
+ Element::TR,
+ Element::U,
+ Element::UL,
+ Element::USE_,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpNestedMenuAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpNestedMenuAllowedDescendants.php
new file mode 100644
index 000000000..906d23a1f
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpNestedMenuAllowedDescendants.php
@@ -0,0 +1,85 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::A,
+ Extension::ACCORDION,
+ Extension::IMG,
+ Extension::LIST_,
+ Element::B,
+ Element::BR,
+ Element::BUTTON,
+ Element::CIRCLE,
+ Element::COL,
+ Element::COLGROUP,
+ Element::DIV,
+ Element::ELLIPSE,
+ Element::EM,
+ Element::FIELDSET,
+ Element::FORM,
+ Element::H1,
+ Element::H2,
+ Element::H3,
+ Element::H4,
+ Element::H5,
+ Element::H6,
+ Element::I,
+ Element::INPUT,
+ Element::LABEL,
+ Element::LI,
+ Element::LINE,
+ Element::MARK,
+ Element::NAV,
+ Element::OL,
+ Element::OPTION,
+ Element::P,
+ Element::PATH,
+ Element::POLYGON,
+ Element::POLYLINE,
+ Element::RECT,
+ Element::SECTION,
+ Element::SELECT,
+ Element::SPAN,
+ Element::STRIKE,
+ Element::STRONG,
+ Element::SUB,
+ Element::SUP,
+ Element::SVG,
+ Element::TABLE,
+ Element::TBODY,
+ Element::TD,
+ Element::TEMPLATE,
+ Element::TH,
+ Element::TIME,
+ Element::TITLE,
+ Element::TR,
+ Element::U,
+ Element::UL,
+ Element::USE_,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpStoryBookendAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpStoryBookendAllowedDescendants.php
new file mode 100644
index 000000000..04567a2ec
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpStoryBookendAllowedDescendants.php
@@ -0,0 +1,31 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::SCRIPT,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpStoryCtaLayerAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpStoryCtaLayerAllowedDescendants.php
new file mode 100644
index 000000000..10959cc0e
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpStoryCtaLayerAllowedDescendants.php
@@ -0,0 +1,142 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::A,
+ Element::ABBR,
+ Element::ADDRESS,
+ Extension::CALL_TRACKING,
+ Extension::DATE_COUNTDOWN,
+ Extension::DATE_DISPLAY,
+ Extension::FIT_TEXT,
+ Extension::FONT,
+ Extension::IMG,
+ Extension::TIMEAGO,
+ Element::B,
+ Element::BDI,
+ Element::BDO,
+ Element::BLOCKQUOTE,
+ Element::BR,
+ Element::BUTTON,
+ Element::CAPTION,
+ Element::CITE,
+ Element::CIRCLE,
+ Element::CLIPPATH,
+ Element::CODE,
+ Element::DATA,
+ Element::DEFS,
+ Element::DEL,
+ Element::DESC,
+ Element::DFN,
+ Element::DIV,
+ Element::ELLIPSE,
+ Element::EM,
+ Element::FECOLORMATRIX,
+ Element::FECOMPOSITE,
+ Element::FEFLOOD,
+ Element::FEGAUSSIANBLUR,
+ Element::FEMERGE,
+ Element::FEMERGENODE,
+ Element::FEOFFSET,
+ Element::FIGCAPTION,
+ Element::FIGURE,
+ Element::FILTER,
+ Element::FOOTER,
+ Element::G,
+ Element::GLYPH,
+ Element::GLYPHREF,
+ Element::H1,
+ Element::H2,
+ Element::H3,
+ Element::H4,
+ Element::H5,
+ Element::H6,
+ Element::HEADER,
+ Element::HGROUP,
+ Element::HKERN,
+ Element::HR,
+ Element::I,
+ Element::IMG,
+ Internal::SIZER,
+ Element::IMAGE,
+ Element::INS,
+ Element::KBD,
+ Element::LI,
+ Element::LINE,
+ Element::LINEARGRADIENT,
+ Element::MAIN,
+ Element::MARKER,
+ Element::MARK,
+ Element::MASK,
+ Element::METADATA,
+ Element::NAV,
+ Element::NOSCRIPT,
+ Element::OL,
+ Element::P,
+ Element::PATH,
+ Element::PATTERN,
+ Element::PRE,
+ Element::POLYGON,
+ Element::POLYLINE,
+ Element::RADIALGRADIENT,
+ Element::Q,
+ Element::RECT,
+ Element::RP,
+ Element::RT,
+ Element::RTC,
+ Element::RUBY,
+ Element::S,
+ Element::SAMP,
+ Element::SECTION,
+ Element::SMALL,
+ Element::SOLIDCOLOR,
+ Element::SPAN,
+ Element::STOP,
+ Element::STRONG,
+ Element::SUB,
+ Element::SUP,
+ Element::SVG,
+ Element::SWITCH_,
+ Element::SYMBOL,
+ Element::TEXT,
+ Element::TEXTPATH,
+ Element::TREF,
+ Element::TSPAN,
+ Element::TITLE,
+ Element::TIME,
+ Element::TR,
+ Element::U,
+ Element::UL,
+ Element::USE_,
+ Element::VAR_,
+ Element::VIEW,
+ Element::VKERN,
+ Element::WBR,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpStoryGridLayerAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpStoryGridLayerAllowedDescendants.php
new file mode 100644
index 000000000..45379be97
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpStoryGridLayerAllowedDescendants.php
@@ -0,0 +1,175 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::A,
+ Element::ABBR,
+ Element::ADDRESS,
+ Extension::ANALYTICS,
+ Extension::AUDIO,
+ Extension::DATE_COUNTDOWN,
+ Extension::DATE_DISPLAY,
+ Extension::EXPERIMENT,
+ Extension::FIT_TEXT,
+ Extension::FONT,
+ Extension::GIST,
+ Extension::IMG,
+ Extension::INSTALL_SERVICEWORKER,
+ Extension::LIST_,
+ Extension::LIVE_LIST,
+ Extension::PIXEL,
+ Extension::STATE,
+ Extension::STORY_360,
+ Extension::STORY_AUTO_ANALYTICS,
+ Extension::STORY_INTERACTIVE_BINARY_POLL,
+ Extension::STORY_INTERACTIVE_POLL,
+ Extension::STORY_INTERACTIVE_QUIZ,
+ Extension::STORY_INTERACTIVE_RESULTS,
+ Extension::STORY_PANNING_MEDIA,
+ Extension::TIMEAGO,
+ Extension::TWITTER,
+ Extension::VIDEO,
+ Element::ARTICLE,
+ Element::ASIDE,
+ Element::B,
+ Element::BDI,
+ Element::BDO,
+ Element::BLOCKQUOTE,
+ Element::BR,
+ Element::CAPTION,
+ Element::CIRCLE,
+ Element::CITE,
+ Element::CLIPPATH,
+ Element::CODE,
+ Element::COL,
+ Element::COLGROUP,
+ Element::DATA,
+ Element::DD,
+ Element::DEFS,
+ Element::DEL,
+ Element::DESC,
+ Element::DFN,
+ Element::DIV,
+ Element::DL,
+ Element::DT,
+ Element::ELLIPSE,
+ Element::EM,
+ Element::FECOLORMATRIX,
+ Element::FECOMPOSITE,
+ Element::FEFLOOD,
+ Element::FEGAUSSIANBLUR,
+ Element::FEMERGE,
+ Element::FEMERGENODE,
+ Element::FEOFFSET,
+ Element::FIGCAPTION,
+ Element::FIGURE,
+ Element::FILTER,
+ Element::FOOTER,
+ Element::G,
+ Element::GLYPH,
+ Element::GLYPHREF,
+ Element::H1,
+ Element::H2,
+ Element::H3,
+ Element::H4,
+ Element::H5,
+ Element::H6,
+ Element::HEADER,
+ Element::HGROUP,
+ Element::HKERN,
+ Element::HR,
+ Element::I,
+ Element::IMAGE,
+ Element::IMG,
+ Internal::SIZER,
+ Element::INS,
+ Element::KBD,
+ Element::LI,
+ Element::LINE,
+ Element::LINEARGRADIENT,
+ Element::MAIN,
+ Element::MARK,
+ Element::MARKER,
+ Element::MASK,
+ Element::METADATA,
+ Element::NAV,
+ Element::NOSCRIPT,
+ Element::OL,
+ Element::P,
+ Element::PATH,
+ Element::PATTERN,
+ Element::POLYGON,
+ Element::POLYLINE,
+ Element::PRE,
+ Element::Q,
+ Element::RADIALGRADIENT,
+ Element::RECT,
+ Element::RP,
+ Element::RT,
+ Element::RTC,
+ Element::RUBY,
+ Element::S,
+ Element::SAMP,
+ Element::SCRIPT,
+ Element::SECTION,
+ Element::SMALL,
+ Element::SOLIDCOLOR,
+ Element::SOURCE,
+ Element::SPAN,
+ Element::STOP,
+ Element::STRONG,
+ Element::SUB,
+ Element::SUP,
+ Element::SVG,
+ Element::SWITCH_,
+ Element::SYMBOL,
+ Element::TABLE,
+ Element::TBODY,
+ Element::TD,
+ Element::TEMPLATE,
+ Element::TEXT,
+ Element::TEXTPATH,
+ Element::TFOOT,
+ Element::TH,
+ Element::THEAD,
+ Element::TIME,
+ Element::TITLE,
+ Element::TR,
+ Element::TRACK,
+ Element::TREF,
+ Element::TSPAN,
+ Element::U,
+ Element::UL,
+ Element::USE_,
+ Element::VAR_,
+ Element::VIEW,
+ Element::VKERN,
+ Element::WBR,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpStoryPageAttachmentAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpStoryPageAttachmentAllowedDescendants.php
new file mode 100644
index 000000000..0e937da7a
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpStoryPageAttachmentAllowedDescendants.php
@@ -0,0 +1,211 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::A,
+ Element::ABBR,
+ Element::ADDRESS,
+ Extension::_3D_GLTF,
+ Extension::_3Q_PLAYER,
+ Extension::ACCORDION,
+ Extension::AUDIO,
+ Extension::BEOPINION,
+ Extension::BODYMOVIN_ANIMATION,
+ Extension::BRID_PLAYER,
+ Extension::BRIGHTCOVE,
+ Extension::BYSIDE_CONTENT,
+ Extension::CALL_TRACKING,
+ Extension::CAROUSEL,
+ Extension::DAILYMOTION,
+ Extension::DATE_COUNTDOWN,
+ Extension::DATE_DISPLAY,
+ Extension::EMBEDLY_CARD,
+ Extension::FACEBOOK,
+ Extension::FACEBOOK_COMMENTS,
+ Extension::FACEBOOK_LIKE,
+ Extension::FACEBOOK_PAGE,
+ Extension::FIT_TEXT,
+ Extension::FX_COLLECTION,
+ Extension::FX_FLYING_CARPET,
+ Extension::GFYCAT,
+ Extension::GIST,
+ Extension::GOOGLE_DOCUMENT_EMBED,
+ Extension::HULU,
+ Extension::IMA_VIDEO,
+ Extension::IMAGE_SLIDER,
+ Extension::IMG,
+ Extension::IMGUR,
+ Extension::INSTAGRAM,
+ Extension::IZLESENE,
+ Extension::JWPLAYER,
+ Extension::KALTURA_PLAYER,
+ Extension::LIST_,
+ Extension::LIVE_LIST,
+ Extension::MATHML,
+ Extension::MEGAPHONE,
+ Extension::MOWPLAYER,
+ Extension::NEXXTV_PLAYER,
+ Extension::O2_PLAYER,
+ Extension::OOYALA_PLAYER,
+ Extension::PAN_ZOOM,
+ Extension::PINTEREST,
+ Extension::PLAYBUZZ,
+ Extension::POWR_PLAYER,
+ Extension::REACH_PLAYER,
+ Extension::REDDIT,
+ Extension::RIDDLE_QUIZ,
+ Extension::SOUNDCLOUD,
+ Extension::SPRINGBOARD_PLAYER,
+ Extension::TIMEAGO,
+ Extension::TWITTER,
+ Extension::VIDEO,
+ Extension::VIDEO_IFRAME,
+ Extension::VIMEO,
+ Extension::VINE,
+ Extension::VIQEO_PLAYER,
+ Extension::VK,
+ Extension::WISTIA_PLAYER,
+ Extension::YOTPO,
+ Extension::YOUTUBE,
+ Element::ARTICLE,
+ Element::ASIDE,
+ Element::B,
+ Element::BDI,
+ Element::BDO,
+ Element::BLOCKQUOTE,
+ Element::BR,
+ Element::BUTTON,
+ Element::CAPTION,
+ Element::CIRCLE,
+ Element::CITE,
+ Element::CLIPPATH,
+ Element::CODE,
+ Element::COL,
+ Element::COLGROUP,
+ Element::DATA,
+ Element::DD,
+ Element::DEFS,
+ Element::DEL,
+ Element::DESC,
+ Element::DFN,
+ Element::DIV,
+ Element::DL,
+ Element::DT,
+ Element::ELLIPSE,
+ Element::EM,
+ Element::FECOLORMATRIX,
+ Element::FECOMPOSITE,
+ Element::FEFLOOD,
+ Element::FEGAUSSIANBLUR,
+ Element::FEMERGE,
+ Element::FEMERGENODE,
+ Element::FEOFFSET,
+ Element::FIGCAPTION,
+ Element::FIGURE,
+ Element::FILTER,
+ Element::FOOTER,
+ Element::G,
+ Element::GLYPH,
+ Element::GLYPHREF,
+ Element::H1,
+ Element::H2,
+ Element::H3,
+ Element::H4,
+ Element::H5,
+ Element::H6,
+ Element::HEADER,
+ Element::HGROUP,
+ Element::HKERN,
+ Element::HR,
+ Element::I,
+ Element::IMAGE,
+ Element::IMG,
+ Internal::SIZER,
+ Element::INS,
+ Element::KBD,
+ Element::LI,
+ Element::LINE,
+ Element::LINEARGRADIENT,
+ Element::MAIN,
+ Element::MARK,
+ Element::MARKER,
+ Element::MASK,
+ Element::METADATA,
+ Element::NAV,
+ Element::OL,
+ Element::P,
+ Element::PATH,
+ Element::PATTERN,
+ Element::POLYGON,
+ Element::POLYLINE,
+ Element::PRE,
+ Element::Q,
+ Element::RADIALGRADIENT,
+ Element::RECT,
+ Element::RP,
+ Element::RT,
+ Element::RTC,
+ Element::RUBY,
+ Element::S,
+ Element::SAMP,
+ Element::SECTION,
+ Element::SMALL,
+ Element::SOLIDCOLOR,
+ Element::SOURCE,
+ Element::SPAN,
+ Element::STOP,
+ Element::STRONG,
+ Element::SUB,
+ Element::SUP,
+ Element::SVG,
+ Element::SWITCH_,
+ Element::SYMBOL,
+ Element::TABLE,
+ Element::TBODY,
+ Element::TD,
+ Element::TEXT,
+ Element::TEXTPATH,
+ Element::TFOOT,
+ Element::TH,
+ Element::THEAD,
+ Element::TIME,
+ Element::TITLE,
+ Element::TR,
+ Element::TRACK,
+ Element::TREF,
+ Element::TSPAN,
+ Element::U,
+ Element::UL,
+ Element::USE_,
+ Element::VAR_,
+ Element::VIEW,
+ Element::VKERN,
+ Element::WBR,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpStoryPlayerAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpStoryPlayerAllowedDescendants.php
new file mode 100644
index 000000000..bb7d6c145
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpStoryPlayerAllowedDescendants.php
@@ -0,0 +1,35 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::A,
+ Element::SPAN,
+ Internal::SIZER,
+ Element::IMG,
+ ];
+}
diff --git a/src/Validator/Spec/DescendantTagList/AmpStorySocialShareAllowedDescendants.php b/src/Validator/Spec/DescendantTagList/AmpStorySocialShareAllowedDescendants.php
new file mode 100644
index 000000000..02d96eb02
--- /dev/null
+++ b/src/Validator/Spec/DescendantTagList/AmpStorySocialShareAllowedDescendants.php
@@ -0,0 +1,31 @@
+
+ */
+ const DESCENDANT_TAGS = [
+ Element::SCRIPT,
+ ];
+}
diff --git a/src/Validator/Spec/DocRuleset.php b/src/Validator/Spec/DocRuleset.php
new file mode 100644
index 000000000..28e1652e7
--- /dev/null
+++ b/src/Validator/Spec/DocRuleset.php
@@ -0,0 +1,92 @@
+ $htmlFormat HTML format that this DocRuleset applies to.
+ */
+class DocRuleset
+{
+ /**
+ * ID of the document ruleset.
+ *
+ * This needs to be overridden in the extending class.
+ *
+ * @var string
+ */
+ const ID = '[document ruleset base class]';
+
+ /**
+ * Spec data of the document ruleset.
+ *
+ * @var array
+ */
+ const SPEC = [];
+
+ /**
+ * Get the ID of the document ruleset.
+ *
+ * @return string ID of the document ruleset.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given spec rule is present.
+ *
+ * @param string $docRulesetName Name of the spec rule to check for.
+ * @return bool Whether the given spec rule is contained in the spec.
+ */
+ public function has($docRulesetName)
+ {
+ return array_key_exists($docRulesetName, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $docRulesetName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($docRulesetName)
+ {
+ if (!$this->has($docRulesetName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($docRulesetName);
+ }
+
+ return static::SPEC[$docRulesetName];
+ }
+
+ /**
+ * Magic getter to return the spec rules.
+ *
+ * @param string $docRulesetName Name of the spec rule to return.
+ * @return mixed Value of the spec rule.
+ */
+ public function __get($docRulesetName)
+ {
+ switch ($docRulesetName) {
+ case SpecRule::HTML_FORMAT:
+ return array_key_exists($docRulesetName, static::SPEC) ? static::SPEC[$docRulesetName] : [];
+ default:
+ if (!array_key_exists($docRulesetName, static::SPEC)) {
+ throw InvalidSpecRuleName::forSpecRuleName($docRulesetName);
+ }
+
+ return static::SPEC[$docRulesetName];
+ }
+ }
+}
diff --git a/src/Validator/Spec/DocRuleset/Amp4email.php b/src/Validator/Spec/DocRuleset/Amp4email.php
new file mode 100644
index 000000000..c7745db22
--- /dev/null
+++ b/src/Validator/Spec/DocRuleset/Amp4email.php
@@ -0,0 +1,35 @@
+ [
+ Format::AMP4EMAIL,
+ ],
+ SpecRule::MAX_BYTES => 200000,
+ SpecRule::MAX_BYTES_SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/email-spec/amp-email-format/?format=email',
+ ];
+}
diff --git a/src/Validator/Spec/Error.php b/src/Validator/Spec/Error.php
new file mode 100644
index 000000000..632d9acb8
--- /dev/null
+++ b/src/Validator/Spec/Error.php
@@ -0,0 +1,70 @@
+
+ */
+ const SPEC = [];
+
+ /**
+ * Get the code of the error.
+ *
+ * @return string Code of the error.
+ */
+ public function getCode()
+ {
+ return static::CODE;
+ }
+
+ /**
+ * Check whether the error has a given spec rule.
+ *
+ * @param string $specRule Spec rule to check for.
+ * @return bool Whether the error has the given spec rule.
+ */
+ public function has($specRule)
+ {
+ return array_key_exists($specRule, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $specRuleName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($specRuleName)
+ {
+ if (!$this->has($specRuleName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($specRuleName);
+ }
+
+ return static::SPEC[$specRuleName];
+ }
+}
diff --git a/src/Validator/Spec/Error/AmpEmailMissingStrictCssAttr.php b/src/Validator/Spec/Error/AmpEmailMissingStrictCssAttr.php
new file mode 100644
index 000000000..514f4eabc
--- /dev/null
+++ b/src/Validator/Spec/Error/AmpEmailMissingStrictCssAttr.php
@@ -0,0 +1,30 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'html\' marked with attribute \'amp4email\' is missing the corresponding attribute \'data-css-strict\' for enabling strict CSS validation. This may become an error in the future.',
+ ];
+}
diff --git a/src/Validator/Spec/Error/AttrDisallowedByImpliedLayout.php b/src/Validator/Spec/Error/AttrDisallowedByImpliedLayout.php
new file mode 100644
index 000000000..f161ce912
--- /dev/null
+++ b/src/Validator/Spec/Error/AttrDisallowedByImpliedLayout.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is disallowed by implied layout \'%3\'.',
+ SpecRule::SPECIFICITY => 48,
+ ];
+}
diff --git a/src/Validator/Spec/Error/AttrDisallowedBySpecifiedLayout.php b/src/Validator/Spec/Error/AttrDisallowedBySpecifiedLayout.php
new file mode 100644
index 000000000..6f246f31b
--- /dev/null
+++ b/src/Validator/Spec/Error/AttrDisallowedBySpecifiedLayout.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is disallowed by specified layout \'%3\'.',
+ SpecRule::SPECIFICITY => 49,
+ ];
+}
diff --git a/src/Validator/Spec/Error/AttrMissingRequiredExtension.php b/src/Validator/Spec/Error/AttrMissingRequiredExtension.php
new file mode 100644
index 000000000..e88f139b0
--- /dev/null
+++ b/src/Validator/Spec/Error/AttrMissingRequiredExtension.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' requires including the \'%2\' extension JavaScript.',
+ SpecRule::SPECIFICITY => 13,
+ ];
+}
diff --git a/src/Validator/Spec/Error/AttrRequiredButMissing.php b/src/Validator/Spec/Error/AttrRequiredButMissing.php
new file mode 100644
index 000000000..4f992ffdc
--- /dev/null
+++ b/src/Validator/Spec/Error/AttrRequiredButMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is missing or incorrect, but required by attribute \'%3\'.',
+ SpecRule::SPECIFICITY => 29,
+ ];
+}
diff --git a/src/Validator/Spec/Error/AttrValueRequiredByLayout.php b/src/Validator/Spec/Error/AttrValueRequiredByLayout.php
new file mode 100644
index 000000000..719bd8bf7
--- /dev/null
+++ b/src/Validator/Spec/Error/AttrValueRequiredByLayout.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Invalid value \'%1\' for attribute \'%2\' in tag \'%3\' - for layout \'%4\', set the attribute \'%2\' to value \'%5\'.',
+ SpecRule::SPECIFICITY => 25,
+ ];
+}
diff --git a/src/Validator/Spec/Error/BaseTagMustPreceedAllUrls.php b/src/Validator/Spec/Error/BaseTagMustPreceedAllUrls.php
new file mode 100644
index 000000000..a3fc4b110
--- /dev/null
+++ b/src/Validator/Spec/Error/BaseTagMustPreceedAllUrls.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\', which contains URLs, was found earlier in the document than the BASE element.',
+ SpecRule::SPECIFICITY => 87,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CdataViolatesDenylist.php b/src/Validator/Spec/Error/CdataViolatesDenylist.php
new file mode 100644
index 000000000..777575b63
--- /dev/null
+++ b/src/Validator/Spec/Error/CdataViolatesDenylist.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The text inside tag \'%1\' contains \'%2\', which is disallowed.',
+ SpecRule::SPECIFICITY => 2,
+ ];
+}
diff --git a/src/Validator/Spec/Error/ChildTagDoesNotSatisfyReferencePoint.php b/src/Validator/Spec/Error/ChildTagDoesNotSatisfyReferencePoint.php
new file mode 100644
index 000000000..30730a90b
--- /dev/null
+++ b/src/Validator/Spec/Error/ChildTagDoesNotSatisfyReferencePoint.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\', a child tag of \'%2\', does not satisfy one of the acceptable reference points: %3.',
+ SpecRule::SPECIFICITY => 77,
+ ];
+}
diff --git a/src/Validator/Spec/Error/ChildTagDoesNotSatisfyReferencePointSingular.php b/src/Validator/Spec/Error/ChildTagDoesNotSatisfyReferencePointSingular.php
new file mode 100644
index 000000000..51b944592
--- /dev/null
+++ b/src/Validator/Spec/Error/ChildTagDoesNotSatisfyReferencePointSingular.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\', a child tag of \'%2\', does not satisfy the reference point \'%3\'.',
+ SpecRule::SPECIFICITY => 81,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssExcessivelyNested.php b/src/Validator/Spec/Error/CssExcessivelyNested.php
new file mode 100644
index 000000000..9b5e47fe4
--- /dev/null
+++ b/src/Validator/Spec/Error/CssExcessivelyNested.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS excessively nested in tag \'%1\'.',
+ SpecRule::SPECIFICITY => 125,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxBadUrl.php b/src/Validator/Spec/Error/CssSyntaxBadUrl.php
new file mode 100644
index 000000000..1687fdb63
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxBadUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - bad url.',
+ SpecRule::SPECIFICITY => 60,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedAttrSelector.php b/src/Validator/Spec/Error/CssSyntaxDisallowedAttrSelector.php
new file mode 100644
index 000000000..e66f22e51
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedAttrSelector.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS error in tag \'%1\' - disallowed attribute selector \'%2\'.',
+ SpecRule::SPECIFICITY => 120,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedDomain.php b/src/Validator/Spec/Error/CssSyntaxDisallowedDomain.php
new file mode 100644
index 000000000..d763f0f66
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedDomain.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid domain \'%2\'.',
+ SpecRule::SPECIFICITY => 69,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedImportant.php b/src/Validator/Spec/Error/CssSyntaxDisallowedImportant.php
new file mode 100644
index 000000000..2ab8e5ec4
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedImportant.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Usage of the !important CSS qualifier is not allowed.',
+ SpecRule::SPECIFICITY => 123,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedKeyframeInsideKeyframe.php b/src/Validator/Spec/Error/CssSyntaxDisallowedKeyframeInsideKeyframe.php
new file mode 100644
index 000000000..ad059a0a4
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedKeyframeInsideKeyframe.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - keyframe inside keyframe is not allowed.',
+ SpecRule::SPECIFICITY => 115,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedMediaFeature.php b/src/Validator/Spec/Error/CssSyntaxDisallowedMediaFeature.php
new file mode 100644
index 000000000..d7347b953
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedMediaFeature.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - disallowed media feature \'%2\'.',
+ SpecRule::SPECIFICITY => 119,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedMediaType.php b/src/Validator/Spec/Error/CssSyntaxDisallowedMediaType.php
new file mode 100644
index 000000000..6129d8e2e
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedMediaType.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - disallowed media type \'%2\'.',
+ SpecRule::SPECIFICITY => 118,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedPropertyValue.php b/src/Validator/Spec/Error/CssSyntaxDisallowedPropertyValue.php
new file mode 100644
index 000000000..d020f7cd2
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedPropertyValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - the property \'%2\' is set to the disallowed value \'%3\'.',
+ SpecRule::SPECIFICITY => 82,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedPropertyValueWithHint.php b/src/Validator/Spec/Error/CssSyntaxDisallowedPropertyValueWithHint.php
new file mode 100644
index 000000000..b83c82b48
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedPropertyValueWithHint.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - the property \'%2\' is set to the disallowed value \'%3\'. Allowed values: %4.',
+ SpecRule::SPECIFICITY => 83,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedPseudoClass.php b/src/Validator/Spec/Error/CssSyntaxDisallowedPseudoClass.php
new file mode 100644
index 000000000..e0b2161d7
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedPseudoClass.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS error in tag \'%1\' - disallowed pseudo class \'%2\'.',
+ SpecRule::SPECIFICITY => 121,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedPseudoElement.php b/src/Validator/Spec/Error/CssSyntaxDisallowedPseudoElement.php
new file mode 100644
index 000000000..3a21f4f06
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedPseudoElement.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS error in tag \'%1\' - disallowed pseudo element \'%2\'.',
+ SpecRule::SPECIFICITY => 122,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedQualifiedRuleMustBeInsideKeyframe.php b/src/Validator/Spec/Error/CssSyntaxDisallowedQualifiedRuleMustBeInsideKeyframe.php
new file mode 100644
index 000000000..e1c9a0403
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedQualifiedRuleMustBeInsideKeyframe.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - qualified rule \'%2\' must be located inside of a keyframe.',
+ SpecRule::SPECIFICITY => 114,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxDisallowedRelativeUrl.php b/src/Validator/Spec/Error/CssSyntaxDisallowedRelativeUrl.php
new file mode 100644
index 000000000..aa7a4b37d
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxDisallowedRelativeUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - disallowed relative url \'%2\'.',
+ SpecRule::SPECIFICITY => 72,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxEofInPreludeOfQualifiedRule.php b/src/Validator/Spec/Error/CssSyntaxEofInPreludeOfQualifiedRule.php
new file mode 100644
index 000000000..8b6b4cb3e
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxEofInPreludeOfQualifiedRule.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - end of stylesheet encountered in prelude of a qualified rule.',
+ SpecRule::SPECIFICITY => 61,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxErrorInPseudoSelector.php b/src/Validator/Spec/Error/CssSyntaxErrorInPseudoSelector.php
new file mode 100644
index 000000000..037345ae3
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxErrorInPseudoSelector.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid pseudo selector.',
+ SpecRule::SPECIFICITY => 64,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxIncompleteDeclaration.php b/src/Validator/Spec/Error/CssSyntaxIncompleteDeclaration.php
new file mode 100644
index 000000000..1706a8d38
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxIncompleteDeclaration.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - incomplete declaration.',
+ SpecRule::SPECIFICITY => 63,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidAtRule.php b/src/Validator/Spec/Error/CssSyntaxInvalidAtRule.php
new file mode 100644
index 000000000..33ed79dad
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidAtRule.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - saw invalid at rule \'@%2\'.',
+ SpecRule::SPECIFICITY => 36,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidAttrSelector.php b/src/Validator/Spec/Error/CssSyntaxInvalidAttrSelector.php
new file mode 100644
index 000000000..813f107fc
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidAttrSelector.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid attribute selector.',
+ SpecRule::SPECIFICITY => 76,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidDeclaration.php b/src/Validator/Spec/Error/CssSyntaxInvalidDeclaration.php
new file mode 100644
index 000000000..bba9e7e1f
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidDeclaration.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid declaration.',
+ SpecRule::SPECIFICITY => 62,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidProperty.php b/src/Validator/Spec/Error/CssSyntaxInvalidProperty.php
new file mode 100644
index 000000000..b0d5fdecd
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidProperty.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid property \'%2\'. The only allowed properties are \'%3\'.',
+ SpecRule::SPECIFICITY => 111,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidPropertyNolist.php b/src/Validator/Spec/Error/CssSyntaxInvalidPropertyNolist.php
new file mode 100644
index 000000000..ae17f5414
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidPropertyNolist.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid property \'%2\'.',
+ SpecRule::SPECIFICITY => 112,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidUrl.php b/src/Validator/Spec/Error/CssSyntaxInvalidUrl.php
new file mode 100644
index 000000000..9d69f8ddc
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid url \'%2\'.',
+ SpecRule::SPECIFICITY => 70,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxInvalidUrlProtocol.php b/src/Validator/Spec/Error/CssSyntaxInvalidUrlProtocol.php
new file mode 100644
index 000000000..b2b5830cb
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxInvalidUrlProtocol.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - invalid url protocol \'%2:\'.',
+ SpecRule::SPECIFICITY => 71,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxMalformedMediaQuery.php b/src/Validator/Spec/Error/CssSyntaxMalformedMediaQuery.php
new file mode 100644
index 000000000..2a3a3b84c
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxMalformedMediaQuery.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - malformed media query.',
+ SpecRule::SPECIFICITY => 117,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxMissingSelector.php b/src/Validator/Spec/Error/CssSyntaxMissingSelector.php
new file mode 100644
index 000000000..bbbbdd338
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxMissingSelector.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - missing selector.',
+ SpecRule::SPECIFICITY => 65,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxMissingUrl.php b/src/Validator/Spec/Error/CssSyntaxMissingUrl.php
new file mode 100644
index 000000000..af082e27c
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxMissingUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - missing url.',
+ SpecRule::SPECIFICITY => 68,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxNotASelectorStart.php b/src/Validator/Spec/Error/CssSyntaxNotASelectorStart.php
new file mode 100644
index 000000000..f4758039e
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxNotASelectorStart.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - not a selector start.',
+ SpecRule::SPECIFICITY => 66,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxPropertyDisallowedTogetherWith.php b/src/Validator/Spec/Error/CssSyntaxPropertyDisallowedTogetherWith.php
new file mode 100644
index 000000000..111e65c96
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxPropertyDisallowedTogetherWith.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - the property \'%2\' is disallowed together with \'%3\'. Allowed properties: %4.',
+ SpecRule::SPECIFICITY => 85,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxPropertyDisallowedWithinAtRule.php b/src/Validator/Spec/Error/CssSyntaxPropertyDisallowedWithinAtRule.php
new file mode 100644
index 000000000..8b22c4a30
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxPropertyDisallowedWithinAtRule.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - the property \'%2\' is disallowed within @%3. Allowed properties: %4.',
+ SpecRule::SPECIFICITY => 84,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxPropertyRequiresQualification.php b/src/Validator/Spec/Error/CssSyntaxPropertyRequiresQualification.php
new file mode 100644
index 000000000..5fe43ad0c
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxPropertyRequiresQualification.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - the property \'%2\' is disallowed unless the enclosing rule is prefixed with the \'%3\' qualification.',
+ SpecRule::SPECIFICITY => 86,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxQualifiedRuleHasNoDeclarations.php b/src/Validator/Spec/Error/CssSyntaxQualifiedRuleHasNoDeclarations.php
new file mode 100644
index 000000000..b1d42808e
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxQualifiedRuleHasNoDeclarations.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - qualified rule \'%2\' has no declarations.',
+ SpecRule::SPECIFICITY => 113,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxStrayTrailingBackslash.php b/src/Validator/Spec/Error/CssSyntaxStrayTrailingBackslash.php
new file mode 100644
index 000000000..03f58b974
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxStrayTrailingBackslash.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - stray trailing backslash.',
+ SpecRule::SPECIFICITY => 57,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxUnparsedInputRemainsInSelector.php b/src/Validator/Spec/Error/CssSyntaxUnparsedInputRemainsInSelector.php
new file mode 100644
index 000000000..74e47b41b
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxUnparsedInputRemainsInSelector.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - unparsed input remains in selector.',
+ SpecRule::SPECIFICITY => 67,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxUnterminatedComment.php b/src/Validator/Spec/Error/CssSyntaxUnterminatedComment.php
new file mode 100644
index 000000000..e8f4966e5
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxUnterminatedComment.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - unterminated comment.',
+ SpecRule::SPECIFICITY => 58,
+ ];
+}
diff --git a/src/Validator/Spec/Error/CssSyntaxUnterminatedString.php b/src/Validator/Spec/Error/CssSyntaxUnterminatedString.php
new file mode 100644
index 000000000..2049227e8
--- /dev/null
+++ b/src/Validator/Spec/Error/CssSyntaxUnterminatedString.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'CSS syntax error in tag \'%1\' - unterminated string.',
+ SpecRule::SPECIFICITY => 59,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DeprecatedAttr.php b/src/Validator/Spec/Error/DeprecatedAttr.php
new file mode 100644
index 000000000..c2ac2ddb6
--- /dev/null
+++ b/src/Validator/Spec/Error/DeprecatedAttr.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is deprecated - use \'%3\' instead.',
+ SpecRule::SPECIFICITY => 104,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DeprecatedTag.php b/src/Validator/Spec/Error/DeprecatedTag.php
new file mode 100644
index 000000000..d0447e645
--- /dev/null
+++ b/src/Validator/Spec/Error/DeprecatedTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is deprecated - use \'%2\' instead.',
+ SpecRule::SPECIFICITY => 105,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DevModeOnly.php b/src/Validator/Spec/Error/DevModeOnly.php
new file mode 100644
index 000000000..54457abfa
--- /dev/null
+++ b/src/Validator/Spec/Error/DevModeOnly.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'html\' marked with attribute \'data-ampdevmode\'. Validator will suppress errors regarding any other tag with this attribute.',
+ SpecRule::SPECIFICITY => 1000,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedAttr.php b/src/Validator/Spec/Error/DisallowedAttr.php
new file mode 100644
index 000000000..88907716b
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedAttr.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' may not appear in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 22,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedChildTagName.php b/src/Validator/Spec/Error/DisallowedChildTagName.php
new file mode 100644
index 000000000..57e110aa9
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedChildTagName.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'%1\' is disallowed as child of tag \'%2\'. Child tag must be one of %3.',
+ SpecRule::SPECIFICITY => 74,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedDomain.php b/src/Validator/Spec/Error/DisallowedDomain.php
new file mode 100644
index 000000000..38ac021a0
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedDomain.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The domain \'%3\' for attribute \'%1\' in tag \'%2\' is disallowed.',
+ SpecRule::SPECIFICITY => 53,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedFirstChildTagName.php b/src/Validator/Spec/Error/DisallowedFirstChildTagName.php
new file mode 100644
index 000000000..1828be6b0
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedFirstChildTagName.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'%1\' is disallowed as first child of tag \'%2\'. First child tag must be one of %3.',
+ SpecRule::SPECIFICITY => 75,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedManufacturedBody.php b/src/Validator/Spec/Error/DisallowedManufacturedBody.php
new file mode 100644
index 000000000..ad28fc0e2
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedManufacturedBody.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag or text which is only allowed inside the body section found outside of the body section.',
+ SpecRule::SPECIFICITY => 106,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedPropertyInAttrValue.php b/src/Validator/Spec/Error/DisallowedPropertyInAttrValue.php
new file mode 100644
index 000000000..3fd67ec34
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedPropertyInAttrValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The property \'%1\' in attribute \'%2\' in tag \'%3\' is disallowed.',
+ SpecRule::SPECIFICITY => 39,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedRelativeUrl.php b/src/Validator/Spec/Error/DisallowedRelativeUrl.php
new file mode 100644
index 000000000..290dc8c51
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedRelativeUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The relative URL \'%3\' for attribute \'%1\' in tag \'%2\' is disallowed.',
+ SpecRule::SPECIFICITY => 51,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedScriptTag.php b/src/Validator/Spec/Error/DisallowedScriptTag.php
new file mode 100644
index 000000000..d5338af54
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedScriptTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Custom JavaScript is not allowed.',
+ SpecRule::SPECIFICITY => 102,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedStyleAttr.php b/src/Validator/Spec/Error/DisallowedStyleAttr.php
new file mode 100644
index 000000000..0231eddc4
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedStyleAttr.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The inline \'style\' attribute is not allowed in AMP documents. Use \'style amp-custom\' tag instead.',
+ SpecRule::SPECIFICITY => 56,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedTag.php b/src/Validator/Spec/Error/DisallowedTag.php
new file mode 100644
index 000000000..18bb8a7a3
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is disallowed.',
+ SpecRule::SPECIFICITY => 21,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DisallowedTagAncestor.php b/src/Validator/Spec/Error/DisallowedTagAncestor.php
new file mode 100644
index 000000000..20f1fc0f1
--- /dev/null
+++ b/src/Validator/Spec/Error/DisallowedTagAncestor.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' may not appear as a descendant of tag \'%2\'.',
+ SpecRule::SPECIFICITY => 5,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DocumentSizeLimitExceeded.php b/src/Validator/Spec/Error/DocumentSizeLimitExceeded.php
new file mode 100644
index 000000000..032088bc3
--- /dev/null
+++ b/src/Validator/Spec/Error/DocumentSizeLimitExceeded.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Document exceeded %1 bytes limit. Actual size %2 bytes.',
+ SpecRule::SPECIFICITY => 126,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DocumentTooComplex.php b/src/Validator/Spec/Error/DocumentTooComplex.php
new file mode 100644
index 000000000..d3c7b3063
--- /dev/null
+++ b/src/Validator/Spec/Error/DocumentTooComplex.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The document is too complex.',
+ SpecRule::SPECIFICITY => 107,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DuplicateAttribute.php b/src/Validator/Spec/Error/DuplicateAttribute.php
new file mode 100644
index 000000000..6477049c9
--- /dev/null
+++ b/src/Validator/Spec/Error/DuplicateAttribute.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' contains the attribute \'%2\' repeated multiple times.',
+ SpecRule::SPECIFICITY => 24,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DuplicateDimension.php b/src/Validator/Spec/Error/DuplicateDimension.php
new file mode 100644
index 000000000..8f74f8cc5
--- /dev/null
+++ b/src/Validator/Spec/Error/DuplicateDimension.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Multiple image candidates with the same width or pixel density found in attribute \'%1\' in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 50,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DuplicateReferencePoint.php b/src/Validator/Spec/Error/DuplicateReferencePoint.php
new file mode 100644
index 000000000..08d1955fa
--- /dev/null
+++ b/src/Validator/Spec/Error/DuplicateReferencePoint.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The reference point \'%1\' for \'%2\' must be unique but a duplicate was encountered.',
+ SpecRule::SPECIFICITY => 79,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DuplicateUniqueTag.php b/src/Validator/Spec/Error/DuplicateUniqueTag.php
new file mode 100644
index 000000000..4c9cbd321
--- /dev/null
+++ b/src/Validator/Spec/Error/DuplicateUniqueTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' appears more than once in the document.',
+ SpecRule::SPECIFICITY => 30,
+ ];
+}
diff --git a/src/Validator/Spec/Error/DuplicateUniqueTagWarning.php b/src/Validator/Spec/Error/DuplicateUniqueTagWarning.php
new file mode 100644
index 000000000..47ad4075a
--- /dev/null
+++ b/src/Validator/Spec/Error/DuplicateUniqueTagWarning.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' appears more than once in the document. This will soon be an error.',
+ SpecRule::SPECIFICITY => 31,
+ ];
+}
diff --git a/src/Validator/Spec/Error/ExtensionUnused.php b/src/Validator/Spec/Error/ExtensionUnused.php
new file mode 100644
index 000000000..bcee41f6c
--- /dev/null
+++ b/src/Validator/Spec/Error/ExtensionUnused.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The extension \'%1\' was found on this page, but is unused. Please remove this extension.',
+ SpecRule::SPECIFICITY => 15,
+ ];
+}
diff --git a/src/Validator/Spec/Error/GeneralDisallowedTag.php b/src/Validator/Spec/Error/GeneralDisallowedTag.php
new file mode 100644
index 000000000..465722a79
--- /dev/null
+++ b/src/Validator/Spec/Error/GeneralDisallowedTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is disallowed except in specific forms.',
+ SpecRule::SPECIFICITY => 103,
+ ];
+}
diff --git a/src/Validator/Spec/Error/ImpliedLayoutInvalid.php b/src/Validator/Spec/Error/ImpliedLayoutInvalid.php
new file mode 100644
index 000000000..378110c21
--- /dev/null
+++ b/src/Validator/Spec/Error/ImpliedLayoutInvalid.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The implied layout \'%1\' is not supported by tag \'%2\'.',
+ SpecRule::SPECIFICITY => 46,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InconsistentUnitsForWidthAndHeight.php b/src/Validator/Spec/Error/InconsistentUnitsForWidthAndHeight.php
new file mode 100644
index 000000000..a94b9359c
--- /dev/null
+++ b/src/Validator/Spec/Error/InconsistentUnitsForWidthAndHeight.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Inconsistent units for width and height in tag \'%1\' - width is specified in \'%2\' whereas height is specified in \'%3\'.',
+ SpecRule::SPECIFICITY => 44,
+ ];
+}
diff --git a/src/Validator/Spec/Error/IncorrectMinNumChildTags.php b/src/Validator/Spec/Error/IncorrectMinNumChildTags.php
new file mode 100644
index 000000000..539d93b70
--- /dev/null
+++ b/src/Validator/Spec/Error/IncorrectMinNumChildTags.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'%1\' must have a minimum of %2 child tags - saw %3 child tags.',
+ SpecRule::SPECIFICITY => 108,
+ ];
+}
diff --git a/src/Validator/Spec/Error/IncorrectNumChildTags.php b/src/Validator/Spec/Error/IncorrectNumChildTags.php
new file mode 100644
index 000000000..65672d4d8
--- /dev/null
+++ b/src/Validator/Spec/Error/IncorrectNumChildTags.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'%1\' must have %2 child tags - saw %3 child tags.',
+ SpecRule::SPECIFICITY => 73,
+ ];
+}
diff --git a/src/Validator/Spec/Error/IncorrectScriptReleaseVersion.php b/src/Validator/Spec/Error/IncorrectScriptReleaseVersion.php
new file mode 100644
index 000000000..dc74b70f7
--- /dev/null
+++ b/src/Validator/Spec/Error/IncorrectScriptReleaseVersion.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The script version for \'%1\' is a %2 version which mismatches with the first script on the page using the %3 version.',
+ SpecRule::SPECIFICITY => 20,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InlineScriptTooLong.php b/src/Validator/Spec/Error/InlineScriptTooLong.php
new file mode 100644
index 000000000..20a88ac26
--- /dev/null
+++ b/src/Validator/Spec/Error/InlineScriptTooLong.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The inline script is %1 bytes, which exceeds the limit of %2 bytes.',
+ SpecRule::SPECIFICITY => 35,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InlineStyleTooLong.php b/src/Validator/Spec/Error/InlineStyleTooLong.php
new file mode 100644
index 000000000..b5eeba5f3
--- /dev/null
+++ b/src/Validator/Spec/Error/InlineStyleTooLong.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The inline style specified in tag \'%1\' is too long - it contains %2 bytes whereas the limit is %3 bytes.',
+ SpecRule::SPECIFICITY => 34,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidAttrValue.php b/src/Validator/Spec/Error/InvalidAttrValue.php
new file mode 100644
index 000000000..a20661b20
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidAttrValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is set to the invalid value \'%3\'.',
+ SpecRule::SPECIFICITY => 23,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidDoctypeHtml.php b/src/Validator/Spec/Error/InvalidDoctypeHtml.php
new file mode 100644
index 000000000..b3e000d7a
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidDoctypeHtml.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Invalid or missing doctype declaration. Should be \'!doctype html\'.',
+ SpecRule::SPECIFICITY => 128,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidJsonCdata.php b/src/Validator/Spec/Error/InvalidJsonCdata.php
new file mode 100644
index 000000000..d4db72056
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidJsonCdata.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The script tag contains invalid JSON that cannot be parsed.',
+ SpecRule::SPECIFICITY => 4,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidPropertyValueInAttrValue.php b/src/Validator/Spec/Error/InvalidPropertyValueInAttrValue.php
new file mode 100644
index 000000000..be5a284b4
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidPropertyValueInAttrValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The property \'%1\' in attribute \'%2\' in tag \'%3\' is set to \'%4\', which is invalid.',
+ SpecRule::SPECIFICITY => 38,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidUrl.php b/src/Validator/Spec/Error/InvalidUrl.php
new file mode 100644
index 000000000..84b45f683
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Malformed URL \'%3\' for attribute \'%1\' in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 55,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidUrlProtocol.php b/src/Validator/Spec/Error/InvalidUrlProtocol.php
new file mode 100644
index 000000000..f1b3a8e06
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidUrlProtocol.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Invalid URL protocol \'%3:\' for attribute \'%1\' in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 54,
+ ];
+}
diff --git a/src/Validator/Spec/Error/InvalidUtf8.php b/src/Validator/Spec/Error/InvalidUtf8.php
new file mode 100644
index 000000000..9633de1c0
--- /dev/null
+++ b/src/Validator/Spec/Error/InvalidUtf8.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The document contains invalid UTF8.',
+ SpecRule::SPECIFICITY => 124,
+ ];
+}
diff --git a/src/Validator/Spec/Error/LtsScriptAfterNonLts.php b/src/Validator/Spec/Error/LtsScriptAfterNonLts.php
new file mode 100644
index 000000000..5d0ba65e5
--- /dev/null
+++ b/src/Validator/Spec/Error/LtsScriptAfterNonLts.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => '\'%1\' must use the non-LTS version to correspond with the first script in the page, which does not use LTS.',
+ SpecRule::SPECIFICITY => 19,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryAnyofAttrMissing.php b/src/Validator/Spec/Error/MandatoryAnyofAttrMissing.php
new file mode 100644
index 000000000..03ddba535
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryAnyofAttrMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is missing a mandatory attribute - pick at least one of %2.',
+ SpecRule::SPECIFICITY => 28,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryAttrMissing.php b/src/Validator/Spec/Error/MandatoryAttrMissing.php
new file mode 100644
index 000000000..8b90ac0c2
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryAttrMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The mandatory attribute \'%1\' is missing in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 26,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryCdataMissingOrIncorrect.php b/src/Validator/Spec/Error/MandatoryCdataMissingOrIncorrect.php
new file mode 100644
index 000000000..946f4fe8c
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryCdataMissingOrIncorrect.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The mandatory text inside tag \'%1\' is missing or incorrect.',
+ SpecRule::SPECIFICITY => 1,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryLastChildTag.php b/src/Validator/Spec/Error/MandatoryLastChildTag.php
new file mode 100644
index 000000000..37702c379
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryLastChildTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'%1\', if present, must be the last child of tag \'%2\'.',
+ SpecRule::SPECIFICITY => 110,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryOneofAttrMissing.php b/src/Validator/Spec/Error/MandatoryOneofAttrMissing.php
new file mode 100644
index 000000000..1a7f14efd
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryOneofAttrMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is missing a mandatory attribute - pick one of %2.',
+ SpecRule::SPECIFICITY => 27,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryPropertyMissingFromAttrValue.php b/src/Validator/Spec/Error/MandatoryPropertyMissingFromAttrValue.php
new file mode 100644
index 000000000..386723406
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryPropertyMissingFromAttrValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The property \'%1\' is missing from attribute \'%2\' in tag \'%3\'.',
+ SpecRule::SPECIFICITY => 37,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryReferencePointMissing.php b/src/Validator/Spec/Error/MandatoryReferencePointMissing.php
new file mode 100644
index 000000000..7cbd242dd
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryReferencePointMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The mandatory reference point \'%1\' for \'%2\' is missing.',
+ SpecRule::SPECIFICITY => 78,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryTagAncestor.php b/src/Validator/Spec/Error/MandatoryTagAncestor.php
new file mode 100644
index 000000000..2b898f988
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryTagAncestor.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' may only appear as a descendant of tag \'%2\'.',
+ SpecRule::SPECIFICITY => 6,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryTagAncestorWithHint.php b/src/Validator/Spec/Error/MandatoryTagAncestorWithHint.php
new file mode 100644
index 000000000..e3b79f172
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryTagAncestorWithHint.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' may only appear as a descendant of tag \'%2\'. Did you mean \'%3\'?',
+ SpecRule::SPECIFICITY => 7,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MandatoryTagMissing.php b/src/Validator/Spec/Error/MandatoryTagMissing.php
new file mode 100644
index 000000000..d582c2430
--- /dev/null
+++ b/src/Validator/Spec/Error/MandatoryTagMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The mandatory tag \'%1\' is missing or incorrect.',
+ SpecRule::SPECIFICITY => 8,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MissingLayoutAttributes.php b/src/Validator/Spec/Error/MissingLayoutAttributes.php
new file mode 100644
index 000000000..e31354c04
--- /dev/null
+++ b/src/Validator/Spec/Error/MissingLayoutAttributes.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Incomplete layout attributes specified for tag \'%1\'. For example, provide attributes \'width\' and \'height\'.',
+ SpecRule::SPECIFICITY => 45,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MissingRequiredExtension.php b/src/Validator/Spec/Error/MissingRequiredExtension.php
new file mode 100644
index 000000000..74c1e5fa3
--- /dev/null
+++ b/src/Validator/Spec/Error/MissingRequiredExtension.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' requires including the \'%2\' extension JavaScript.',
+ SpecRule::SPECIFICITY => 12,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MissingUrl.php b/src/Validator/Spec/Error/MissingUrl.php
new file mode 100644
index 000000000..82542ab21
--- /dev/null
+++ b/src/Validator/Spec/Error/MissingUrl.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Missing URL for attribute \'%1\' in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 52,
+ ];
+}
diff --git a/src/Validator/Spec/Error/MutuallyExclusiveAttrs.php b/src/Validator/Spec/Error/MutuallyExclusiveAttrs.php
new file mode 100644
index 000000000..b53b2fcde
--- /dev/null
+++ b/src/Validator/Spec/Error/MutuallyExclusiveAttrs.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Mutually exclusive attributes encountered in tag \'%1\' - pick one of %2.',
+ SpecRule::SPECIFICITY => 40,
+ ];
+}
diff --git a/src/Validator/Spec/Error/NonLtsScriptAfterLts.php b/src/Validator/Spec/Error/NonLtsScriptAfterLts.php
new file mode 100644
index 000000000..94a1136d7
--- /dev/null
+++ b/src/Validator/Spec/Error/NonLtsScriptAfterLts.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => '\'%1\' must use the LTS version to correspond with the first script in the page, which uses LTS.',
+ SpecRule::SPECIFICITY => 18,
+ ];
+}
diff --git a/src/Validator/Spec/Error/NonWhitespaceCdataEncountered.php b/src/Validator/Spec/Error/NonWhitespaceCdataEncountered.php
new file mode 100644
index 000000000..0c12de307
--- /dev/null
+++ b/src/Validator/Spec/Error/NonWhitespaceCdataEncountered.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' contains text, which is disallowed.',
+ SpecRule::SPECIFICITY => 3,
+ ];
+}
diff --git a/src/Validator/Spec/Error/SpecifiedLayoutInvalid.php b/src/Validator/Spec/Error/SpecifiedLayoutInvalid.php
new file mode 100644
index 000000000..4217bcdcc
--- /dev/null
+++ b/src/Validator/Spec/Error/SpecifiedLayoutInvalid.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The specified layout \'%1\' is not supported by tag \'%2\'.',
+ SpecRule::SPECIFICITY => 47,
+ ];
+}
diff --git a/src/Validator/Spec/Error/StylesheetAndInlineStyleTooLong.php b/src/Validator/Spec/Error/StylesheetAndInlineStyleTooLong.php
new file mode 100644
index 000000000..3eb45aecd
--- /dev/null
+++ b/src/Validator/Spec/Error/StylesheetAndInlineStyleTooLong.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The author stylesheet specified in tag \'style amp-custom\' and the combined inline styles is too large - document contains %1 bytes whereas the limit is %2 bytes.',
+ SpecRule::SPECIFICITY => 33,
+ ];
+}
diff --git a/src/Validator/Spec/Error/StylesheetTooLong.php b/src/Validator/Spec/Error/StylesheetTooLong.php
new file mode 100644
index 000000000..ca9ff0a21
--- /dev/null
+++ b/src/Validator/Spec/Error/StylesheetTooLong.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The author stylesheet specified in tag \'%1\' is too long - document contains %2 bytes whereas the limit is %3 bytes.',
+ SpecRule::SPECIFICITY => 32,
+ ];
+}
diff --git a/src/Validator/Spec/Error/TagExcludedByTag.php b/src/Validator/Spec/Error/TagExcludedByTag.php
new file mode 100644
index 000000000..f31efa25b
--- /dev/null
+++ b/src/Validator/Spec/Error/TagExcludedByTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is present, but is excluded by the presence of \'%2\'.',
+ SpecRule::SPECIFICITY => 11,
+ ];
+}
diff --git a/src/Validator/Spec/Error/TagNotAllowedToHaveSiblings.php b/src/Validator/Spec/Error/TagNotAllowedToHaveSiblings.php
new file mode 100644
index 000000000..c3aa566d1
--- /dev/null
+++ b/src/Validator/Spec/Error/TagNotAllowedToHaveSiblings.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Tag \'%1\' is not allowed to have any sibling tags (\'%2\' should only have 1 child).',
+ SpecRule::SPECIFICITY => 109,
+ ];
+}
diff --git a/src/Validator/Spec/Error/TagReferencePointConflict.php b/src/Validator/Spec/Error/TagReferencePointConflict.php
new file mode 100644
index 000000000..818e7da4a
--- /dev/null
+++ b/src/Validator/Spec/Error/TagReferencePointConflict.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' conflicts with reference point \'%2\' because both define reference points.',
+ SpecRule::SPECIFICITY => 80,
+ ];
+}
diff --git a/src/Validator/Spec/Error/TagRequiredByMissing.php b/src/Validator/Spec/Error/TagRequiredByMissing.php
new file mode 100644
index 000000000..089389e79
--- /dev/null
+++ b/src/Validator/Spec/Error/TagRequiredByMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is missing or incorrect, but required by \'%2\'.',
+ SpecRule::SPECIFICITY => 10,
+ ];
+}
diff --git a/src/Validator/Spec/Error/TemplateInAttrName.php b/src/Validator/Spec/Error/TemplateInAttrName.php
new file mode 100644
index 000000000..aa1d96d46
--- /dev/null
+++ b/src/Validator/Spec/Error/TemplateInAttrName.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Mustache template syntax in attribute name \'%1\' in tag \'%2\'.',
+ SpecRule::SPECIFICITY => 43,
+ ];
+}
diff --git a/src/Validator/Spec/Error/TemplatePartialInAttrValue.php b/src/Validator/Spec/Error/TemplatePartialInAttrValue.php
new file mode 100644
index 000000000..999e761b7
--- /dev/null
+++ b/src/Validator/Spec/Error/TemplatePartialInAttrValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is set to \'%3\', which contains a Mustache template partial.',
+ SpecRule::SPECIFICITY => 42,
+ ];
+}
diff --git a/src/Validator/Spec/Error/UnescapedTemplateInAttrValue.php b/src/Validator/Spec/Error/UnescapedTemplateInAttrValue.php
new file mode 100644
index 000000000..31a281fb9
--- /dev/null
+++ b/src/Validator/Spec/Error/UnescapedTemplateInAttrValue.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The attribute \'%1\' in tag \'%2\' is set to \'%3\', which contains unescaped Mustache template syntax.',
+ SpecRule::SPECIFICITY => 41,
+ ];
+}
diff --git a/src/Validator/Spec/Error/UnknownCode.php b/src/Validator/Spec/Error/UnknownCode.php
new file mode 100644
index 000000000..be8b171cd
--- /dev/null
+++ b/src/Validator/Spec/Error/UnknownCode.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Unknown error.',
+ SpecRule::SPECIFICITY => 0,
+ ];
+}
diff --git a/src/Validator/Spec/Error/ValueSetMismatch.php b/src/Validator/Spec/Error/ValueSetMismatch.php
new file mode 100644
index 000000000..a4f4e1af5
--- /dev/null
+++ b/src/Validator/Spec/Error/ValueSetMismatch.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'Attribute \'%1\' in tag \'%2\' contains a value that does not match any other tags on the page.',
+ SpecRule::SPECIFICITY => 127,
+ ];
+}
diff --git a/src/Validator/Spec/Error/WarningExtensionDeprecatedVersion.php b/src/Validator/Spec/Error/WarningExtensionDeprecatedVersion.php
new file mode 100644
index 000000000..24a95ede5
--- /dev/null
+++ b/src/Validator/Spec/Error/WarningExtensionDeprecatedVersion.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The extension \'%1\' is referenced at version \'%2\' which is a deprecated version. Please use a more recent version of this extension. This may become an error in the future.',
+ SpecRule::SPECIFICITY => 17,
+ ];
+}
diff --git a/src/Validator/Spec/Error/WarningExtensionUnused.php b/src/Validator/Spec/Error/WarningExtensionUnused.php
new file mode 100644
index 000000000..1c0fadef1
--- /dev/null
+++ b/src/Validator/Spec/Error/WarningExtensionUnused.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The extension \'%1\' was found on this page, but is unused (no \'%2\' tag seen). This may become an error in the future.',
+ SpecRule::SPECIFICITY => 16,
+ ];
+}
diff --git a/src/Validator/Spec/Error/WarningTagRequiredByMissing.php b/src/Validator/Spec/Error/WarningTagRequiredByMissing.php
new file mode 100644
index 000000000..eca4023a7
--- /dev/null
+++ b/src/Validator/Spec/Error/WarningTagRequiredByMissing.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The tag \'%1\' is missing or incorrect, but required by \'%2\'. This will soon be an error.',
+ SpecRule::SPECIFICITY => 14,
+ ];
+}
diff --git a/src/Validator/Spec/Error/WrongParentTag.php b/src/Validator/Spec/Error/WrongParentTag.php
new file mode 100644
index 000000000..a60e8711f
--- /dev/null
+++ b/src/Validator/Spec/Error/WrongParentTag.php
@@ -0,0 +1,31 @@
+
+ */
+ const SPEC = [
+ SpecRule::FORMAT => 'The parent tag of tag \'%1\' is \'%2\', but it can only be \'%3\'.',
+ SpecRule::SPECIFICITY => 9,
+ ];
+}
diff --git a/src/Validator/Spec/ExtensionSpec.php b/src/Validator/Spec/ExtensionSpec.php
new file mode 100644
index 000000000..3c716fff6
--- /dev/null
+++ b/src/Validator/Spec/ExtensionSpec.php
@@ -0,0 +1,82 @@
+ Array of available keys.
+ */
+ public function getAvailableKeys();
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key);
+}
diff --git a/src/Validator/Spec/Iteration.php b/src/Validator/Spec/Iteration.php
new file mode 100644
index 000000000..18a323dea
--- /dev/null
+++ b/src/Validator/Spec/Iteration.php
@@ -0,0 +1,125 @@
+initIterationArray();
+
+ $key = current($this->iterationArray);
+
+ return $this->findByKey($key);
+ }
+
+ /**
+ * Move forward to next iterable object.
+ *
+ * @return void Any returned value is ignored.
+ */
+ public function next()
+ {
+ $this->initIterationArray();
+
+ next($this->iterationArray);
+ }
+
+ /**
+ * Return the ID of the current iterable object.
+ *
+ * @return string|null ID of the current iterable object, or null if out of bounds.
+ */
+ public function key()
+ {
+ $this->initIterationArray();
+
+ return key($this->iterationArray);
+ }
+
+ /**
+ * Checks if current position is valid.
+ *
+ * @return bool The return value will be casted to boolean and then evaluated.
+ * Returns true on success or false on failure.
+ */
+ public function valid()
+ {
+ $this->initIterationArray();
+
+ $key = $this->key();
+
+ return $key !== null && $key !== false;
+ }
+
+ /**
+ * Rewind the Iterator to the first iterable object.
+ *
+ * @return void Any returned value is ignored.
+ */
+ public function rewind()
+ {
+ $this->initIterationArray();
+
+ reset($this->iterationArray);
+ }
+
+ /**
+ * Initialize the iteration array.
+ */
+ private function initIterationArray()
+ {
+ if ($this->iterationArray === null) {
+ $this->iterationArray = $this->getAvailableKeys();
+ }
+ }
+
+ /**
+ * Count elements of an iterable section.
+ *
+ * @return int The custom count as an integer.
+ */
+ public function count()
+ {
+ return count($this->getAvailableKeys());
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ abstract public function getAvailableKeys();
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ abstract public function findByKey($key);
+}
diff --git a/src/Validator/Spec/Section/AttributeLists.php b/src/Validator/Spec/Section/AttributeLists.php
new file mode 100644
index 000000000..b2ae2ffd9
--- /dev/null
+++ b/src/Validator/Spec/Section/AttributeLists.php
@@ -0,0 +1,150 @@
+
+ */
+ const ATTRIBUTE_LISTS = [
+ AttributeList\CommonLinkAttrs::ID => AttributeList\CommonLinkAttrs::class,
+ AttributeList\PooolAccessAttrs::ID => AttributeList\PooolAccessAttrs::class,
+ AttributeList\CiteAttr::ID => AttributeList\CiteAttr::class,
+ AttributeList\TrackAttrsNoSubtitles::ID => AttributeList\TrackAttrsNoSubtitles::class,
+ AttributeList\TrackAttrsSubtitles::ID => AttributeList\TrackAttrsSubtitles::class,
+ AttributeList\InputCommonAttr::ID => AttributeList\InputCommonAttr::class,
+ AttributeList\AmphtmlEngineAttrs::ID => AttributeList\AmphtmlEngineAttrs::class,
+ AttributeList\AmphtmlModuleEngineAttrs::ID => AttributeList\AmphtmlModuleEngineAttrs::class,
+ AttributeList\AmphtmlNomoduleEngineAttrs::ID => AttributeList\AmphtmlNomoduleEngineAttrs::class,
+ AttributeList\MandatorySrcOrSrcset::ID => AttributeList\MandatorySrcOrSrcset::class,
+ AttributeList\MandatorySrcAmp4email::ID => AttributeList\MandatorySrcAmp4email::class,
+ AttributeList\OptionalSrcAmp4email::ID => AttributeList\OptionalSrcAmp4email::class,
+ AttributeList\ExtendedAmpGlobal::ID => AttributeList\ExtendedAmpGlobal::class,
+ AttributeList\AmpLayoutAttrs::ID => AttributeList\AmpLayoutAttrs::class,
+ AttributeList\NonceAttr::ID => AttributeList\NonceAttr::class,
+ AttributeList\CommonExtensionAttrs::ID => AttributeList\CommonExtensionAttrs::class,
+ AttributeList\MandatoryIdAttr::ID => AttributeList\MandatoryIdAttr::class,
+ AttributeList\FormNameAttr::ID => AttributeList\FormNameAttr::class,
+ AttributeList\NameAttr::ID => AttributeList\NameAttr::class,
+ AttributeList\MandatoryNameAttr::ID => AttributeList\MandatoryNameAttr::class,
+ AttributeList\GlobalAttrs::ID => AttributeList\GlobalAttrs::class,
+ AttributeList\SvgConditionalProcessingAttributes::ID => AttributeList\SvgConditionalProcessingAttributes::class,
+ AttributeList\SvgCoreAttributes::ID => AttributeList\SvgCoreAttributes::class,
+ AttributeList\SvgFilterPrimitiveAttributes::ID => AttributeList\SvgFilterPrimitiveAttributes::class,
+ AttributeList\SvgPresentationAttributes::ID => AttributeList\SvgPresentationAttributes::class,
+ AttributeList\SvgTransferFunctionAttributes::ID => AttributeList\SvgTransferFunctionAttributes::class,
+ AttributeList\SvgXlinkAttributes::ID => AttributeList\SvgXlinkAttributes::class,
+ AttributeList\SvgStyleAttr::ID => AttributeList\SvgStyleAttr::class,
+ AttributeList\AmpAudioCommon::ID => AttributeList\AmpAudioCommon::class,
+ AttributeList\AmpBaseCarouselCommon::ID => AttributeList\AmpBaseCarouselCommon::class,
+ AttributeList\AmpCarouselCommon::ID => AttributeList\AmpCarouselCommon::class,
+ AttributeList\AmpDatePickerCommonAttributes::ID => AttributeList\AmpDatePickerCommonAttributes::class,
+ AttributeList\AmpDatePickerRangeTypeAttributes::ID => AttributeList\AmpDatePickerRangeTypeAttributes::class,
+ AttributeList\AmpDatePickerSingleTypeAttributes::ID => AttributeList\AmpDatePickerSingleTypeAttributes::class,
+ AttributeList\AmpDatePickerStaticModeAttributes::ID => AttributeList\AmpDatePickerStaticModeAttributes::class,
+ AttributeList\AmpDatePickerOverlayModeAttributes::ID => AttributeList\AmpDatePickerOverlayModeAttributes::class,
+ AttributeList\AmpInputmaskCommonAttr::ID => AttributeList\AmpInputmaskCommonAttr::class,
+ AttributeList\LightboxableElements::ID => AttributeList\LightboxableElements::class,
+ AttributeList\AmpMegaphoneCommon::ID => AttributeList\AmpMegaphoneCommon::class,
+ AttributeList\AmpNestedMenuActions::ID => AttributeList\AmpNestedMenuActions::class,
+ AttributeList\InteractiveSharedConfigsAttrs::ID => AttributeList\InteractiveSharedConfigsAttrs::class,
+ AttributeList\InteractiveOptionsTextAttrs::ID => AttributeList\InteractiveOptionsTextAttrs::class,
+ AttributeList\InteractiveOptionsConfettiAttrs::ID => AttributeList\InteractiveOptionsConfettiAttrs::class,
+ AttributeList\InteractiveOptionsResultsCategoryAttrs::ID => AttributeList\InteractiveOptionsResultsCategoryAttrs::class,
+ AttributeList\AmpStreamGalleryCommon::ID => AttributeList\AmpStreamGalleryCommon::class,
+ AttributeList\AmpVideoIframeCommon::ID => AttributeList\AmpVideoIframeCommon::class,
+ AttributeList\AmpVideoCommon::ID => AttributeList\AmpVideoCommon::class,
+ ];
+
+ /**
+ * Cache of instantiated AttributeList objects.
+ *
+ * @var array
+ */
+ private $attributeLists = [];
+
+ /**
+ * Get a specific attribute list.
+ *
+ * @param string $attributeListName Name of the attribute list to get.
+ * @return Spec\AttributeList Attribute list with the given attribute list name.
+ * @throws InvalidListName If an invalid attribute list name is requested.
+ */
+ public function get($attributeListName)
+ {
+ if (!array_key_exists($attributeListName, self::ATTRIBUTE_LISTS)) {
+ throw InvalidListName::forAttributeList($attributeListName);
+ }
+
+ if (array_key_exists($attributeListName, $this->attributeLists)) {
+ return $this->attributeLists[$attributeListName];
+ }
+
+ $attributeListClassName = self::ATTRIBUTE_LISTS[$attributeListName];
+
+ /** @var Spec\AttributeList $attributeList */
+ $attributeList = new $attributeListClassName();
+
+ $this->attributeLists[$attributeListName] = $attributeList;
+
+ return $attributeList;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::ATTRIBUTE_LISTS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return AttributeList Attribute list object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/Section/CssRulesets.php b/src/Validator/Spec/Section/CssRulesets.php
new file mode 100644
index 000000000..740cde6e7
--- /dev/null
+++ b/src/Validator/Spec/Section/CssRulesets.php
@@ -0,0 +1,163 @@
+
+ */
+ const CSS_RULESETS = [
+ CssRuleset\AmpNoTransformed::ID => CssRuleset\AmpNoTransformed::class,
+ CssRuleset\AmpTransformed::ID => CssRuleset\AmpTransformed::class,
+ CssRuleset\Amp4ads::ID => CssRuleset\Amp4ads::class,
+ CssRuleset\Amp4emailDataCssStrict::ID => CssRuleset\Amp4emailDataCssStrict::class,
+ CssRuleset\Amp4emailNoDataCssStrict::ID => CssRuleset\Amp4emailNoDataCssStrict::class,
+ ];
+
+ /**
+ * Mapping of AMP format to array of CSS ruleset IDs.
+ *
+ * This is used to optimize querying by AMP format.
+ *
+ * @var array>
+ */
+ const BY_FORMAT = [
+ Format::AMP => [
+ CssRuleset\AmpNoTransformed::ID,
+ CssRuleset\AmpTransformed::ID,
+ ],
+ Format::AMP4ADS => [
+ CssRuleset\Amp4ads::ID,
+ ],
+ Format::AMP4EMAIL => [
+ CssRuleset\Amp4emailDataCssStrict::ID,
+ CssRuleset\Amp4emailNoDataCssStrict::ID,
+ ],
+ ];
+
+ /**
+ * Cache of instantiated CssRuleset objects.
+ *
+ * @var array
+ */
+ private $cssRulesetsCache = [];
+
+ /**
+ * Array used for storing the iteration index in.
+ *
+ * @var array|null
+ */
+ private $iterationArray;
+
+ /**
+ * Get a CSS ruleset by its CSS ruleset ID.
+ *
+ * @param string $cssRulesetId CSS ruleset ID to get the collection of CSS rulesets for.
+ * @return CssRuleset Requested CSS ruleset.
+ * @throws InvalidCssRulesetName If an invalid CSS ruleset name is requested.
+ */
+ public function get($cssRulesetId)
+ {
+ if (!array_key_exists($cssRulesetId, self::CSS_RULESETS)) {
+ throw InvalidCssRulesetName::forCssRulesetName($cssRulesetId);
+ }
+
+ if (array_key_exists($cssRulesetId, $this->cssRulesetsCache)) {
+ return $this->cssRulesetsCache[$cssRulesetId];
+ }
+
+ $cssRulesetClassName = self::CSS_RULESETS[$cssRulesetId];
+
+ /** @var CssRuleset $cssRuleset */
+ $cssRuleset = new $cssRulesetClassName();
+
+ $this->cssRulesetsCache[$cssRulesetId] = $cssRuleset;
+
+ return $cssRuleset;
+ }
+
+ /**
+ * Get a collection of CSS rulesets for a given AMP HTML format name.
+ *
+ * @param string $format AMP HTML format to get the CSS rulesets for.
+ * @return array Array of CSS rulesets matching the requested AMP HTML format.
+ * @throws InvalidFormat If an invalid AMP HTML format is requested.
+ */
+ public function byFormat($format)
+ {
+ if (!array_key_exists($format, self::BY_FORMAT)) {
+ throw InvalidFormat::forFormat($format);
+ }
+
+ $cssRulesetIds = self::BY_FORMAT[$format];
+ if (!is_array($cssRulesetIds)) {
+ $cssRulesetIds = [$cssRulesetIds];
+ }
+
+ $cssRulesets = [];
+ foreach ($cssRulesetIds as $cssRulesetId) {
+ $cssRulesets[] = $this->get($cssRulesetId);
+ }
+
+ return $cssRulesets;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::CSS_RULESETS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return CssRuleset Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return CssRuleset CssRuleset object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/Section/DeclarationLists.php b/src/Validator/Spec/Section/DeclarationLists.php
new file mode 100644
index 000000000..46366548d
--- /dev/null
+++ b/src/Validator/Spec/Section/DeclarationLists.php
@@ -0,0 +1,106 @@
+
+ */
+ const DECLARATION_LISTS = [
+ DeclarationList\BasicDeclarations::ID => DeclarationList\BasicDeclarations::class,
+ DeclarationList\SvgBasicDeclarations::ID => DeclarationList\SvgBasicDeclarations::class,
+ DeclarationList\EmailSpecificDeclarations::ID => DeclarationList\EmailSpecificDeclarations::class,
+ ];
+
+ /**
+ * Cache of instantiated declaration list objects.
+ *
+ * @var array
+ */
+ private $declarationLists = [];
+
+ /**
+ * Get a specific declaration list.
+ *
+ * @param string $declarationListName Name of the declaration list to get.
+ * @return Spec\DeclarationList Declaration list with the given declaration list name.
+ * @throws InvalidListName If an invalid declaration list name is requested.
+ */
+ public function get($declarationListName)
+ {
+ if (!array_key_exists($declarationListName, self::DECLARATION_LISTS)) {
+ throw InvalidListName::forDeclarationList($declarationListName);
+ }
+
+ if (array_key_exists($declarationListName, $this->declarationLists)) {
+ return $this->declarationLists[$declarationListName];
+ }
+
+ $declarationListClassName = self::DECLARATION_LISTS[$declarationListName];
+
+ /** @var Spec\DeclarationList $declarationList */
+ $declarationList = new $declarationListClassName();
+
+ $this->declarationLists[$declarationListName] = $declarationList;
+
+ return $declarationList;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::DECLARATION_LISTS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return DeclarationList Declaration list object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/Section/DescendantTagLists.php b/src/Validator/Spec/Section/DescendantTagLists.php
new file mode 100644
index 000000000..1aff128b5
--- /dev/null
+++ b/src/Validator/Spec/Section/DescendantTagLists.php
@@ -0,0 +1,111 @@
+
+ */
+ const DESCENDANT_TAG_LISTS = [
+ DescendantTagList\AmpMegaMenuAllowedDescendants::ID => DescendantTagList\AmpMegaMenuAllowedDescendants::class,
+ DescendantTagList\AmpNestedMenuAllowedDescendants::ID => DescendantTagList\AmpNestedMenuAllowedDescendants::class,
+ DescendantTagList\AmpStoryPlayerAllowedDescendants::ID => DescendantTagList\AmpStoryPlayerAllowedDescendants::class,
+ DescendantTagList\AmpStoryBookendAllowedDescendants::ID => DescendantTagList\AmpStoryBookendAllowedDescendants::class,
+ DescendantTagList\AmpStorySocialShareAllowedDescendants::ID => DescendantTagList\AmpStorySocialShareAllowedDescendants::class,
+ DescendantTagList\AmpStoryCtaLayerAllowedDescendants::ID => DescendantTagList\AmpStoryCtaLayerAllowedDescendants::class,
+ DescendantTagList\AmpStoryGridLayerAllowedDescendants::ID => DescendantTagList\AmpStoryGridLayerAllowedDescendants::class,
+ DescendantTagList\AmpStoryPageAttachmentAllowedDescendants::ID => DescendantTagList\AmpStoryPageAttachmentAllowedDescendants::class,
+ ];
+
+ /**
+ * Cache of instantiated descendant tag list objects.
+ *
+ * @var array
+ */
+ private $descendantTagLists = [];
+
+ /**
+ * Get a specific descendantTag list.
+ *
+ * @param string $descendantTagListName Name of the descendant tag list to get.
+ * @return Spec\DescendantTagList Descendant tag list with the given descendant tag list name.
+ * @throws InvalidListName If an invalid descendant tag list name is requested.
+ */
+ public function get($descendantTagListName)
+ {
+ if (!array_key_exists($descendantTagListName, self::DESCENDANT_TAG_LISTS)) {
+ throw InvalidListName::forDescendantTagList($descendantTagListName);
+ }
+
+ if (array_key_exists($descendantTagListName, $this->descendantTagLists)) {
+ return $this->descendantTagLists[$descendantTagListName];
+ }
+
+ $descendantTagListClassName = self::DESCENDANT_TAG_LISTS[$descendantTagListName];
+
+ /** @var Spec\DescendantTagList $descendantTagList */
+ $descendantTagList = new $descendantTagListClassName();
+
+ $this->descendantTagLists[$descendantTagListName] = $descendantTagList;
+
+ return $descendantTagList;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::DESCENDANT_TAG_LISTS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return DescendantTagList Descendant tag list object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/Section/DocRulesets.php b/src/Validator/Spec/Section/DocRulesets.php
new file mode 100644
index 000000000..9a8f18610
--- /dev/null
+++ b/src/Validator/Spec/Section/DocRulesets.php
@@ -0,0 +1,151 @@
+
+ */
+ const DOC_RULESETS = [
+ DocRuleset\Amp4email::ID => DocRuleset\Amp4email::class,
+ ];
+
+ /**
+ * Mapping of AMP format to array of document ruleset IDs.
+ *
+ * This is used to optimize querying by AMP format.
+ *
+ * @var array>
+ */
+ const BY_FORMAT = [
+ Format::AMP4EMAIL => [
+ DocRuleset\Amp4email::ID,
+ ],
+ ];
+
+ /**
+ * Cache of instantiated DocRuleset objects.
+ *
+ * @var array
+ */
+ private $docRulesetsCache = [];
+
+ /**
+ * Array used for storing the iteration index in.
+ *
+ * @var array|null
+ */
+ private $iterationArray;
+
+ /**
+ * Get a document ruleset by its document ruleset ID.
+ *
+ * @param string $docRulesetId document ruleset ID to get the collection of document rulesets for.
+ * @return DocRuleset Requested document ruleset.
+ * @throws InvalidDocRulesetName If an invalid document ruleset name is requested.
+ */
+ public function get($docRulesetId)
+ {
+ if (!array_key_exists($docRulesetId, self::DOC_RULESETS)) {
+ throw InvalidDocRulesetName::forDocRulesetName($docRulesetId);
+ }
+
+ if (array_key_exists($docRulesetId, $this->docRulesetsCache)) {
+ return $this->docRulesetsCache[$docRulesetId];
+ }
+
+ $docRulesetClassName = self::DOC_RULESETS[$docRulesetId];
+
+ /** @var DocRuleset $docRuleset */
+ $docRuleset = new $docRulesetClassName();
+
+ $this->docRulesetsCache[$docRulesetId] = $docRuleset;
+
+ return $docRuleset;
+ }
+
+ /**
+ * Get a collection of document rulesets for a given AMP HTML format name.
+ *
+ * @param string $format AMP HTML format to get the document rulesets for.
+ * @return array Array of document rulesets matching the requested AMP HTML format.
+ * @throws InvalidFormat If an invalid AMP HTML format is requested.
+ */
+ public function byFormat($format)
+ {
+ if (!array_key_exists($format, self::BY_FORMAT)) {
+ throw InvalidFormat::forFormat($format);
+ }
+
+ $docRulesetIds = self::BY_FORMAT[$format];
+ if (!is_array($docRulesetIds)) {
+ $docRulesetIds = [$docRulesetIds];
+ }
+
+ $docRulesets = [];
+ foreach ($docRulesetIds as $docRulesetId) {
+ $docRulesets[] = $this->get($docRulesetId);
+ }
+
+ return $docRulesets;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::DOC_RULESETS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return DocRuleset Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return DocRuleset DocRuleset object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/Section/Errors.php b/src/Validator/Spec/Section/Errors.php
new file mode 100644
index 000000000..dd0e9e2fd
--- /dev/null
+++ b/src/Validator/Spec/Section/Errors.php
@@ -0,0 +1,220 @@
+
+ */
+ const ERRORS = [
+ Error\UnknownCode::CODE => Error\UnknownCode::class,
+ Error\InvalidDoctypeHtml::CODE => Error\InvalidDoctypeHtml::class,
+ Error\MandatoryTagMissing::CODE => Error\MandatoryTagMissing::class,
+ Error\TagRequiredByMissing::CODE => Error\TagRequiredByMissing::class,
+ Error\WarningTagRequiredByMissing::CODE => Error\WarningTagRequiredByMissing::class,
+ Error\TagExcludedByTag::CODE => Error\TagExcludedByTag::class,
+ Error\WarningExtensionUnused::CODE => Error\WarningExtensionUnused::class,
+ Error\ExtensionUnused::CODE => Error\ExtensionUnused::class,
+ Error\WarningExtensionDeprecatedVersion::CODE => Error\WarningExtensionDeprecatedVersion::class,
+ Error\IncorrectScriptReleaseVersion::CODE => Error\IncorrectScriptReleaseVersion::class,
+ Error\NonLtsScriptAfterLts::CODE => Error\NonLtsScriptAfterLts::class,
+ Error\LtsScriptAfterNonLts::CODE => Error\LtsScriptAfterNonLts::class,
+ Error\AttrRequiredButMissing::CODE => Error\AttrRequiredButMissing::class,
+ Error\DisallowedTag::CODE => Error\DisallowedTag::class,
+ Error\GeneralDisallowedTag::CODE => Error\GeneralDisallowedTag::class,
+ Error\DisallowedScriptTag::CODE => Error\DisallowedScriptTag::class,
+ Error\DisallowedAttr::CODE => Error\DisallowedAttr::class,
+ Error\DisallowedStyleAttr::CODE => Error\DisallowedStyleAttr::class,
+ Error\InvalidAttrValue::CODE => Error\InvalidAttrValue::class,
+ Error\DuplicateAttribute::CODE => Error\DuplicateAttribute::class,
+ Error\AttrValueRequiredByLayout::CODE => Error\AttrValueRequiredByLayout::class,
+ Error\MissingLayoutAttributes::CODE => Error\MissingLayoutAttributes::class,
+ Error\ImpliedLayoutInvalid::CODE => Error\ImpliedLayoutInvalid::class,
+ Error\SpecifiedLayoutInvalid::CODE => Error\SpecifiedLayoutInvalid::class,
+ Error\MandatoryAttrMissing::CODE => Error\MandatoryAttrMissing::class,
+ Error\InconsistentUnitsForWidthAndHeight::CODE => Error\InconsistentUnitsForWidthAndHeight::class,
+ Error\StylesheetTooLong::CODE => Error\StylesheetTooLong::class,
+ Error\StylesheetAndInlineStyleTooLong::CODE => Error\StylesheetAndInlineStyleTooLong::class,
+ Error\InlineStyleTooLong::CODE => Error\InlineStyleTooLong::class,
+ Error\InlineScriptTooLong::CODE => Error\InlineScriptTooLong::class,
+ Error\MandatoryCdataMissingOrIncorrect::CODE => Error\MandatoryCdataMissingOrIncorrect::class,
+ Error\CdataViolatesDenylist::CODE => Error\CdataViolatesDenylist::class,
+ Error\NonWhitespaceCdataEncountered::CODE => Error\NonWhitespaceCdataEncountered::class,
+ Error\InvalidJsonCdata::CODE => Error\InvalidJsonCdata::class,
+ Error\DisallowedPropertyInAttrValue::CODE => Error\DisallowedPropertyInAttrValue::class,
+ Error\InvalidPropertyValueInAttrValue::CODE => Error\InvalidPropertyValueInAttrValue::class,
+ Error\DuplicateDimension::CODE => Error\DuplicateDimension::class,
+ Error\MissingUrl::CODE => Error\MissingUrl::class,
+ Error\InvalidUrl::CODE => Error\InvalidUrl::class,
+ Error\InvalidUrlProtocol::CODE => Error\InvalidUrlProtocol::class,
+ Error\DisallowedDomain::CODE => Error\DisallowedDomain::class,
+ Error\DisallowedRelativeUrl::CODE => Error\DisallowedRelativeUrl::class,
+ Error\MandatoryPropertyMissingFromAttrValue::CODE => Error\MandatoryPropertyMissingFromAttrValue::class,
+ Error\UnescapedTemplateInAttrValue::CODE => Error\UnescapedTemplateInAttrValue::class,
+ Error\TemplatePartialInAttrValue::CODE => Error\TemplatePartialInAttrValue::class,
+ Error\DeprecatedTag::CODE => Error\DeprecatedTag::class,
+ Error\DeprecatedAttr::CODE => Error\DeprecatedAttr::class,
+ Error\MutuallyExclusiveAttrs::CODE => Error\MutuallyExclusiveAttrs::class,
+ Error\MandatoryOneofAttrMissing::CODE => Error\MandatoryOneofAttrMissing::class,
+ Error\MandatoryAnyofAttrMissing::CODE => Error\MandatoryAnyofAttrMissing::class,
+ Error\WrongParentTag::CODE => Error\WrongParentTag::class,
+ Error\DisallowedTagAncestor::CODE => Error\DisallowedTagAncestor::class,
+ Error\MandatoryTagAncestor::CODE => Error\MandatoryTagAncestor::class,
+ Error\MandatoryTagAncestorWithHint::CODE => Error\MandatoryTagAncestorWithHint::class,
+ Error\DuplicateUniqueTag::CODE => Error\DuplicateUniqueTag::class,
+ Error\DuplicateUniqueTagWarning::CODE => Error\DuplicateUniqueTagWarning::class,
+ Error\TemplateInAttrName::CODE => Error\TemplateInAttrName::class,
+ Error\AttrDisallowedByImpliedLayout::CODE => Error\AttrDisallowedByImpliedLayout::class,
+ Error\AttrDisallowedBySpecifiedLayout::CODE => Error\AttrDisallowedBySpecifiedLayout::class,
+ Error\IncorrectNumChildTags::CODE => Error\IncorrectNumChildTags::class,
+ Error\IncorrectMinNumChildTags::CODE => Error\IncorrectMinNumChildTags::class,
+ Error\TagNotAllowedToHaveSiblings::CODE => Error\TagNotAllowedToHaveSiblings::class,
+ Error\MandatoryLastChildTag::CODE => Error\MandatoryLastChildTag::class,
+ Error\DisallowedChildTagName::CODE => Error\DisallowedChildTagName::class,
+ Error\DisallowedFirstChildTagName::CODE => Error\DisallowedFirstChildTagName::class,
+ Error\DisallowedManufacturedBody::CODE => Error\DisallowedManufacturedBody::class,
+ Error\ChildTagDoesNotSatisfyReferencePoint::CODE => Error\ChildTagDoesNotSatisfyReferencePoint::class,
+ Error\ChildTagDoesNotSatisfyReferencePointSingular::CODE => Error\ChildTagDoesNotSatisfyReferencePointSingular::class,
+ Error\MandatoryReferencePointMissing::CODE => Error\MandatoryReferencePointMissing::class,
+ Error\DuplicateReferencePoint::CODE => Error\DuplicateReferencePoint::class,
+ Error\TagReferencePointConflict::CODE => Error\TagReferencePointConflict::class,
+ Error\BaseTagMustPreceedAllUrls::CODE => Error\BaseTagMustPreceedAllUrls::class,
+ Error\MissingRequiredExtension::CODE => Error\MissingRequiredExtension::class,
+ Error\AttrMissingRequiredExtension::CODE => Error\AttrMissingRequiredExtension::class,
+ Error\DocumentTooComplex::CODE => Error\DocumentTooComplex::class,
+ Error\InvalidUtf8::CODE => Error\InvalidUtf8::class,
+ Error\CssSyntaxInvalidAtRule::CODE => Error\CssSyntaxInvalidAtRule::class,
+ Error\CssSyntaxStrayTrailingBackslash::CODE => Error\CssSyntaxStrayTrailingBackslash::class,
+ Error\CssSyntaxUnterminatedComment::CODE => Error\CssSyntaxUnterminatedComment::class,
+ Error\CssSyntaxUnterminatedString::CODE => Error\CssSyntaxUnterminatedString::class,
+ Error\CssSyntaxBadUrl::CODE => Error\CssSyntaxBadUrl::class,
+ Error\CssSyntaxEofInPreludeOfQualifiedRule::CODE => Error\CssSyntaxEofInPreludeOfQualifiedRule::class,
+ Error\CssSyntaxInvalidProperty::CODE => Error\CssSyntaxInvalidProperty::class,
+ Error\CssSyntaxInvalidPropertyNolist::CODE => Error\CssSyntaxInvalidPropertyNolist::class,
+ Error\CssSyntaxQualifiedRuleHasNoDeclarations::CODE => Error\CssSyntaxQualifiedRuleHasNoDeclarations::class,
+ Error\CssSyntaxDisallowedQualifiedRuleMustBeInsideKeyframe::CODE => Error\CssSyntaxDisallowedQualifiedRuleMustBeInsideKeyframe::class,
+ Error\CssSyntaxDisallowedKeyframeInsideKeyframe::CODE => Error\CssSyntaxDisallowedKeyframeInsideKeyframe::class,
+ Error\CssSyntaxInvalidDeclaration::CODE => Error\CssSyntaxInvalidDeclaration::class,
+ Error\CssSyntaxIncompleteDeclaration::CODE => Error\CssSyntaxIncompleteDeclaration::class,
+ Error\CssSyntaxErrorInPseudoSelector::CODE => Error\CssSyntaxErrorInPseudoSelector::class,
+ Error\CssSyntaxMissingSelector::CODE => Error\CssSyntaxMissingSelector::class,
+ Error\CssSyntaxNotASelectorStart::CODE => Error\CssSyntaxNotASelectorStart::class,
+ Error\CssSyntaxUnparsedInputRemainsInSelector::CODE => Error\CssSyntaxUnparsedInputRemainsInSelector::class,
+ Error\CssSyntaxMissingUrl::CODE => Error\CssSyntaxMissingUrl::class,
+ Error\CssSyntaxInvalidUrl::CODE => Error\CssSyntaxInvalidUrl::class,
+ Error\CssSyntaxInvalidUrlProtocol::CODE => Error\CssSyntaxInvalidUrlProtocol::class,
+ Error\CssSyntaxDisallowedDomain::CODE => Error\CssSyntaxDisallowedDomain::class,
+ Error\CssSyntaxDisallowedRelativeUrl::CODE => Error\CssSyntaxDisallowedRelativeUrl::class,
+ Error\CssSyntaxInvalidAttrSelector::CODE => Error\CssSyntaxInvalidAttrSelector::class,
+ Error\CssSyntaxDisallowedPropertyValue::CODE => Error\CssSyntaxDisallowedPropertyValue::class,
+ Error\CssSyntaxDisallowedPropertyValueWithHint::CODE => Error\CssSyntaxDisallowedPropertyValueWithHint::class,
+ Error\CssSyntaxDisallowedImportant::CODE => Error\CssSyntaxDisallowedImportant::class,
+ Error\CssSyntaxPropertyDisallowedWithinAtRule::CODE => Error\CssSyntaxPropertyDisallowedWithinAtRule::class,
+ Error\CssSyntaxPropertyDisallowedTogetherWith::CODE => Error\CssSyntaxPropertyDisallowedTogetherWith::class,
+ Error\CssSyntaxPropertyRequiresQualification::CODE => Error\CssSyntaxPropertyRequiresQualification::class,
+ Error\CssSyntaxMalformedMediaQuery::CODE => Error\CssSyntaxMalformedMediaQuery::class,
+ Error\CssSyntaxDisallowedMediaType::CODE => Error\CssSyntaxDisallowedMediaType::class,
+ Error\CssSyntaxDisallowedMediaFeature::CODE => Error\CssSyntaxDisallowedMediaFeature::class,
+ Error\CssSyntaxDisallowedAttrSelector::CODE => Error\CssSyntaxDisallowedAttrSelector::class,
+ Error\CssSyntaxDisallowedPseudoClass::CODE => Error\CssSyntaxDisallowedPseudoClass::class,
+ Error\CssSyntaxDisallowedPseudoElement::CODE => Error\CssSyntaxDisallowedPseudoElement::class,
+ Error\CssExcessivelyNested::CODE => Error\CssExcessivelyNested::class,
+ Error\DocumentSizeLimitExceeded::CODE => Error\DocumentSizeLimitExceeded::class,
+ Error\ValueSetMismatch::CODE => Error\ValueSetMismatch::class,
+ Error\DevModeOnly::CODE => Error\DevModeOnly::class,
+ Error\AmpEmailMissingStrictCssAttr::CODE => Error\AmpEmailMissingStrictCssAttr::class,
+ ];
+
+ /**
+ * Cache of instantiated Error objects.
+ *
+ * @var array
+ */
+ private $errors = [];
+
+ /**
+ * Get a specific error.
+ *
+ * @param string $errorCode Code of the error to get.
+ * @return Spec\Error Error with the given error code.
+ * @throws InvalidErrorCode If an invalid error code is requested.
+ */
+ public function get($errorCode)
+ {
+ if (!array_key_exists($errorCode, self::ERRORS)) {
+ throw InvalidErrorCode::forErrorCode($errorCode);
+ }
+
+ if (array_key_exists($errorCode, $this->errors)) {
+ return $this->errors[$errorCode];
+ }
+
+ $errorClassName = self::ERRORS[$errorCode];
+
+ /** @var Spec\Error $error */
+ $error = new $errorClassName();
+
+ $this->errors[$errorCode] = $error;
+
+ return $error;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::ERRORS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return Error Error object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/Section/Tags.php b/src/Validator/Spec/Section/Tags.php
new file mode 100644
index 000000000..d3895288e
--- /dev/null
+++ b/src/Validator/Spec/Section/Tags.php
@@ -0,0 +1,3121 @@
+
+ */
+ const TAGS = [
+ Tag\HtmlDoctype::ID => Tag\HtmlDoctype::class,
+ Tag\HtmlDoctypeAmp4ads::ID => Tag\HtmlDoctypeAmp4ads::class,
+ Tag\Html::ID => Tag\Html::class,
+ Tag\HtmlTransformed::ID => Tag\HtmlTransformed::class,
+ Tag\Head::ID => Tag\Head::class,
+ Tag\Title::ID => Tag\Title::class,
+ Tag\TitleAmp4email::ID => Tag\TitleAmp4email::class,
+ Tag\Base::ID => Tag\Base::class,
+ Tag\LinkRel::ID => Tag\LinkRel::class,
+ Tag\LinkRelCanonical::ID => Tag\LinkRelCanonical::class,
+ Tag\LinkRelManifest::ID => Tag\LinkRelManifest::class,
+ Tag\LinkRelModulepreload::ID => Tag\LinkRelModulepreload::class,
+ Tag\LinkRelPreload::ID => Tag\LinkRelPreload::class,
+ Tag\LinkRelStylesheetForFonts::ID => Tag\LinkRelStylesheetForFonts::class,
+ Tag\LinkItempropSameas::ID => Tag\LinkItempropSameas::class,
+ Tag\LinkItemprop::ID => Tag\LinkItemprop::class,
+ Tag\LinkProperty::ID => Tag\LinkProperty::class,
+ Tag\MetaCharsetUtf8::ID => Tag\MetaCharsetUtf8::class,
+ Tag\MetaNameViewport::ID => Tag\MetaNameViewport::class,
+ Tag\MetaHttpEquivXUaCompatible::ID => Tag\MetaHttpEquivXUaCompatible::class,
+ Tag\MetaNameAppleItunesApp::ID => Tag\MetaNameAppleItunesApp::class,
+ Tag\MetaNameAmpExperimentsOptIn::ID => Tag\MetaNameAmpExperimentsOptIn::class,
+ Tag\MetaNameAmp3pIframeSrc::ID => Tag\MetaNameAmp3pIframeSrc::class,
+ Tag\MetaNameAmpConsentBlocking::ID => Tag\MetaNameAmpConsentBlocking::class,
+ Tag\MetaNameAmpExperimentToken::ID => Tag\MetaNameAmpExperimentToken::class,
+ Tag\MetaNameAmpLinkVariableAllowedOrigin::ID => Tag\MetaNameAmpLinkVariableAllowedOrigin::class,
+ Tag\MetaNameAmpGoogleClientidIdApi::ID => Tag\MetaNameAmpGoogleClientidIdApi::class,
+ Tag\MetaNameAmpAdDoubleclickSra::ID => Tag\MetaNameAmpAdDoubleclickSra::class,
+ Tag\MetaNameAmpListLoadMore::ID => Tag\MetaNameAmpListLoadMore::class,
+ Tag\MetaNameAmpRecaptchaInput::ID => Tag\MetaNameAmpRecaptchaInput::class,
+ Tag\MetaNameAmpScriptSrc::ID => Tag\MetaNameAmpScriptSrc::class,
+ Tag\MetaNameAmp4adsId::ID => Tag\MetaNameAmp4adsId::class,
+ Tag\MetaNameAndContent::ID => Tag\MetaNameAndContent::class,
+ Tag\MetaHttpEquivContentType::ID => Tag\MetaHttpEquivContentType::class,
+ Tag\MetaHttpEquivContentLanguage::ID => Tag\MetaHttpEquivContentLanguage::class,
+ Tag\MetaHttpEquivPicsLabel::ID => Tag\MetaHttpEquivPicsLabel::class,
+ Tag\MetaHttpEquivImagetoolbar::ID => Tag\MetaHttpEquivImagetoolbar::class,
+ Tag\MetaHttpEquivContentStyleType::ID => Tag\MetaHttpEquivContentStyleType::class,
+ Tag\MetaHttpEquivContentScriptType::ID => Tag\MetaHttpEquivContentScriptType::class,
+ Tag\MetaHttpEquivOriginTrial::ID => Tag\MetaHttpEquivOriginTrial::class,
+ Tag\MetaHttpEquivResourceType::ID => Tag\MetaHttpEquivResourceType::class,
+ Tag\MetaHttpEquivXDnsPrefetchControl::ID => Tag\MetaHttpEquivXDnsPrefetchControl::class,
+ Tag\MetaNameAmpAdEnableRefresh::ID => Tag\MetaNameAmpAdEnableRefresh::class,
+ Tag\MetaNameAmpToAmpNavigation::ID => Tag\MetaNameAmpToAmpNavigation::class,
+ Tag\MetaNameAmpCtaType::ID => Tag\MetaNameAmpCtaType::class,
+ Tag\MetaNameAmpCtaUrl::ID => Tag\MetaNameAmpCtaUrl::class,
+ Tag\MetaNameAmpCtaLandingPageType::ID => Tag\MetaNameAmpCtaLandingPageType::class,
+ Tag\MetaNameAmp4adsVars::ID => Tag\MetaNameAmp4adsVars::class,
+ Tag\Body::ID => Tag\Body::class,
+ Tag\Article::ID => Tag\Article::class,
+ Tag\Section::ID => Tag\Section::class,
+ Tag\SectionAmp4email::ID => Tag\SectionAmp4email::class,
+ Tag\Nav::ID => Tag\Nav::class,
+ Tag\Aside::ID => Tag\Aside::class,
+ Tag\H1::ID => Tag\H1::class,
+ Tag\H2::ID => Tag\H2::class,
+ Tag\H3::ID => Tag\H3::class,
+ Tag\H4::ID => Tag\H4::class,
+ Tag\H5::ID => Tag\H5::class,
+ Tag\H6::ID => Tag\H6::class,
+ Tag\Header::ID => Tag\Header::class,
+ Tag\Footer::ID => Tag\Footer::class,
+ Tag\Address::ID => Tag\Address::class,
+ Tag\P::ID => Tag\P::class,
+ Tag\Hr::ID => Tag\Hr::class,
+ Tag\Pre::ID => Tag\Pre::class,
+ Tag\Blockquote::ID => Tag\Blockquote::class,
+ Tag\Ol::ID => Tag\Ol::class,
+ Tag\Ul::ID => Tag\Ul::class,
+ Tag\Li::ID => Tag\Li::class,
+ Tag\Dl::ID => Tag\Dl::class,
+ Tag\Dt::ID => Tag\Dt::class,
+ Tag\Dd::ID => Tag\Dd::class,
+ Tag\Figure::ID => Tag\Figure::class,
+ Tag\Figcaption::ID => Tag\Figcaption::class,
+ Tag\Div::ID => Tag\Div::class,
+ Tag\Main::ID => Tag\Main::class,
+ Tag\A::ID => Tag\A::class,
+ Tag\AAmp4email::ID => Tag\AAmp4email::class,
+ Tag\Em::ID => Tag\Em::class,
+ Tag\Strong::ID => Tag\Strong::class,
+ Tag\Small::ID => Tag\Small::class,
+ Tag\S::ID => Tag\S::class,
+ Tag\Cite::ID => Tag\Cite::class,
+ Tag\Q::ID => Tag\Q::class,
+ Tag\Dfn::ID => Tag\Dfn::class,
+ Tag\Abbr::ID => Tag\Abbr::class,
+ Tag\Data::ID => Tag\Data::class,
+ Tag\Time::ID => Tag\Time::class,
+ Tag\Code::ID => Tag\Code::class,
+ Tag\Var_::ID => Tag\Var_::class,
+ Tag\Samp::ID => Tag\Samp::class,
+ Tag\Kbd::ID => Tag\Kbd::class,
+ Tag\Sub::ID => Tag\Sub::class,
+ Tag\Sup::ID => Tag\Sup::class,
+ Tag\I::ID => Tag\I::class,
+ Tag\B::ID => Tag\B::class,
+ Tag\U::ID => Tag\U::class,
+ Tag\Mark::ID => Tag\Mark::class,
+ Tag\Ruby::ID => Tag\Ruby::class,
+ Tag\Rb::ID => Tag\Rb::class,
+ Tag\Rt::ID => Tag\Rt::class,
+ Tag\Rtc::ID => Tag\Rtc::class,
+ Tag\Rp::ID => Tag\Rp::class,
+ Tag\Bdi::ID => Tag\Bdi::class,
+ Tag\Bdo::ID => Tag\Bdo::class,
+ Tag\Span::ID => Tag\Span::class,
+ Tag\Br::ID => Tag\Br::class,
+ Tag\Wbr::ID => Tag\Wbr::class,
+ Tag\Ins::ID => Tag\Ins::class,
+ Tag\Del::ID => Tag\Del::class,
+ Tag\NoscriptImg::ID => Tag\NoscriptImg::class,
+ Tag\Iframe::ID => Tag\Iframe::class,
+ Tag\Video::ID => Tag\Video::class,
+ Tag\Audio::ID => Tag\Audio::class,
+ Tag\Picture::ID => Tag\Picture::class,
+ Tag\PictureSource::ID => Tag\PictureSource::class,
+ Tag\AmpVideoSource::ID => Tag\AmpVideoSource::class,
+ Tag\AmpAudioSource::ID => Tag\AmpAudioSource::class,
+ Tag\AudioSource::ID => Tag\AudioSource::class,
+ Tag\VideoSource::ID => Tag\VideoSource::class,
+ Tag\AmpImaVideoSource::ID => Tag\AmpImaVideoSource::class,
+ Tag\AudioTrack::ID => Tag\AudioTrack::class,
+ Tag\AudioTrackKindSubtitles::ID => Tag\AudioTrackKindSubtitles::class,
+ Tag\VideoTrack::ID => Tag\VideoTrack::class,
+ Tag\VideoTrackKindSubtitles::ID => Tag\VideoTrackKindSubtitles::class,
+ Tag\AmpAudioTrack::ID => Tag\AmpAudioTrack::class,
+ Tag\AmpAudioTrackKindSubtitles::ID => Tag\AmpAudioTrackKindSubtitles::class,
+ Tag\AmpVideoTrack::ID => Tag\AmpVideoTrack::class,
+ Tag\AmpVideoTrackKindSubtitles::ID => Tag\AmpVideoTrackKindSubtitles::class,
+ Tag\AmpImaVideoTrack::ID => Tag\AmpImaVideoTrack::class,
+ Tag\AmpImaVideoTrackKindSubtitles::ID => Tag\AmpImaVideoTrackKindSubtitles::class,
+ Tag\Table::ID => Tag\Table::class,
+ Tag\Caption::ID => Tag\Caption::class,
+ Tag\Colgroup::ID => Tag\Colgroup::class,
+ Tag\Col::ID => Tag\Col::class,
+ Tag\Tbody::ID => Tag\Tbody::class,
+ Tag\Thead::ID => Tag\Thead::class,
+ Tag\Tfoot::ID => Tag\Tfoot::class,
+ Tag\Tr::ID => Tag\Tr::class,
+ Tag\Td::ID => Tag\Td::class,
+ Tag\Th::ID => Tag\Th::class,
+ Tag\FormMethodGet::ID => Tag\FormMethodGet::class,
+ Tag\FormMethodPost::ID => Tag\FormMethodPost::class,
+ Tag\FormMethodGetAmp4email::ID => Tag\FormMethodGetAmp4email::class,
+ Tag\FormMethodPostAmp4email::ID => Tag\FormMethodPostAmp4email::class,
+ Tag\FormDivVerifyError::ID => Tag\FormDivVerifyError::class,
+ Tag\FormDivVerifyErrorTemplate::ID => Tag\FormDivVerifyErrorTemplate::class,
+ Tag\FormDivSubmitting::ID => Tag\FormDivSubmitting::class,
+ Tag\FormDivSubmittingTemplate::ID => Tag\FormDivSubmittingTemplate::class,
+ Tag\FormDivSubmitSuccess::ID => Tag\FormDivSubmitSuccess::class,
+ Tag\FormDivSubmitSuccessTemplate::ID => Tag\FormDivSubmitSuccessTemplate::class,
+ Tag\FormDivSubmitError::ID => Tag\FormDivSubmitError::class,
+ Tag\FormDivSubmitErrorTemplate::ID => Tag\FormDivSubmitErrorTemplate::class,
+ Tag\Label::ID => Tag\Label::class,
+ Tag\Input::ID => Tag\Input::class,
+ Tag\InputTypeFile::ID => Tag\InputTypeFile::class,
+ Tag\InputTypePassword::ID => Tag\InputTypePassword::class,
+ Tag\Button::ID => Tag\Button::class,
+ Tag\AmpAppBannerButtonOpenButton::ID => Tag\AmpAppBannerButtonOpenButton::class,
+ Tag\Select::ID => Tag\Select::class,
+ Tag\Datalist::ID => Tag\Datalist::class,
+ Tag\Optgroup::ID => Tag\Optgroup::class,
+ Tag\Option::ID => Tag\Option::class,
+ Tag\Textarea::ID => Tag\Textarea::class,
+ Tag\Output::ID => Tag\Output::class,
+ Tag\Progress::ID => Tag\Progress::class,
+ Tag\Meter::ID => Tag\Meter::class,
+ Tag\Fieldset::ID => Tag\Fieldset::class,
+ Tag\Legend::ID => Tag\Legend::class,
+ Tag\Details::ID => Tag\Details::class,
+ Tag\Summary::ID => Tag\Summary::class,
+ Tag\AmphtmlEngineScript::ID => Tag\AmphtmlEngineScript::class,
+ Tag\AmphtmlEngineScriptLts::ID => Tag\AmphtmlEngineScriptLts::class,
+ Tag\AmphtmlModuleEngineScript::ID => Tag\AmphtmlModuleEngineScript::class,
+ Tag\AmphtmlNomoduleEngineScript::ID => Tag\AmphtmlNomoduleEngineScript::class,
+ Tag\AmphtmlModuleLtsEngineScript::ID => Tag\AmphtmlModuleLtsEngineScript::class,
+ Tag\AmphtmlNomoduleLtsEngineScript::ID => Tag\AmphtmlNomoduleLtsEngineScript::class,
+ Tag\AmphtmlEngineScriptAmp4email::ID => Tag\AmphtmlEngineScriptAmp4email::class,
+ Tag\Amp4adsEngineScript::ID => Tag\Amp4adsEngineScript::class,
+ Tag\ScriptTypeApplicationLdJson::ID => Tag\ScriptTypeApplicationLdJson::class,
+ Tag\ScriptIdAmpRtc::ID => Tag\ScriptIdAmpRtc::class,
+ Tag\AmpImaVideoScriptTypeApplicationJson::ID => Tag\AmpImaVideoScriptTypeApplicationJson::class,
+ Tag\ScriptAmpOnerror::ID => Tag\ScriptAmpOnerror::class,
+ Tag\NoscriptEnclosureForBoilerplate::ID => Tag\NoscriptEnclosureForBoilerplate::class,
+ Tag\NoscriptEnclosureForBoilerplateTransformed::ID => Tag\NoscriptEnclosureForBoilerplateTransformed::class,
+ Tag\Noscript::ID => Tag\Noscript::class,
+ Tag\Acronym::ID => Tag\Acronym::class,
+ Tag\Big::ID => Tag\Big::class,
+ Tag\Center::ID => Tag\Center::class,
+ Tag\Dir::ID => Tag\Dir::class,
+ Tag\Hgroup::ID => Tag\Hgroup::class,
+ Tag\Listing::ID => Tag\Listing::class,
+ Tag\Multicol::ID => Tag\Multicol::class,
+ Tag\Nextid::ID => Tag\Nextid::class,
+ Tag\Nobr::ID => Tag\Nobr::class,
+ Tag\Spacer::ID => Tag\Spacer::class,
+ Tag\Strike::ID => Tag\Strike::class,
+ Tag\Tt::ID => Tag\Tt::class,
+ Tag\Slot::ID => Tag\Slot::class,
+ Tag\OP::ID => Tag\OP::class,
+ Tag\AmpImg::ID => Tag\AmpImg::class,
+ Tag\AmpImgTransformed::ID => Tag\AmpImgTransformed::class,
+ Tag\AmpImgAmp4email::ID => Tag\AmpImgAmp4email::class,
+ Tag\AmpLayout::ID => Tag\AmpLayout::class,
+ Tag\AmpPixel::ID => Tag\AmpPixel::class,
+ Tag\IAmphtmlSizerResponsive::ID => Tag\IAmphtmlSizerResponsive::class,
+ Tag\IAmphtmlSizerIntrinsic::ID => Tag\IAmphtmlSizerIntrinsic::class,
+ Tag\ImgIAmphtmlIntrinsicSizer::ID => Tag\ImgIAmphtmlIntrinsicSizer::class,
+ Tag\AmpImgImgTransformed::ID => Tag\AmpImgImgTransformed::class,
+ Tag\AmpImgImgPlaceholderTransformed::ID => Tag\AmpImgImgPlaceholderTransformed::class,
+ Tag\StyleAmpCustomLengthCheck::ID => Tag\StyleAmpCustomLengthCheck::class,
+ Tag\StyleAmpCustom::ID => Tag\StyleAmpCustom::class,
+ Tag\StyleAmpCustomAmp4ads::ID => Tag\StyleAmpCustomAmp4ads::class,
+ Tag\StyleAmpCustomAmp4email::ID => Tag\StyleAmpCustomAmp4email::class,
+ Tag\StyleAmpCustomCssStrict::ID => Tag\StyleAmpCustomCssStrict::class,
+ Tag\HeadStyleAmpBoilerplate::ID => Tag\HeadStyleAmpBoilerplate::class,
+ Tag\HeadStyleAmpBoilerplateTransformed::ID => Tag\HeadStyleAmpBoilerplateTransformed::class,
+ Tag\HeadStyleAmp4adsBoilerplate::ID => Tag\HeadStyleAmp4adsBoilerplate::class,
+ Tag\HeadStyleAmp4emailBoilerplate::ID => Tag\HeadStyleAmp4emailBoilerplate::class,
+ Tag\NoscriptStyleAmpBoilerplate::ID => Tag\NoscriptStyleAmpBoilerplate::class,
+ Tag\NoscriptStyleAmpBoilerplateTransformed::ID => Tag\NoscriptStyleAmpBoilerplateTransformed::class,
+ Tag\StyleAmpKeyframes::ID => Tag\StyleAmpKeyframes::class,
+ Tag\StyleAmpRuntimeTransformed::ID => Tag\StyleAmpRuntimeTransformed::class,
+ Tag\G::ID => Tag\G::class,
+ Tag\Glyph::ID => Tag\Glyph::class,
+ Tag\Glyphref::ID => Tag\Glyphref::class,
+ Tag\Image::ID => Tag\Image::class,
+ Tag\Marker::ID => Tag\Marker::class,
+ Tag\Metadata::ID => Tag\Metadata::class,
+ Tag\Path::ID => Tag\Path::class,
+ Tag\Solidcolor::ID => Tag\Solidcolor::class,
+ Tag\Svg::ID => Tag\Svg::class,
+ Tag\Switch_::ID => Tag\Switch_::class,
+ Tag\View::ID => Tag\View::class,
+ Tag\Circle::ID => Tag\Circle::class,
+ Tag\Ellipse::ID => Tag\Ellipse::class,
+ Tag\Line::ID => Tag\Line::class,
+ Tag\Polygon::ID => Tag\Polygon::class,
+ Tag\Polyline::ID => Tag\Polyline::class,
+ Tag\Rect::ID => Tag\Rect::class,
+ Tag\Text::ID => Tag\Text::class,
+ Tag\Textpath::ID => Tag\Textpath::class,
+ Tag\Tref::ID => Tag\Tref::class,
+ Tag\Tspan::ID => Tag\Tspan::class,
+ Tag\Clippath::ID => Tag\Clippath::class,
+ Tag\Filter::ID => Tag\Filter::class,
+ Tag\Hkern::ID => Tag\Hkern::class,
+ Tag\Lineargradient::ID => Tag\Lineargradient::class,
+ Tag\Mask::ID => Tag\Mask::class,
+ Tag\Pattern::ID => Tag\Pattern::class,
+ Tag\Radialgradient::ID => Tag\Radialgradient::class,
+ Tag\LineargradientStop::ID => Tag\LineargradientStop::class,
+ Tag\RadialgradientStop::ID => Tag\RadialgradientStop::class,
+ Tag\Vkern::ID => Tag\Vkern::class,
+ Tag\Defs::ID => Tag\Defs::class,
+ Tag\Symbol::ID => Tag\Symbol::class,
+ Tag\Use_::ID => Tag\Use_::class,
+ Tag\Feblend::ID => Tag\Feblend::class,
+ Tag\Fecolormatrix::ID => Tag\Fecolormatrix::class,
+ Tag\Fecomponenttransfer::ID => Tag\Fecomponenttransfer::class,
+ Tag\Fecomposite::ID => Tag\Fecomposite::class,
+ Tag\Feconvolvematrix::ID => Tag\Feconvolvematrix::class,
+ Tag\Fediffuselighting::ID => Tag\Fediffuselighting::class,
+ Tag\Fedisplacementmap::ID => Tag\Fedisplacementmap::class,
+ Tag\Fedistantlight::ID => Tag\Fedistantlight::class,
+ Tag\Fedropshadow::ID => Tag\Fedropshadow::class,
+ Tag\Feflood::ID => Tag\Feflood::class,
+ Tag\Fefunca::ID => Tag\Fefunca::class,
+ Tag\Fefuncb::ID => Tag\Fefuncb::class,
+ Tag\Fefuncg::ID => Tag\Fefuncg::class,
+ Tag\Fefuncr::ID => Tag\Fefuncr::class,
+ Tag\Fegaussianblur::ID => Tag\Fegaussianblur::class,
+ Tag\Femerge::ID => Tag\Femerge::class,
+ Tag\Femergenode::ID => Tag\Femergenode::class,
+ Tag\Femorphology::ID => Tag\Femorphology::class,
+ Tag\Feoffset::ID => Tag\Feoffset::class,
+ Tag\Fepointlight::ID => Tag\Fepointlight::class,
+ Tag\Fespecularlighting::ID => Tag\Fespecularlighting::class,
+ Tag\Fespotlight::ID => Tag\Fespotlight::class,
+ Tag\Fetile::ID => Tag\Fetile::class,
+ Tag\Feturbulence::ID => Tag\Feturbulence::class,
+ Tag\Desc::ID => Tag\Desc::class,
+ Tag\SvgTitle::ID => Tag\SvgTitle::class,
+ Tag\ScriptAmp3dGltf::ID => Tag\ScriptAmp3dGltf::class,
+ Tag\Amp3dGltf::ID => Tag\Amp3dGltf::class,
+ Tag\ScriptAmp3qPlayer::ID => Tag\ScriptAmp3qPlayer::class,
+ Tag\Amp3qPlayer::ID => Tag\Amp3qPlayer::class,
+ Tag\ScriptAmpAccessLaterpay::ID => Tag\ScriptAmpAccessLaterpay::class,
+ Tag\ScriptAmpAccessPoool::ID => Tag\ScriptAmpAccessPoool::class,
+ Tag\ScriptAmpAccessScroll::ID => Tag\ScriptAmpAccessScroll::class,
+ Tag\ScriptAmpAccess::ID => Tag\ScriptAmpAccess::class,
+ Tag\AmpAccessExtensionJsonScript::ID => Tag\AmpAccessExtensionJsonScript::class,
+ Tag\ScriptAmpAccordion::ID => Tag\ScriptAmpAccordion::class,
+ Tag\ScriptAmpAccordion2::ID => Tag\ScriptAmpAccordion2::class,
+ Tag\ScriptCustomElementAmpAccordionAmp4email::ID => Tag\ScriptCustomElementAmpAccordionAmp4email::class,
+ Tag\AmpAccordion::ID => Tag\AmpAccordion::class,
+ Tag\AmpAccordionSection::ID => Tag\AmpAccordionSection::class,
+ Tag\ScriptAmpActionMacro::ID => Tag\ScriptAmpActionMacro::class,
+ Tag\AmpActionMacro::ID => Tag\AmpActionMacro::class,
+ Tag\ScriptAmpAdCustom::ID => Tag\ScriptAmpAdCustom::class,
+ Tag\AmpAdCustom::ID => Tag\AmpAdCustom::class,
+ Tag\ScriptAmpAdExit::ID => Tag\ScriptAmpAdExit::class,
+ Tag\AmpAdExit::ID => Tag\AmpAdExit::class,
+ Tag\AmpAdExitConfigurationJson::ID => Tag\AmpAdExitConfigurationJson::class,
+ Tag\AmpAdExtensionScript::ID => Tag\AmpAdExtensionScript::class,
+ Tag\AmpAd::ID => Tag\AmpAd::class,
+ Tag\AmpAdWithTypeCustom::ID => Tag\AmpAdWithTypeCustom::class,
+ Tag\AmpAdWithDataMultiSizeAttribute::ID => Tag\AmpAdWithDataMultiSizeAttribute::class,
+ Tag\AmpAdWithDataEnableRefreshAttribute::ID => Tag\AmpAdWithDataEnableRefreshAttribute::class,
+ Tag\AmpEmbed::ID => Tag\AmpEmbed::class,
+ Tag\AmpEmbedWithDataMultiSizeAttribute::ID => Tag\AmpEmbedWithDataMultiSizeAttribute::class,
+ Tag\ScriptAmpAddthis::ID => Tag\ScriptAmpAddthis::class,
+ Tag\AmpAddthis::ID => Tag\AmpAddthis::class,
+ Tag\ScriptAmpAnalytics::ID => Tag\ScriptAmpAnalytics::class,
+ Tag\AmpAnalyticsExtensionJsonScript::ID => Tag\AmpAnalyticsExtensionJsonScript::class,
+ Tag\AmpAnalytics::ID => Tag\AmpAnalytics::class,
+ Tag\ScriptAmpAnim::ID => Tag\ScriptAmpAnim::class,
+ Tag\AmpAnimExtensionScriptAmp4email::ID => Tag\AmpAnimExtensionScriptAmp4email::class,
+ Tag\AmpAnim::ID => Tag\AmpAnim::class,
+ Tag\AmpAnimAmp4email::ID => Tag\AmpAnimAmp4email::class,
+ Tag\ScriptAmpAnimation::ID => Tag\ScriptAmpAnimation::class,
+ Tag\AmpAnimationExtensionJsonScript::ID => Tag\AmpAnimationExtensionJsonScript::class,
+ Tag\AmpAnimation::ID => Tag\AmpAnimation::class,
+ Tag\ScriptAmpApesterMedia::ID => Tag\ScriptAmpApesterMedia::class,
+ Tag\AmpApesterMedia::ID => Tag\AmpApesterMedia::class,
+ Tag\ScriptAmpAppBanner::ID => Tag\ScriptAmpAppBanner::class,
+ Tag\AmpAppBanner::ID => Tag\AmpAppBanner::class,
+ Tag\ScriptAmpAudio::ID => Tag\ScriptAmpAudio::class,
+ Tag\AmpAudio::ID => Tag\AmpAudio::class,
+ Tag\AmpStoryAmpAudio::ID => Tag\AmpStoryAmpAudio::class,
+ Tag\AmpAudioA4a::ID => Tag\AmpAudioA4a::class,
+ Tag\ScriptAmpAutoAds::ID => Tag\ScriptAmpAutoAds::class,
+ Tag\AmpAutoAds::ID => Tag\AmpAutoAds::class,
+ Tag\ScriptAmpAutocomplete::ID => Tag\ScriptAmpAutocomplete::class,
+ Tag\ScriptCustomElementAmpAutocompleteAmp4email::ID => Tag\ScriptCustomElementAmpAutocompleteAmp4email::class,
+ Tag\AmpAutocomplete::ID => Tag\AmpAutocomplete::class,
+ Tag\AmpAutocompleteAmp4email::ID => Tag\AmpAutocompleteAmp4email::class,
+ Tag\AmpAutocompleteInput::ID => Tag\AmpAutocompleteInput::class,
+ Tag\AmpAutocompleteJson::ID => Tag\AmpAutocompleteJson::class,
+ Tag\ScriptAmpBaseCarousel::ID => Tag\ScriptAmpBaseCarousel::class,
+ Tag\AmpBaseCarousel::ID => Tag\AmpBaseCarousel::class,
+ Tag\AmpBaseCarouselLightbox::ID => Tag\AmpBaseCarouselLightbox::class,
+ Tag\AmpBaseCarouselLightboxLightboxExclude::ID => Tag\AmpBaseCarouselLightboxLightboxExclude::class,
+ Tag\AmpBaseCarouselLightboxChild::ID => Tag\AmpBaseCarouselLightboxChild::class,
+ Tag\ScriptAmpBeopinion::ID => Tag\ScriptAmpBeopinion::class,
+ Tag\AmpBeopinion::ID => Tag\AmpBeopinion::class,
+ Tag\ScriptAmpBind::ID => Tag\ScriptAmpBind::class,
+ Tag\ScriptCustomElementAmpBindAmp4email::ID => Tag\ScriptCustomElementAmpBindAmp4email::class,
+ Tag\AmpBindExtensionJsonScript::ID => Tag\AmpBindExtensionJsonScript::class,
+ Tag\AmpState::ID => Tag\AmpState::class,
+ Tag\AmpStateAmp4email::ID => Tag\AmpStateAmp4email::class,
+ Tag\AmpBindMacro::ID => Tag\AmpBindMacro::class,
+ Tag\ScriptAmpBodymovinAnimation::ID => Tag\ScriptAmpBodymovinAnimation::class,
+ Tag\AmpBodymovinAnimation::ID => Tag\AmpBodymovinAnimation::class,
+ Tag\ScriptAmpBridPlayer::ID => Tag\ScriptAmpBridPlayer::class,
+ Tag\AmpBridPlayer::ID => Tag\AmpBridPlayer::class,
+ Tag\ScriptAmpBrightcove::ID => Tag\ScriptAmpBrightcove::class,
+ Tag\AmpBrightcove::ID => Tag\AmpBrightcove::class,
+ Tag\ScriptAmpBysideContent::ID => Tag\ScriptAmpBysideContent::class,
+ Tag\AmpBysideContent::ID => Tag\AmpBysideContent::class,
+ Tag\ScriptAmpCacheUrl::ID => Tag\ScriptAmpCacheUrl::class,
+ Tag\ScriptAmpCallTracking::ID => Tag\ScriptAmpCallTracking::class,
+ Tag\AmpCallTracking::ID => Tag\AmpCallTracking::class,
+ Tag\ScriptAmpCarousel::ID => Tag\ScriptAmpCarousel::class,
+ Tag\ScriptCustomElementAmpCarouselAmp4email::ID => Tag\ScriptCustomElementAmpCarouselAmp4email::class,
+ Tag\AmpCarousel::ID => Tag\AmpCarousel::class,
+ Tag\AmpCarouselLightbox::ID => Tag\AmpCarouselLightbox::class,
+ Tag\AmpCarouselLightboxLightboxExclude::ID => Tag\AmpCarouselLightboxLightboxExclude::class,
+ Tag\AmpCarouselLightboxChild::ID => Tag\AmpCarouselLightboxChild::class,
+ Tag\ScriptAmpConnatixPlayer::ID => Tag\ScriptAmpConnatixPlayer::class,
+ Tag\AmpConnatixPlayer::ID => Tag\AmpConnatixPlayer::class,
+ Tag\ScriptAmpConsent::ID => Tag\ScriptAmpConsent::class,
+ Tag\AmpConsentExtensionJsonScript::ID => Tag\AmpConsentExtensionJsonScript::class,
+ Tag\AmpConsent::ID => Tag\AmpConsent::class,
+ Tag\AmpConsentType::ID => Tag\AmpConsentType::class,
+ Tag\ScriptAmpDailymotion::ID => Tag\ScriptAmpDailymotion::class,
+ Tag\AmpDailymotion::ID => Tag\AmpDailymotion::class,
+ Tag\ScriptAmpDateCountdown::ID => Tag\ScriptAmpDateCountdown::class,
+ Tag\AmpDateCountdown::ID => Tag\AmpDateCountdown::class,
+ Tag\ScriptAmpDateDisplay::ID => Tag\ScriptAmpDateDisplay::class,
+ Tag\AmpDateDisplay::ID => Tag\AmpDateDisplay::class,
+ Tag\ScriptAmpDatePicker::ID => Tag\ScriptAmpDatePicker::class,
+ Tag\AmpDatePickerTypeSingleModeStatic::ID => Tag\AmpDatePickerTypeSingleModeStatic::class,
+ Tag\AmpDatePickerTypeSingleModeOverlay::ID => Tag\AmpDatePickerTypeSingleModeOverlay::class,
+ Tag\AmpDatePickerTypeRangeModeStatic::ID => Tag\AmpDatePickerTypeRangeModeStatic::class,
+ Tag\AmpDatePickerTypeRangeModeOverlay::ID => Tag\AmpDatePickerTypeRangeModeOverlay::class,
+ Tag\AmpDatePickerTemplateDateTemplate::ID => Tag\AmpDatePickerTemplateDateTemplate::class,
+ Tag\AmpDatePickerTemplateInfoTemplate::ID => Tag\AmpDatePickerTemplateInfoTemplate::class,
+ Tag\ScriptAmpDelightPlayer::ID => Tag\ScriptAmpDelightPlayer::class,
+ Tag\AmpDelightPlayer::ID => Tag\AmpDelightPlayer::class,
+ Tag\ScriptAmpDynamicCssClasses::ID => Tag\ScriptAmpDynamicCssClasses::class,
+ Tag\ScriptAmpEmbedlyCard::ID => Tag\ScriptAmpEmbedlyCard::class,
+ Tag\AmpEmbedlyCard::ID => Tag\AmpEmbedlyCard::class,
+ Tag\AmpEmbedlyKey::ID => Tag\AmpEmbedlyKey::class,
+ Tag\ScriptAmpExperiment::ID => Tag\ScriptAmpExperiment::class,
+ Tag\AmpExperimentExtensionJsonScript::ID => Tag\AmpExperimentExtensionJsonScript::class,
+ Tag\AmpExperiment::ID => Tag\AmpExperiment::class,
+ Tag\ScriptAmpFacebookComments::ID => Tag\ScriptAmpFacebookComments::class,
+ Tag\AmpFacebookComments::ID => Tag\AmpFacebookComments::class,
+ Tag\ScriptAmpFacebookLike::ID => Tag\ScriptAmpFacebookLike::class,
+ Tag\AmpFacebookLike::ID => Tag\AmpFacebookLike::class,
+ Tag\ScriptAmpFacebookPage::ID => Tag\ScriptAmpFacebookPage::class,
+ Tag\AmpFacebookPage::ID => Tag\AmpFacebookPage::class,
+ Tag\ScriptAmpFacebook::ID => Tag\ScriptAmpFacebook::class,
+ Tag\AmpFacebook::ID => Tag\AmpFacebook::class,
+ Tag\ScriptAmpFitText::ID => Tag\ScriptAmpFitText::class,
+ Tag\ScriptAmpFitText2::ID => Tag\ScriptAmpFitText2::class,
+ Tag\ScriptCustomElementAmpFitTextAmp4email::ID => Tag\ScriptCustomElementAmpFitTextAmp4email::class,
+ Tag\AmpFitText::ID => Tag\AmpFitText::class,
+ Tag\ScriptAmpFont::ID => Tag\ScriptAmpFont::class,
+ Tag\AmpFont::ID => Tag\AmpFont::class,
+ Tag\ScriptAmpForm::ID => Tag\ScriptAmpForm::class,
+ Tag\ScriptCustomElementAmpFormAmp4email::ID => Tag\ScriptCustomElementAmpFormAmp4email::class,
+ Tag\ScriptAmpFxCollection::ID => Tag\ScriptAmpFxCollection::class,
+ Tag\ScriptAmpFxFlyingCarpet::ID => Tag\ScriptAmpFxFlyingCarpet::class,
+ Tag\AmpFxFlyingCarpet::ID => Tag\AmpFxFlyingCarpet::class,
+ Tag\ScriptAmpGeo::ID => Tag\ScriptAmpGeo::class,
+ Tag\AmpGeo::ID => Tag\AmpGeo::class,
+ Tag\AmpGeoExtensionJsonScript::ID => Tag\AmpGeoExtensionJsonScript::class,
+ Tag\ScriptAmpGfycat::ID => Tag\ScriptAmpGfycat::class,
+ Tag\AmpGfycat::ID => Tag\AmpGfycat::class,
+ Tag\ScriptAmpGist::ID => Tag\ScriptAmpGist::class,
+ Tag\AmpGist::ID => Tag\AmpGist::class,
+ Tag\ScriptAmpGoogleAssistantAssistjs::ID => Tag\ScriptAmpGoogleAssistantAssistjs::class,
+ Tag\AmpGoogleAssistantAssistjsConfig::ID => Tag\AmpGoogleAssistantAssistjsConfig::class,
+ Tag\AmpGoogleAssistantVoiceButton::ID => Tag\AmpGoogleAssistantVoiceButton::class,
+ Tag\AmpGoogleAssistantVoiceBar::ID => Tag\AmpGoogleAssistantVoiceBar::class,
+ Tag\AmpGoogleAssistantInlineSuggestionBar::ID => Tag\AmpGoogleAssistantInlineSuggestionBar::class,
+ Tag\ScriptAmpGoogleDocumentEmbed::ID => Tag\ScriptAmpGoogleDocumentEmbed::class,
+ Tag\AmpGoogleDocumentEmbed::ID => Tag\AmpGoogleDocumentEmbed::class,
+ Tag\ScriptAmpGwdAnimation::ID => Tag\ScriptAmpGwdAnimation::class,
+ Tag\AmpGwdAnimation::ID => Tag\AmpGwdAnimation::class,
+ Tag\ScriptAmpHulu::ID => Tag\ScriptAmpHulu::class,
+ Tag\AmpHulu::ID => Tag\AmpHulu::class,
+ Tag\ScriptAmpIframe::ID => Tag\ScriptAmpIframe::class,
+ Tag\AmpIframe::ID => Tag\AmpIframe::class,
+ Tag\ScriptAmpIframely::ID => Tag\ScriptAmpIframely::class,
+ Tag\AmpIframely::ID => Tag\AmpIframely::class,
+ Tag\ScriptAmpImaVideo::ID => Tag\ScriptAmpImaVideo::class,
+ Tag\AmpImaVideo::ID => Tag\AmpImaVideo::class,
+ Tag\ScriptAmpImageLightbox::ID => Tag\ScriptAmpImageLightbox::class,
+ Tag\ScriptCustomElementAmpImageLightboxAmp4email::ID => Tag\ScriptCustomElementAmpImageLightboxAmp4email::class,
+ Tag\AmpImageLightbox::ID => Tag\AmpImageLightbox::class,
+ Tag\ScriptAmpImageSlider::ID => Tag\ScriptAmpImageSlider::class,
+ Tag\AmpImageSlider::ID => Tag\AmpImageSlider::class,
+ Tag\AmpImageSliderTransformed::ID => Tag\AmpImageSliderTransformed::class,
+ Tag\AmpImageSliderDivFirst::ID => Tag\AmpImageSliderDivFirst::class,
+ Tag\AmpImageSliderDivSecond::ID => Tag\AmpImageSliderDivSecond::class,
+ Tag\ScriptAmpImgur::ID => Tag\ScriptAmpImgur::class,
+ Tag\AmpImgur::ID => Tag\AmpImgur::class,
+ Tag\ScriptAmpInlineGallery::ID => Tag\ScriptAmpInlineGallery::class,
+ Tag\AmpInlineGallery::ID => Tag\AmpInlineGallery::class,
+ Tag\AmpInlineGalleryPagination::ID => Tag\AmpInlineGalleryPagination::class,
+ Tag\AmpInlineGalleryPaginationInset::ID => Tag\AmpInlineGalleryPaginationInset::class,
+ Tag\AmpInlineGalleryThumbnails::ID => Tag\AmpInlineGalleryThumbnails::class,
+ Tag\ScriptAmpInputmask::ID => Tag\ScriptAmpInputmask::class,
+ Tag\InputMaskCustomMask::ID => Tag\InputMaskCustomMask::class,
+ Tag\InputMaskPaymentCard::ID => Tag\InputMaskPaymentCard::class,
+ Tag\InputMaskDateDdMmYyyy::ID => Tag\InputMaskDateDdMmYyyy::class,
+ Tag\InputMaskDateMmDdYyyy::ID => Tag\InputMaskDateMmDdYyyy::class,
+ Tag\InputMaskDateMmYy::ID => Tag\InputMaskDateMmYy::class,
+ Tag\InputMaskDateYyyyMmDd::ID => Tag\InputMaskDateYyyyMmDd::class,
+ Tag\ScriptAmpInstagram::ID => Tag\ScriptAmpInstagram::class,
+ Tag\ScriptAmpInstagram2::ID => Tag\ScriptAmpInstagram2::class,
+ Tag\AmpInstagram::ID => Tag\AmpInstagram::class,
+ Tag\ScriptAmpInstallServiceworker::ID => Tag\ScriptAmpInstallServiceworker::class,
+ Tag\AmpInstallServiceworker::ID => Tag\AmpInstallServiceworker::class,
+ Tag\ScriptAmpIzlesene::ID => Tag\ScriptAmpIzlesene::class,
+ Tag\AmpIzlesene::ID => Tag\AmpIzlesene::class,
+ Tag\ScriptAmpJwplayer::ID => Tag\ScriptAmpJwplayer::class,
+ Tag\AmpJwplayer::ID => Tag\AmpJwplayer::class,
+ Tag\ScriptAmpKalturaPlayer::ID => Tag\ScriptAmpKalturaPlayer::class,
+ Tag\AmpKalturaPlayer::ID => Tag\AmpKalturaPlayer::class,
+ Tag\ScriptAmpLightboxGallery::ID => Tag\ScriptAmpLightboxGallery::class,
+ Tag\ScriptAmpLightbox::ID => Tag\ScriptAmpLightbox::class,
+ Tag\ScriptAmpLightbox2::ID => Tag\ScriptAmpLightbox2::class,
+ Tag\ScriptCustomElementAmpLightboxAmp4ads::ID => Tag\ScriptCustomElementAmpLightboxAmp4ads::class,
+ Tag\ScriptCustomElementAmpLightboxAmp4email::ID => Tag\ScriptCustomElementAmpLightboxAmp4email::class,
+ Tag\AmpLightbox::ID => Tag\AmpLightbox::class,
+ Tag\AmpLightboxAmp4ads::ID => Tag\AmpLightboxAmp4ads::class,
+ Tag\ScriptAmpLinkRewriter::ID => Tag\ScriptAmpLinkRewriter::class,
+ Tag\AmpLinkRewriterExtensionJsonScript::ID => Tag\AmpLinkRewriterExtensionJsonScript::class,
+ Tag\AmpLinkRewriter::ID => Tag\AmpLinkRewriter::class,
+ Tag\ScriptAmpList::ID => Tag\ScriptAmpList::class,
+ Tag\ScriptCustomElementAmpListAmp4email::ID => Tag\ScriptCustomElementAmpListAmp4email::class,
+ Tag\AmpList::ID => Tag\AmpList::class,
+ Tag\AmpListDivFetchError::ID => Tag\AmpListDivFetchError::class,
+ Tag\AmpListLoadMore::ID => Tag\AmpListLoadMore::class,
+ Tag\AmpListLoadMoreButtonLoadMoreClickable::ID => Tag\AmpListLoadMoreButtonLoadMoreClickable::class,
+ Tag\AmpListAmp4email::ID => Tag\AmpListAmp4email::class,
+ Tag\ScriptAmpLiveList::ID => Tag\ScriptAmpLiveList::class,
+ Tag\AmpLiveList::ID => Tag\AmpLiveList::class,
+ Tag\AmpLiveListUpdate::ID => Tag\AmpLiveListUpdate::class,
+ Tag\AmpLiveListItems::ID => Tag\AmpLiveListItems::class,
+ Tag\AmpLiveListPagination::ID => Tag\AmpLiveListPagination::class,
+ Tag\AmpLiveListItemsItem::ID => Tag\AmpLiveListItemsItem::class,
+ Tag\ScriptAmpMathml::ID => Tag\ScriptAmpMathml::class,
+ Tag\AmpMathml::ID => Tag\AmpMathml::class,
+ Tag\ScriptAmpMegaMenu::ID => Tag\ScriptAmpMegaMenu::class,
+ Tag\AmpMegaMenu::ID => Tag\AmpMegaMenu::class,
+ Tag\AmpMegaMenuNav::ID => Tag\AmpMegaMenuNav::class,
+ Tag\AmpMegaMenuAmpList::ID => Tag\AmpMegaMenuAmpList::class,
+ Tag\AmpMegaMenuAmpListTemplate::ID => Tag\AmpMegaMenuAmpListTemplate::class,
+ Tag\AmpMegaMenuNavUlOl::ID => Tag\AmpMegaMenuNavUlOl::class,
+ Tag\AmpMegaMenuNavUlOlLi::ID => Tag\AmpMegaMenuNavUlOlLi::class,
+ Tag\AmpMegaMenuItemHeading::ID => Tag\AmpMegaMenuItemHeading::class,
+ Tag\AmpMegaMenuItemContent::ID => Tag\AmpMegaMenuItemContent::class,
+ Tag\ScriptAmpMegaphone::ID => Tag\ScriptAmpMegaphone::class,
+ Tag\AmpMegaphoneDataPlaylist::ID => Tag\AmpMegaphoneDataPlaylist::class,
+ Tag\AmpMegaphoneDataEpisode::ID => Tag\AmpMegaphoneDataEpisode::class,
+ Tag\ScriptAmpMinuteMediaPlayer::ID => Tag\ScriptAmpMinuteMediaPlayer::class,
+ Tag\AmpMinuteMediaPlayer::ID => Tag\AmpMinuteMediaPlayer::class,
+ Tag\ScriptAmpMowplayer::ID => Tag\ScriptAmpMowplayer::class,
+ Tag\AmpMowplayer::ID => Tag\AmpMowplayer::class,
+ Tag\ScriptAmpMraid::ID => Tag\ScriptAmpMraid::class,
+ Tag\ScriptAmpMustache::ID => Tag\ScriptAmpMustache::class,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4ads::ID => Tag\ScriptCustomTemplateAmpMustacheAmp4ads::class,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4email::ID => Tag\ScriptCustomTemplateAmpMustacheAmp4email::class,
+ Tag\ScriptTypeTextPlain::ID => Tag\ScriptTypeTextPlain::class,
+ Tag\ScriptTypeTextPlainAmp4email::ID => Tag\ScriptTypeTextPlainAmp4email::class,
+ Tag\Template::ID => Tag\Template::class,
+ Tag\TemplateAmp4email::ID => Tag\TemplateAmp4email::class,
+ Tag\ScriptAmpNestedMenu::ID => Tag\ScriptAmpNestedMenu::class,
+ Tag\AmpNestedMenu::ID => Tag\AmpNestedMenu::class,
+ Tag\DivAmpNestedMenu::ID => Tag\DivAmpNestedMenu::class,
+ Tag\ButtonAmpNestedMenu::ID => Tag\ButtonAmpNestedMenu::class,
+ Tag\H2AmpNestedMenu::ID => Tag\H2AmpNestedMenu::class,
+ Tag\H3AmpNestedMenu::ID => Tag\H3AmpNestedMenu::class,
+ Tag\H4AmpNestedMenu::ID => Tag\H4AmpNestedMenu::class,
+ Tag\H5AmpNestedMenu::ID => Tag\H5AmpNestedMenu::class,
+ Tag\H6AmpNestedMenu::ID => Tag\H6AmpNestedMenu::class,
+ Tag\SpanAmpNestedMenu::ID => Tag\SpanAmpNestedMenu::class,
+ Tag\ScriptAmpNextPage::ID => Tag\ScriptAmpNextPage::class,
+ Tag\AmpNextPageScriptTypeApplicationJson::ID => Tag\AmpNextPageScriptTypeApplicationJson::class,
+ Tag\AmpNextPageSeparator::ID => Tag\AmpNextPageSeparator::class,
+ Tag\AmpNextPageRecommendationBox::ID => Tag\AmpNextPageRecommendationBox::class,
+ Tag\AmpNextPageFooter::ID => Tag\AmpNextPageFooter::class,
+ Tag\AmpNextPageWithInlineConfig::ID => Tag\AmpNextPageWithInlineConfig::class,
+ Tag\AmpNextPageWithSrcAttribute::ID => Tag\AmpNextPageWithSrcAttribute::class,
+ Tag\AmpNextPageTypeAdsense::ID => Tag\AmpNextPageTypeAdsense::class,
+ Tag\ScriptAmpNexxtvPlayer::ID => Tag\ScriptAmpNexxtvPlayer::class,
+ Tag\AmpNexxtvPlayer::ID => Tag\AmpNexxtvPlayer::class,
+ Tag\ScriptAmpO2Player::ID => Tag\ScriptAmpO2Player::class,
+ Tag\AmpO2Player::ID => Tag\AmpO2Player::class,
+ Tag\ScriptAmpOnetapGoogle::ID => Tag\ScriptAmpOnetapGoogle::class,
+ Tag\AmpOnetapGoogle::ID => Tag\AmpOnetapGoogle::class,
+ Tag\ScriptAmpOoyalaPlayer::ID => Tag\ScriptAmpOoyalaPlayer::class,
+ Tag\AmpOoyalaPlayer::ID => Tag\AmpOoyalaPlayer::class,
+ Tag\ScriptAmpOrientationObserver::ID => Tag\ScriptAmpOrientationObserver::class,
+ Tag\AmpOrientationObserver::ID => Tag\AmpOrientationObserver::class,
+ Tag\ScriptAmpPanZoom::ID => Tag\ScriptAmpPanZoom::class,
+ Tag\AmpPanZoom::ID => Tag\AmpPanZoom::class,
+ Tag\ScriptAmpPinterest::ID => Tag\ScriptAmpPinterest::class,
+ Tag\AmpPinterest::ID => Tag\AmpPinterest::class,
+ Tag\ScriptAmpPlaybuzz::ID => Tag\ScriptAmpPlaybuzz::class,
+ Tag\AmpPlaybuzz::ID => Tag\AmpPlaybuzz::class,
+ Tag\ScriptAmpPositionObserver::ID => Tag\ScriptAmpPositionObserver::class,
+ Tag\AmpPositionObserver::ID => Tag\AmpPositionObserver::class,
+ Tag\ScriptAmpPowrPlayer::ID => Tag\ScriptAmpPowrPlayer::class,
+ Tag\AmpPowrPlayer::ID => Tag\AmpPowrPlayer::class,
+ Tag\ScriptAmpReachPlayer::ID => Tag\ScriptAmpReachPlayer::class,
+ Tag\AmpReachPlayer::ID => Tag\AmpReachPlayer::class,
+ Tag\ScriptAmpRecaptchaInput::ID => Tag\ScriptAmpRecaptchaInput::class,
+ Tag\AmpRecaptchaInput::ID => Tag\AmpRecaptchaInput::class,
+ Tag\ScriptAmpRedbullPlayer::ID => Tag\ScriptAmpRedbullPlayer::class,
+ Tag\AmpRedbullPlayer::ID => Tag\AmpRedbullPlayer::class,
+ Tag\ScriptAmpReddit::ID => Tag\ScriptAmpReddit::class,
+ Tag\AmpReddit::ID => Tag\AmpReddit::class,
+ Tag\ScriptAmpRiddleQuiz::ID => Tag\ScriptAmpRiddleQuiz::class,
+ Tag\AmpRiddleQuiz::ID => Tag\AmpRiddleQuiz::class,
+ Tag\ScriptAmpScript::ID => Tag\ScriptAmpScript::class,
+ Tag\AmpScriptExtensionLocalScript::ID => Tag\AmpScriptExtensionLocalScript::class,
+ Tag\AmpScript::ID => Tag\AmpScript::class,
+ Tag\Canvas::ID => Tag\Canvas::class,
+ Tag\ScriptAmpSelector::ID => Tag\ScriptAmpSelector::class,
+ Tag\ScriptAmpSelector2::ID => Tag\ScriptAmpSelector2::class,
+ Tag\ScriptCustomElementAmpSelectorAmp4email::ID => Tag\ScriptCustomElementAmpSelectorAmp4email::class,
+ Tag\AmpSelector::ID => Tag\AmpSelector::class,
+ Tag\AmpSelectorOption::ID => Tag\AmpSelectorOption::class,
+ Tag\AmpSelectorChild::ID => Tag\AmpSelectorChild::class,
+ Tag\ScriptAmpSidebar::ID => Tag\ScriptAmpSidebar::class,
+ Tag\ScriptCustomElementAmpSidebarAmp4email::ID => Tag\ScriptCustomElementAmpSidebarAmp4email::class,
+ Tag\AmpSidebar::ID => Tag\AmpSidebar::class,
+ Tag\AmpSidebarAmp4email::ID => Tag\AmpSidebarAmp4email::class,
+ Tag\AmpStoryAmpSidebar::ID => Tag\AmpStoryAmpSidebar::class,
+ Tag\AmpSidebarNav::ID => Tag\AmpSidebarNav::class,
+ Tag\ScriptAmpSkimlinks::ID => Tag\ScriptAmpSkimlinks::class,
+ Tag\AmpSkimlinks::ID => Tag\AmpSkimlinks::class,
+ Tag\ScriptAmpSlides::ID => Tag\ScriptAmpSlides::class,
+ Tag\ScriptAmpSmartlinks::ID => Tag\ScriptAmpSmartlinks::class,
+ Tag\AmpSmartlinks::ID => Tag\AmpSmartlinks::class,
+ Tag\ScriptAmpSocialShare::ID => Tag\ScriptAmpSocialShare::class,
+ Tag\ScriptAmpSocialShare2::ID => Tag\ScriptAmpSocialShare2::class,
+ Tag\AmpSocialShare::ID => Tag\AmpSocialShare::class,
+ Tag\ScriptAmpSoundcloud::ID => Tag\ScriptAmpSoundcloud::class,
+ Tag\AmpSoundcloud::ID => Tag\AmpSoundcloud::class,
+ Tag\ScriptAmpSpringboardPlayer::ID => Tag\ScriptAmpSpringboardPlayer::class,
+ Tag\AmpSpringboardPlayer::ID => Tag\AmpSpringboardPlayer::class,
+ Tag\ScriptAmpStickyAd::ID => Tag\ScriptAmpStickyAd::class,
+ Tag\AmpStickyAd::ID => Tag\AmpStickyAd::class,
+ Tag\ScriptAmpStory360::ID => Tag\ScriptAmpStory360::class,
+ Tag\AmpStory360::ID => Tag\AmpStory360::class,
+ Tag\ScriptAmpStoryAutoAds::ID => Tag\ScriptAmpStoryAutoAds::class,
+ Tag\AmpStoryAutoAds::ID => Tag\AmpStoryAutoAds::class,
+ Tag\AmpStoryAutoAdsConfigScript::ID => Tag\AmpStoryAutoAdsConfigScript::class,
+ Tag\AmpStoryAutoAdsTemplate::ID => Tag\AmpStoryAutoAdsTemplate::class,
+ Tag\ScriptAmpStoryAutoAnalytics::ID => Tag\ScriptAmpStoryAutoAnalytics::class,
+ Tag\AmpStoryAutoAnalytics::ID => Tag\AmpStoryAutoAnalytics::class,
+ Tag\ScriptAmpStoryInteractive::ID => Tag\ScriptAmpStoryInteractive::class,
+ Tag\AmpStoryInteractiveQuiz::ID => Tag\AmpStoryInteractiveQuiz::class,
+ Tag\AmpStoryInteractivePoll::ID => Tag\AmpStoryInteractivePoll::class,
+ Tag\AmpStoryInteractiveBinaryPoll::ID => Tag\AmpStoryInteractiveBinaryPoll::class,
+ Tag\AmpStoryInteractiveResults::ID => Tag\AmpStoryInteractiveResults::class,
+ Tag\ScriptAmpStoryPanningMedia::ID => Tag\ScriptAmpStoryPanningMedia::class,
+ Tag\AmpStoryPanningMedia::ID => Tag\AmpStoryPanningMedia::class,
+ Tag\ScriptAmpStoryPlayer::ID => Tag\ScriptAmpStoryPlayer::class,
+ Tag\AmpStoryPlayer::ID => Tag\AmpStoryPlayer::class,
+ Tag\ImgIAmphtmlIntrinsicSizerAmpStoryPlayer::ID => Tag\ImgIAmphtmlIntrinsicSizerAmpStoryPlayer::class,
+ Tag\AmpStoryPlayerImg::ID => Tag\AmpStoryPlayerImg::class,
+ Tag\ScriptAmpStory::ID => Tag\ScriptAmpStory::class,
+ Tag\AmpStory::ID => Tag\AmpStory::class,
+ Tag\AmpStoryPage::ID => Tag\AmpStoryPage::class,
+ Tag\AmpStoryGridLayer::ID => Tag\AmpStoryGridLayer::class,
+ Tag\AmpStoryGridLayerDefault::ID => Tag\AmpStoryGridLayerDefault::class,
+ Tag\AmpStoryGridLayerAnimateIn::ID => Tag\AmpStoryGridLayerAnimateIn::class,
+ Tag\AmpStoryBookend::ID => Tag\AmpStoryBookend::class,
+ Tag\AmpStoryBookendExtensionJsonScript::ID => Tag\AmpStoryBookendExtensionJsonScript::class,
+ Tag\AmpStorySocialShare::ID => Tag\AmpStorySocialShare::class,
+ Tag\AmpStorySocialShareExtensionJsonScript::ID => Tag\AmpStorySocialShareExtensionJsonScript::class,
+ Tag\AmpStoryConsentExtensionJsonScript::ID => Tag\AmpStoryConsentExtensionJsonScript::class,
+ Tag\AmpStoryConsent::ID => Tag\AmpStoryConsent::class,
+ Tag\AmpStoryCtaLayer::ID => Tag\AmpStoryCtaLayer::class,
+ Tag\AmpExperimentStoryExtensionJsonScript::ID => Tag\AmpExperimentStoryExtensionJsonScript::class,
+ Tag\AmpStoryCtaLayerAnimateIn::ID => Tag\AmpStoryCtaLayerAnimateIn::class,
+ Tag\AmpStoryPageAttachmentHref::ID => Tag\AmpStoryPageAttachmentHref::class,
+ Tag\AmpStoryPageAttachment::ID => Tag\AmpStoryPageAttachment::class,
+ Tag\AmpStoryPageOutlink::ID => Tag\AmpStoryPageOutlink::class,
+ Tag\AmpStoryAnimationJsonScript::ID => Tag\AmpStoryAnimationJsonScript::class,
+ Tag\AmpStoryAnimation::ID => Tag\AmpStoryAnimation::class,
+ Tag\MetaNameAmpStoryGeneratorName::ID => Tag\MetaNameAmpStoryGeneratorName::class,
+ Tag\MetaNameAmpStoryGeneratorVersion::ID => Tag\MetaNameAmpStoryGeneratorVersion::class,
+ Tag\ScriptAmpStreamGallery::ID => Tag\ScriptAmpStreamGallery::class,
+ Tag\AmpStreamGallery::ID => Tag\AmpStreamGallery::class,
+ Tag\ScriptAmpSubscriptions::ID => Tag\ScriptAmpSubscriptions::class,
+ Tag\AmpSubscriptionsExtensionJsonScript::ID => Tag\AmpSubscriptionsExtensionJsonScript::class,
+ Tag\ScriptAmpSubscriptionsGoogle::ID => Tag\ScriptAmpSubscriptionsGoogle::class,
+ Tag\CryptokeysJsonScript::ID => Tag\CryptokeysJsonScript::class,
+ Tag\SubscriptionsSectionContentSwgAmpCacheNonce::ID => Tag\SubscriptionsSectionContentSwgAmpCacheNonce::class,
+ Tag\SubscriptionsScriptCiphertext::ID => Tag\SubscriptionsScriptCiphertext::class,
+ Tag\SpanSwgAmpCacheNonce::ID => Tag\SpanSwgAmpCacheNonce::class,
+ Tag\ScriptAmpTimeago::ID => Tag\ScriptAmpTimeago::class,
+ Tag\ScriptCustomElementAmpTimeagoAmp4email::ID => Tag\ScriptCustomElementAmpTimeagoAmp4email::class,
+ Tag\AmpTimeago::ID => Tag\AmpTimeago::class,
+ Tag\ScriptAmpTruncateText::ID => Tag\ScriptAmpTruncateText::class,
+ Tag\AmpTruncateText::ID => Tag\AmpTruncateText::class,
+ Tag\ScriptAmpTwitter::ID => Tag\ScriptAmpTwitter::class,
+ Tag\AmpTwitter::ID => Tag\AmpTwitter::class,
+ Tag\ScriptAmpUserNotification::ID => Tag\ScriptAmpUserNotification::class,
+ Tag\AmpUserNotification::ID => Tag\AmpUserNotification::class,
+ Tag\ScriptAmpVideoDocking::ID => Tag\ScriptAmpVideoDocking::class,
+ Tag\ScriptAmpVideoIframe::ID => Tag\ScriptAmpVideoIframe::class,
+ Tag\ScriptAmpVideoIframe2::ID => Tag\ScriptAmpVideoIframe2::class,
+ Tag\AmpVideoIframe::ID => Tag\AmpVideoIframe::class,
+ Tag\AmpVideoIframeTransformed::ID => Tag\AmpVideoIframeTransformed::class,
+ Tag\ScriptAmpVideo::ID => Tag\ScriptAmpVideo::class,
+ Tag\ScriptAmpVideo2::ID => Tag\ScriptAmpVideo2::class,
+ Tag\AmpVideo::ID => Tag\AmpVideo::class,
+ Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::ID => Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::class,
+ Tag\AmpStoryAmpVideo::ID => Tag\AmpStoryAmpVideo::class,
+ Tag\ScriptAmpVimeo::ID => Tag\ScriptAmpVimeo::class,
+ Tag\ScriptAmpVimeo2::ID => Tag\ScriptAmpVimeo2::class,
+ Tag\AmpVimeo::ID => Tag\AmpVimeo::class,
+ Tag\ScriptAmpVine::ID => Tag\ScriptAmpVine::class,
+ Tag\AmpVine::ID => Tag\AmpVine::class,
+ Tag\ScriptAmpViqeoPlayer::ID => Tag\ScriptAmpViqeoPlayer::class,
+ Tag\AmpViqeoPlayer::ID => Tag\AmpViqeoPlayer::class,
+ Tag\ScriptAmpVk::ID => Tag\ScriptAmpVk::class,
+ Tag\AmpVk::ID => Tag\AmpVk::class,
+ Tag\ScriptAmpWebPush::ID => Tag\ScriptAmpWebPush::class,
+ Tag\AmpWebPush::ID => Tag\AmpWebPush::class,
+ Tag\AmpWebPushWidget::ID => Tag\AmpWebPushWidget::class,
+ Tag\ScriptAmpWistiaPlayer::ID => Tag\ScriptAmpWistiaPlayer::class,
+ Tag\AmpWistiaPlayer::ID => Tag\AmpWistiaPlayer::class,
+ Tag\ScriptAmpYotpo::ID => Tag\ScriptAmpYotpo::class,
+ Tag\AmpYotpo::ID => Tag\AmpYotpo::class,
+ Tag\ScriptAmpYoutube::ID => Tag\ScriptAmpYoutube::class,
+ Tag\ScriptAmpYoutube2::ID => Tag\ScriptAmpYoutube2::class,
+ Tag\AmpYoutube::ID => Tag\AmpYoutube::class,
+ ];
+
+ /**
+ * Mapping of tag name to tag ID or array of tag IDs.
+ *
+ * This is used to optimize querying by tag name.
+ *
+ * @var array>
+ */
+ const BY_TAG_NAME = [
+ Element::A => [
+ Tag\A::ID,
+ Tag\AAmp4email::ID,
+ ],
+ Element::ABBR => Tag\Abbr::ID,
+ Element::ACRONYM => Tag\Acronym::ID,
+ Element::ADDRESS => Tag\Address::ID,
+ Extension::_3D_GLTF => Tag\Amp3dGltf::ID,
+ Extension::_3Q_PLAYER => Tag\Amp3qPlayer::ID,
+ Element::SCRIPT => [
+ Tag\AmpAccessExtensionJsonScript::ID,
+ Tag\AmpAdExitConfigurationJson::ID,
+ Tag\AmpAdExtensionScript::ID,
+ Tag\AmpAnalyticsExtensionJsonScript::ID,
+ Tag\AmpAnimationExtensionJsonScript::ID,
+ Tag\AmpAnimExtensionScriptAmp4email::ID,
+ Tag\AmpAutocompleteJson::ID,
+ Tag\AmpBindExtensionJsonScript::ID,
+ Tag\AmpConsentExtensionJsonScript::ID,
+ Tag\AmpExperimentExtensionJsonScript::ID,
+ Tag\AmpExperimentStoryExtensionJsonScript::ID,
+ Tag\AmpGeoExtensionJsonScript::ID,
+ Tag\AmpImaVideoScriptTypeApplicationJson::ID,
+ Tag\AmpLinkRewriterExtensionJsonScript::ID,
+ Tag\AmpNextPageScriptTypeApplicationJson::ID,
+ Tag\AmpScriptExtensionLocalScript::ID,
+ Tag\AmpStoryAnimationJsonScript::ID,
+ Tag\AmpStoryAutoAdsConfigScript::ID,
+ Tag\AmpStoryBookendExtensionJsonScript::ID,
+ Tag\AmpStoryConsentExtensionJsonScript::ID,
+ Tag\AmpStorySocialShareExtensionJsonScript::ID,
+ Tag\AmpSubscriptionsExtensionJsonScript::ID,
+ Tag\Amp4adsEngineScript::ID,
+ Tag\AmphtmlEngineScript::ID,
+ Tag\AmphtmlEngineScriptLts::ID,
+ Tag\AmphtmlEngineScriptAmp4email::ID,
+ Tag\AmphtmlModuleEngineScript::ID,
+ Tag\AmphtmlModuleLtsEngineScript::ID,
+ Tag\AmphtmlNomoduleEngineScript::ID,
+ Tag\AmphtmlNomoduleLtsEngineScript::ID,
+ Tag\CryptokeysJsonScript::ID,
+ Tag\ScriptAmpOnerror::ID,
+ Tag\ScriptIdAmpRtc::ID,
+ Tag\ScriptTypeApplicationLdJson::ID,
+ Tag\ScriptTypeTextPlain::ID,
+ Tag\ScriptTypeTextPlainAmp4email::ID,
+ Tag\ScriptAmp3dGltf::ID,
+ Tag\ScriptAmp3qPlayer::ID,
+ Tag\ScriptAmpAccessLaterpay::ID,
+ Tag\ScriptAmpAccessPoool::ID,
+ Tag\ScriptAmpAccessScroll::ID,
+ Tag\ScriptAmpAccess::ID,
+ Tag\ScriptAmpAccordion::ID,
+ Tag\ScriptAmpAccordion2::ID,
+ Tag\ScriptAmpActionMacro::ID,
+ Tag\ScriptAmpAdCustom::ID,
+ Tag\ScriptAmpAdExit::ID,
+ Tag\ScriptAmpAddthis::ID,
+ Tag\ScriptAmpAnalytics::ID,
+ Tag\ScriptAmpAnimation::ID,
+ Tag\ScriptAmpAnim::ID,
+ Tag\ScriptAmpApesterMedia::ID,
+ Tag\ScriptAmpAppBanner::ID,
+ Tag\ScriptAmpAudio::ID,
+ Tag\ScriptAmpAutoAds::ID,
+ Tag\ScriptAmpAutocomplete::ID,
+ Tag\ScriptAmpBaseCarousel::ID,
+ Tag\ScriptAmpBeopinion::ID,
+ Tag\ScriptAmpBind::ID,
+ Tag\ScriptAmpBodymovinAnimation::ID,
+ Tag\ScriptAmpBridPlayer::ID,
+ Tag\ScriptAmpBrightcove::ID,
+ Tag\ScriptAmpBysideContent::ID,
+ Tag\ScriptAmpCacheUrl::ID,
+ Tag\ScriptAmpCallTracking::ID,
+ Tag\ScriptAmpCarousel::ID,
+ Tag\ScriptAmpConnatixPlayer::ID,
+ Tag\ScriptAmpConsent::ID,
+ Tag\ScriptAmpDailymotion::ID,
+ Tag\ScriptAmpDateCountdown::ID,
+ Tag\ScriptAmpDateDisplay::ID,
+ Tag\ScriptAmpDatePicker::ID,
+ Tag\ScriptAmpDelightPlayer::ID,
+ Tag\ScriptAmpDynamicCssClasses::ID,
+ Tag\ScriptAmpEmbedlyCard::ID,
+ Tag\ScriptAmpExperiment::ID,
+ Tag\ScriptAmpFacebookComments::ID,
+ Tag\ScriptAmpFacebookLike::ID,
+ Tag\ScriptAmpFacebookPage::ID,
+ Tag\ScriptAmpFacebook::ID,
+ Tag\ScriptAmpFitText::ID,
+ Tag\ScriptAmpFitText2::ID,
+ Tag\ScriptAmpFont::ID,
+ Tag\ScriptAmpForm::ID,
+ Tag\ScriptAmpFxCollection::ID,
+ Tag\ScriptAmpFxFlyingCarpet::ID,
+ Tag\ScriptAmpGeo::ID,
+ Tag\ScriptAmpGfycat::ID,
+ Tag\ScriptAmpGist::ID,
+ Tag\ScriptAmpGoogleAssistantAssistjs::ID,
+ Tag\ScriptAmpGoogleDocumentEmbed::ID,
+ Tag\ScriptAmpGwdAnimation::ID,
+ Tag\ScriptAmpHulu::ID,
+ Tag\ScriptAmpIframely::ID,
+ Tag\ScriptAmpIframe::ID,
+ Tag\ScriptAmpImaVideo::ID,
+ Tag\ScriptAmpImageLightbox::ID,
+ Tag\ScriptAmpImageSlider::ID,
+ Tag\ScriptAmpImgur::ID,
+ Tag\ScriptAmpInlineGallery::ID,
+ Tag\ScriptAmpInputmask::ID,
+ Tag\ScriptAmpInstagram::ID,
+ Tag\ScriptAmpInstagram2::ID,
+ Tag\ScriptAmpInstallServiceworker::ID,
+ Tag\ScriptAmpIzlesene::ID,
+ Tag\ScriptAmpJwplayer::ID,
+ Tag\ScriptAmpKalturaPlayer::ID,
+ Tag\ScriptAmpLightboxGallery::ID,
+ Tag\ScriptAmpLightbox::ID,
+ Tag\ScriptAmpLightbox2::ID,
+ Tag\ScriptAmpLinkRewriter::ID,
+ Tag\ScriptAmpList::ID,
+ Tag\ScriptAmpLiveList::ID,
+ Tag\ScriptAmpMathml::ID,
+ Tag\ScriptAmpMegaMenu::ID,
+ Tag\ScriptAmpMegaphone::ID,
+ Tag\ScriptAmpMinuteMediaPlayer::ID,
+ Tag\ScriptAmpMowplayer::ID,
+ Tag\ScriptAmpMraid::ID,
+ Tag\ScriptAmpMustache::ID,
+ Tag\ScriptAmpNestedMenu::ID,
+ Tag\ScriptAmpNextPage::ID,
+ Tag\ScriptAmpNexxtvPlayer::ID,
+ Tag\ScriptAmpO2Player::ID,
+ Tag\ScriptAmpOnetapGoogle::ID,
+ Tag\ScriptAmpOoyalaPlayer::ID,
+ Tag\ScriptAmpOrientationObserver::ID,
+ Tag\ScriptAmpPanZoom::ID,
+ Tag\ScriptAmpPinterest::ID,
+ Tag\ScriptAmpPlaybuzz::ID,
+ Tag\ScriptAmpPositionObserver::ID,
+ Tag\ScriptAmpPowrPlayer::ID,
+ Tag\ScriptAmpReachPlayer::ID,
+ Tag\ScriptAmpRecaptchaInput::ID,
+ Tag\ScriptAmpRedbullPlayer::ID,
+ Tag\ScriptAmpReddit::ID,
+ Tag\ScriptAmpRiddleQuiz::ID,
+ Tag\ScriptAmpScript::ID,
+ Tag\ScriptAmpSelector::ID,
+ Tag\ScriptAmpSelector2::ID,
+ Tag\ScriptAmpSidebar::ID,
+ Tag\ScriptAmpSkimlinks::ID,
+ Tag\ScriptAmpSlides::ID,
+ Tag\ScriptAmpSmartlinks::ID,
+ Tag\ScriptAmpSocialShare::ID,
+ Tag\ScriptAmpSocialShare2::ID,
+ Tag\ScriptAmpSoundcloud::ID,
+ Tag\ScriptAmpSpringboardPlayer::ID,
+ Tag\ScriptAmpStickyAd::ID,
+ Tag\ScriptAmpStory360::ID,
+ Tag\ScriptAmpStoryAutoAds::ID,
+ Tag\ScriptAmpStoryAutoAnalytics::ID,
+ Tag\ScriptAmpStoryInteractive::ID,
+ Tag\ScriptAmpStoryPanningMedia::ID,
+ Tag\ScriptAmpStoryPlayer::ID,
+ Tag\ScriptAmpStory::ID,
+ Tag\ScriptAmpStreamGallery::ID,
+ Tag\ScriptAmpSubscriptionsGoogle::ID,
+ Tag\ScriptAmpSubscriptions::ID,
+ Tag\ScriptAmpTimeago::ID,
+ Tag\ScriptAmpTruncateText::ID,
+ Tag\ScriptAmpTwitter::ID,
+ Tag\ScriptAmpUserNotification::ID,
+ Tag\ScriptAmpVideoDocking::ID,
+ Tag\ScriptAmpVideoIframe::ID,
+ Tag\ScriptAmpVideoIframe2::ID,
+ Tag\ScriptAmpVideo::ID,
+ Tag\ScriptAmpVideo2::ID,
+ Tag\ScriptAmpVimeo::ID,
+ Tag\ScriptAmpVimeo2::ID,
+ Tag\ScriptAmpVine::ID,
+ Tag\ScriptAmpViqeoPlayer::ID,
+ Tag\ScriptAmpVk::ID,
+ Tag\ScriptAmpWebPush::ID,
+ Tag\ScriptAmpWistiaPlayer::ID,
+ Tag\ScriptAmpYotpo::ID,
+ Tag\ScriptAmpYoutube::ID,
+ Tag\ScriptAmpYoutube2::ID,
+ Tag\ScriptCustomElementAmpAccordionAmp4email::ID,
+ Tag\ScriptCustomElementAmpAutocompleteAmp4email::ID,
+ Tag\ScriptCustomElementAmpBindAmp4email::ID,
+ Tag\ScriptCustomElementAmpCarouselAmp4email::ID,
+ Tag\ScriptCustomElementAmpFitTextAmp4email::ID,
+ Tag\ScriptCustomElementAmpFormAmp4email::ID,
+ Tag\ScriptCustomElementAmpImageLightboxAmp4email::ID,
+ Tag\ScriptCustomElementAmpLightboxAmp4ads::ID,
+ Tag\ScriptCustomElementAmpLightboxAmp4email::ID,
+ Tag\ScriptCustomElementAmpListAmp4email::ID,
+ Tag\ScriptCustomElementAmpSelectorAmp4email::ID,
+ Tag\ScriptCustomElementAmpSidebarAmp4email::ID,
+ Tag\ScriptCustomElementAmpTimeagoAmp4email::ID,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4ads::ID,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4email::ID,
+ Tag\SubscriptionsScriptCiphertext::ID,
+ ],
+ Extension::ACCORDION => Tag\AmpAccordion::ID,
+ Element::SECTION => [
+ Tag\AmpAccordionSection::ID,
+ Tag\Section::ID,
+ Tag\SectionAmp4email::ID,
+ Tag\SubscriptionsSectionContentSwgAmpCacheNonce::ID,
+ ],
+ Extension::ACTION_MACRO => Tag\AmpActionMacro::ID,
+ Extension::AD => [
+ Tag\AmpAd::ID,
+ Tag\AmpAdWithDataEnableRefreshAttribute::ID,
+ Tag\AmpAdWithDataMultiSizeAttribute::ID,
+ Tag\AmpAdWithTypeCustom::ID,
+ ],
+ Extension::AD_CUSTOM => Tag\AmpAdCustom::ID,
+ Extension::AD_EXIT => Tag\AmpAdExit::ID,
+ Extension::ADDTHIS => Tag\AmpAddthis::ID,
+ Extension::ANALYTICS => Tag\AmpAnalytics::ID,
+ Extension::ANIM => [
+ Tag\AmpAnim::ID,
+ Tag\AmpAnimAmp4email::ID,
+ ],
+ Extension::ANIMATION => Tag\AmpAnimation::ID,
+ Extension::APESTER_MEDIA => Tag\AmpApesterMedia::ID,
+ Extension::APP_BANNER => Tag\AmpAppBanner::ID,
+ Element::BUTTON => [
+ Tag\AmpAppBannerButtonOpenButton::ID,
+ Tag\AmpListLoadMoreButtonLoadMoreClickable::ID,
+ Tag\Button::ID,
+ Tag\ButtonAmpNestedMenu::ID,
+ ],
+ Extension::AUDIO => [
+ Tag\AmpAudio::ID,
+ Tag\AmpAudioA4a::ID,
+ Tag\AmpStoryAmpAudio::ID,
+ ],
+ Element::SOURCE => [
+ Tag\AmpAudioSource::ID,
+ Tag\AmpImaVideoSource::ID,
+ Tag\AmpVideoSource::ID,
+ Tag\AudioSource::ID,
+ Tag\PictureSource::ID,
+ Tag\VideoSource::ID,
+ ],
+ Element::TRACK => [
+ Tag\AmpAudioTrack::ID,
+ Tag\AmpAudioTrackKindSubtitles::ID,
+ Tag\AmpImaVideoTrack::ID,
+ Tag\AmpImaVideoTrackKindSubtitles::ID,
+ Tag\AmpVideoTrack::ID,
+ Tag\AmpVideoTrackKindSubtitles::ID,
+ Tag\AudioTrack::ID,
+ Tag\AudioTrackKindSubtitles::ID,
+ Tag\VideoTrack::ID,
+ Tag\VideoTrackKindSubtitles::ID,
+ ],
+ Extension::AUTO_ADS => Tag\AmpAutoAds::ID,
+ Extension::AUTOCOMPLETE => [
+ Tag\AmpAutocomplete::ID,
+ Tag\AmpAutocompleteAmp4email::ID,
+ ],
+ Element::INPUT => [
+ Tag\AmpAutocompleteInput::ID,
+ Tag\Input::ID,
+ Tag\InputMaskDateDdMmYyyy::ID,
+ Tag\InputMaskDateMmDdYyyy::ID,
+ Tag\InputMaskDateMmYy::ID,
+ Tag\InputMaskDateYyyyMmDd::ID,
+ Tag\InputMaskPaymentCard::ID,
+ Tag\InputMaskCustomMask::ID,
+ Tag\InputTypeFile::ID,
+ Tag\InputTypePassword::ID,
+ ],
+ Extension::BASE_CAROUSEL => [
+ Tag\AmpBaseCarousel::ID,
+ Tag\AmpBaseCarouselLightbox::ID,
+ ],
+ Extension::BEOPINION => Tag\AmpBeopinion::ID,
+ Extension::BIND_MACRO => Tag\AmpBindMacro::ID,
+ Extension::BODYMOVIN_ANIMATION => Tag\AmpBodymovinAnimation::ID,
+ Extension::BRID_PLAYER => Tag\AmpBridPlayer::ID,
+ Extension::BRIGHTCOVE => Tag\AmpBrightcove::ID,
+ Extension::BYSIDE_CONTENT => Tag\AmpBysideContent::ID,
+ Extension::CALL_TRACKING => Tag\AmpCallTracking::ID,
+ Extension::CAROUSEL => [
+ Tag\AmpCarousel::ID,
+ Tag\AmpCarouselLightbox::ID,
+ ],
+ Extension::CONNATIX_PLAYER => Tag\AmpConnatixPlayer::ID,
+ Extension::CONSENT => [
+ Tag\AmpConsent::ID,
+ Tag\AmpConsentType::ID,
+ ],
+ Extension::DAILYMOTION => Tag\AmpDailymotion::ID,
+ Extension::DATE_COUNTDOWN => Tag\AmpDateCountdown::ID,
+ Extension::DATE_DISPLAY => Tag\AmpDateDisplay::ID,
+ Element::TEMPLATE => [
+ Tag\AmpDatePickerTemplateDateTemplate::ID,
+ Tag\AmpDatePickerTemplateInfoTemplate::ID,
+ Tag\AmpStoryAutoAdsTemplate::ID,
+ Tag\Template::ID,
+ Tag\TemplateAmp4email::ID,
+ ],
+ Extension::DATE_PICKER => [
+ Tag\AmpDatePickerTypeRangeModeOverlay::ID,
+ Tag\AmpDatePickerTypeRangeModeStatic::ID,
+ Tag\AmpDatePickerTypeSingleModeOverlay::ID,
+ Tag\AmpDatePickerTypeSingleModeStatic::ID,
+ ],
+ Extension::DELIGHT_PLAYER => Tag\AmpDelightPlayer::ID,
+ Extension::EMBED => [
+ Tag\AmpEmbed::ID,
+ Tag\AmpEmbedWithDataMultiSizeAttribute::ID,
+ ],
+ Extension::EMBEDLY_CARD => Tag\AmpEmbedlyCard::ID,
+ Extension::EMBEDLY_KEY => Tag\AmpEmbedlyKey::ID,
+ Extension::EXPERIMENT => Tag\AmpExperiment::ID,
+ Extension::FACEBOOK => Tag\AmpFacebook::ID,
+ Extension::FACEBOOK_COMMENTS => Tag\AmpFacebookComments::ID,
+ Extension::FACEBOOK_LIKE => Tag\AmpFacebookLike::ID,
+ Extension::FACEBOOK_PAGE => Tag\AmpFacebookPage::ID,
+ Extension::FIT_TEXT => Tag\AmpFitText::ID,
+ Extension::FONT => Tag\AmpFont::ID,
+ Extension::FX_FLYING_CARPET => Tag\AmpFxFlyingCarpet::ID,
+ Extension::GEO => Tag\AmpGeo::ID,
+ Extension::GFYCAT => Tag\AmpGfycat::ID,
+ Extension::GIST => Tag\AmpGist::ID,
+ Extension::GOOGLE_ASSISTANT_ASSISTJS_CONFIG => Tag\AmpGoogleAssistantAssistjsConfig::ID,
+ Extension::GOOGLE_ASSISTANT_INLINE_SUGGESTION_BAR => Tag\AmpGoogleAssistantInlineSuggestionBar::ID,
+ Extension::GOOGLE_ASSISTANT_VOICE_BAR => Tag\AmpGoogleAssistantVoiceBar::ID,
+ Extension::GOOGLE_ASSISTANT_VOICE_BUTTON => Tag\AmpGoogleAssistantVoiceButton::ID,
+ Extension::GOOGLE_DOCUMENT_EMBED => Tag\AmpGoogleDocumentEmbed::ID,
+ Extension::GWD_ANIMATION => Tag\AmpGwdAnimation::ID,
+ Extension::HULU => Tag\AmpHulu::ID,
+ Extension::IFRAME => Tag\AmpIframe::ID,
+ Extension::IFRAMELY => Tag\AmpIframely::ID,
+ Extension::IMA_VIDEO => Tag\AmpImaVideo::ID,
+ Extension::IMAGE_LIGHTBOX => Tag\AmpImageLightbox::ID,
+ Extension::IMAGE_SLIDER => [
+ Tag\AmpImageSlider::ID,
+ Tag\AmpImageSliderTransformed::ID,
+ ],
+ Element::DIV => [
+ Tag\AmpImageSliderDivFirst::ID,
+ Tag\AmpImageSliderDivSecond::ID,
+ Tag\AmpListDivFetchError::ID,
+ Tag\Div::ID,
+ Tag\DivAmpNestedMenu::ID,
+ Tag\FormDivSubmitError::ID,
+ Tag\FormDivSubmitErrorTemplate::ID,
+ Tag\FormDivSubmitSuccess::ID,
+ Tag\FormDivSubmitSuccessTemplate::ID,
+ Tag\FormDivSubmitting::ID,
+ Tag\FormDivSubmittingTemplate::ID,
+ Tag\FormDivVerifyError::ID,
+ Tag\FormDivVerifyErrorTemplate::ID,
+ ],
+ Extension::IMG => [
+ Tag\AmpImg::ID,
+ Tag\AmpImgAmp4email::ID,
+ Tag\AmpImgTransformed::ID,
+ ],
+ Element::IMG => [
+ Tag\AmpImgImgTransformed::ID,
+ Tag\AmpImgImgPlaceholderTransformed::ID,
+ Tag\AmpStoryPlayerImg::ID,
+ Tag\ImgIAmphtmlIntrinsicSizer::ID,
+ Tag\ImgIAmphtmlIntrinsicSizerAmpStoryPlayer::ID,
+ Tag\NoscriptImg::ID,
+ ],
+ Extension::IMGUR => Tag\AmpImgur::ID,
+ Extension::INLINE_GALLERY => Tag\AmpInlineGallery::ID,
+ Extension::INLINE_GALLERY_PAGINATION => [
+ Tag\AmpInlineGalleryPagination::ID,
+ Tag\AmpInlineGalleryPaginationInset::ID,
+ ],
+ Extension::INLINE_GALLERY_THUMBNAILS => Tag\AmpInlineGalleryThumbnails::ID,
+ Extension::INSTAGRAM => Tag\AmpInstagram::ID,
+ Extension::INSTALL_SERVICEWORKER => Tag\AmpInstallServiceworker::ID,
+ Extension::IZLESENE => Tag\AmpIzlesene::ID,
+ Extension::JWPLAYER => Tag\AmpJwplayer::ID,
+ Extension::KALTURA_PLAYER => Tag\AmpKalturaPlayer::ID,
+ Extension::LAYOUT => Tag\AmpLayout::ID,
+ Extension::LIGHTBOX => [
+ Tag\AmpLightbox::ID,
+ Tag\AmpLightboxAmp4ads::ID,
+ ],
+ Extension::LINK_REWRITER => Tag\AmpLinkRewriter::ID,
+ Extension::LIST_ => [
+ Tag\AmpList::ID,
+ Tag\AmpListAmp4email::ID,
+ ],
+ Extension::LIST_LOAD_MORE => Tag\AmpListLoadMore::ID,
+ Extension::LIVE_LIST => Tag\AmpLiveList::ID,
+ Extension::MATHML => Tag\AmpMathml::ID,
+ Extension::MEGA_MENU => Tag\AmpMegaMenu::ID,
+ Extension::MEGAPHONE => [
+ Tag\AmpMegaphoneDataEpisode::ID,
+ Tag\AmpMegaphoneDataPlaylist::ID,
+ ],
+ Extension::MINUTE_MEDIA_PLAYER => Tag\AmpMinuteMediaPlayer::ID,
+ Extension::MOWPLAYER => Tag\AmpMowplayer::ID,
+ Extension::NESTED_MENU => Tag\AmpNestedMenu::ID,
+ Extension::NEXT_PAGE => [
+ Tag\AmpNextPageWithInlineConfig::ID,
+ Tag\AmpNextPageWithSrcAttribute::ID,
+ Tag\AmpNextPageTypeAdsense::ID,
+ ],
+ Extension::NEXXTV_PLAYER => Tag\AmpNexxtvPlayer::ID,
+ Extension::O2_PLAYER => Tag\AmpO2Player::ID,
+ Extension::ONETAP_GOOGLE => Tag\AmpOnetapGoogle::ID,
+ Extension::OOYALA_PLAYER => Tag\AmpOoyalaPlayer::ID,
+ Extension::ORIENTATION_OBSERVER => Tag\AmpOrientationObserver::ID,
+ Extension::PAN_ZOOM => Tag\AmpPanZoom::ID,
+ Extension::PINTEREST => Tag\AmpPinterest::ID,
+ Extension::PIXEL => Tag\AmpPixel::ID,
+ Extension::PLAYBUZZ => Tag\AmpPlaybuzz::ID,
+ Extension::POSITION_OBSERVER => Tag\AmpPositionObserver::ID,
+ Extension::POWR_PLAYER => Tag\AmpPowrPlayer::ID,
+ Extension::REACH_PLAYER => Tag\AmpReachPlayer::ID,
+ Extension::RECAPTCHA_INPUT => Tag\AmpRecaptchaInput::ID,
+ Extension::REDBULL_PLAYER => Tag\AmpRedbullPlayer::ID,
+ Extension::REDDIT => Tag\AmpReddit::ID,
+ Extension::RIDDLE_QUIZ => Tag\AmpRiddleQuiz::ID,
+ Extension::SCRIPT => Tag\AmpScript::ID,
+ Extension::SELECTOR => Tag\AmpSelector::ID,
+ Extension::SIDEBAR => [
+ Tag\AmpSidebar::ID,
+ Tag\AmpSidebarAmp4email::ID,
+ Tag\AmpStoryAmpSidebar::ID,
+ ],
+ Element::NAV => [
+ Tag\AmpSidebarNav::ID,
+ Tag\Nav::ID,
+ ],
+ Extension::SKIMLINKS => Tag\AmpSkimlinks::ID,
+ Extension::SMARTLINKS => Tag\AmpSmartlinks::ID,
+ Extension::SOCIAL_SHARE => Tag\AmpSocialShare::ID,
+ Extension::SOUNDCLOUD => Tag\AmpSoundcloud::ID,
+ Extension::SPRINGBOARD_PLAYER => Tag\AmpSpringboardPlayer::ID,
+ Extension::STATE => [
+ Tag\AmpState::ID,
+ Tag\AmpStateAmp4email::ID,
+ ],
+ Extension::STICKY_AD => Tag\AmpStickyAd::ID,
+ Extension::STORY => Tag\AmpStory::ID,
+ Extension::STORY_360 => Tag\AmpStory360::ID,
+ Extension::STORY_ANIMATION => Tag\AmpStoryAnimation::ID,
+ Extension::STORY_AUTO_ADS => Tag\AmpStoryAutoAds::ID,
+ Extension::STORY_AUTO_ANALYTICS => Tag\AmpStoryAutoAnalytics::ID,
+ Extension::STORY_BOOKEND => Tag\AmpStoryBookend::ID,
+ Extension::STORY_CONSENT => Tag\AmpStoryConsent::ID,
+ Extension::STORY_CTA_LAYER => Tag\AmpStoryCtaLayer::ID,
+ Extension::STORY_GRID_LAYER => Tag\AmpStoryGridLayer::ID,
+ Extension::STORY_INTERACTIVE_BINARY_POLL => Tag\AmpStoryInteractiveBinaryPoll::ID,
+ Extension::STORY_INTERACTIVE_POLL => Tag\AmpStoryInteractivePoll::ID,
+ Extension::STORY_INTERACTIVE_QUIZ => Tag\AmpStoryInteractiveQuiz::ID,
+ Extension::STORY_INTERACTIVE_RESULTS => Tag\AmpStoryInteractiveResults::ID,
+ Extension::STORY_PAGE => Tag\AmpStoryPage::ID,
+ Extension::STORY_PAGE_ATTACHMENT => [
+ Tag\AmpStoryPageAttachment::ID,
+ Tag\AmpStoryPageAttachmentHref::ID,
+ ],
+ Extension::STORY_PAGE_OUTLINK => Tag\AmpStoryPageOutlink::ID,
+ Extension::STORY_PANNING_MEDIA => Tag\AmpStoryPanningMedia::ID,
+ Extension::STORY_PLAYER => Tag\AmpStoryPlayer::ID,
+ Extension::STORY_SOCIAL_SHARE => Tag\AmpStorySocialShare::ID,
+ Extension::VIDEO => [
+ Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::ID,
+ Tag\AmpStoryAmpVideo::ID,
+ Tag\AmpVideo::ID,
+ ],
+ Extension::STREAM_GALLERY => Tag\AmpStreamGallery::ID,
+ Extension::TIMEAGO => Tag\AmpTimeago::ID,
+ Extension::TRUNCATE_TEXT => Tag\AmpTruncateText::ID,
+ Extension::TWITTER => Tag\AmpTwitter::ID,
+ Extension::USER_NOTIFICATION => Tag\AmpUserNotification::ID,
+ Extension::VIDEO_IFRAME => [
+ Tag\AmpVideoIframe::ID,
+ Tag\AmpVideoIframeTransformed::ID,
+ ],
+ Extension::VIMEO => Tag\AmpVimeo::ID,
+ Extension::VINE => Tag\AmpVine::ID,
+ Extension::VIQEO_PLAYER => Tag\AmpViqeoPlayer::ID,
+ Extension::VK => Tag\AmpVk::ID,
+ Extension::WEB_PUSH => Tag\AmpWebPush::ID,
+ Extension::WEB_PUSH_WIDGET => Tag\AmpWebPushWidget::ID,
+ Extension::WISTIA_PLAYER => Tag\AmpWistiaPlayer::ID,
+ Extension::YOTPO => Tag\AmpYotpo::ID,
+ Extension::YOUTUBE => Tag\AmpYoutube::ID,
+ Element::ARTICLE => Tag\Article::ID,
+ Element::ASIDE => Tag\Aside::ID,
+ Element::AUDIO => Tag\Audio::ID,
+ Element::B => Tag\B::ID,
+ Element::BASE => Tag\Base::ID,
+ Element::BDI => Tag\Bdi::ID,
+ Element::BDO => Tag\Bdo::ID,
+ Element::BIG => Tag\Big::ID,
+ Element::BLOCKQUOTE => Tag\Blockquote::ID,
+ Element::BODY => Tag\Body::ID,
+ Element::BR => Tag\Br::ID,
+ Element::CANVAS => Tag\Canvas::ID,
+ Element::CAPTION => Tag\Caption::ID,
+ Element::CENTER => Tag\Center::ID,
+ Element::CIRCLE => Tag\Circle::ID,
+ Element::CITE => Tag\Cite::ID,
+ Element::CLIPPATH => Tag\Clippath::ID,
+ Element::CODE => Tag\Code::ID,
+ Element::COL => Tag\Col::ID,
+ Element::COLGROUP => Tag\Colgroup::ID,
+ Element::DATA => Tag\Data::ID,
+ Element::DATALIST => Tag\Datalist::ID,
+ Element::DD => Tag\Dd::ID,
+ Element::DEFS => Tag\Defs::ID,
+ Element::DEL => Tag\Del::ID,
+ Element::DESC => Tag\Desc::ID,
+ Element::DETAILS => Tag\Details::ID,
+ Element::DFN => Tag\Dfn::ID,
+ Element::DIR => Tag\Dir::ID,
+ Element::DL => Tag\Dl::ID,
+ Element::DT => Tag\Dt::ID,
+ Element::ELLIPSE => Tag\Ellipse::ID,
+ Element::EM => Tag\Em::ID,
+ Element::FEBLEND => Tag\Feblend::ID,
+ Element::FECOLORMATRIX => Tag\Fecolormatrix::ID,
+ Element::FECOMPONENTTRANSFER => Tag\Fecomponenttransfer::ID,
+ Element::FECOMPOSITE => Tag\Fecomposite::ID,
+ Element::FECONVOLVEMATRIX => Tag\Feconvolvematrix::ID,
+ Element::FEDIFFUSELIGHTING => Tag\Fediffuselighting::ID,
+ Element::FEDISPLACEMENTMAP => Tag\Fedisplacementmap::ID,
+ Element::FEDISTANTLIGHT => Tag\Fedistantlight::ID,
+ Element::FEDROPSHADOW => Tag\Fedropshadow::ID,
+ Element::FEFLOOD => Tag\Feflood::ID,
+ Element::FEFUNCA => Tag\Fefunca::ID,
+ Element::FEFUNCB => Tag\Fefuncb::ID,
+ Element::FEFUNCG => Tag\Fefuncg::ID,
+ Element::FEFUNCR => Tag\Fefuncr::ID,
+ Element::FEGAUSSIANBLUR => Tag\Fegaussianblur::ID,
+ Element::FEMERGE => Tag\Femerge::ID,
+ Element::FEMERGENODE => Tag\Femergenode::ID,
+ Element::FEMORPHOLOGY => Tag\Femorphology::ID,
+ Element::FEOFFSET => Tag\Feoffset::ID,
+ Element::FEPOINTLIGHT => Tag\Fepointlight::ID,
+ Element::FESPECULARLIGHTING => Tag\Fespecularlighting::ID,
+ Element::FESPOTLIGHT => Tag\Fespotlight::ID,
+ Element::FETILE => Tag\Fetile::ID,
+ Element::FETURBULENCE => Tag\Feturbulence::ID,
+ Element::FIELDSET => Tag\Fieldset::ID,
+ Element::FIGCAPTION => Tag\Figcaption::ID,
+ Element::FIGURE => Tag\Figure::ID,
+ Element::FILTER => Tag\Filter::ID,
+ Element::FOOTER => Tag\Footer::ID,
+ Element::FORM => [
+ Tag\FormMethodGet::ID,
+ Tag\FormMethodGetAmp4email::ID,
+ Tag\FormMethodPost::ID,
+ Tag\FormMethodPostAmp4email::ID,
+ ],
+ Element::G => Tag\G::ID,
+ Element::GLYPH => Tag\Glyph::ID,
+ Element::GLYPHREF => Tag\Glyphref::ID,
+ Element::H1 => Tag\H1::ID,
+ Element::H2 => [
+ Tag\H2::ID,
+ Tag\H2AmpNestedMenu::ID,
+ ],
+ Element::H3 => [
+ Tag\H3::ID,
+ Tag\H3AmpNestedMenu::ID,
+ ],
+ Element::H4 => [
+ Tag\H4::ID,
+ Tag\H4AmpNestedMenu::ID,
+ ],
+ Element::H5 => [
+ Tag\H5::ID,
+ Tag\H5AmpNestedMenu::ID,
+ ],
+ Element::H6 => [
+ Tag\H6::ID,
+ Tag\H6AmpNestedMenu::ID,
+ ],
+ Element::HEAD => Tag\Head::ID,
+ Element::STYLE => [
+ Tag\HeadStyleAmpBoilerplate::ID,
+ Tag\HeadStyleAmpBoilerplateTransformed::ID,
+ Tag\HeadStyleAmp4adsBoilerplate::ID,
+ Tag\HeadStyleAmp4emailBoilerplate::ID,
+ Tag\NoscriptStyleAmpBoilerplate::ID,
+ Tag\NoscriptStyleAmpBoilerplateTransformed::ID,
+ Tag\StyleAmpCustom::ID,
+ Tag\StyleAmpCustomAmp4ads::ID,
+ Tag\StyleAmpCustomAmp4email::ID,
+ Tag\StyleAmpCustomCssStrict::ID,
+ Tag\StyleAmpCustomLengthCheck::ID,
+ Tag\StyleAmpKeyframes::ID,
+ Tag\StyleAmpRuntimeTransformed::ID,
+ ],
+ Element::HEADER => Tag\Header::ID,
+ Element::HGROUP => Tag\Hgroup::ID,
+ Element::HKERN => Tag\Hkern::ID,
+ Element::HR => Tag\Hr::ID,
+ Element::HTML => [
+ Tag\Html::ID,
+ Tag\HtmlTransformed::ID,
+ ],
+ Element::_DOCTYPE => [
+ Tag\HtmlDoctype::ID,
+ Tag\HtmlDoctypeAmp4ads::ID,
+ ],
+ Element::I => Tag\I::ID,
+ Internal::SIZER => [
+ Tag\IAmphtmlSizerIntrinsic::ID,
+ Tag\IAmphtmlSizerResponsive::ID,
+ ],
+ Element::IFRAME => Tag\Iframe::ID,
+ Element::IMAGE => Tag\Image::ID,
+ Element::INS => Tag\Ins::ID,
+ Element::KBD => Tag\Kbd::ID,
+ Element::LABEL => Tag\Label::ID,
+ Element::LEGEND => Tag\Legend::ID,
+ Element::LI => Tag\Li::ID,
+ Element::LINE => Tag\Line::ID,
+ Element::LINEARGRADIENT => Tag\Lineargradient::ID,
+ Element::STOP => [
+ Tag\LineargradientStop::ID,
+ Tag\RadialgradientStop::ID,
+ ],
+ Element::LINK => [
+ Tag\LinkItemprop::ID,
+ Tag\LinkItempropSameas::ID,
+ Tag\LinkProperty::ID,
+ Tag\LinkRel::ID,
+ Tag\LinkRelCanonical::ID,
+ Tag\LinkRelManifest::ID,
+ Tag\LinkRelModulepreload::ID,
+ Tag\LinkRelPreload::ID,
+ Tag\LinkRelStylesheetForFonts::ID,
+ ],
+ Element::LISTING => Tag\Listing::ID,
+ Element::MAIN => Tag\Main::ID,
+ Element::MARK => Tag\Mark::ID,
+ Element::MARKER => Tag\Marker::ID,
+ Element::MASK => Tag\Mask::ID,
+ Element::META => [
+ Tag\MetaCharsetUtf8::ID,
+ Tag\MetaHttpEquivContentLanguage::ID,
+ Tag\MetaHttpEquivContentScriptType::ID,
+ Tag\MetaHttpEquivContentStyleType::ID,
+ Tag\MetaHttpEquivContentType::ID,
+ Tag\MetaHttpEquivImagetoolbar::ID,
+ Tag\MetaHttpEquivOriginTrial::ID,
+ Tag\MetaHttpEquivPicsLabel::ID,
+ Tag\MetaHttpEquivResourceType::ID,
+ Tag\MetaHttpEquivXDnsPrefetchControl::ID,
+ Tag\MetaHttpEquivXUaCompatible::ID,
+ Tag\MetaNameAmp3pIframeSrc::ID,
+ Tag\MetaNameAmpAdDoubleclickSra::ID,
+ Tag\MetaNameAmpAdEnableRefresh::ID,
+ Tag\MetaNameAmpConsentBlocking::ID,
+ Tag\MetaNameAmpCtaLandingPageType::ID,
+ Tag\MetaNameAmpCtaType::ID,
+ Tag\MetaNameAmpCtaUrl::ID,
+ Tag\MetaNameAmpExperimentToken::ID,
+ Tag\MetaNameAmpExperimentsOptIn::ID,
+ Tag\MetaNameAmpGoogleClientidIdApi::ID,
+ Tag\MetaNameAmpLinkVariableAllowedOrigin::ID,
+ Tag\MetaNameAmpListLoadMore::ID,
+ Tag\MetaNameAmpRecaptchaInput::ID,
+ Tag\MetaNameAmpScriptSrc::ID,
+ Tag\MetaNameAmpStoryGeneratorName::ID,
+ Tag\MetaNameAmpStoryGeneratorVersion::ID,
+ Tag\MetaNameAmpToAmpNavigation::ID,
+ Tag\MetaNameAmp4adsId::ID,
+ Tag\MetaNameAmp4adsVars::ID,
+ Tag\MetaNameAndContent::ID,
+ Tag\MetaNameAppleItunesApp::ID,
+ Tag\MetaNameViewport::ID,
+ ],
+ Element::METADATA => Tag\Metadata::ID,
+ Element::METER => Tag\Meter::ID,
+ Element::MULTICOL => Tag\Multicol::ID,
+ Element::NEXTID => Tag\Nextid::ID,
+ Element::NOBR => Tag\Nobr::ID,
+ Element::NOSCRIPT => [
+ Tag\Noscript::ID,
+ Tag\NoscriptEnclosureForBoilerplate::ID,
+ Tag\NoscriptEnclosureForBoilerplateTransformed::ID,
+ ],
+ Element::O_P => Tag\OP::ID,
+ Element::OL => Tag\Ol::ID,
+ Element::OPTGROUP => Tag\Optgroup::ID,
+ Element::OPTION => Tag\Option::ID,
+ Element::OUTPUT => Tag\Output::ID,
+ Element::P => Tag\P::ID,
+ Element::PATH => Tag\Path::ID,
+ Element::PATTERN => Tag\Pattern::ID,
+ Element::PICTURE => Tag\Picture::ID,
+ Element::POLYGON => Tag\Polygon::ID,
+ Element::POLYLINE => Tag\Polyline::ID,
+ Element::PRE => Tag\Pre::ID,
+ Element::PROGRESS => Tag\Progress::ID,
+ Element::Q => Tag\Q::ID,
+ Element::RADIALGRADIENT => Tag\Radialgradient::ID,
+ Element::RB => Tag\Rb::ID,
+ Element::RECT => Tag\Rect::ID,
+ Element::RP => Tag\Rp::ID,
+ Element::RT => Tag\Rt::ID,
+ Element::RTC => Tag\Rtc::ID,
+ Element::RUBY => Tag\Ruby::ID,
+ Element::S => Tag\S::ID,
+ Element::SAMP => Tag\Samp::ID,
+ Element::SELECT => Tag\Select::ID,
+ Element::SLOT => Tag\Slot::ID,
+ Element::SMALL => Tag\Small::ID,
+ Element::SOLIDCOLOR => Tag\Solidcolor::ID,
+ Element::SPACER => Tag\Spacer::ID,
+ Element::SPAN => [
+ Tag\Span::ID,
+ Tag\SpanAmpNestedMenu::ID,
+ Tag\SpanSwgAmpCacheNonce::ID,
+ ],
+ Element::STRIKE => Tag\Strike::ID,
+ Element::STRONG => Tag\Strong::ID,
+ Element::SUB => Tag\Sub::ID,
+ Element::SUMMARY => Tag\Summary::ID,
+ Element::SUP => Tag\Sup::ID,
+ Element::SVG => Tag\Svg::ID,
+ Element::TITLE => [
+ Tag\SvgTitle::ID,
+ Tag\Title::ID,
+ Tag\TitleAmp4email::ID,
+ ],
+ Element::SWITCH_ => Tag\Switch_::ID,
+ Element::SYMBOL => Tag\Symbol::ID,
+ Element::TABLE => Tag\Table::ID,
+ Element::TBODY => Tag\Tbody::ID,
+ Element::TD => Tag\Td::ID,
+ Element::TEXT => Tag\Text::ID,
+ Element::TEXTAREA => Tag\Textarea::ID,
+ Element::TEXTPATH => Tag\Textpath::ID,
+ Element::TFOOT => Tag\Tfoot::ID,
+ Element::TH => Tag\Th::ID,
+ Element::THEAD => Tag\Thead::ID,
+ Element::TIME => Tag\Time::ID,
+ Element::TR => Tag\Tr::ID,
+ Element::TREF => Tag\Tref::ID,
+ Element::TSPAN => Tag\Tspan::ID,
+ Element::TT => Tag\Tt::ID,
+ Element::U => Tag\U::ID,
+ Element::UL => Tag\Ul::ID,
+ Element::USE_ => Tag\Use_::ID,
+ Element::VAR_ => Tag\Var_::ID,
+ Element::VIDEO => Tag\Video::ID,
+ Element::VIEW => Tag\View::ID,
+ Element::VKERN => Tag\Vkern::ID,
+ Element::WBR => Tag\Wbr::ID,
+ ];
+
+ /**
+ * Mapping of spec name to tag ID.
+ *
+ * This is used to optimize querying by spec name.
+ *
+ * @var array
+ */
+ const BY_SPEC_NAME = [
+ Tag\AAmp4email::ID => Tag\AAmp4email::ID,
+ Tag\AmpAccessExtensionJsonScript::ID => Tag\AmpAccessExtensionJsonScript::ID,
+ Tag\AmpAccordionSection::ID => Tag\AmpAccordionSection::ID,
+ Tag\AmpAdExitConfigurationJson::ID => Tag\AmpAdExitConfigurationJson::ID,
+ Tag\AmpAdExtensionScript::ID => Tag\AmpAdExtensionScript::ID,
+ Tag\AmpAdWithDataEnableRefreshAttribute::ID => Tag\AmpAdWithDataEnableRefreshAttribute::ID,
+ Tag\AmpAdWithDataMultiSizeAttribute::ID => Tag\AmpAdWithDataMultiSizeAttribute::ID,
+ Tag\AmpAdWithTypeCustom::ID => Tag\AmpAdWithTypeCustom::ID,
+ Tag\AmpAnalyticsExtensionJsonScript::ID => Tag\AmpAnalyticsExtensionJsonScript::ID,
+ Tag\AmpAnimAmp4email::ID => Tag\AmpAnimAmp4email::ID,
+ Tag\AmpAnimationExtensionJsonScript::ID => Tag\AmpAnimationExtensionJsonScript::ID,
+ Tag\AmpAnimExtensionScriptAmp4email::ID => Tag\AmpAnimExtensionScriptAmp4email::ID,
+ Tag\AmpAppBannerButtonOpenButton::ID => Tag\AmpAppBannerButtonOpenButton::ID,
+ Tag\AmpAudioA4a::ID => Tag\AmpAudioA4a::ID,
+ Tag\AmpAudioSource::ID => Tag\AmpAudioSource::ID,
+ Tag\AmpAudioTrack::ID => Tag\AmpAudioTrack::ID,
+ Tag\AmpAudioTrackKindSubtitles::ID => Tag\AmpAudioTrackKindSubtitles::ID,
+ Tag\AmpAutocomplete::ID => Tag\AmpAutocomplete::ID,
+ Tag\AmpAutocompleteAmp4email::ID => Tag\AmpAutocompleteAmp4email::ID,
+ Tag\AmpAutocompleteInput::ID => Tag\AmpAutocompleteInput::ID,
+ Tag\AmpAutocompleteJson::ID => Tag\AmpAutocompleteJson::ID,
+ Tag\AmpBaseCarouselLightboxChild::ID => Tag\AmpBaseCarouselLightboxChild::ID,
+ Tag\AmpBaseCarouselLightboxLightboxExclude::ID => Tag\AmpBaseCarouselLightboxLightboxExclude::ID,
+ Tag\AmpBaseCarouselLightbox::ID => Tag\AmpBaseCarouselLightbox::ID,
+ Tag\AmpBindExtensionJsonScript::ID => Tag\AmpBindExtensionJsonScript::ID,
+ Tag\AmpCarousel::ID => Tag\AmpCarousel::ID,
+ Tag\AmpCarouselLightbox::ID => Tag\AmpCarouselLightbox::ID,
+ Tag\AmpCarouselLightboxChild::ID => Tag\AmpCarouselLightboxChild::ID,
+ Tag\AmpCarouselLightboxLightboxExclude::ID => Tag\AmpCarouselLightboxLightboxExclude::ID,
+ Tag\AmpConsentExtensionJsonScript::ID => Tag\AmpConsentExtensionJsonScript::ID,
+ Tag\AmpConsentType::ID => Tag\AmpConsentType::ID,
+ Tag\AmpDatePickerTemplateDateTemplate::ID => Tag\AmpDatePickerTemplateDateTemplate::ID,
+ Tag\AmpDatePickerTemplateInfoTemplate::ID => Tag\AmpDatePickerTemplateInfoTemplate::ID,
+ Tag\AmpDatePickerTypeRangeModeOverlay::ID => Tag\AmpDatePickerTypeRangeModeOverlay::ID,
+ Tag\AmpDatePickerTypeRangeModeStatic::ID => Tag\AmpDatePickerTypeRangeModeStatic::ID,
+ Tag\AmpDatePickerTypeSingleModeOverlay::ID => Tag\AmpDatePickerTypeSingleModeOverlay::ID,
+ Tag\AmpDatePickerTypeSingleModeStatic::ID => Tag\AmpDatePickerTypeSingleModeStatic::ID,
+ Tag\AmpEmbedWithDataMultiSizeAttribute::ID => Tag\AmpEmbedWithDataMultiSizeAttribute::ID,
+ Tag\AmpExperimentExtensionJsonScript::ID => Tag\AmpExperimentExtensionJsonScript::ID,
+ Tag\AmpExperimentStoryExtensionJsonScript::ID => Tag\AmpExperimentStoryExtensionJsonScript::ID,
+ Tag\AmpGeoExtensionJsonScript::ID => Tag\AmpGeoExtensionJsonScript::ID,
+ Tag\AmpImaVideoScriptTypeApplicationJson::ID => Tag\AmpImaVideoScriptTypeApplicationJson::ID,
+ Tag\AmpImaVideoSource::ID => Tag\AmpImaVideoSource::ID,
+ Tag\AmpImaVideoTrack::ID => Tag\AmpImaVideoTrack::ID,
+ Tag\AmpImaVideoTrackKindSubtitles::ID => Tag\AmpImaVideoTrackKindSubtitles::ID,
+ Tag\AmpImageSliderTransformed::ID => Tag\AmpImageSliderTransformed::ID,
+ Tag\AmpImageSliderDivFirst::ID => Tag\AmpImageSliderDivFirst::ID,
+ Tag\AmpImageSliderDivSecond::ID => Tag\AmpImageSliderDivSecond::ID,
+ Tag\AmpImgAmp4email::ID => Tag\AmpImgAmp4email::ID,
+ Tag\AmpImgTransformed::ID => Tag\AmpImgTransformed::ID,
+ Tag\AmpImgImgTransformed::ID => Tag\AmpImgImgTransformed::ID,
+ Tag\AmpImgImgPlaceholderTransformed::ID => Tag\AmpImgImgPlaceholderTransformed::ID,
+ Tag\AmpInlineGalleryPagination::ID => Tag\AmpInlineGalleryPagination::ID,
+ Tag\AmpInlineGalleryPaginationInset::ID => Tag\AmpInlineGalleryPaginationInset::ID,
+ Tag\AmpLightboxAmp4ads::ID => Tag\AmpLightboxAmp4ads::ID,
+ Tag\AmpLinkRewriterExtensionJsonScript::ID => Tag\AmpLinkRewriterExtensionJsonScript::ID,
+ Tag\AmpListAmp4email::ID => Tag\AmpListAmp4email::ID,
+ Tag\AmpListLoadMoreButtonLoadMoreClickable::ID => Tag\AmpListLoadMoreButtonLoadMoreClickable::ID,
+ Tag\AmpListDivFetchError::ID => Tag\AmpListDivFetchError::ID,
+ Tag\AmpLiveListItems::ID => Tag\AmpLiveListItems::ID,
+ Tag\AmpLiveListItemsItem::ID => Tag\AmpLiveListItemsItem::ID,
+ Tag\AmpLiveListPagination::ID => Tag\AmpLiveListPagination::ID,
+ Tag\AmpLiveListUpdate::ID => Tag\AmpLiveListUpdate::ID,
+ Tag\AmpMegaMenuAmpList::ID => Tag\AmpMegaMenuAmpList::ID,
+ Tag\AmpMegaMenuAmpListTemplate::ID => Tag\AmpMegaMenuAmpListTemplate::ID,
+ Tag\AmpMegaMenuNav::ID => Tag\AmpMegaMenuNav::ID,
+ Tag\AmpMegaMenuItemContent::ID => Tag\AmpMegaMenuItemContent::ID,
+ Tag\AmpMegaMenuItemHeading::ID => Tag\AmpMegaMenuItemHeading::ID,
+ Tag\AmpMegaMenuNavUlOl::ID => Tag\AmpMegaMenuNavUlOl::ID,
+ Tag\AmpMegaMenuNavUlOlLi::ID => Tag\AmpMegaMenuNavUlOlLi::ID,
+ Tag\AmpMegaphoneDataEpisode::ID => Tag\AmpMegaphoneDataEpisode::ID,
+ Tag\AmpMegaphoneDataPlaylist::ID => Tag\AmpMegaphoneDataPlaylist::ID,
+ Tag\AmpNextPageScriptTypeApplicationJson::ID => Tag\AmpNextPageScriptTypeApplicationJson::ID,
+ Tag\AmpNextPageFooter::ID => Tag\AmpNextPageFooter::ID,
+ Tag\AmpNextPageRecommendationBox::ID => Tag\AmpNextPageRecommendationBox::ID,
+ Tag\AmpNextPageSeparator::ID => Tag\AmpNextPageSeparator::ID,
+ Tag\AmpNextPageWithInlineConfig::ID => Tag\AmpNextPageWithInlineConfig::ID,
+ Tag\AmpNextPageWithSrcAttribute::ID => Tag\AmpNextPageWithSrcAttribute::ID,
+ Tag\AmpNextPageTypeAdsense::ID => Tag\AmpNextPageTypeAdsense::ID,
+ Tag\AmpScriptExtensionLocalScript::ID => Tag\AmpScriptExtensionLocalScript::ID,
+ Tag\AmpSelectorChild::ID => Tag\AmpSelectorChild::ID,
+ Tag\AmpSelectorOption::ID => Tag\AmpSelectorOption::ID,
+ Tag\AmpSidebar::ID => Tag\AmpSidebar::ID,
+ Tag\AmpSidebarAmp4email::ID => Tag\AmpSidebarAmp4email::ID,
+ Tag\AmpSidebarNav::ID => Tag\AmpSidebarNav::ID,
+ Tag\AmpState::ID => Tag\AmpState::ID,
+ Tag\AmpStateAmp4email::ID => Tag\AmpStateAmp4email::ID,
+ Tag\AmpStoryAnimationJsonScript::ID => Tag\AmpStoryAnimationJsonScript::ID,
+ Tag\AmpStoryAutoAdsTemplate::ID => Tag\AmpStoryAutoAdsTemplate::ID,
+ Tag\AmpStoryAutoAdsConfigScript::ID => Tag\AmpStoryAutoAdsConfigScript::ID,
+ Tag\AmpStoryBookendExtensionJsonScript::ID => Tag\AmpStoryBookendExtensionJsonScript::ID,
+ Tag\AmpStoryConsentExtensionJsonScript::ID => Tag\AmpStoryConsentExtensionJsonScript::ID,
+ Tag\AmpStoryCtaLayerAnimateIn::ID => Tag\AmpStoryCtaLayerAnimateIn::ID,
+ Tag\AmpStoryGridLayerAnimateIn::ID => Tag\AmpStoryGridLayerAnimateIn::ID,
+ Tag\AmpStoryGridLayerDefault::ID => Tag\AmpStoryGridLayerDefault::ID,
+ Tag\AmpStoryPageAttachment::ID => Tag\AmpStoryPageAttachment::ID,
+ Tag\AmpStoryPageAttachmentHref::ID => Tag\AmpStoryPageAttachmentHref::ID,
+ Tag\AmpStoryPageOutlink::ID => Tag\AmpStoryPageOutlink::ID,
+ Tag\AmpStoryPlayerImg::ID => Tag\AmpStoryPlayerImg::ID,
+ Tag\AmpStorySocialShareExtensionJsonScript::ID => Tag\AmpStorySocialShareExtensionJsonScript::ID,
+ Tag\AmpStoryAmpAudio::ID => Tag\AmpStoryAmpAudio::ID,
+ Tag\AmpStoryAmpSidebar::ID => Tag\AmpStoryAmpSidebar::ID,
+ Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::ID => Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::ID,
+ Tag\AmpStoryAmpVideo::ID => Tag\AmpStoryAmpVideo::ID,
+ Tag\AmpSubscriptionsExtensionJsonScript::ID => Tag\AmpSubscriptionsExtensionJsonScript::ID,
+ Tag\AmpVideoIframeTransformed::ID => Tag\AmpVideoIframeTransformed::ID,
+ Tag\AmpVideoSource::ID => Tag\AmpVideoSource::ID,
+ Tag\AmpVideoTrack::ID => Tag\AmpVideoTrack::ID,
+ Tag\AmpVideoTrackKindSubtitles::ID => Tag\AmpVideoTrackKindSubtitles::ID,
+ Tag\Amp4adsEngineScript::ID => Tag\Amp4adsEngineScript::ID,
+ Tag\AmphtmlEngineScript::ID => Tag\AmphtmlEngineScript::ID,
+ Tag\AmphtmlEngineScriptLts::ID => Tag\AmphtmlEngineScriptLts::ID,
+ Tag\AmphtmlEngineScriptAmp4email::ID => Tag\AmphtmlEngineScriptAmp4email::ID,
+ Tag\AmphtmlModuleEngineScript::ID => Tag\AmphtmlModuleEngineScript::ID,
+ Tag\AmphtmlModuleLtsEngineScript::ID => Tag\AmphtmlModuleLtsEngineScript::ID,
+ Tag\AmphtmlNomoduleEngineScript::ID => Tag\AmphtmlNomoduleEngineScript::ID,
+ Tag\AmphtmlNomoduleLtsEngineScript::ID => Tag\AmphtmlNomoduleLtsEngineScript::ID,
+ Tag\AudioSource::ID => Tag\AudioSource::ID,
+ Tag\AudioTrack::ID => Tag\AudioTrack::ID,
+ Tag\AudioTrackKindSubtitles::ID => Tag\AudioTrackKindSubtitles::ID,
+ Tag\ButtonAmpNestedMenu::ID => Tag\ButtonAmpNestedMenu::ID,
+ Tag\CryptokeysJsonScript::ID => Tag\CryptokeysJsonScript::ID,
+ Tag\DivAmpNestedMenu::ID => Tag\DivAmpNestedMenu::ID,
+ Tag\FormDivSubmitError::ID => Tag\FormDivSubmitError::ID,
+ Tag\FormDivSubmitErrorTemplate::ID => Tag\FormDivSubmitErrorTemplate::ID,
+ Tag\FormDivSubmitSuccess::ID => Tag\FormDivSubmitSuccess::ID,
+ Tag\FormDivSubmitSuccessTemplate::ID => Tag\FormDivSubmitSuccessTemplate::ID,
+ Tag\FormDivSubmitting::ID => Tag\FormDivSubmitting::ID,
+ Tag\FormDivSubmittingTemplate::ID => Tag\FormDivSubmittingTemplate::ID,
+ Tag\FormDivVerifyError::ID => Tag\FormDivVerifyError::ID,
+ Tag\FormDivVerifyErrorTemplate::ID => Tag\FormDivVerifyErrorTemplate::ID,
+ Tag\FormMethodGet::ID => Tag\FormMethodGet::ID,
+ Tag\FormMethodGetAmp4email::ID => Tag\FormMethodGetAmp4email::ID,
+ Tag\FormMethodPost::ID => Tag\FormMethodPost::ID,
+ Tag\FormMethodPostAmp4email::ID => Tag\FormMethodPostAmp4email::ID,
+ Tag\H2AmpNestedMenu::ID => Tag\H2AmpNestedMenu::ID,
+ Tag\H3AmpNestedMenu::ID => Tag\H3AmpNestedMenu::ID,
+ Tag\H4AmpNestedMenu::ID => Tag\H4AmpNestedMenu::ID,
+ Tag\H5AmpNestedMenu::ID => Tag\H5AmpNestedMenu::ID,
+ Tag\H6AmpNestedMenu::ID => Tag\H6AmpNestedMenu::ID,
+ Tag\HeadStyleAmpBoilerplate::ID => Tag\HeadStyleAmpBoilerplate::ID,
+ Tag\HeadStyleAmpBoilerplateTransformed::ID => Tag\HeadStyleAmpBoilerplateTransformed::ID,
+ Tag\HeadStyleAmp4adsBoilerplate::ID => Tag\HeadStyleAmp4adsBoilerplate::ID,
+ Tag\HeadStyleAmp4emailBoilerplate::ID => Tag\HeadStyleAmp4emailBoilerplate::ID,
+ Tag\HtmlTransformed::ID => Tag\HtmlTransformed::ID,
+ Tag\HtmlDoctype::ID => Tag\HtmlDoctype::ID,
+ Tag\HtmlDoctypeAmp4ads::ID => Tag\HtmlDoctypeAmp4ads::ID,
+ Tag\IAmphtmlSizerIntrinsic::ID => Tag\IAmphtmlSizerIntrinsic::ID,
+ Tag\IAmphtmlSizerResponsive::ID => Tag\IAmphtmlSizerResponsive::ID,
+ Tag\ImgIAmphtmlIntrinsicSizer::ID => Tag\ImgIAmphtmlIntrinsicSizer::ID,
+ Tag\ImgIAmphtmlIntrinsicSizerAmpStoryPlayer::ID => Tag\ImgIAmphtmlIntrinsicSizerAmpStoryPlayer::ID,
+ Tag\InputMaskDateDdMmYyyy::ID => Tag\InputMaskDateDdMmYyyy::ID,
+ Tag\InputMaskDateMmDdYyyy::ID => Tag\InputMaskDateMmDdYyyy::ID,
+ Tag\InputMaskDateMmYy::ID => Tag\InputMaskDateMmYy::ID,
+ Tag\InputMaskDateYyyyMmDd::ID => Tag\InputMaskDateYyyyMmDd::ID,
+ Tag\InputMaskPaymentCard::ID => Tag\InputMaskPaymentCard::ID,
+ Tag\InputMaskCustomMask::ID => Tag\InputMaskCustomMask::ID,
+ Tag\InputTypeFile::ID => Tag\InputTypeFile::ID,
+ Tag\InputTypePassword::ID => Tag\InputTypePassword::ID,
+ Tag\LineargradientStop::ID => Tag\LineargradientStop::ID,
+ Tag\LinkItemprop::ID => Tag\LinkItemprop::ID,
+ Tag\LinkItempropSameas::ID => Tag\LinkItempropSameas::ID,
+ Tag\LinkProperty::ID => Tag\LinkProperty::ID,
+ Tag\LinkRel::ID => Tag\LinkRel::ID,
+ Tag\LinkRelCanonical::ID => Tag\LinkRelCanonical::ID,
+ Tag\LinkRelManifest::ID => Tag\LinkRelManifest::ID,
+ Tag\LinkRelModulepreload::ID => Tag\LinkRelModulepreload::ID,
+ Tag\LinkRelPreload::ID => Tag\LinkRelPreload::ID,
+ Tag\LinkRelStylesheetForFonts::ID => Tag\LinkRelStylesheetForFonts::ID,
+ Tag\MetaCharsetUtf8::ID => Tag\MetaCharsetUtf8::ID,
+ Tag\MetaHttpEquivContentLanguage::ID => Tag\MetaHttpEquivContentLanguage::ID,
+ Tag\MetaHttpEquivContentScriptType::ID => Tag\MetaHttpEquivContentScriptType::ID,
+ Tag\MetaHttpEquivContentStyleType::ID => Tag\MetaHttpEquivContentStyleType::ID,
+ Tag\MetaHttpEquivContentType::ID => Tag\MetaHttpEquivContentType::ID,
+ Tag\MetaHttpEquivImagetoolbar::ID => Tag\MetaHttpEquivImagetoolbar::ID,
+ Tag\MetaHttpEquivOriginTrial::ID => Tag\MetaHttpEquivOriginTrial::ID,
+ Tag\MetaHttpEquivPicsLabel::ID => Tag\MetaHttpEquivPicsLabel::ID,
+ Tag\MetaHttpEquivResourceType::ID => Tag\MetaHttpEquivResourceType::ID,
+ Tag\MetaHttpEquivXDnsPrefetchControl::ID => Tag\MetaHttpEquivXDnsPrefetchControl::ID,
+ Tag\MetaHttpEquivXUaCompatible::ID => Tag\MetaHttpEquivXUaCompatible::ID,
+ Tag\MetaNameAmp3pIframeSrc::ID => Tag\MetaNameAmp3pIframeSrc::ID,
+ Tag\MetaNameAmpAdDoubleclickSra::ID => Tag\MetaNameAmpAdDoubleclickSra::ID,
+ Tag\MetaNameAmpAdEnableRefresh::ID => Tag\MetaNameAmpAdEnableRefresh::ID,
+ Tag\MetaNameAmpConsentBlocking::ID => Tag\MetaNameAmpConsentBlocking::ID,
+ Tag\MetaNameAmpCtaLandingPageType::ID => Tag\MetaNameAmpCtaLandingPageType::ID,
+ Tag\MetaNameAmpCtaType::ID => Tag\MetaNameAmpCtaType::ID,
+ Tag\MetaNameAmpCtaUrl::ID => Tag\MetaNameAmpCtaUrl::ID,
+ Tag\MetaNameAmpExperimentToken::ID => Tag\MetaNameAmpExperimentToken::ID,
+ Tag\MetaNameAmpExperimentsOptIn::ID => Tag\MetaNameAmpExperimentsOptIn::ID,
+ Tag\MetaNameAmpGoogleClientidIdApi::ID => Tag\MetaNameAmpGoogleClientidIdApi::ID,
+ Tag\MetaNameAmpLinkVariableAllowedOrigin::ID => Tag\MetaNameAmpLinkVariableAllowedOrigin::ID,
+ Tag\MetaNameAmpListLoadMore::ID => Tag\MetaNameAmpListLoadMore::ID,
+ Tag\MetaNameAmpRecaptchaInput::ID => Tag\MetaNameAmpRecaptchaInput::ID,
+ Tag\MetaNameAmpScriptSrc::ID => Tag\MetaNameAmpScriptSrc::ID,
+ Tag\MetaNameAmpStoryGeneratorName::ID => Tag\MetaNameAmpStoryGeneratorName::ID,
+ Tag\MetaNameAmpStoryGeneratorVersion::ID => Tag\MetaNameAmpStoryGeneratorVersion::ID,
+ Tag\MetaNameAmpToAmpNavigation::ID => Tag\MetaNameAmpToAmpNavigation::ID,
+ Tag\MetaNameAmp4adsId::ID => Tag\MetaNameAmp4adsId::ID,
+ Tag\MetaNameAmp4adsVars::ID => Tag\MetaNameAmp4adsVars::ID,
+ Tag\MetaNameAndContent::ID => Tag\MetaNameAndContent::ID,
+ Tag\MetaNameAppleItunesApp::ID => Tag\MetaNameAppleItunesApp::ID,
+ Tag\MetaNameViewport::ID => Tag\MetaNameViewport::ID,
+ Tag\NoscriptImg::ID => Tag\NoscriptImg::ID,
+ Tag\NoscriptStyleAmpBoilerplate::ID => Tag\NoscriptStyleAmpBoilerplate::ID,
+ Tag\NoscriptStyleAmpBoilerplateTransformed::ID => Tag\NoscriptStyleAmpBoilerplateTransformed::ID,
+ Tag\NoscriptEnclosureForBoilerplate::ID => Tag\NoscriptEnclosureForBoilerplate::ID,
+ Tag\NoscriptEnclosureForBoilerplateTransformed::ID => Tag\NoscriptEnclosureForBoilerplateTransformed::ID,
+ Tag\PictureSource::ID => Tag\PictureSource::ID,
+ Tag\RadialgradientStop::ID => Tag\RadialgradientStop::ID,
+ Tag\ScriptAmpOnerror::ID => Tag\ScriptAmpOnerror::ID,
+ Tag\ScriptIdAmpRtc::ID => Tag\ScriptIdAmpRtc::ID,
+ Tag\ScriptTypeApplicationLdJson::ID => Tag\ScriptTypeApplicationLdJson::ID,
+ Tag\ScriptTypeTextPlain::ID => Tag\ScriptTypeTextPlain::ID,
+ Tag\ScriptTypeTextPlainAmp4email::ID => Tag\ScriptTypeTextPlainAmp4email::ID,
+ Tag\ScriptCustomElementAmpAccordionAmp4email::ID => Tag\ScriptCustomElementAmpAccordionAmp4email::ID,
+ Tag\ScriptCustomElementAmpAutocompleteAmp4email::ID => Tag\ScriptCustomElementAmpAutocompleteAmp4email::ID,
+ Tag\ScriptCustomElementAmpBindAmp4email::ID => Tag\ScriptCustomElementAmpBindAmp4email::ID,
+ Tag\ScriptCustomElementAmpCarouselAmp4email::ID => Tag\ScriptCustomElementAmpCarouselAmp4email::ID,
+ Tag\ScriptCustomElementAmpFitTextAmp4email::ID => Tag\ScriptCustomElementAmpFitTextAmp4email::ID,
+ Tag\ScriptCustomElementAmpFormAmp4email::ID => Tag\ScriptCustomElementAmpFormAmp4email::ID,
+ Tag\ScriptCustomElementAmpImageLightboxAmp4email::ID => Tag\ScriptCustomElementAmpImageLightboxAmp4email::ID,
+ Tag\ScriptCustomElementAmpLightboxAmp4ads::ID => Tag\ScriptCustomElementAmpLightboxAmp4ads::ID,
+ Tag\ScriptCustomElementAmpLightboxAmp4email::ID => Tag\ScriptCustomElementAmpLightboxAmp4email::ID,
+ Tag\ScriptCustomElementAmpListAmp4email::ID => Tag\ScriptCustomElementAmpListAmp4email::ID,
+ Tag\ScriptCustomElementAmpSelectorAmp4email::ID => Tag\ScriptCustomElementAmpSelectorAmp4email::ID,
+ Tag\ScriptCustomElementAmpSidebarAmp4email::ID => Tag\ScriptCustomElementAmpSidebarAmp4email::ID,
+ Tag\ScriptCustomElementAmpTimeagoAmp4email::ID => Tag\ScriptCustomElementAmpTimeagoAmp4email::ID,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4ads::ID => Tag\ScriptCustomTemplateAmpMustacheAmp4ads::ID,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4email::ID => Tag\ScriptCustomTemplateAmpMustacheAmp4email::ID,
+ Tag\SectionAmp4email::ID => Tag\SectionAmp4email::ID,
+ Tag\SpanAmpNestedMenu::ID => Tag\SpanAmpNestedMenu::ID,
+ Tag\SpanSwgAmpCacheNonce::ID => Tag\SpanSwgAmpCacheNonce::ID,
+ Tag\StyleAmpCustom::ID => Tag\StyleAmpCustom::ID,
+ Tag\StyleAmpCustomAmp4ads::ID => Tag\StyleAmpCustomAmp4ads::ID,
+ Tag\StyleAmpCustomAmp4email::ID => Tag\StyleAmpCustomAmp4email::ID,
+ Tag\StyleAmpCustomCssStrict::ID => Tag\StyleAmpCustomCssStrict::ID,
+ Tag\StyleAmpCustomLengthCheck::ID => Tag\StyleAmpCustomLengthCheck::ID,
+ Tag\StyleAmpKeyframes::ID => Tag\StyleAmpKeyframes::ID,
+ Tag\StyleAmpRuntimeTransformed::ID => Tag\StyleAmpRuntimeTransformed::ID,
+ Tag\SubscriptionsSectionContentSwgAmpCacheNonce::ID => Tag\SubscriptionsSectionContentSwgAmpCacheNonce::ID,
+ Tag\SubscriptionsScriptCiphertext::ID => Tag\SubscriptionsScriptCiphertext::ID,
+ Tag\SvgTitle::ID => Tag\SvgTitle::ID,
+ Tag\TemplateAmp4email::ID => Tag\TemplateAmp4email::ID,
+ Tag\Title::ID => Tag\Title::ID,
+ Tag\TitleAmp4email::ID => Tag\TitleAmp4email::ID,
+ Tag\VideoSource::ID => Tag\VideoSource::ID,
+ Tag\VideoTrack::ID => Tag\VideoTrack::ID,
+ Tag\VideoTrackKindSubtitles::ID => Tag\VideoTrackKindSubtitles::ID,
+ ];
+
+ /**
+ * Mapping of AMP format to array of tag IDs.
+ *
+ * This is used to optimize querying by AMP format.
+ *
+ * @var array>
+ */
+ const BY_FORMAT = [
+ Format::AMP => [
+ Tag\A::ID,
+ Tag\Abbr::ID,
+ Tag\Acronym::ID,
+ Tag\Address::ID,
+ Tag\Amp3dGltf::ID,
+ Tag\Amp3qPlayer::ID,
+ Tag\AmpAccessExtensionJsonScript::ID,
+ Tag\AmpAccordion::ID,
+ Tag\AmpAccordionSection::ID,
+ Tag\AmpActionMacro::ID,
+ Tag\AmpAd::ID,
+ Tag\AmpAdCustom::ID,
+ Tag\AmpAddthis::ID,
+ Tag\AmpAdExtensionScript::ID,
+ Tag\AmpAdWithDataEnableRefreshAttribute::ID,
+ Tag\AmpAdWithDataMultiSizeAttribute::ID,
+ Tag\AmpAdWithTypeCustom::ID,
+ Tag\AmpAnalytics::ID,
+ Tag\AmpAnalyticsExtensionJsonScript::ID,
+ Tag\AmpAnim::ID,
+ Tag\AmpAnimation::ID,
+ Tag\AmpAnimationExtensionJsonScript::ID,
+ Tag\AmpApesterMedia::ID,
+ Tag\AmpAppBanner::ID,
+ Tag\AmpAppBannerButtonOpenButton::ID,
+ Tag\AmpAudio::ID,
+ Tag\AmpAudioSource::ID,
+ Tag\AmpAudioTrack::ID,
+ Tag\AmpAudioTrackKindSubtitles::ID,
+ Tag\AmpAutoAds::ID,
+ Tag\AmpAutocomplete::ID,
+ Tag\AmpAutocompleteInput::ID,
+ Tag\AmpAutocompleteJson::ID,
+ Tag\AmpBaseCarousel::ID,
+ Tag\AmpBaseCarouselLightboxChild::ID,
+ Tag\AmpBaseCarouselLightboxLightboxExclude::ID,
+ Tag\AmpBaseCarouselLightbox::ID,
+ Tag\AmpBeopinion::ID,
+ Tag\AmpBindMacro::ID,
+ Tag\AmpBindExtensionJsonScript::ID,
+ Tag\AmpBodymovinAnimation::ID,
+ Tag\AmpBridPlayer::ID,
+ Tag\AmpBrightcove::ID,
+ Tag\AmpBysideContent::ID,
+ Tag\AmpCallTracking::ID,
+ Tag\AmpCarousel::ID,
+ Tag\AmpCarouselLightbox::ID,
+ Tag\AmpCarouselLightboxChild::ID,
+ Tag\AmpCarouselLightboxLightboxExclude::ID,
+ Tag\AmpConnatixPlayer::ID,
+ Tag\AmpConsent::ID,
+ Tag\AmpConsentExtensionJsonScript::ID,
+ Tag\AmpConsentType::ID,
+ Tag\AmpDailymotion::ID,
+ Tag\AmpDateCountdown::ID,
+ Tag\AmpDateDisplay::ID,
+ Tag\AmpDatePickerTemplateDateTemplate::ID,
+ Tag\AmpDatePickerTemplateInfoTemplate::ID,
+ Tag\AmpDatePickerTypeRangeModeOverlay::ID,
+ Tag\AmpDatePickerTypeRangeModeStatic::ID,
+ Tag\AmpDatePickerTypeSingleModeOverlay::ID,
+ Tag\AmpDatePickerTypeSingleModeStatic::ID,
+ Tag\AmpDelightPlayer::ID,
+ Tag\AmpEmbed::ID,
+ Tag\AmpEmbedlyCard::ID,
+ Tag\AmpEmbedlyKey::ID,
+ Tag\AmpEmbedWithDataMultiSizeAttribute::ID,
+ Tag\AmpExperiment::ID,
+ Tag\AmpExperimentExtensionJsonScript::ID,
+ Tag\AmpExperimentStoryExtensionJsonScript::ID,
+ Tag\AmpFacebook::ID,
+ Tag\AmpFacebookComments::ID,
+ Tag\AmpFacebookLike::ID,
+ Tag\AmpFacebookPage::ID,
+ Tag\AmpFitText::ID,
+ Tag\AmpFont::ID,
+ Tag\AmpFxFlyingCarpet::ID,
+ Tag\AmpGeo::ID,
+ Tag\AmpGeoExtensionJsonScript::ID,
+ Tag\AmpGfycat::ID,
+ Tag\AmpGist::ID,
+ Tag\AmpGoogleAssistantAssistjsConfig::ID,
+ Tag\AmpGoogleAssistantInlineSuggestionBar::ID,
+ Tag\AmpGoogleAssistantVoiceBar::ID,
+ Tag\AmpGoogleAssistantVoiceButton::ID,
+ Tag\AmpGoogleDocumentEmbed::ID,
+ Tag\AmpHulu::ID,
+ Tag\AmpIframe::ID,
+ Tag\AmpIframely::ID,
+ Tag\AmpImaVideo::ID,
+ Tag\AmpImaVideoScriptTypeApplicationJson::ID,
+ Tag\AmpImaVideoSource::ID,
+ Tag\AmpImaVideoTrack::ID,
+ Tag\AmpImaVideoTrackKindSubtitles::ID,
+ Tag\AmpImageLightbox::ID,
+ Tag\AmpImageSlider::ID,
+ Tag\AmpImageSliderTransformed::ID,
+ Tag\AmpImageSliderDivFirst::ID,
+ Tag\AmpImageSliderDivSecond::ID,
+ Tag\AmpImg::ID,
+ Tag\AmpImgTransformed::ID,
+ Tag\AmpImgImgTransformed::ID,
+ Tag\AmpImgImgPlaceholderTransformed::ID,
+ Tag\AmpImgur::ID,
+ Tag\AmpInlineGallery::ID,
+ Tag\AmpInlineGalleryPagination::ID,
+ Tag\AmpInlineGalleryPaginationInset::ID,
+ Tag\AmpInlineGalleryThumbnails::ID,
+ Tag\AmpInstagram::ID,
+ Tag\AmpInstallServiceworker::ID,
+ Tag\AmpIzlesene::ID,
+ Tag\AmpJwplayer::ID,
+ Tag\AmpKalturaPlayer::ID,
+ Tag\AmpLayout::ID,
+ Tag\AmpLightbox::ID,
+ Tag\AmpLinkRewriter::ID,
+ Tag\AmpLinkRewriterExtensionJsonScript::ID,
+ Tag\AmpList::ID,
+ Tag\AmpListLoadMore::ID,
+ Tag\AmpListLoadMoreButtonLoadMoreClickable::ID,
+ Tag\AmpListDivFetchError::ID,
+ Tag\AmpLiveList::ID,
+ Tag\AmpLiveListItems::ID,
+ Tag\AmpLiveListItemsItem::ID,
+ Tag\AmpLiveListPagination::ID,
+ Tag\AmpLiveListUpdate::ID,
+ Tag\AmpMathml::ID,
+ Tag\AmpMegaMenu::ID,
+ Tag\AmpMegaMenuAmpList::ID,
+ Tag\AmpMegaMenuAmpListTemplate::ID,
+ Tag\AmpMegaMenuNav::ID,
+ Tag\AmpMegaMenuItemContent::ID,
+ Tag\AmpMegaMenuItemHeading::ID,
+ Tag\AmpMegaMenuNavUlOl::ID,
+ Tag\AmpMegaMenuNavUlOlLi::ID,
+ Tag\AmpMegaphoneDataEpisode::ID,
+ Tag\AmpMegaphoneDataPlaylist::ID,
+ Tag\AmpMinuteMediaPlayer::ID,
+ Tag\AmpMowplayer::ID,
+ Tag\AmpNestedMenu::ID,
+ Tag\AmpNextPageScriptTypeApplicationJson::ID,
+ Tag\AmpNextPageFooter::ID,
+ Tag\AmpNextPageRecommendationBox::ID,
+ Tag\AmpNextPageSeparator::ID,
+ Tag\AmpNextPageWithInlineConfig::ID,
+ Tag\AmpNextPageWithSrcAttribute::ID,
+ Tag\AmpNextPageTypeAdsense::ID,
+ Tag\AmpNexxtvPlayer::ID,
+ Tag\AmpO2Player::ID,
+ Tag\AmpOnetapGoogle::ID,
+ Tag\AmpOoyalaPlayer::ID,
+ Tag\AmpOrientationObserver::ID,
+ Tag\AmpPanZoom::ID,
+ Tag\AmpPinterest::ID,
+ Tag\AmpPixel::ID,
+ Tag\AmpPlaybuzz::ID,
+ Tag\AmpPositionObserver::ID,
+ Tag\AmpPowrPlayer::ID,
+ Tag\AmpReachPlayer::ID,
+ Tag\AmpRecaptchaInput::ID,
+ Tag\AmpRedbullPlayer::ID,
+ Tag\AmpReddit::ID,
+ Tag\AmpRiddleQuiz::ID,
+ Tag\AmpScript::ID,
+ Tag\AmpScriptExtensionLocalScript::ID,
+ Tag\AmpSelector::ID,
+ Tag\AmpSelectorChild::ID,
+ Tag\AmpSelectorOption::ID,
+ Tag\AmpSidebar::ID,
+ Tag\AmpSidebarNav::ID,
+ Tag\AmpSkimlinks::ID,
+ Tag\AmpSmartlinks::ID,
+ Tag\AmpSocialShare::ID,
+ Tag\AmpSoundcloud::ID,
+ Tag\AmpSpringboardPlayer::ID,
+ Tag\AmpState::ID,
+ Tag\AmpStickyAd::ID,
+ Tag\AmpStory::ID,
+ Tag\AmpStory360::ID,
+ Tag\AmpStoryAnimation::ID,
+ Tag\AmpStoryAnimationJsonScript::ID,
+ Tag\AmpStoryAutoAds::ID,
+ Tag\AmpStoryAutoAdsTemplate::ID,
+ Tag\AmpStoryAutoAdsConfigScript::ID,
+ Tag\AmpStoryAutoAnalytics::ID,
+ Tag\AmpStoryBookend::ID,
+ Tag\AmpStoryBookendExtensionJsonScript::ID,
+ Tag\AmpStoryConsent::ID,
+ Tag\AmpStoryConsentExtensionJsonScript::ID,
+ Tag\AmpStoryCtaLayer::ID,
+ Tag\AmpStoryCtaLayerAnimateIn::ID,
+ Tag\AmpStoryGridLayer::ID,
+ Tag\AmpStoryGridLayerAnimateIn::ID,
+ Tag\AmpStoryGridLayerDefault::ID,
+ Tag\AmpStoryInteractiveBinaryPoll::ID,
+ Tag\AmpStoryInteractivePoll::ID,
+ Tag\AmpStoryInteractiveQuiz::ID,
+ Tag\AmpStoryInteractiveResults::ID,
+ Tag\AmpStoryPage::ID,
+ Tag\AmpStoryPageAttachment::ID,
+ Tag\AmpStoryPageAttachmentHref::ID,
+ Tag\AmpStoryPageOutlink::ID,
+ Tag\AmpStoryPanningMedia::ID,
+ Tag\AmpStoryPlayer::ID,
+ Tag\AmpStoryPlayerImg::ID,
+ Tag\AmpStorySocialShare::ID,
+ Tag\AmpStorySocialShareExtensionJsonScript::ID,
+ Tag\AmpStoryAmpAudio::ID,
+ Tag\AmpStoryAmpSidebar::ID,
+ Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::ID,
+ Tag\AmpStoryAmpVideo::ID,
+ Tag\AmpStreamGallery::ID,
+ Tag\AmpSubscriptionsExtensionJsonScript::ID,
+ Tag\AmpTimeago::ID,
+ Tag\AmpTruncateText::ID,
+ Tag\AmpTwitter::ID,
+ Tag\AmpUserNotification::ID,
+ Tag\AmpVideo::ID,
+ Tag\AmpVideoIframe::ID,
+ Tag\AmpVideoIframeTransformed::ID,
+ Tag\AmpVideoSource::ID,
+ Tag\AmpVideoTrack::ID,
+ Tag\AmpVideoTrackKindSubtitles::ID,
+ Tag\AmpVimeo::ID,
+ Tag\AmpVine::ID,
+ Tag\AmpViqeoPlayer::ID,
+ Tag\AmpVk::ID,
+ Tag\AmpWebPush::ID,
+ Tag\AmpWebPushWidget::ID,
+ Tag\AmpWistiaPlayer::ID,
+ Tag\AmpYotpo::ID,
+ Tag\AmpYoutube::ID,
+ Tag\AmphtmlEngineScript::ID,
+ Tag\AmphtmlEngineScriptLts::ID,
+ Tag\AmphtmlModuleEngineScript::ID,
+ Tag\AmphtmlModuleLtsEngineScript::ID,
+ Tag\AmphtmlNomoduleEngineScript::ID,
+ Tag\AmphtmlNomoduleLtsEngineScript::ID,
+ Tag\Article::ID,
+ Tag\Aside::ID,
+ Tag\Audio::ID,
+ Tag\AudioSource::ID,
+ Tag\AudioTrack::ID,
+ Tag\AudioTrackKindSubtitles::ID,
+ Tag\B::ID,
+ Tag\Base::ID,
+ Tag\Bdi::ID,
+ Tag\Bdo::ID,
+ Tag\Big::ID,
+ Tag\Blockquote::ID,
+ Tag\Body::ID,
+ Tag\Br::ID,
+ Tag\Button::ID,
+ Tag\ButtonAmpNestedMenu::ID,
+ Tag\Canvas::ID,
+ Tag\Caption::ID,
+ Tag\Center::ID,
+ Tag\Circle::ID,
+ Tag\Cite::ID,
+ Tag\Clippath::ID,
+ Tag\Code::ID,
+ Tag\Col::ID,
+ Tag\Colgroup::ID,
+ Tag\CryptokeysJsonScript::ID,
+ Tag\Data::ID,
+ Tag\Datalist::ID,
+ Tag\Dd::ID,
+ Tag\Defs::ID,
+ Tag\Del::ID,
+ Tag\Desc::ID,
+ Tag\Details::ID,
+ Tag\Dfn::ID,
+ Tag\Dir::ID,
+ Tag\Div::ID,
+ Tag\DivAmpNestedMenu::ID,
+ Tag\Dl::ID,
+ Tag\Dt::ID,
+ Tag\Ellipse::ID,
+ Tag\Em::ID,
+ Tag\Feblend::ID,
+ Tag\Fecolormatrix::ID,
+ Tag\Fecomponenttransfer::ID,
+ Tag\Fecomposite::ID,
+ Tag\Feconvolvematrix::ID,
+ Tag\Fediffuselighting::ID,
+ Tag\Fedisplacementmap::ID,
+ Tag\Fedistantlight::ID,
+ Tag\Fedropshadow::ID,
+ Tag\Feflood::ID,
+ Tag\Fefunca::ID,
+ Tag\Fefuncb::ID,
+ Tag\Fefuncg::ID,
+ Tag\Fefuncr::ID,
+ Tag\Fegaussianblur::ID,
+ Tag\Femerge::ID,
+ Tag\Femergenode::ID,
+ Tag\Femorphology::ID,
+ Tag\Feoffset::ID,
+ Tag\Fepointlight::ID,
+ Tag\Fespecularlighting::ID,
+ Tag\Fespotlight::ID,
+ Tag\Fetile::ID,
+ Tag\Feturbulence::ID,
+ Tag\Fieldset::ID,
+ Tag\Figcaption::ID,
+ Tag\Figure::ID,
+ Tag\Filter::ID,
+ Tag\Footer::ID,
+ Tag\FormDivSubmitError::ID,
+ Tag\FormDivSubmitErrorTemplate::ID,
+ Tag\FormDivSubmitSuccess::ID,
+ Tag\FormDivSubmitSuccessTemplate::ID,
+ Tag\FormDivSubmitting::ID,
+ Tag\FormDivSubmittingTemplate::ID,
+ Tag\FormDivVerifyError::ID,
+ Tag\FormDivVerifyErrorTemplate::ID,
+ Tag\FormMethodGet::ID,
+ Tag\FormMethodPost::ID,
+ Tag\G::ID,
+ Tag\Glyph::ID,
+ Tag\Glyphref::ID,
+ Tag\H1::ID,
+ Tag\H2::ID,
+ Tag\H2AmpNestedMenu::ID,
+ Tag\H3::ID,
+ Tag\H3AmpNestedMenu::ID,
+ Tag\H4::ID,
+ Tag\H4AmpNestedMenu::ID,
+ Tag\H5::ID,
+ Tag\H5AmpNestedMenu::ID,
+ Tag\H6::ID,
+ Tag\H6AmpNestedMenu::ID,
+ Tag\Head::ID,
+ Tag\HeadStyleAmpBoilerplate::ID,
+ Tag\HeadStyleAmpBoilerplateTransformed::ID,
+ Tag\Header::ID,
+ Tag\Hgroup::ID,
+ Tag\Hkern::ID,
+ Tag\Hr::ID,
+ Tag\Html::ID,
+ Tag\HtmlTransformed::ID,
+ Tag\HtmlDoctype::ID,
+ Tag\I::ID,
+ Tag\IAmphtmlSizerIntrinsic::ID,
+ Tag\IAmphtmlSizerResponsive::ID,
+ Tag\Iframe::ID,
+ Tag\Image::ID,
+ Tag\ImgIAmphtmlIntrinsicSizer::ID,
+ Tag\ImgIAmphtmlIntrinsicSizerAmpStoryPlayer::ID,
+ Tag\Input::ID,
+ Tag\InputMaskDateDdMmYyyy::ID,
+ Tag\InputMaskDateMmDdYyyy::ID,
+ Tag\InputMaskDateMmYy::ID,
+ Tag\InputMaskDateYyyyMmDd::ID,
+ Tag\InputMaskPaymentCard::ID,
+ Tag\InputMaskCustomMask::ID,
+ Tag\InputTypeFile::ID,
+ Tag\InputTypePassword::ID,
+ Tag\Ins::ID,
+ Tag\Kbd::ID,
+ Tag\Label::ID,
+ Tag\Legend::ID,
+ Tag\Li::ID,
+ Tag\Line::ID,
+ Tag\Lineargradient::ID,
+ Tag\LineargradientStop::ID,
+ Tag\LinkItemprop::ID,
+ Tag\LinkItempropSameas::ID,
+ Tag\LinkProperty::ID,
+ Tag\LinkRel::ID,
+ Tag\LinkRelCanonical::ID,
+ Tag\LinkRelManifest::ID,
+ Tag\LinkRelModulepreload::ID,
+ Tag\LinkRelPreload::ID,
+ Tag\LinkRelStylesheetForFonts::ID,
+ Tag\Listing::ID,
+ Tag\Main::ID,
+ Tag\Mark::ID,
+ Tag\Marker::ID,
+ Tag\Mask::ID,
+ Tag\MetaCharsetUtf8::ID,
+ Tag\Metadata::ID,
+ Tag\MetaHttpEquivContentLanguage::ID,
+ Tag\MetaHttpEquivContentScriptType::ID,
+ Tag\MetaHttpEquivContentStyleType::ID,
+ Tag\MetaHttpEquivContentType::ID,
+ Tag\MetaHttpEquivImagetoolbar::ID,
+ Tag\MetaHttpEquivOriginTrial::ID,
+ Tag\MetaHttpEquivPicsLabel::ID,
+ Tag\MetaHttpEquivResourceType::ID,
+ Tag\MetaHttpEquivXDnsPrefetchControl::ID,
+ Tag\MetaHttpEquivXUaCompatible::ID,
+ Tag\MetaNameAmp3pIframeSrc::ID,
+ Tag\MetaNameAmpAdDoubleclickSra::ID,
+ Tag\MetaNameAmpAdEnableRefresh::ID,
+ Tag\MetaNameAmpConsentBlocking::ID,
+ Tag\MetaNameAmpExperimentToken::ID,
+ Tag\MetaNameAmpExperimentsOptIn::ID,
+ Tag\MetaNameAmpGoogleClientidIdApi::ID,
+ Tag\MetaNameAmpLinkVariableAllowedOrigin::ID,
+ Tag\MetaNameAmpListLoadMore::ID,
+ Tag\MetaNameAmpRecaptchaInput::ID,
+ Tag\MetaNameAmpScriptSrc::ID,
+ Tag\MetaNameAmpStoryGeneratorName::ID,
+ Tag\MetaNameAmpStoryGeneratorVersion::ID,
+ Tag\MetaNameAmpToAmpNavigation::ID,
+ Tag\MetaNameAndContent::ID,
+ Tag\MetaNameAppleItunesApp::ID,
+ Tag\MetaNameViewport::ID,
+ Tag\Meter::ID,
+ Tag\Multicol::ID,
+ Tag\Nav::ID,
+ Tag\Nextid::ID,
+ Tag\Nobr::ID,
+ Tag\Noscript::ID,
+ Tag\NoscriptImg::ID,
+ Tag\NoscriptStyleAmpBoilerplate::ID,
+ Tag\NoscriptStyleAmpBoilerplateTransformed::ID,
+ Tag\NoscriptEnclosureForBoilerplate::ID,
+ Tag\NoscriptEnclosureForBoilerplateTransformed::ID,
+ Tag\OP::ID,
+ Tag\Ol::ID,
+ Tag\Optgroup::ID,
+ Tag\Option::ID,
+ Tag\Output::ID,
+ Tag\P::ID,
+ Tag\Path::ID,
+ Tag\Pattern::ID,
+ Tag\Picture::ID,
+ Tag\PictureSource::ID,
+ Tag\Polygon::ID,
+ Tag\Polyline::ID,
+ Tag\Pre::ID,
+ Tag\Progress::ID,
+ Tag\Q::ID,
+ Tag\Radialgradient::ID,
+ Tag\RadialgradientStop::ID,
+ Tag\Rb::ID,
+ Tag\Rect::ID,
+ Tag\Rp::ID,
+ Tag\Rt::ID,
+ Tag\Rtc::ID,
+ Tag\Ruby::ID,
+ Tag\S::ID,
+ Tag\Samp::ID,
+ Tag\ScriptAmpOnerror::ID,
+ Tag\ScriptIdAmpRtc::ID,
+ Tag\ScriptTypeApplicationLdJson::ID,
+ Tag\ScriptTypeTextPlain::ID,
+ Tag\ScriptAmp3dGltf::ID,
+ Tag\ScriptAmp3qPlayer::ID,
+ Tag\ScriptAmpAccessLaterpay::ID,
+ Tag\ScriptAmpAccessPoool::ID,
+ Tag\ScriptAmpAccessScroll::ID,
+ Tag\ScriptAmpAccess::ID,
+ Tag\ScriptAmpAccordion::ID,
+ Tag\ScriptAmpAccordion2::ID,
+ Tag\ScriptAmpActionMacro::ID,
+ Tag\ScriptAmpAdCustom::ID,
+ Tag\ScriptAmpAddthis::ID,
+ Tag\ScriptAmpAnalytics::ID,
+ Tag\ScriptAmpAnimation::ID,
+ Tag\ScriptAmpAnim::ID,
+ Tag\ScriptAmpApesterMedia::ID,
+ Tag\ScriptAmpAppBanner::ID,
+ Tag\ScriptAmpAudio::ID,
+ Tag\ScriptAmpAutoAds::ID,
+ Tag\ScriptAmpAutocomplete::ID,
+ Tag\ScriptAmpBaseCarousel::ID,
+ Tag\ScriptAmpBeopinion::ID,
+ Tag\ScriptAmpBind::ID,
+ Tag\ScriptAmpBodymovinAnimation::ID,
+ Tag\ScriptAmpBridPlayer::ID,
+ Tag\ScriptAmpBrightcove::ID,
+ Tag\ScriptAmpBysideContent::ID,
+ Tag\ScriptAmpCacheUrl::ID,
+ Tag\ScriptAmpCallTracking::ID,
+ Tag\ScriptAmpCarousel::ID,
+ Tag\ScriptAmpConnatixPlayer::ID,
+ Tag\ScriptAmpConsent::ID,
+ Tag\ScriptAmpDailymotion::ID,
+ Tag\ScriptAmpDateCountdown::ID,
+ Tag\ScriptAmpDateDisplay::ID,
+ Tag\ScriptAmpDatePicker::ID,
+ Tag\ScriptAmpDelightPlayer::ID,
+ Tag\ScriptAmpDynamicCssClasses::ID,
+ Tag\ScriptAmpEmbedlyCard::ID,
+ Tag\ScriptAmpExperiment::ID,
+ Tag\ScriptAmpFacebookComments::ID,
+ Tag\ScriptAmpFacebookLike::ID,
+ Tag\ScriptAmpFacebookPage::ID,
+ Tag\ScriptAmpFacebook::ID,
+ Tag\ScriptAmpFitText::ID,
+ Tag\ScriptAmpFitText2::ID,
+ Tag\ScriptAmpFont::ID,
+ Tag\ScriptAmpForm::ID,
+ Tag\ScriptAmpFxCollection::ID,
+ Tag\ScriptAmpFxFlyingCarpet::ID,
+ Tag\ScriptAmpGeo::ID,
+ Tag\ScriptAmpGfycat::ID,
+ Tag\ScriptAmpGist::ID,
+ Tag\ScriptAmpGoogleAssistantAssistjs::ID,
+ Tag\ScriptAmpGoogleDocumentEmbed::ID,
+ Tag\ScriptAmpHulu::ID,
+ Tag\ScriptAmpIframely::ID,
+ Tag\ScriptAmpIframe::ID,
+ Tag\ScriptAmpImaVideo::ID,
+ Tag\ScriptAmpImageLightbox::ID,
+ Tag\ScriptAmpImageSlider::ID,
+ Tag\ScriptAmpImgur::ID,
+ Tag\ScriptAmpInlineGallery::ID,
+ Tag\ScriptAmpInputmask::ID,
+ Tag\ScriptAmpInstagram::ID,
+ Tag\ScriptAmpInstagram2::ID,
+ Tag\ScriptAmpInstallServiceworker::ID,
+ Tag\ScriptAmpIzlesene::ID,
+ Tag\ScriptAmpJwplayer::ID,
+ Tag\ScriptAmpKalturaPlayer::ID,
+ Tag\ScriptAmpLightboxGallery::ID,
+ Tag\ScriptAmpLightbox::ID,
+ Tag\ScriptAmpLightbox2::ID,
+ Tag\ScriptAmpLinkRewriter::ID,
+ Tag\ScriptAmpList::ID,
+ Tag\ScriptAmpLiveList::ID,
+ Tag\ScriptAmpMathml::ID,
+ Tag\ScriptAmpMegaMenu::ID,
+ Tag\ScriptAmpMegaphone::ID,
+ Tag\ScriptAmpMinuteMediaPlayer::ID,
+ Tag\ScriptAmpMowplayer::ID,
+ Tag\ScriptAmpMustache::ID,
+ Tag\ScriptAmpNestedMenu::ID,
+ Tag\ScriptAmpNextPage::ID,
+ Tag\ScriptAmpNexxtvPlayer::ID,
+ Tag\ScriptAmpO2Player::ID,
+ Tag\ScriptAmpOnetapGoogle::ID,
+ Tag\ScriptAmpOoyalaPlayer::ID,
+ Tag\ScriptAmpOrientationObserver::ID,
+ Tag\ScriptAmpPanZoom::ID,
+ Tag\ScriptAmpPinterest::ID,
+ Tag\ScriptAmpPlaybuzz::ID,
+ Tag\ScriptAmpPositionObserver::ID,
+ Tag\ScriptAmpPowrPlayer::ID,
+ Tag\ScriptAmpReachPlayer::ID,
+ Tag\ScriptAmpRecaptchaInput::ID,
+ Tag\ScriptAmpRedbullPlayer::ID,
+ Tag\ScriptAmpReddit::ID,
+ Tag\ScriptAmpRiddleQuiz::ID,
+ Tag\ScriptAmpScript::ID,
+ Tag\ScriptAmpSelector::ID,
+ Tag\ScriptAmpSelector2::ID,
+ Tag\ScriptAmpSidebar::ID,
+ Tag\ScriptAmpSkimlinks::ID,
+ Tag\ScriptAmpSlides::ID,
+ Tag\ScriptAmpSmartlinks::ID,
+ Tag\ScriptAmpSocialShare::ID,
+ Tag\ScriptAmpSocialShare2::ID,
+ Tag\ScriptAmpSoundcloud::ID,
+ Tag\ScriptAmpSpringboardPlayer::ID,
+ Tag\ScriptAmpStickyAd::ID,
+ Tag\ScriptAmpStory360::ID,
+ Tag\ScriptAmpStoryAutoAds::ID,
+ Tag\ScriptAmpStoryAutoAnalytics::ID,
+ Tag\ScriptAmpStoryInteractive::ID,
+ Tag\ScriptAmpStoryPanningMedia::ID,
+ Tag\ScriptAmpStoryPlayer::ID,
+ Tag\ScriptAmpStory::ID,
+ Tag\ScriptAmpStreamGallery::ID,
+ Tag\ScriptAmpSubscriptionsGoogle::ID,
+ Tag\ScriptAmpSubscriptions::ID,
+ Tag\ScriptAmpTimeago::ID,
+ Tag\ScriptAmpTruncateText::ID,
+ Tag\ScriptAmpTwitter::ID,
+ Tag\ScriptAmpUserNotification::ID,
+ Tag\ScriptAmpVideoDocking::ID,
+ Tag\ScriptAmpVideoIframe::ID,
+ Tag\ScriptAmpVideoIframe2::ID,
+ Tag\ScriptAmpVideo::ID,
+ Tag\ScriptAmpVideo2::ID,
+ Tag\ScriptAmpVimeo::ID,
+ Tag\ScriptAmpVimeo2::ID,
+ Tag\ScriptAmpVine::ID,
+ Tag\ScriptAmpViqeoPlayer::ID,
+ Tag\ScriptAmpVk::ID,
+ Tag\ScriptAmpWebPush::ID,
+ Tag\ScriptAmpWistiaPlayer::ID,
+ Tag\ScriptAmpYotpo::ID,
+ Tag\ScriptAmpYoutube::ID,
+ Tag\ScriptAmpYoutube2::ID,
+ Tag\Section::ID,
+ Tag\Select::ID,
+ Tag\Slot::ID,
+ Tag\Small::ID,
+ Tag\Solidcolor::ID,
+ Tag\Spacer::ID,
+ Tag\Span::ID,
+ Tag\SpanAmpNestedMenu::ID,
+ Tag\SpanSwgAmpCacheNonce::ID,
+ Tag\Strike::ID,
+ Tag\Strong::ID,
+ Tag\StyleAmpCustom::ID,
+ Tag\StyleAmpCustomLengthCheck::ID,
+ Tag\StyleAmpKeyframes::ID,
+ Tag\StyleAmpRuntimeTransformed::ID,
+ Tag\Sub::ID,
+ Tag\SubscriptionsSectionContentSwgAmpCacheNonce::ID,
+ Tag\SubscriptionsScriptCiphertext::ID,
+ Tag\Summary::ID,
+ Tag\Sup::ID,
+ Tag\Svg::ID,
+ Tag\SvgTitle::ID,
+ Tag\Switch_::ID,
+ Tag\Symbol::ID,
+ Tag\Table::ID,
+ Tag\Tbody::ID,
+ Tag\Td::ID,
+ Tag\Template::ID,
+ Tag\Text::ID,
+ Tag\Textarea::ID,
+ Tag\Textpath::ID,
+ Tag\Tfoot::ID,
+ Tag\Th::ID,
+ Tag\Thead::ID,
+ Tag\Time::ID,
+ Tag\Title::ID,
+ Tag\Tr::ID,
+ Tag\Tref::ID,
+ Tag\Tspan::ID,
+ Tag\Tt::ID,
+ Tag\U::ID,
+ Tag\Ul::ID,
+ Tag\Use_::ID,
+ Tag\Var_::ID,
+ Tag\Video::ID,
+ Tag\VideoSource::ID,
+ Tag\VideoTrack::ID,
+ Tag\VideoTrackKindSubtitles::ID,
+ Tag\View::ID,
+ Tag\Vkern::ID,
+ Tag\Wbr::ID,
+ ],
+ Format::AMP4ADS => [
+ Tag\A::ID,
+ Tag\Abbr::ID,
+ Tag\Address::ID,
+ Tag\AmpAccordion::ID,
+ Tag\AmpAccordionSection::ID,
+ Tag\AmpAdExit::ID,
+ Tag\AmpAdExitConfigurationJson::ID,
+ Tag\AmpAnalytics::ID,
+ Tag\AmpAnalyticsExtensionJsonScript::ID,
+ Tag\AmpAnim::ID,
+ Tag\AmpAnimation::ID,
+ Tag\AmpAnimationExtensionJsonScript::ID,
+ Tag\AmpAppBannerButtonOpenButton::ID,
+ Tag\AmpAudioA4a::ID,
+ Tag\AmpAudioSource::ID,
+ Tag\AmpAudioTrack::ID,
+ Tag\AmpAudioTrackKindSubtitles::ID,
+ Tag\AmpBindExtensionJsonScript::ID,
+ Tag\AmpCarousel::ID,
+ Tag\AmpFitText::ID,
+ Tag\AmpFont::ID,
+ Tag\AmpGwdAnimation::ID,
+ Tag\AmpImaVideoSource::ID,
+ Tag\AmpImg::ID,
+ Tag\AmpLayout::ID,
+ Tag\AmpLightboxAmp4ads::ID,
+ Tag\AmpListDivFetchError::ID,
+ Tag\AmpPixel::ID,
+ Tag\AmpPositionObserver::ID,
+ Tag\AmpSelector::ID,
+ Tag\AmpSelectorChild::ID,
+ Tag\AmpSelectorOption::ID,
+ Tag\AmpSocialShare::ID,
+ Tag\AmpStateAmp4email::ID,
+ Tag\AmpStoryAmpStoryPageAttachmentAmpVideo::ID,
+ Tag\AmpStoryAmpVideo::ID,
+ Tag\AmpVideo::ID,
+ Tag\AmpVideoSource::ID,
+ Tag\AmpVideoTrack::ID,
+ Tag\AmpVideoTrackKindSubtitles::ID,
+ Tag\Amp4adsEngineScript::ID,
+ Tag\Article::ID,
+ Tag\Aside::ID,
+ Tag\AudioSource::ID,
+ Tag\AudioTrack::ID,
+ Tag\AudioTrackKindSubtitles::ID,
+ Tag\B::ID,
+ Tag\Base::ID,
+ Tag\Bdi::ID,
+ Tag\Bdo::ID,
+ Tag\Blockquote::ID,
+ Tag\Body::ID,
+ Tag\Br::ID,
+ Tag\Button::ID,
+ Tag\Caption::ID,
+ Tag\Circle::ID,
+ Tag\Cite::ID,
+ Tag\Clippath::ID,
+ Tag\Code::ID,
+ Tag\Col::ID,
+ Tag\Colgroup::ID,
+ Tag\Data::ID,
+ Tag\Datalist::ID,
+ Tag\Dd::ID,
+ Tag\Defs::ID,
+ Tag\Del::ID,
+ Tag\Desc::ID,
+ Tag\Details::ID,
+ Tag\Dfn::ID,
+ Tag\Div::ID,
+ Tag\Dl::ID,
+ Tag\Dt::ID,
+ Tag\Ellipse::ID,
+ Tag\Em::ID,
+ Tag\Feblend::ID,
+ Tag\Fecolormatrix::ID,
+ Tag\Fecomponenttransfer::ID,
+ Tag\Fecomposite::ID,
+ Tag\Feconvolvematrix::ID,
+ Tag\Fediffuselighting::ID,
+ Tag\Fedisplacementmap::ID,
+ Tag\Fedistantlight::ID,
+ Tag\Fedropshadow::ID,
+ Tag\Feflood::ID,
+ Tag\Fefunca::ID,
+ Tag\Fefuncb::ID,
+ Tag\Fefuncg::ID,
+ Tag\Fefuncr::ID,
+ Tag\Fegaussianblur::ID,
+ Tag\Femerge::ID,
+ Tag\Femergenode::ID,
+ Tag\Femorphology::ID,
+ Tag\Feoffset::ID,
+ Tag\Fepointlight::ID,
+ Tag\Fespecularlighting::ID,
+ Tag\Fespotlight::ID,
+ Tag\Fetile::ID,
+ Tag\Feturbulence::ID,
+ Tag\Fieldset::ID,
+ Tag\Figcaption::ID,
+ Tag\Figure::ID,
+ Tag\Filter::ID,
+ Tag\Footer::ID,
+ Tag\FormDivSubmitError::ID,
+ Tag\FormDivSubmitErrorTemplate::ID,
+ Tag\FormDivSubmitSuccess::ID,
+ Tag\FormDivSubmitSuccessTemplate::ID,
+ Tag\FormDivSubmitting::ID,
+ Tag\FormDivSubmittingTemplate::ID,
+ Tag\FormDivVerifyError::ID,
+ Tag\FormDivVerifyErrorTemplate::ID,
+ Tag\FormMethodGet::ID,
+ Tag\FormMethodPost::ID,
+ Tag\G::ID,
+ Tag\Glyph::ID,
+ Tag\Glyphref::ID,
+ Tag\H1::ID,
+ Tag\H2::ID,
+ Tag\H3::ID,
+ Tag\H4::ID,
+ Tag\H5::ID,
+ Tag\H6::ID,
+ Tag\Head::ID,
+ Tag\HeadStyleAmp4adsBoilerplate::ID,
+ Tag\Header::ID,
+ Tag\Hkern::ID,
+ Tag\Hr::ID,
+ Tag\Html::ID,
+ Tag\HtmlDoctypeAmp4ads::ID,
+ Tag\I::ID,
+ Tag\Image::ID,
+ Tag\Input::ID,
+ Tag\Ins::ID,
+ Tag\Kbd::ID,
+ Tag\Label::ID,
+ Tag\Legend::ID,
+ Tag\Li::ID,
+ Tag\Line::ID,
+ Tag\Lineargradient::ID,
+ Tag\LineargradientStop::ID,
+ Tag\LinkItemprop::ID,
+ Tag\LinkItempropSameas::ID,
+ Tag\LinkProperty::ID,
+ Tag\LinkRel::ID,
+ Tag\LinkRelManifest::ID,
+ Tag\LinkRelPreload::ID,
+ Tag\LinkRelStylesheetForFonts::ID,
+ Tag\Main::ID,
+ Tag\Mark::ID,
+ Tag\Marker::ID,
+ Tag\Mask::ID,
+ Tag\MetaCharsetUtf8::ID,
+ Tag\Metadata::ID,
+ Tag\MetaHttpEquivContentLanguage::ID,
+ Tag\MetaHttpEquivContentScriptType::ID,
+ Tag\MetaHttpEquivContentStyleType::ID,
+ Tag\MetaHttpEquivContentType::ID,
+ Tag\MetaHttpEquivImagetoolbar::ID,
+ Tag\MetaHttpEquivOriginTrial::ID,
+ Tag\MetaHttpEquivPicsLabel::ID,
+ Tag\MetaHttpEquivResourceType::ID,
+ Tag\MetaHttpEquivXUaCompatible::ID,
+ Tag\MetaNameAmpAdEnableRefresh::ID,
+ Tag\MetaNameAmpCtaLandingPageType::ID,
+ Tag\MetaNameAmpCtaType::ID,
+ Tag\MetaNameAmpCtaUrl::ID,
+ Tag\MetaNameAmpExperimentsOptIn::ID,
+ Tag\MetaNameAmp4adsId::ID,
+ Tag\MetaNameAmp4adsVars::ID,
+ Tag\MetaNameAndContent::ID,
+ Tag\MetaNameAppleItunesApp::ID,
+ Tag\MetaNameViewport::ID,
+ Tag\Meter::ID,
+ Tag\Nav::ID,
+ Tag\Ol::ID,
+ Tag\Optgroup::ID,
+ Tag\Option::ID,
+ Tag\Output::ID,
+ Tag\P::ID,
+ Tag\Path::ID,
+ Tag\Pattern::ID,
+ Tag\Polygon::ID,
+ Tag\Polyline::ID,
+ Tag\Pre::ID,
+ Tag\Progress::ID,
+ Tag\Q::ID,
+ Tag\Radialgradient::ID,
+ Tag\RadialgradientStop::ID,
+ Tag\Rb::ID,
+ Tag\Rect::ID,
+ Tag\Rp::ID,
+ Tag\Rt::ID,
+ Tag\Rtc::ID,
+ Tag\Ruby::ID,
+ Tag\S::ID,
+ Tag\Samp::ID,
+ Tag\ScriptTypeApplicationLdJson::ID,
+ Tag\ScriptTypeTextPlain::ID,
+ Tag\ScriptAmpAccordion2::ID,
+ Tag\ScriptAmpAdExit::ID,
+ Tag\ScriptAmpAnalytics::ID,
+ Tag\ScriptAmpAnimation::ID,
+ Tag\ScriptAmpAnim::ID,
+ Tag\ScriptAmpAudio::ID,
+ Tag\ScriptAmpBind::ID,
+ Tag\ScriptAmpCarousel::ID,
+ Tag\ScriptAmpFitText2::ID,
+ Tag\ScriptAmpFont::ID,
+ Tag\ScriptAmpForm::ID,
+ Tag\ScriptAmpGwdAnimation::ID,
+ Tag\ScriptAmpMraid::ID,
+ Tag\ScriptAmpPositionObserver::ID,
+ Tag\ScriptAmpSelector2::ID,
+ Tag\ScriptAmpSocialShare2::ID,
+ Tag\ScriptAmpVideo2::ID,
+ Tag\ScriptCustomElementAmpLightboxAmp4ads::ID,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4ads::ID,
+ Tag\Section::ID,
+ Tag\Select::ID,
+ Tag\Small::ID,
+ Tag\Solidcolor::ID,
+ Tag\Span::ID,
+ Tag\Strong::ID,
+ Tag\StyleAmpCustomAmp4ads::ID,
+ Tag\StyleAmpCustomLengthCheck::ID,
+ Tag\StyleAmpKeyframes::ID,
+ Tag\Sub::ID,
+ Tag\Summary::ID,
+ Tag\Sup::ID,
+ Tag\Svg::ID,
+ Tag\SvgTitle::ID,
+ Tag\Switch_::ID,
+ Tag\Symbol::ID,
+ Tag\Table::ID,
+ Tag\Tbody::ID,
+ Tag\Td::ID,
+ Tag\Template::ID,
+ Tag\Text::ID,
+ Tag\Textarea::ID,
+ Tag\Textpath::ID,
+ Tag\Tfoot::ID,
+ Tag\Th::ID,
+ Tag\Thead::ID,
+ Tag\Time::ID,
+ Tag\Title::ID,
+ Tag\Tr::ID,
+ Tag\Tref::ID,
+ Tag\Tspan::ID,
+ Tag\U::ID,
+ Tag\Ul::ID,
+ Tag\Use_::ID,
+ Tag\Var_::ID,
+ Tag\VideoSource::ID,
+ Tag\VideoTrack::ID,
+ Tag\VideoTrackKindSubtitles::ID,
+ Tag\View::ID,
+ Tag\Vkern::ID,
+ Tag\Wbr::ID,
+ ],
+ Format::AMP4EMAIL => [
+ Tag\AAmp4email::ID,
+ Tag\Abbr::ID,
+ Tag\Address::ID,
+ Tag\AmpAccordion::ID,
+ Tag\AmpAccordionSection::ID,
+ Tag\AmpAnimAmp4email::ID,
+ Tag\AmpAnimExtensionScriptAmp4email::ID,
+ Tag\AmpAutocompleteAmp4email::ID,
+ Tag\AmpBindMacro::ID,
+ Tag\AmpBindExtensionJsonScript::ID,
+ Tag\AmpCarousel::ID,
+ Tag\AmpFitText::ID,
+ Tag\AmpImageLightbox::ID,
+ Tag\AmpImgAmp4email::ID,
+ Tag\AmpLayout::ID,
+ Tag\AmpLightbox::ID,
+ Tag\AmpListAmp4email::ID,
+ Tag\AmpListDivFetchError::ID,
+ Tag\AmpSelector::ID,
+ Tag\AmpSelectorChild::ID,
+ Tag\AmpSelectorOption::ID,
+ Tag\AmpSidebarAmp4email::ID,
+ Tag\AmpSidebarNav::ID,
+ Tag\AmpStateAmp4email::ID,
+ Tag\AmpTimeago::ID,
+ Tag\AmphtmlEngineScriptAmp4email::ID,
+ Tag\Article::ID,
+ Tag\Aside::ID,
+ Tag\B::ID,
+ Tag\Bdo::ID,
+ Tag\Blockquote::ID,
+ Tag\Body::ID,
+ Tag\Br::ID,
+ Tag\Button::ID,
+ Tag\Caption::ID,
+ Tag\Cite::ID,
+ Tag\Code::ID,
+ Tag\Col::ID,
+ Tag\Colgroup::ID,
+ Tag\Data::ID,
+ Tag\Datalist::ID,
+ Tag\Dd::ID,
+ Tag\Del::ID,
+ Tag\Details::ID,
+ Tag\Dfn::ID,
+ Tag\Div::ID,
+ Tag\Dl::ID,
+ Tag\Dt::ID,
+ Tag\Em::ID,
+ Tag\Fieldset::ID,
+ Tag\Figcaption::ID,
+ Tag\Figure::ID,
+ Tag\Footer::ID,
+ Tag\FormDivSubmitError::ID,
+ Tag\FormDivSubmitErrorTemplate::ID,
+ Tag\FormDivSubmitSuccess::ID,
+ Tag\FormDivSubmitSuccessTemplate::ID,
+ Tag\FormDivSubmitting::ID,
+ Tag\FormMethodGetAmp4email::ID,
+ Tag\FormMethodPostAmp4email::ID,
+ Tag\H1::ID,
+ Tag\H2::ID,
+ Tag\H3::ID,
+ Tag\H4::ID,
+ Tag\H5::ID,
+ Tag\H6::ID,
+ Tag\Head::ID,
+ Tag\HeadStyleAmp4emailBoilerplate::ID,
+ Tag\Header::ID,
+ Tag\Hr::ID,
+ Tag\Html::ID,
+ Tag\HtmlDoctype::ID,
+ Tag\I::ID,
+ Tag\Input::ID,
+ Tag\Ins::ID,
+ Tag\Kbd::ID,
+ Tag\Label::ID,
+ Tag\Legend::ID,
+ Tag\Li::ID,
+ Tag\Main::ID,
+ Tag\Mark::ID,
+ Tag\MetaCharsetUtf8::ID,
+ Tag\MetaNameAndContent::ID,
+ Tag\Meter::ID,
+ Tag\Nav::ID,
+ Tag\Ol::ID,
+ Tag\Optgroup::ID,
+ Tag\Option::ID,
+ Tag\Output::ID,
+ Tag\P::ID,
+ Tag\Pre::ID,
+ Tag\Progress::ID,
+ Tag\Q::ID,
+ Tag\Rb::ID,
+ Tag\Rp::ID,
+ Tag\Rt::ID,
+ Tag\Ruby::ID,
+ Tag\S::ID,
+ Tag\Samp::ID,
+ Tag\ScriptTypeApplicationLdJson::ID,
+ Tag\ScriptTypeTextPlainAmp4email::ID,
+ Tag\ScriptCustomElementAmpAccordionAmp4email::ID,
+ Tag\ScriptCustomElementAmpAutocompleteAmp4email::ID,
+ Tag\ScriptCustomElementAmpBindAmp4email::ID,
+ Tag\ScriptCustomElementAmpCarouselAmp4email::ID,
+ Tag\ScriptCustomElementAmpFitTextAmp4email::ID,
+ Tag\ScriptCustomElementAmpFormAmp4email::ID,
+ Tag\ScriptCustomElementAmpImageLightboxAmp4email::ID,
+ Tag\ScriptCustomElementAmpLightboxAmp4email::ID,
+ Tag\ScriptCustomElementAmpListAmp4email::ID,
+ Tag\ScriptCustomElementAmpSelectorAmp4email::ID,
+ Tag\ScriptCustomElementAmpSidebarAmp4email::ID,
+ Tag\ScriptCustomElementAmpTimeagoAmp4email::ID,
+ Tag\ScriptCustomTemplateAmpMustacheAmp4email::ID,
+ Tag\SectionAmp4email::ID,
+ Tag\Select::ID,
+ Tag\Small::ID,
+ Tag\Span::ID,
+ Tag\Strong::ID,
+ Tag\StyleAmpCustomAmp4email::ID,
+ Tag\StyleAmpCustomCssStrict::ID,
+ Tag\StyleAmpCustomLengthCheck::ID,
+ Tag\Sub::ID,
+ Tag\Summary::ID,
+ Tag\Sup::ID,
+ Tag\Table::ID,
+ Tag\Tbody::ID,
+ Tag\Td::ID,
+ Tag\TemplateAmp4email::ID,
+ Tag\Textarea::ID,
+ Tag\Tfoot::ID,
+ Tag\Th::ID,
+ Tag\Thead::ID,
+ Tag\Time::ID,
+ Tag\TitleAmp4email::ID,
+ Tag\Tr::ID,
+ Tag\U::ID,
+ Tag\Ul::ID,
+ Tag\Var_::ID,
+ Tag\Wbr::ID,
+ ],
+ ];
+
+ /**
+ * Mapping of extension name to tag ID.
+ *
+ * This is used to optimize querying by extension spec.
+ *
+ * @var array
+ */
+ const BY_EXTENSION_SPEC = [
+ Extension::AD => Tag\AmpAdExtensionScript::ID,
+ Extension::ANIM => Tag\ScriptAmpAnim::ID,
+ Extension::_3D_GLTF => Tag\ScriptAmp3dGltf::ID,
+ Extension::_3Q_PLAYER => Tag\ScriptAmp3qPlayer::ID,
+ Extension::ACCESS_LATERPAY => Tag\ScriptAmpAccessLaterpay::ID,
+ Extension::ACCESS_POOOL => Tag\ScriptAmpAccessPoool::ID,
+ Extension::ACCESS_SCROLL => Tag\ScriptAmpAccessScroll::ID,
+ Extension::ACCESS => Tag\ScriptAmpAccess::ID,
+ Extension::ACCORDION => Tag\ScriptCustomElementAmpAccordionAmp4email::ID,
+ Extension::ACTION_MACRO => Tag\ScriptAmpActionMacro::ID,
+ Extension::AD_CUSTOM => Tag\ScriptAmpAdCustom::ID,
+ Extension::AD_EXIT => Tag\ScriptAmpAdExit::ID,
+ Extension::ADDTHIS => Tag\ScriptAmpAddthis::ID,
+ Extension::ANALYTICS => Tag\ScriptAmpAnalytics::ID,
+ Extension::ANIMATION => Tag\ScriptAmpAnimation::ID,
+ Extension::APESTER_MEDIA => Tag\ScriptAmpApesterMedia::ID,
+ Extension::APP_BANNER => Tag\ScriptAmpAppBanner::ID,
+ Extension::AUDIO => Tag\ScriptAmpAudio::ID,
+ Extension::AUTO_ADS => Tag\ScriptAmpAutoAds::ID,
+ Extension::AUTOCOMPLETE => Tag\ScriptCustomElementAmpAutocompleteAmp4email::ID,
+ Extension::BASE_CAROUSEL => Tag\ScriptAmpBaseCarousel::ID,
+ Extension::BEOPINION => Tag\ScriptAmpBeopinion::ID,
+ Extension::BIND => Tag\ScriptCustomElementAmpBindAmp4email::ID,
+ Extension::BODYMOVIN_ANIMATION => Tag\ScriptAmpBodymovinAnimation::ID,
+ Extension::BRID_PLAYER => Tag\ScriptAmpBridPlayer::ID,
+ Extension::BRIGHTCOVE => Tag\ScriptAmpBrightcove::ID,
+ Extension::BYSIDE_CONTENT => Tag\ScriptAmpBysideContent::ID,
+ Extension::CACHE_URL => Tag\ScriptAmpCacheUrl::ID,
+ Extension::CALL_TRACKING => Tag\ScriptAmpCallTracking::ID,
+ Extension::CAROUSEL => Tag\ScriptCustomElementAmpCarouselAmp4email::ID,
+ Extension::CONNATIX_PLAYER => Tag\ScriptAmpConnatixPlayer::ID,
+ Extension::CONSENT => Tag\ScriptAmpConsent::ID,
+ Extension::DAILYMOTION => Tag\ScriptAmpDailymotion::ID,
+ Extension::DATE_COUNTDOWN => Tag\ScriptAmpDateCountdown::ID,
+ Extension::DATE_DISPLAY => Tag\ScriptAmpDateDisplay::ID,
+ Extension::DATE_PICKER => Tag\ScriptAmpDatePicker::ID,
+ Extension::DELIGHT_PLAYER => Tag\ScriptAmpDelightPlayer::ID,
+ Extension::DYNAMIC_CSS_CLASSES => Tag\ScriptAmpDynamicCssClasses::ID,
+ Extension::EMBEDLY_CARD => Tag\ScriptAmpEmbedlyCard::ID,
+ Extension::EXPERIMENT => Tag\ScriptAmpExperiment::ID,
+ Extension::FACEBOOK_COMMENTS => Tag\ScriptAmpFacebookComments::ID,
+ Extension::FACEBOOK_LIKE => Tag\ScriptAmpFacebookLike::ID,
+ Extension::FACEBOOK_PAGE => Tag\ScriptAmpFacebookPage::ID,
+ Extension::FACEBOOK => Tag\ScriptAmpFacebook::ID,
+ Extension::FIT_TEXT => Tag\ScriptCustomElementAmpFitTextAmp4email::ID,
+ Extension::FONT => Tag\ScriptAmpFont::ID,
+ Extension::FORM => Tag\ScriptCustomElementAmpFormAmp4email::ID,
+ Extension::FX_COLLECTION => Tag\ScriptAmpFxCollection::ID,
+ Extension::FX_FLYING_CARPET => Tag\ScriptAmpFxFlyingCarpet::ID,
+ Extension::GEO => Tag\ScriptAmpGeo::ID,
+ Extension::GFYCAT => Tag\ScriptAmpGfycat::ID,
+ Extension::GIST => Tag\ScriptAmpGist::ID,
+ Extension::GOOGLE_ASSISTANT_ASSISTJS => Tag\ScriptAmpGoogleAssistantAssistjs::ID,
+ Extension::GOOGLE_DOCUMENT_EMBED => Tag\ScriptAmpGoogleDocumentEmbed::ID,
+ Extension::GWD_ANIMATION => Tag\ScriptAmpGwdAnimation::ID,
+ Extension::HULU => Tag\ScriptAmpHulu::ID,
+ Extension::IFRAMELY => Tag\ScriptAmpIframely::ID,
+ Extension::IFRAME => Tag\ScriptAmpIframe::ID,
+ Extension::IMA_VIDEO => Tag\ScriptAmpImaVideo::ID,
+ Extension::IMAGE_LIGHTBOX => Tag\ScriptCustomElementAmpImageLightboxAmp4email::ID,
+ Extension::IMAGE_SLIDER => Tag\ScriptAmpImageSlider::ID,
+ Extension::IMGUR => Tag\ScriptAmpImgur::ID,
+ Extension::INLINE_GALLERY => Tag\ScriptAmpInlineGallery::ID,
+ Extension::INPUTMASK => Tag\ScriptAmpInputmask::ID,
+ Extension::INSTAGRAM => Tag\ScriptAmpInstagram2::ID,
+ Extension::INSTALL_SERVICEWORKER => Tag\ScriptAmpInstallServiceworker::ID,
+ Extension::IZLESENE => Tag\ScriptAmpIzlesene::ID,
+ Extension::JWPLAYER => Tag\ScriptAmpJwplayer::ID,
+ Extension::KALTURA_PLAYER => Tag\ScriptAmpKalturaPlayer::ID,
+ Extension::LIGHTBOX_GALLERY => Tag\ScriptAmpLightboxGallery::ID,
+ Extension::LIGHTBOX => Tag\ScriptCustomElementAmpLightboxAmp4email::ID,
+ Extension::LINK_REWRITER => Tag\ScriptAmpLinkRewriter::ID,
+ Extension::LIST_ => Tag\ScriptCustomElementAmpListAmp4email::ID,
+ Extension::LIVE_LIST => Tag\ScriptAmpLiveList::ID,
+ Extension::MATHML => Tag\ScriptAmpMathml::ID,
+ Extension::MEGA_MENU => Tag\ScriptAmpMegaMenu::ID,
+ Extension::MEGAPHONE => Tag\ScriptAmpMegaphone::ID,
+ Extension::MINUTE_MEDIA_PLAYER => Tag\ScriptAmpMinuteMediaPlayer::ID,
+ Extension::MOWPLAYER => Tag\ScriptAmpMowplayer::ID,
+ Extension::MRAID => Tag\ScriptAmpMraid::ID,
+ Extension::MUSTACHE => Tag\ScriptCustomTemplateAmpMustacheAmp4email::ID,
+ Extension::NESTED_MENU => Tag\ScriptAmpNestedMenu::ID,
+ Extension::NEXT_PAGE => Tag\ScriptAmpNextPage::ID,
+ Extension::NEXXTV_PLAYER => Tag\ScriptAmpNexxtvPlayer::ID,
+ Extension::O2_PLAYER => Tag\ScriptAmpO2Player::ID,
+ Extension::ONETAP_GOOGLE => Tag\ScriptAmpOnetapGoogle::ID,
+ Extension::OOYALA_PLAYER => Tag\ScriptAmpOoyalaPlayer::ID,
+ Extension::ORIENTATION_OBSERVER => Tag\ScriptAmpOrientationObserver::ID,
+ Extension::PAN_ZOOM => Tag\ScriptAmpPanZoom::ID,
+ Extension::PINTEREST => Tag\ScriptAmpPinterest::ID,
+ Extension::PLAYBUZZ => Tag\ScriptAmpPlaybuzz::ID,
+ Extension::POSITION_OBSERVER => Tag\ScriptAmpPositionObserver::ID,
+ Extension::POWR_PLAYER => Tag\ScriptAmpPowrPlayer::ID,
+ Extension::REACH_PLAYER => Tag\ScriptAmpReachPlayer::ID,
+ Extension::RECAPTCHA_INPUT => Tag\ScriptAmpRecaptchaInput::ID,
+ Extension::REDBULL_PLAYER => Tag\ScriptAmpRedbullPlayer::ID,
+ Extension::REDDIT => Tag\ScriptAmpReddit::ID,
+ Extension::RIDDLE_QUIZ => Tag\ScriptAmpRiddleQuiz::ID,
+ Extension::SCRIPT => Tag\ScriptAmpScript::ID,
+ Extension::SELECTOR => Tag\ScriptCustomElementAmpSelectorAmp4email::ID,
+ Extension::SIDEBAR => Tag\ScriptCustomElementAmpSidebarAmp4email::ID,
+ Extension::SKIMLINKS => Tag\ScriptAmpSkimlinks::ID,
+ Extension::SLIDES => Tag\ScriptAmpSlides::ID,
+ Extension::SMARTLINKS => Tag\ScriptAmpSmartlinks::ID,
+ Extension::SOCIAL_SHARE => Tag\ScriptAmpSocialShare2::ID,
+ Extension::SOUNDCLOUD => Tag\ScriptAmpSoundcloud::ID,
+ Extension::SPRINGBOARD_PLAYER => Tag\ScriptAmpSpringboardPlayer::ID,
+ Extension::STICKY_AD => Tag\ScriptAmpStickyAd::ID,
+ Extension::STORY_360 => Tag\ScriptAmpStory360::ID,
+ Extension::STORY_AUTO_ADS => Tag\ScriptAmpStoryAutoAds::ID,
+ Extension::STORY_AUTO_ANALYTICS => Tag\ScriptAmpStoryAutoAnalytics::ID,
+ Extension::STORY_INTERACTIVE => Tag\ScriptAmpStoryInteractive::ID,
+ Extension::STORY_PANNING_MEDIA => Tag\ScriptAmpStoryPanningMedia::ID,
+ Extension::STORY_PLAYER => Tag\ScriptAmpStoryPlayer::ID,
+ Extension::STORY => Tag\ScriptAmpStory::ID,
+ Extension::STREAM_GALLERY => Tag\ScriptAmpStreamGallery::ID,
+ Extension::SUBSCRIPTIONS_GOOGLE => Tag\ScriptAmpSubscriptionsGoogle::ID,
+ Extension::SUBSCRIPTIONS => Tag\ScriptAmpSubscriptions::ID,
+ Extension::TIMEAGO => Tag\ScriptCustomElementAmpTimeagoAmp4email::ID,
+ Extension::TRUNCATE_TEXT => Tag\ScriptAmpTruncateText::ID,
+ Extension::TWITTER => Tag\ScriptAmpTwitter::ID,
+ Extension::USER_NOTIFICATION => Tag\ScriptAmpUserNotification::ID,
+ Extension::VIDEO_DOCKING => Tag\ScriptAmpVideoDocking::ID,
+ Extension::VIDEO_IFRAME => Tag\ScriptAmpVideoIframe2::ID,
+ Extension::VIDEO => Tag\ScriptAmpVideo2::ID,
+ Extension::VIMEO => Tag\ScriptAmpVimeo2::ID,
+ Extension::VINE => Tag\ScriptAmpVine::ID,
+ Extension::VIQEO_PLAYER => Tag\ScriptAmpViqeoPlayer::ID,
+ Extension::VK => Tag\ScriptAmpVk::ID,
+ Extension::WEB_PUSH => Tag\ScriptAmpWebPush::ID,
+ Extension::WISTIA_PLAYER => Tag\ScriptAmpWistiaPlayer::ID,
+ Extension::YOTPO => Tag\ScriptAmpYotpo::ID,
+ Extension::YOUTUBE => Tag\ScriptAmpYoutube2::ID,
+ ];
+
+ /**
+ * Cache of instantiated Tag objects.
+ *
+ * @var array
+ */
+ private $tagsCache = [];
+
+ /**
+ * Array used for storing the iteration index in.
+ *
+ * @var array|null
+ */
+ private $iterationArray;
+
+ /**
+ * Get a collection of tags by tag name.
+ *
+ * @param string $tagName Tag name to get the collection of tags for.
+ * @return array Array of tags. Empty array if tag name not found.
+ */
+ public function byTagName($tagName)
+ {
+ $tagName = strtolower($tagName);
+
+ if (!array_key_exists($tagName, self::BY_TAG_NAME)) {
+ return [];
+ }
+
+ $tagIds = self::BY_TAG_NAME[$tagName];
+ if (!is_array($tagIds)) {
+ $tagIds = [$tagIds];
+ }
+
+ $tags = [];
+ foreach ($tagIds as $tagId) {
+ $tags[] = $this->byTagId($tagId);
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Get the tag for a given spec name.
+ *
+ * @param string $specName Spec name to get the tag for.
+ * @return Tag Tag with the given spec name.
+ * @throws InvalidSpecName If an invalid spec name is requested.
+ */
+ public function bySpecName($specName)
+ {
+ if (!array_key_exists($specName, self::BY_SPEC_NAME)) {
+ throw InvalidSpecName::forSpecName($specName);
+ }
+
+ return $this->byTagId(self::BY_SPEC_NAME[$specName]);
+ }
+
+ /**
+ * Get a collection of tags for a given AMP HTML format name.
+ *
+ * @param string $format AMP HTML format to get the tags for.
+ * @return array Array of tags matching the requested AMP HTML format.
+ * @throws InvalidFormat If an invalid AMP HTML format is requested.
+ */
+ public function byFormat($format)
+ {
+ if (!array_key_exists($format, self::BY_FORMAT)) {
+ throw InvalidFormat::forFormat($format);
+ }
+
+ $tagIds = self::BY_FORMAT[$format];
+ if (!is_array($tagIds)) {
+ $tagIds = [$tagIds];
+ }
+
+ $tags = [];
+ foreach ($tagIds as $tagId) {
+ $tags[] = $this->byTagId($tagId);
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Get the tag for a given extension spec name.
+ *
+ * @param string $extension Extension name to get the extension spec for.
+ * @return TagWithExtensionSpec Tag with the given extension spec name.
+ * @throws InvalidExtension If an invalid extension name is requested.
+ */
+ public function byExtensionSpec($extension)
+ {
+ if (!array_key_exists($extension, self::BY_EXTENSION_SPEC)) {
+ throw InvalidExtension::forExtension($extension);
+ }
+
+ $tag = $this->byTagId(self::BY_EXTENSION_SPEC[$extension]);
+
+ if (!$tag instanceof \AmpProject\Validator\Spec\TagWithExtensionSpec) {
+ throw new \LogicException('Tags::byExtensionSpec returned tag without extension spec');
+ }
+
+ return $tag;
+ }
+
+ /**
+ * Get a tag by its tag ID.
+ *
+ * @param string $tagId Tag ID for which to get the tag.
+ * @return Tag Tag object.
+ */
+ public function byTagId($tagId)
+ {
+ if (array_key_exists($tagId, $this->tagsCache)) {
+ return $this->tagsCache[$tagId];
+ }
+
+ if (!array_key_exists($tagId, self::TAGS)) {
+ throw InvalidTagId::forTagId($tagId);
+ }
+
+ $tagClassName = self::TAGS[$tagId];
+ $tag = new $tagClassName();
+
+ $this->tagsCache[$tagId] = $tag;
+
+ return $tag;
+ }
+
+ /**
+ * Get the list of available keys.
+ *
+ * @return array Array of available keys.
+ */
+ public function getAvailableKeys()
+ {
+ return array_keys(self::TAGS);
+ }
+
+ /**
+ * Find the instantiated object for the current key.
+ *
+ * This should use its own caching mechanism as needed.
+ *
+ * Ideally, current() should be overridden as well to provide the correct object type-hint.
+ *
+ * @param string $key Key to retrieve the instantiated object for.
+ * @return object Instantiated object for the current key.
+ */
+ public function findByKey($key)
+ {
+ return $this->byTagId($key);
+ }
+
+ /**
+ * Return the current iterable object.
+ *
+ * @return Tag Tag object.
+ */
+ public function current()
+ {
+ return $this->parentCurrent();
+ }
+}
diff --git a/src/Validator/Spec/SpecRule.php b/src/Validator/Spec/SpecRule.php
new file mode 100644
index 000000000..1c552599b
--- /dev/null
+++ b/src/Validator/Spec/SpecRule.php
@@ -0,0 +1,121 @@
+ $alsoRequiresTagWarning
+ * @property-read array $ampLayout
+ * @property-read array $attrLists
+ * @property-read array $attrs
+ * @property-read array $cdata
+ * @property-read array $childTags
+ * @property-read string $deprecation
+ * @property-read string $deprecationUrl
+ * @property-read string $descendantTagList
+ * @property-read string $descriptiveName
+ * @property-read array $disabledBy
+ * @property-read array $disallowedAncestor
+ * @property-read array $enabledBy
+ * @property-read array $excludes
+ * @property-read bool $explicitAttrsOnly
+ * @property-read array $extensionSpec
+ * @property-read array $htmlFormat
+ * @property-read bool $mandatory
+ * @property-read string $mandatoryAlternatives
+ * @property-read string $mandatoryAncestor
+ * @property-read string $mandatoryAncestorSuggestedAlternative
+ * @property-read bool $mandatoryLastChild
+ * @property-read string $mandatoryParent
+ * @property-read array $markDescendants
+ * @property-read string $namedId
+ * @property-read array $referencePoints
+ * @property-read array $requires
+ * @property-read array $requiresExtension
+ * @property-read array $satisfies
+ * @property-read bool $siblingsDisallowed
+ * @property-read string $specName
+ * @property-read string $specUrl
+ * @property-read string $tagName
+ * @property-read bool $unique
+ * @property-read bool $uniqueWarning
+ */
+class Tag
+{
+ /**
+ * ID of the tag.
+ *
+ * This needs to be overridden in the extending class.
+ *
+ * @var string
+ */
+ const ID = '[tag base class]';
+
+ /**
+ * Spec data of the tag.
+ *
+ * @var array
+ */
+ const SPEC = [];
+
+ /**
+ * Get the ID of the tag.
+ *
+ * @return string ID of the tag.
+ */
+ public function getId()
+ {
+ return static::ID;
+ }
+
+ /**
+ * Check whether a given spec rule is present.
+ *
+ * @param string $specRuleName Name of the spec rule to check for.
+ * @return bool Whether the given spec rule is contained in the spec.
+ */
+ public function has($specRuleName)
+ {
+ return array_key_exists($specRuleName, static::SPEC);
+ }
+
+ /**
+ * Get a specific spec rule.
+ *
+ * @param string $specRuleName Name of the spec rule to get.
+ * @return array Spec rule data that was requested.
+ */
+ public function get($specRuleName)
+ {
+ if (!$this->has($specRuleName)) {
+ throw InvalidSpecRuleName::forSpecRuleName($specRuleName);
+ }
+
+ return static::SPEC[$specRuleName];
+ }
+
+ /**
+ * Magic getter to return the spec rules.
+ *
+ * @param string $specRuleName Name of the spec rule to return.
+ * @return mixed Value of the spec rule.
+ */
+ public function __get($specRuleName)
+ {
+ switch ($specRuleName) {
+ case SpecRule::EXPLICIT_ATTRS_ONLY:
+ case SpecRule::MANDATORY:
+ case SpecRule::MANDATORY_LAST_CHILD:
+ case SpecRule::SIBLINGS_DISALLOWED:
+ case SpecRule::UNIQUE:
+ case SpecRule::UNIQUE_WARNING:
+ return array_key_exists($specRuleName, static::SPEC) ? static::SPEC[$specRuleName] : false;
+ case SpecRule::ALSO_REQUIRES_TAG_WARNING:
+ case SpecRule::ATTR_LISTS:
+ case SpecRule::DISABLED_BY:
+ case SpecRule::DISALLOWED_ANCESTOR:
+ case SpecRule::ENABLED_BY:
+ case SpecRule::EXCLUDES:
+ case SpecRule::HTML_FORMAT:
+ case SpecRule::REQUIRES:
+ case SpecRule::REQUIRES_EXTENSION:
+ case SpecRule::SATISFIES:
+ return array_key_exists($specRuleName, static::SPEC) ? static::SPEC[$specRuleName] : [];
+ default:
+ if (!array_key_exists($specRuleName, static::SPEC)) {
+ throw InvalidSpecRuleName::forSpecRuleName($specRuleName);
+ }
+
+ return static::SPEC[$specRuleName];
+ }
+ }
+}
diff --git a/src/Validator/Spec/Tag/A.php b/src/Validator/Spec/Tag/A.php
new file mode 100644
index 000000000..48773c7f1
--- /dev/null
+++ b/src/Validator/Spec/Tag/A.php
@@ -0,0 +1,134 @@
+ Element::A,
+ SpecRule::ATTRS => [
+ [
+ SpecRule::NAME => Attribute::BORDER,
+ ],
+ [
+ SpecRule::NAME => Attribute::DOWNLOAD,
+ ],
+ [
+ SpecRule::NAME => Attribute::HREF,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::FTP,
+ Protocol::GEO,
+ Protocol::HTTP,
+ Protocol::HTTPS,
+ Protocol::MAILTO,
+ Protocol::MAPS,
+ Protocol::BIP,
+ Protocol::BBMI,
+ Protocol::CHROME,
+ Protocol::ITMS_SERVICES,
+ Protocol::FACETIME,
+ Protocol::FB_ME,
+ Protocol::FB_MESSENGER,
+ Protocol::FEED,
+ Protocol::INTENT,
+ Protocol::LINE,
+ Protocol::SKYPE,
+ Protocol::SMS,
+ Protocol::SNAPCHAT,
+ Protocol::TEL,
+ Protocol::TG,
+ Protocol::THREEMA,
+ Protocol::TWITTER,
+ Protocol::VIBER,
+ Protocol::WEBCAL,
+ Protocol::WEB_MASTODON,
+ Protocol::WH,
+ Protocol::WHATSAPP,
+ ],
+ SpecRule::ALLOW_EMPTY => true,
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::HREFLANG,
+ ],
+ [
+ SpecRule::NAME => Attribute::MEDIA,
+ ],
+ [
+ SpecRule::NAME => Attribute::REFERRERPOLICY,
+ ],
+ [
+ SpecRule::NAME => Attribute::REL,
+ SpecRule::DISALLOWED_VALUE_REGEX => '(^|\s)(components|dns-prefetch|import|manifest|preconnect|prefetch|preload|prerender|serviceworker|stylesheet|subresource)(\s|$)',
+ ],
+ [
+ SpecRule::NAME => Attribute::ROLE,
+ SpecRule::IMPLICIT => true,
+ ],
+ [
+ SpecRule::NAME => Attribute::TABINDEX,
+ SpecRule::IMPLICIT => true,
+ ],
+ [
+ SpecRule::NAME => Attribute::TARGET,
+ SpecRule::VALUE => [
+ '_blank',
+ '_self',
+ '_top',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::TYPE,
+ SpecRule::VALUE_CASEI => [
+ 'text/html',
+ 'application/rss+xml',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::SHOW_TOOLTIP,
+ SpecRule::VALUE => [
+ 'auto',
+ 'true',
+ ],
+ ],
+ [
+ SpecRule::NAME => '[href]',
+ ],
+ ],
+ SpecRule::ATTR_LISTS => [
+ AttributeList\NameAttr::ID,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#links',
+ SpecRule::HTML_FORMAT => [
+ Format::AMP,
+ Format::AMP4ADS,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/AAmp4email.php b/src/Validator/Spec/Tag/AAmp4email.php
new file mode 100644
index 000000000..58d4b29ad
--- /dev/null
+++ b/src/Validator/Spec/Tag/AAmp4email.php
@@ -0,0 +1,82 @@
+ Element::A,
+ SpecRule::SPEC_NAME => 'A (AMP4EMAIL)',
+ SpecRule::ATTRS => [
+ [
+ SpecRule::NAME => Attribute::BORDER,
+ ],
+ [
+ SpecRule::NAME => Attribute::HREF,
+ SpecRule::DISALLOWED_VALUE_REGEX => '__amp_source_origin|(.|\s){{|}}(.|\s)|^{{.*[^}][^}]$|^[^{][^{].*}}$|^}}|{{$|{{#|{{/|{{\^',
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTP,
+ Protocol::HTTPS,
+ Protocol::MAILTO,
+ Protocol::TEL,
+ ],
+ SpecRule::ALLOW_RELATIVE => false,
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::HREFLANG,
+ ],
+ [
+ SpecRule::NAME => Attribute::MEDIA,
+ ],
+ [
+ SpecRule::NAME => Attribute::ROLE,
+ SpecRule::IMPLICIT => true,
+ ],
+ [
+ SpecRule::NAME => Attribute::TABINDEX,
+ SpecRule::IMPLICIT => true,
+ ],
+ [
+ SpecRule::NAME => Attribute::TARGET,
+ SpecRule::VALUE => [
+ '_blank',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::TYPE,
+ SpecRule::VALUE_CASEI => [
+ 'text/html',
+ ],
+ ],
+ ],
+ SpecRule::HTML_FORMAT => [
+ Format::AMP4EMAIL,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/Abbr.php b/src/Validator/Spec/Tag/Abbr.php
new file mode 100644
index 000000000..4530fb937
--- /dev/null
+++ b/src/Validator/Spec/Tag/Abbr.php
@@ -0,0 +1,37 @@
+ Element::ABBR,
+ SpecRule::HTML_FORMAT => [
+ Format::AMP,
+ Format::AMP4ADS,
+ Format::AMP4EMAIL,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/Acronym.php b/src/Validator/Spec/Tag/Acronym.php
new file mode 100644
index 000000000..3a22c3c21
--- /dev/null
+++ b/src/Validator/Spec/Tag/Acronym.php
@@ -0,0 +1,35 @@
+ Element::ACRONYM,
+ SpecRule::HTML_FORMAT => [
+ Format::AMP,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/Address.php b/src/Validator/Spec/Tag/Address.php
new file mode 100644
index 000000000..d84dbd14a
--- /dev/null
+++ b/src/Validator/Spec/Tag/Address.php
@@ -0,0 +1,37 @@
+ Element::ADDRESS,
+ SpecRule::HTML_FORMAT => [
+ Format::AMP,
+ Format::AMP4ADS,
+ Format::AMP4EMAIL,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/Amp3dGltf.php b/src/Validator/Spec/Tag/Amp3dGltf.php
new file mode 100644
index 000000000..4f3f3247f
--- /dev/null
+++ b/src/Validator/Spec/Tag/Amp3dGltf.php
@@ -0,0 +1,100 @@
+ Extension::_3D_GLTF,
+ SpecRule::ATTRS => [
+ [
+ SpecRule::NAME => Attribute::ALPHA,
+ SpecRule::VALUE => [
+ 'false',
+ 'true',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::ANTIALIASING,
+ SpecRule::VALUE => [
+ 'false',
+ 'true',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::AUTOROTATE,
+ SpecRule::VALUE => [
+ 'false',
+ 'true',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::CLEARCOLOR,
+ ],
+ [
+ SpecRule::NAME => Attribute::ENABLEZOOM,
+ SpecRule::VALUE => [
+ 'false',
+ 'true',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::MAXPIXELRATIO,
+ SpecRule::VALUE_REGEX => '[+-]?(\d*\.)?\d+',
+ ],
+ [
+ SpecRule::NAME => Attribute::SRC,
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE_URL => [
+ SpecRule::PROTOCOL => [
+ Protocol::HTTPS,
+ ],
+ ],
+ ],
+ ],
+ SpecRule::ATTR_LISTS => [
+ AttributeList\ExtendedAmpGlobal::ID,
+ ],
+ SpecRule::AMP_LAYOUT => [
+ SpecRule::SUPPORTED_LAYOUTS => [
+ Layout::FILL,
+ Layout::FIXED,
+ Layout::FIXED_HEIGHT,
+ Layout::FLEX_ITEM,
+ Layout::RESPONSIVE,
+ ],
+ ],
+ SpecRule::HTML_FORMAT => [
+ Format::AMP,
+ ],
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::_3D_GLTF,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/Amp3qPlayer.php b/src/Validator/Spec/Tag/Amp3qPlayer.php
new file mode 100644
index 000000000..5d9172d21
--- /dev/null
+++ b/src/Validator/Spec/Tag/Amp3qPlayer.php
@@ -0,0 +1,64 @@
+ Extension::_3Q_PLAYER,
+ SpecRule::ATTRS => [
+ [
+ SpecRule::NAME => Attribute::AUTOPLAY,
+ SpecRule::VALUE => [
+ '',
+ ],
+ ],
+ [
+ SpecRule::NAME => Attribute::DATA_ID,
+ SpecRule::MANDATORY => true,
+ ],
+ ],
+ SpecRule::ATTR_LISTS => [
+ AttributeList\ExtendedAmpGlobal::ID,
+ ],
+ SpecRule::AMP_LAYOUT => [
+ SpecRule::SUPPORTED_LAYOUTS => [
+ Layout::FILL,
+ Layout::FIXED,
+ Layout::FLEX_ITEM,
+ Layout::RESPONSIVE,
+ ],
+ ],
+ SpecRule::HTML_FORMAT => [
+ Format::AMP,
+ ],
+ SpecRule::REQUIRES_EXTENSION => [
+ Extension::_3Q_PLAYER,
+ ],
+ ];
+}
diff --git a/src/Validator/Spec/Tag/Amp4adsEngineScript.php b/src/Validator/Spec/Tag/Amp4adsEngineScript.php
new file mode 100644
index 000000000..f5025791b
--- /dev/null
+++ b/src/Validator/Spec/Tag/Amp4adsEngineScript.php
@@ -0,0 +1,65 @@
+ Element::SCRIPT,
+ SpecRule::SPEC_NAME => 'amp4ads engine script',
+ SpecRule::MANDATORY => true,
+ SpecRule::UNIQUE => true,
+ SpecRule::MANDATORY_PARENT => Element::HEAD,
+ SpecRule::ATTRS => [
+ [
+ SpecRule::NAME => Attribute::SRC,
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ 'https://cdn.ampproject.org/amp4ads-v0.js',
+ ],
+ SpecRule::DISPATCH_KEY => 'NAME_VALUE_DISPATCH',
+ ],
+ ],
+ SpecRule::ATTR_LISTS => [
+ AttributeList\NonceAttr::ID,
+ AttributeList\AmphtmlEngineAttrs::ID,
+ ],
+ SpecRule::SPEC_URL => 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/#required-markup',
+ SpecRule::CDATA => [
+ SpecRule::DISALLOWED_CDATA_REGEX => [
+ [
+ SpecRule::REGEX => '.',
+ SpecRule::ERROR_MESSAGE => 'contents',
+ ],
+ ],
+ ],
+ SpecRule::HTML_FORMAT => [
+ Format::AMP4ADS,
+ ],
+ SpecRule::DESCRIPTIVE_NAME => 'amphtml engine script',
+ ];
+}
diff --git a/src/Validator/Spec/Tag/AmpAccessExtensionJsonScript.php b/src/Validator/Spec/Tag/AmpAccessExtensionJsonScript.php
new file mode 100644
index 000000000..637add4a0
--- /dev/null
+++ b/src/Validator/Spec/Tag/AmpAccessExtensionJsonScript.php
@@ -0,0 +1,72 @@
+ Element::SCRIPT,
+ SpecRule::SPEC_NAME => 'amp-access extension .json script',
+ SpecRule::UNIQUE => true,
+ SpecRule::MANDATORY_PARENT => Element::HEAD,
+ SpecRule::ATTRS => [
+ [
+ SpecRule::NAME => Attribute::ID,
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE => [
+ 'amp-access',
+ ],
+ SpecRule::DISPATCH_KEY => 'NAME_VALUE_DISPATCH',
+ ],
+ [
+ SpecRule::NAME => Attribute::TYPE,
+ SpecRule::MANDATORY => true,
+ SpecRule::VALUE_CASEI => [
+ 'application/json',
+ ],
+ ],
+ ],
+ SpecRule::ATTR_LISTS => [
+ AttributeList\NonceAttr::ID,
+ ],
+ SpecRule::CDATA => [
+ SpecRule::DISALLOWED_CDATA_REGEX => [
+ [
+ SpecRule::REGEX => '