doc/dev/plugins/presentation/classes/Parser.php

342 lines
12 KiB
PHP

<?php
/**
* Presentation Plugin, Parser API
*
* PHP version 7
*
* @category API
* @package Grav\Plugin\PresentationPlugin
* @subpackage Grav\Plugin\PresentationPlugin\API
* @author Ole Vik <git@olevik.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://github.com/OleVik/grav-plugin-presentation
*/
namespace Grav\Plugin\PresentationPlugin\API;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Plugin\PresentationPlugin\Utilities;
use Thunder\Shortcode\Parser\RegularParser;
use Thunder\Shortcode\Parser\RegexParser;
use Thunder\Shortcode\Parser\WordpressParser;
use Thunder\Shortcode\Processor\Processor;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;
use Thunder\Shortcode\HandlerContainer\HandlerContainer;
/**
* Parser API
*
* Parser API for parsing content
*
* @category Extensions
* @package Grav\Plugin\PresentationPlugin\API
* @author Ole Vik <git@olevik.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://github.com/OleVik/grav-plugin-presentation
*/
class Parser implements ParserInterface
{
/**
* Regular expressions
*/
const REGEX_MEDIA_P = '/<p>\s*(<a .*>\s*<img.*\s*<\/a>|\s*<img.*|<img.*\s*|<video.*|<audio.*)\s*<\/p>/i';
/**
* Instantiate Parser API
*
* @param array $config Plugin configuration
* @param Transport $transport Transport API
*/
public function __construct($config, $transport)
{
$this->config = $config;
$this->transport = $transport;
// @deprecated 2.0.0
$this->props = [];
}
/**
* Parse shortcodes
*
* @param string $content Markdown content in Page
* @param string $id Slide ID
* @param array $page Page configuration
*
* @return array Processed content and shortcodes
*/
public function processShortcodes(string $content, string $id, array $page)
{
$handlers = new HandlerContainer();
$path = $page['path'];
$handlers->setDefault(
function (ShortcodeInterface $sc) use ($id, $path) {
$name = $sc->getName();
$value = $sc->getParameter($name, $sc->getBbCode()) ?? '';
if (Utils::startsWith($name, 'class')) {
$this->transport->setClass($id, $value);
} elseif (Utils::startsWith($name, 'style')) {
$property = str_replace('style-', '', $name);
$this->styleProcessor($id, $property, $value, [$path]);
} elseif (Utils::startsWith($name, 'styles')) {
$property = str_replace('styles-', '', $name);
$this->styleProcessor($id, $property, $value, [$path]);
} elseif (Utils::startsWith($name, 'data')) {
$property = str_replace('data-', '', $name);
$this->transport->setDataAttribute($id, $property, $value);
} elseif (Utils::startsWith($name, 'hide')) {
$this->transport->setDataAttribute($id, 'hide', "true");
}
return;
}
);
$parser = "Thunder\Shortcode\Parser\\" . $this->config['shortcode_parser'];
$parser = new $parser();
$processor = new Processor($parser, $handlers);
return [
'content' => $processor->process($content),
'shortcodes' => $parser->parse($content)
];
}
/**
* Process key-value pairs of options
*
* @param array $data Key-value pairs of options
* @param string $id Target id-attribute
* @param array $paths Paths to use for file-finding
* @param string $mode Which Transport to perform
*
* @return boolean
*/
public function processor(array $data, string $id, array $paths = [], string $mode = 'style')
{
if (empty($data)) {
return false;
}
foreach ($data as $key => $value) {
if (Utils::startsWith('style-', $key)) {
$mode = 'style';
} elseif (Utils::startsWith('styles-', $key)) {
$mode = 'style';
} elseif (Utils::startsWith('data-', $key)) {
$mode = 'data';
} elseif (Utils::startsWith('aria-', $key)) {
$mode = 'aria';
}
$key = str_replace($mode . '-', '', $key);
if ($mode == 'style') {
if (empty($value)) {
continue;
}
$this->styleProcessor($id, $key, $value, $paths);
} elseif ($mode == 'data') {
$this->transport->setDataAttribute($id, $key, $value);
} elseif ($mode == 'aria') {
$this->transport->setAriaAttribute($id, $key, $value);
}
}
return true;
}
/**
* Process style
*
* @param string $id Target id-attribute
* @param string $property CSS property name
* @param string $value CSS property value
* @param array $paths Locations to search for asset in
*
* @return void
*/
public function styleProcessor(string $id, string $property, string $value, array $paths = [])
{
if ($property == 'background-image') {
if (!Uri::isValidUrl($value)) {
$locations = array_merge(
$paths,
[
'',
'user/pages',
'user/pages/images',
]
);
$locations = Utilities::explodeFileLocations($locations, GRAV_ROOT, '/', '/');
$file = Utilities::fileFinder($value, $locations);
$file = str_ireplace(GRAV_ROOT, '', $file);
$value = $file;
}
$this->transport->setStyle($id, "{\n$property: url($value);\n}");
} elseif ($property == 'header-font-family') {
$this->transport->setStyle($id, "{\nfont-family:$value;\n}", 'h1,h2,h3,h4,h5,h6');
} elseif ($property == 'header-color') {
$this->transport->setStyle($id, "{\ncolor:$value;\n}", 'h1,h2,h3,h4,h5,h6');
} elseif ($property == 'block-font-family') {
$this->transport->setStyle($id, "{\nfont-family:$value;\n}");
} elseif ($property == 'block-color') {
$this->transport->setStyle($id, "{\ncolor:$value;\n}");
} else {
$this->transport->setStyle($id, "{\n$property:$value;\n}");
}
}
/**
* Set modular scales in CSS
*
* @param string $id Slide id-attribute
* @param string $scale Modular Scale Ratio
* @param float $modifier Optional multiplication-parameter
*
* @return void
*/
public function setModularScale(string $id, string $scale, float $modifier = null)
{
$scale = (float) $scale;
$steps = array(6, 5, 4, 3, 2, 1, 0);
for ($i = 1; $i <= 6; $i++) {
$value = self::modularScale($steps[$i], 16, $scale, true);
$value = $modifier != null ? $value * $modifier : $value;
$this->transport->setStyle($id, '{font-size:' . $value . 'em;}', 'h' . $i);
}
}
/**
* Get font-size in pixels
*
* @param integer $step Step in scale
* @param integer $base Base font-size
* @param float $ratio Rhythm
* @param bool $relative Output relative units
*
* @return float Modular Scale breakpoint
*/
public static function modularScale(int $step, int $base, float $ratio, bool $relative = null)
{
if ($relative == true) {
return round((pow($ratio, $step) * $base) / $base, 3);
} else {
return round((pow($ratio, $step) * $base), 2);
}
}
/**
* Remove wrapping paragraph from img-element
*
* @param string $content Markdown content in Page
*
* @return string Processed content
*/
public static function unwrapImage(string $content)
{
$unwrap = self::REGEX_MEDIA_P;
$content = preg_replace($unwrap, "$1", $content);
return $content;
}
/**
* Parse shortcodes
*
* @param string $content Markdown content in Page
* @param string $id Slide id-attribute
*
* @deprecated 2.0.0
*
* @return array Processed contents and properties
*/
public function interpretShortcodes(string $content, string $id)
{
$handlers = new HandlerContainer();
$handlers->setDefault(
function (ShortcodeInterface $sc) {
$return = array();
$name = $sc->getName();
$value = $sc->getParameter($name, $sc->getBbCode());
if (Utils::startsWith($name, 'class')) {
$this->props['class'] = $value;
} elseif (Utils::startsWith($name, 'style')) {
$name = str_replace('style-', '', $name);
$this->props['styles'][$name] = $value;
} elseif (Utils::startsWith($name, 'data')) {
$this->props['styles'][$name] = $value;
} elseif (Utils::startsWith($name, 'hide')) {
$this->props['hide'] = [true];
}
return;
}
);
$parser = "Thunder\Shortcode\Parser\\" . $this->config['shortcode_parser'];
$processor = new Processor(new $parser(), $handlers);
return ['content' => $processor->process($content), 'props' => $this->props];
}
/**
* Create HTML for fragments
*
* @param string $content Markdown content in Page
*
* @deprecated 2.0.0
*
* @return string Processed contents
*/
public function processFragments(string $content)
{
$content = preg_replace(
self::REGEX_FRAGMENT_SHORTCODE,
'<span class="fragment \\1">\\2</span>',
$content
);
return $content;
}
/**
* Process styles and data-attributes
*
* @param array $styles List of key-value pairs
* @param string $route Route to Page for relative assets
* @param string $id Slide id-attribute
* @param string $base Base path to prepend to file
*
* @deprecated 2.0.0
*
* @return string Processed styles, in inline string
*/
public function processStylesData(array $styles, string $route, string $id, string $base = "")
{
$inline = $data = '';
foreach ($styles as $property => $value) {
if ($property == 'background-image') {
if (!Uri::isValidUrl($value)) {
$locations = array(
'',
'user/pages',
'user/pages/images',
);
$locations = Utilities::explodeFileLocations($locations, GRAV_ROOT, '/', '/');
$file = Utilities::fileFinder($value, $locations);
$file = str_ireplace(GRAV_ROOT, '', $file);
$value = $base . $file;
}
$inline .= $property . ': url(' . $value . ');';
} elseif (Utils::startsWith($property, 'data')) {
$data .= ' ' . $property . '="' . $value . '"';
if ($property == 'data-textsize-scale') {
$this->transport->setClass($id, 'textsizing');
}
} elseif ($property == 'header-font-family') {
$this->transport->setStyle($id, "{\nfont-family:$value;\n}", 'h1,h2,h3,h4,h5,h6');
} elseif ($property == 'header-color') {
$this->transport->setStyle($id, "{\ncolor:$value;\n}", 'h1,h2,h3,h4,h5,h6');
} elseif ($property == 'block-font-family') {
$this->transport->setStyle($id, "{\nfont-family:$value;\n}");
} elseif ($property == 'block-color') {
$this->transport->setStyle($id, "{\ncolor:$value;\n}");
} else {
$inline .= $property . ': ' . $value . ';';
}
}
return array(
'style' => $inline,
'data' => $data
);
}
}