public function get($node) { Type::enforce($node, 'DOMNode'); $elements = self::findAll($node, $this->selector); if (!empty($elements) && $elements->item(0)) { $element = $elements->item(0); do { $element = $element->nextSibling; } while ($element !== null && !Type::is($element, 'DOMElement')); if ($element && Type::is($element, 'DOMElement')) { if ($this->siblingSelector) { $siblings = self::findAll($element, $this->siblingSelector); if (!empty($siblings) && $siblings->item(0)) { $siblingElement = $siblings->item(0); } else { // Returns null because sibling content doesn't match return null; } } else { $siblingElement = $element; } Transformer::markAsProcessed($siblingElement); return Transformer::cloneNode($siblingElement); } } return null; }
public function apply($transformer, $context_element, $node) { $h2 = H2::create(); if (Type::is($context_element, array(Header::getClassName(), Caption::getClassName()))) { $context_element->withSubTitle($h2); } elseif (Type::is($context_element, InstantArticle::getClassName())) { $context_element->addChild($h2); } if ($this->getProperty(Caption::POSITION_BELOW, $node)) { $h2->withPosition(Caption::POSITION_BELOW); } if ($this->getProperty(Caption::POSITION_CENTER, $node)) { $h2->withPosition(Caption::POSITION_CENTER); } if ($this->getProperty(Caption::POSITION_ABOVE, $node)) { $h2->withPosition(Caption::POSITION_ABOVE); } if ($this->getProperty(Caption::ALIGN_LEFT, $node)) { $h2->withTextAlignment(Caption::ALIGN_LEFT); } if ($this->getProperty(Caption::ALIGN_CENTER, $node)) { $h2->withTextAlignment(Caption::ALIGN_CENTER); } if ($this->getProperty(Caption::ALIGN_RIGHT, $node)) { $h2->withTextAlignment(Caption::ALIGN_RIGHT); } $transformer->transform($h2, $node); return $context_element; }
/** * Adds a new item to the List * * @param string|ListItem $new_item The new item that will be pushed to the end of the list * * @return $this */ public function addItem($new_item) { Type::enforce($new_item, [ListItem::getClassName(), Type::STRING]); if (Type::is($new_item, Type::STRING)) { $new_item = ListItem::create()->appendText($new_item); } $this->items[] = $new_item; return $this; }
/** * Sets the attribution string * * @param string|Cite $attribution The attribution text * * @return $this */ public function withAttribution($attribution) { Type::enforce($attribution, [Type::STRING, Cite::getClassName()]); if (Type::is($attribution, Type::STRING)) { $this->attribution = Cite::create()->appendText($attribution); } else { $this->attribution = $attribution; } return $this; }
/** * Sets the unescaped HTML. * * @param \DOMNode|string $html The unescaped HTML. * * @return $this */ public function withHTML($html) { Type::enforce($html, ['DOMNode', Type::STRING]); // If this is raw HTML source, wrap in a CDATA section as it could contain JS etc. with characters (such as &) that are not allowed in unescaped form if (Type::is($html, Type::STRING)) { $html = new \DOMCdataSection($html); } $this->html = $html; return $this; }
public function matchesNode($node) { // Only matches DOMElements (ignore text and comments) if (!Type::is($node, 'DOMElement')) { return false; } // Handles selector = tag if ($node->nodeName === $this->selector) { return true; } // Handles selector = .class if (preg_match('/^\\.[a-zA-Z][a-zA-Z0-9-]*$/', $this->selector) === 1) { // Tries every class $classNames = explode(' ', $node->getAttribute('class')); foreach ($classNames as $className) { if ('.' . $className === $this->selector) { return true; } } // No match! return false; } // Handles selector = tag.class if (preg_match('/^[a-zA-Z][a-zA-Z0-9-]*(\\.[a-zA-Z][a-zA-Z0-9-]*)?$/', $this->selector) === 1) { // Tries every class $classNames = explode(' ', $node->getAttribute('class')); foreach ($classNames as $className) { if ($node->nodeName . '.' . $className === $this->selector) { return true; } } // No match! return false; } // Proceed with the more expensive XPath query $document = $node->ownerDocument; $domXPath = new \DOMXPath($document); if (substr($this->selector, 0, 1) === '/') { $xpath = $this->selector; } else { $converter = new CssSelectorConverter(); $xpath = $converter->toXPath($this->selector); } $results = $domXPath->query($xpath); if (false !== $results) { foreach ($results as $result) { if ($result === $node) { return true; } } } return false; }
public function get($node) { $fragment = $node->ownerDocument->createDocumentFragment(); foreach ($this->children as $child) { $cloned_node = $child->get($node); if (Type::is($cloned_node, 'DOMNode')) { $fragment->appendChild($cloned_node); } } if ($fragment->hasChildNodes()) { return $fragment; } return null; }
public function apply($transformer, $context, $node) { $interactive = Interactive::create(); if (Type::is($context, InstantArticle::getClassName())) { $instant_article = $context; } elseif ($transformer->getInstantArticle()) { $instant_article = $transformer->getInstantArticle(); $context->disableEmptyValidation(); $context = Paragraph::create(); $context->disableEmptyValidation(); } else { $transformer->addWarning(new NoRootInstantArticleFoundWarning(null, $node)); return $context; } // Builds the interactive $iframe = $this->getProperty(self::PROPERTY_IFRAME, $node); $url = $this->getProperty(self::PROPERTY_URL, $node); if ($iframe) { $interactive->withHTML($iframe); } if ($url) { $interactive->withSource($url); } if ($iframe || $url) { $instant_article->addChild($interactive); if ($instant_article !== $context) { $instant_article->addChild($context); } } else { $transformer->addWarning(new InvalidSelector(self::PROPERTY_IFRAME, $instant_article, $node, $this)); } if ($this->getProperty(self::PROPERTY_WIDTH_COLUMN_WIDTH, $node)) { $interactive->withMargin(Interactive::COLUMN_WIDTH); } else { $interactive->withMargin(Interactive::NO_MARGIN); } $width = $this->getProperty(self::PROPERTY_WIDTH, $node); if ($width) { $interactive->withWidth($width); } $height = $this->getProperty(self::PROPERTY_HEIGHT, $node); if ($height) { $interactive->withHeight($height); } $suppress_warnings = $transformer->suppress_warnings; $transformer->suppress_warnings = true; $transformer->transform($interactive, $node); $transformer->suppress_warnings = $suppress_warnings; return $context; }
/** * @param string|DOMDocument $document The document html of an Instant Article * * @return InstantArticle filled element that was parsed from the DOMDocument parameter */ public function parse($content) { if (Type::is($content, Type::STRING)) { libxml_use_internal_errors(true); $document = new \DOMDocument(); $document->loadHTML($content); libxml_use_internal_errors(false); } else { $document = $content; } $json_file = file_get_contents(__DIR__ . '/instant-articles-rules.json'); $instant_article = InstantArticle::create(); $transformer = new Transformer(); $transformer->loadRules($json_file); $transformer->transform($instant_article, $document); return $instant_article; }
public function apply($transformer, $context, $node) { $image = Image::create(); if (Type::is($context, InstantArticle::getClassName())) { $instant_article = $context; } elseif ($transformer->getInstantArticle()) { $instant_article = $transformer->getInstantArticle(); $context->disableEmptyValidation(); $context = Paragraph::create(); $context->disableEmptyValidation(); } else { $transformer->addWarning(new NoRootInstantArticleFoundWarning(null, $node)); return $context; } // Builds the image $url = $this->getProperty(self::PROPERTY_IMAGE_URL, $node); if ($url) { $image->withURL($url); $instant_article->addChild($image); if ($instant_article !== $context) { $instant_article->addChild($context); } } else { $transformer->addWarning(new InvalidSelector(self::PROPERTY_IMAGE_URL, $instant_article, $node, $this)); } if ($this->getProperty(Image::ASPECT_FIT, $node)) { $image->withPresentation(Image::ASPECT_FIT); } elseif ($this->getProperty(Image::ASPECT_FIT_ONLY, $node)) { $image->withPresentation(Image::ASPECT_FIT_ONLY); } elseif ($this->getProperty(Image::FULLSCREEN, $node)) { $image->withPresentation(Image::FULLSCREEN); } elseif ($this->getProperty(Image::NON_INTERACTIVE, $node)) { $image->withPresentation(Image::NON_INTERACTIVE); } if ($this->getProperty(self::PROPERTY_LIKE, $node)) { $image->enableLike(); } if ($this->getProperty(self::PROPERTY_COMMENTS, $node)) { $image->enableComments(); } $suppress_warnings = $transformer->suppress_warnings; $transformer->suppress_warnings = true; $transformer->transform($image, $node); $transformer->suppress_warnings = $suppress_warnings; return $context; }
/** * @return string */ public function __toString() { $reflection = new \ReflectionClass(get_class($this->context)); $className = $reflection->getShortName(); $nodeName = $this->node->nodeName; if (substr($nodeName, 0, 1) === '#') { $nodeDescription = '"' . mb_strimwidth($this->node->textContent, 0, 30, '...') . '"'; } else { $nodeDescription = '<'; $nodeDescription .= $nodeName; if (Type::is($this->node, 'DOMElement')) { $class = $this->node->getAttribute('class'); if ($class) { $nodeDescription .= ' class="' . $class . '"'; } } $nodeDescription .= '>'; } return "No rules defined for {$nodeDescription} in the context of {$className}"; }
/** * Implements the Container::getContainerChildren(). * * @see Container::getContainerChildren(). * @return array of TextContainer */ public function getContainerChildren() { $children = array(); foreach ($this->textChildren as $content) { // Recursive check on TextContainer, if something inside is valid, this is valid. if (Type::is($content, TextContainer::getClassName())) { $children[] = $content; } } return $children; }
/** * The caption credit. optional. * * @param string $credit the caption credit text that will be shown * * @return $this */ public function withCredit($credit) { Type::enforce($credit, [Type::STRING, Cite::getClassName()]); if (Type::is($credit, Type::STRING)) { $credit = Cite::create()->appendText($credit); } $this->credit = $credit; return $this; }
public function testIsNotString() { $result = Type::is(1, Type::STRING); $this->assertFalse($result); }
/** * Kicker text for the article header. * * @param H3|string The kicker text to be set * * @return $this */ public function withKicker($kicker) { Type::enforce($kicker, array(Type::STRING, H3::getClassName())); if (Type::is($kicker, Type::STRING)) { $this->kicker = H3::create()->appendText($kicker); } else { $this->kicker = $kicker; } return $this; }
/** * Sets the geotag on the video. * * @see {link:http://geojson.org/} * * @param string $geoTag * * @return $this */ public function withGeoTag($geoTag) { Type::enforce($geoTag, [Type::STRING, GeoTag::getClassName()]); if (Type::is($geoTag, Type::STRING)) { $this->geoTag = GeoTag::create()->withScript($geoTag); } elseif (Type::is($geoTag, GeoTag::getClassName())) { $this->geoTag = $geoTag; } return $this; }
/** * @param InstantArticle $context * @param \DOMNode $node * * @return mixed */ public function transform($context, $node) { if (Type::is($context, InstantArticle::getClassName())) { $context->addMetaProperty('op:generator:transformer', 'facebook-instant-articles-sdk-php'); $context->addMetaProperty('op:generator:transformer:version', InstantArticle::CURRENT_VERSION); $this->instantArticle = $context; } $log = \Logger::getLogger('facebook-instantarticles-transformer'); if (!$node) { $e = new \Exception(); $log->error('Transformer::transform($context, $node) requires $node' . ' to be a valid one. Check on the stacktrace if this is ' . 'some nested transform operation and fix the selector.', $e->getTraceAsString()); } $current_context = $context; if ($node->hasChildNodes()) { foreach ($node->childNodes as $child) { if (self::isProcessed($child)) { continue; } $matched = false; $log->debug("==========================="); $log->debug($child->ownerDocument->saveHtml($child)); // Get all classes and interfaces this context extends/implements $contextClassNames = self::getAllClassTypes($context->getClassName()); // Look for rules applying to any of them as context $matchingContextRules = []; foreach ($contextClassNames as $contextClassName) { if (isset($this->rules[$contextClassName])) { // Use array union (+) instead of merge to preserve // indexes (as they represent the order of insertion) $matchingContextRules = $matchingContextRules + $this->rules[$contextClassName]; } } // Sort by insertion order ksort($matchingContextRules); // Process in reverse order $matchingContextRules = array_reverse($matchingContextRules); foreach ($matchingContextRules as $rule) { // We know context was matched, now check if it matches the node if ($rule->matchesNode($child)) { $current_context = $rule->apply($this, $current_context, $child); $matched = true; // Just a single rule for each node, so move on break; } } if (!$matched && !($child->nodeName === '#text' && trim($child->textContent) === '') && !($child->nodeName === '#comment') && !($child->nodeName === 'html' && Type::is($child, 'DOMDocumentType')) && !($child->nodeName === 'xml' && Type::is($child, 'DOMProcessingInstruction')) && !$this->suppress_warnings) { $tag_content = $child->ownerDocument->saveXML($child); $tag_trimmed = trim($tag_content); if (!empty($tag_trimmed)) { $log->debug('context class: ' . get_class($context)); $log->debug('node name: ' . $child->nodeName); $log->debug("CONTENT NOT MATCHED: \n" . $tag_content); } else { $log->debug('empty content ignored'); } $this->addWarning(new UnrecognizedElement($current_context, $child)); } } } return $context; }
public function toDOMElement($document = null) { if (!$document) { $document = new \DOMDocument(); } // Builds and appends head to the HTML document $html = $document->createElement('html'); if ($this->isRTLEnabled) { $html->setAttribute('dir', 'rtl'); } $head = $document->createElement('head'); $html->appendChild($head); $link = $document->createElement('link'); $link->setAttribute('rel', 'canonical'); $link->setAttribute('href', $this->canonicalURL); $head->appendChild($link); $charset = $document->createElement('meta'); $charset->setAttribute('charset', $this->charset); $head->appendChild($charset); $this->addMetaProperty('op:markup_version', $this->markupVersion); if ($this->header && count($this->header->getAds()) > 0) { $this->addMetaProperty('fb:use_automatic_ad_placement', $this->isAutomaticAdPlaced ? 'true' : 'false'); } if ($this->style) { $this->addMetaProperty('fb:article_style', $this->style); } // Adds all meta properties foreach ($this->metaProperties as $property_name => $property_content) { $head->appendChild($this->createMetaElement($document, $property_name, $property_content)); } // Build and append body and article tags to the HTML document $body = $document->createElement('body'); $article = $document->createElement('article'); $body->appendChild($article); $html->appendChild($body); if ($this->header && $this->header->isValid()) { $article->appendChild($this->header->toDOMElement($document)); } if ($this->children) { foreach ($this->children as $child) { if (Type::is($child, TextContainer::getClassName())) { if (count($child->getTextChildren()) === 0) { continue; } elseif (count($child->getTextChildren()) === 1) { if (Type::is($child->getTextChildren()[0], Type::STRING) && trim($child->getTextChildren()[0]) === '') { continue; } } } $article->appendChild($child->toDOMElement($document)); } if ($this->footer && $this->footer->isValid()) { $article->appendChild($this->footer->toDOMElement($document)); } } else { $article->appendChild($document->createTextNode('')); } return $html; }
/** * Implements the Container::getContainerChildren(). * * @see Container::getContainerChildren() * @return array of Paragraph|RelatedArticles */ public function getContainerChildren() { $children = array(); if ($this->credits) { if (is_array($this->credits)) { foreach ($this->credits as $paragraph) { if (Type::is($paragraph, Element::getClassName())) { $children[] = $paragraph; } } } else { if (Type::is($this->credits, Element::getClassName())) { $children[] = $this->credits; } } } if ($this->relatedArticles) { $children[] = $this->relatedArticles; } return $children; }