コード例 #1
0
 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;
 }
コード例 #2
0
 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;
 }
コード例 #3
0
 /**
  * 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;
 }
コード例 #4
0
 /**
  * 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;
 }
コード例 #5
0
 /**
  * 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;
 }
コード例 #6
0
 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;
 }
コード例 #7
0
 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;
 }
コード例 #8
0
 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;
 }
コード例 #9
0
 /**
  * @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;
 }
コード例 #10
0
 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;
 }
コード例 #11
0
 /**
  * @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}";
 }
コード例 #12
0
 /**
  * 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;
 }
コード例 #13
0
 /**
  * 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;
 }
コード例 #14
0
 public function testIsNotString()
 {
     $result = Type::is(1, Type::STRING);
     $this->assertFalse($result);
 }
コード例 #15
0
 /**
  * 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;
 }
コード例 #16
0
 /**
  * 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;
 }
コード例 #17
0
 /**
  * @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;
 }
コード例 #18
0
 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;
 }
コード例 #19
0
 /**
  * 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;
 }