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 => '