Skip to content
This repository has been archived by the owner on Apr 20, 2023. It is now read-only.

How do you use the PullquoteRule and PullquoteCiteRule classes to put together a pull quote with a cite? #18

Closed
m4olivei opened this issue Apr 8, 2016 · 5 comments
Assignees

Comments

@m4olivei
Copy link
Contributor

m4olivei commented Apr 8, 2016

I'm using the Transformer. My target markup is this:

<blockquote class="pull-quote">
  <div class="field-quote">
    <p>Here is a fancy pull quote for the world to see it all.</p> 
  </div>
  <div class="field-quote-author">Matthew Oliveira</div>
</blockquote>

How do I use PullquoteRule and/or PullquoteCiteRule to transform to this:

<aside>
  Here is a fancy pull quote for the world to see it all.
  <cite>Matthew Oliveira</cite>
</aside>

At least it seems like PullquoteRule class is missing a property for the attribution?

@simonengelhardt
Copy link
Contributor

@everton-rosario would you be able to give @m4olivei some guidance? Thanks!

@everton-rosario
Copy link
Contributor

Here is a snippet of the rules configuration json that have your markup covered.
Please check if it works for you.
I've also added a test case under PullquoteRuleTest.php.

   ...
    {
        "class": "TextNodeRule"
    },
    {
        "class": "ItalicRule",
        "selector": "em"
    },
    {
        "class": "ParagraphRule",
        "selector": "p"
    },
    {
        "class": "PassThroughRule",
        "selector": "div.field-quote > p"
    },
    {
        "class": "PassThroughRule",
        "selector" : "div.field-quote"
    },
    {
        "class" : "PullquoteRule",
        "selector" : "blockquote.pull-quote"
    },
    {
        "class" : "PullquoteCiteRule",
        "selector" : "div.field-quote-author"
    }
    ...

everton-rosario added a commit that referenced this issue Apr 11, 2016
Closes #18 - Add test to cover and document a different Pullquote markup
@m4olivei
Copy link
Contributor Author

Totally works. I've got a better understanding of the Transformer now as a result of this example. The Transformer has been a difficult thing to wrap my head around. There has been much xdebug-ing to better understand it, and this example helped with that too. Thanks a bunch!

@everton-rosario
Copy link
Contributor

We are working to make it as simple as possible.
The problem is complex and too wide (markup interpreting/transforming).

Maybe better docs and some "why's" of architecture decisions would make it easier to extend.

If you have any other feedback regarding this, let me know.

@m4olivei
Copy link
Contributor Author

Yeah, that would be good, documenting the why's. Also more examples with source markup, transformer rules, and destination markup.

Another thing that would be good is to flesh out your custom Rule example. I ended up figuring it out, but it was tricky. Here's my example, and maybe there are a series of out of the box transformer rules that could do this:

Source markup (eg. http://www.bravotv.com/blogs/local-regional-chains-we-miss-from-our-hometowns-wawa-carvel-skyline)

<h2 class="listicle">
  <span class="listicle__number">1</span><span class="listicle__title">Carvel</span>
</h2>

Target markup:

<h2>1. Caravel</h2>

So this annoying need to get a ". " in there between the number and the title itself. Ended up writing this custom rule for it:

<?php

/**
 * @file
 * Class definition of ListicleRule.
 */

use Facebook\InstantArticles\Elements\H2;
use Facebook\InstantArticles\Elements\InstantArticle;
use Facebook\InstantArticles\Transformer\Rules\ConfigurationSelectorRule;
use Facebook\InstantArticles\Transformer\Transformer;
use Facebook\InstantArticles\Transformer\Warnings\InvalidSelector;

/**
 * Class ListicleRule.  Transforms Listicle markup to markup appropriate for
 * Facebook Instant Articles.
 */
class ListicleRule extends ConfigurationSelectorRule  {

  const PROPERTY_NUMBER = 'listicle.number';
  const PROPERTY_TITLE = 'listicle.title';

  /**
   * {@inheritdoc}
   */
  public function getContextClass() {
    return array(InstantArticle::class);
  }

  /**
   * Create an instant of ListicleRule.
   * @return \ListicleRule
   */
  public static function create() {
    return new ListicleRule();
  }

  /**
   * Create an instance from a transformer rules configuration array.
   *
   * @param array $configuration
   * @return \ListicleRule
   */
  public static function createFrom($configuration) {
    $rule = self::create();
    $rule->withSelector($configuration['selector']);

    $rule->withProperties(
      array(
        self::PROPERTY_NUMBER,
        self::PROPERTY_TITLE,
      ),
      $configuration
    );

    return $rule;
  }

  /**
   * Apply the transformation from Listicle to FB Instant Articles markup.
   *
   * @param Transformer $transformer
   * @param InstantArticle $instant_article
   * @param DOMElement $node
   * @return mixed
   */
  public function apply($transformer, $instant_article, $node) {
    $number = $this->getProperty(self::PROPERTY_NUMBER, $node);
    $title = $this->getProperty(self::PROPERTY_TITLE, $node);
    $h2 = H2::create();

    if ($title) {
      if ($number) {
        $h2->appendText($number . '. ' . $title);
      }
      else {
        $h2->appendText($title);
      }
      $instant_article->addChild($h2);
    }
    else {
      $transformer->addWarning(
        new InvalidSelector(
          'title',
          $instant_article,
          $node,
          $this
        )
      );
    }

    return $instant_article;
  }
}

And finally, here is the rule config:

/** @var \Facebook\InstantArticles\Transformer\Transformer $transformer */
// Transform Listicle
$listicle_config = array(
  'class' => 'ListicleRule',
  'selector' => '.listicle',
  'properties' => array(
    'listicle.number' => array(
      'type' => 'string',
      'selector' => '.listicle__number',
    ),
    'listicle.title' => array(
      'type' => 'string',
      'selector' => '.listicle__title',
    ),
  ),
);
$listicle_rule = ListicleRule::createFrom($listicle_config);
$transformer->addRule($listicle_rule);

The most confusing, undocumented part for me was figuring out how the properties worked. It's super cleaver how it works, what with the Getters and all, but until you wrap your head around the Getters, it's all very magical and confusing. Here all I needed was type => 'string', which uses the StringGetter, which is pretty straightforward after you know about it, the other getters are even more confusing though.

I also ended up needing to write a custom Getter. Admittedly, maybe this is getting into major edge case land, but I'll tell you about it anyway in case it helps the education effort. Our web articles have images throughout, and each image is a derivative of the giant source image (responsive images, yay).

<picture>
  <!--bunch of srcset tags--->
  <!--fallback img--->
  <img src="http://www.bravotv.com/sites/nbcubravotv/files/styles/blog-post--computer/public/the-feast-regional-chains-carvel.jpg?itok=3DKQrLSK" alt="" title="" />
</picture>

So http://www.bravotv.com/sites/nbcubravotv/files/styles/blog-post--computer/public/the-feast-regional-chains-carvel.jpg is the derivative image, and http://www.bravotv.com/sites/nbcubravotv/files/the-feast-regional-chains-carvel.jpg is the source. I wanted to have the source image in the Instant Articles feed, so you guys could have all the pixels at your disposal. There were a bunch of different approaches that I tried, but in the end, I wrote a custom Getter to work with the ImageRule. Here's the custom Getter:

<?php

/**
 * @file
 * Contains Drupal\FBInstantArticles\ScrSetImageStyleGetter
 */

use Facebook\InstantArticles\Transformer\Getters\StringGetter;

/**
 * Custom Facebook Instant Articles SDK 'Getter'.  Extend the simple
 * StringGetter, but add some processing to strip any descriptors and change
 * the URL to the original image from an image style.
 */
class SrcSetImageStyleGetter extends StringGetter {
  /**
   * {@inheritdoc}
   */
  public function get($node) {
    $srcset = parent::get($node);

    // Trim off any images past the first
    $srcset = explode(',', $srcset);
    $srcset = $srcset[0];
    preg_match('~^(?:.(?!\d+[w|x]))+~', $srcset, $matches);
    $url = $matches[0];

    // Turn an image style URL into the src image.
    return preg_replace('~styles/.*?/public/~', '', $url);
  }
}

Paired with this rule:

// Find <picture><img /><picture> constructs and turn them into Image
// elements.  Uses a custom Getter, to take a srcset attribute that has an
// image style on it and change it to the original image.
$picture_config = array(
  'class' => 'ImageRule',
  'selector' => '//p[picture[img]]',
  'properties' => array(
    'image.url' => array(
      'type' => 'SrcSetImageStyleGetter',
      'selector' => 'img',
      'attribute' => 'srcset',
    ),
    'image.caption' => array(
      'type' => 'element',
      'selector' => 'img[@alt]',
    ),
  ),
);
$image_rule = ImageRule::createFrom($picture_config);
$transformer->addRule($image_rule);

It works nicely, and didn't require that I change the rendering of our web articles at all, but it took a lot to figure out.

So yeah, I think more education about the Rules and Getters and when you might use them, ie the limitations of the out of the box stuff, would be helpful.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants