From 297ff0a0547926b383a975778b949ab2298f1d2a Mon Sep 17 00:00:00 2001 From: Llewellyn van der Merwe Date: Sat, 15 Apr 2023 16:58:37 +0200 Subject: [PATCH] first commit --- README.md | 213 +++++ .../README.md | 81 ++ .../code.php | 207 +++++ .../code.power | 183 ++++ .../settings.json | 20 + .../README.md | 21 + .../code.php | 25 + .../code.power | 1 + .../settings.json | 18 + .../README.md | 21 + .../code.php | 26 + .../code.power | 1 + .../settings.json | 19 + .../README.md | 174 ++++ .../code.php | 754 +++++++++++++++ .../code.power | 721 +++++++++++++++ .../settings.json | 31 + .../README.md | 21 + .../code.php | 25 + .../code.power | 1 + .../settings.json | 18 + .../README.md | 45 + .../code.php | 91 ++ .../code.power | 63 ++ .../settings.json | 27 + .../README.md | 36 + .../code.php | 24 + .../code.power | 8 + .../settings.json | 18 + .../README.md | 177 ++++ .../code.php | 513 +++++++++++ .../code.power | 482 ++++++++++ .../settings.json | 27 + .../README.md | 108 +++ .../code.php | 861 ++++++++++++++++++ .../code.power | 830 +++++++++++++++++ .../settings.json | 18 + super-powers.json | 92 ++ 38 files changed, 6001 insertions(+) create mode 100644 README.md create mode 100644 src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/README.md create mode 100644 src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.php create mode 100644 src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.power create mode 100644 src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/settings.json create mode 100644 src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/README.md create mode 100644 src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.php create mode 100644 src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.power create mode 100644 src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/settings.json create mode 100644 src/47448ada-54df-45f3-b05d-1c66c584b1bd/README.md create mode 100644 src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.php create mode 100644 src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.power create mode 100644 src/47448ada-54df-45f3-b05d-1c66c584b1bd/settings.json create mode 100644 src/70530246-2903-4c02-af9a-61182e4d97b8/README.md create mode 100644 src/70530246-2903-4c02-af9a-61182e4d97b8/code.php create mode 100644 src/70530246-2903-4c02-af9a-61182e4d97b8/code.power create mode 100644 src/70530246-2903-4c02-af9a-61182e4d97b8/settings.json create mode 100644 src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/README.md create mode 100644 src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.php create mode 100644 src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.power create mode 100644 src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/settings.json create mode 100644 src/b591855d-d106-4fe5-90ad-8e706f6267cf/README.md create mode 100644 src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.php create mode 100644 src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.power create mode 100644 src/b591855d-d106-4fe5-90ad-8e706f6267cf/settings.json create mode 100644 src/cc482882-5eef-476c-b835-7b0c5a17c0db/README.md create mode 100644 src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.php create mode 100644 src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.power create mode 100644 src/cc482882-5eef-476c-b835-7b0c5a17c0db/settings.json create mode 100644 src/d2b8599a-d5ce-49b1-b992-764bb7d55137/README.md create mode 100644 src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.php create mode 100644 src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.power create mode 100644 src/d2b8599a-d5ce-49b1-b992-764bb7d55137/settings.json create mode 100644 src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/README.md create mode 100644 src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/code.php create mode 100644 src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/code.power create mode 100644 src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/settings.json create mode 100644 super-powers.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..441b566 --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +``` +███████╗██╗ ██╗██████╗ ███████╗██████╗ +██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗ +███████╗██║ ██║██████╔╝█████╗ ██████╔╝ +╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗ +███████║╚██████╔╝██║ ███████╗██║ ██║ +╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ ███████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗██╔════╝ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝███████╗ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗╚════██║ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║███████║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝╚══════╝ +``` + +### What is JCB Super Powers? +The Joomla Component Builder (JCB) Super Power features are designed to enhance JCB's functionality and streamline the development process. These Super Powers enable developers to efficiently manage and share their custom powers across multiple JCB instances through repositories hosted on [https://git.vdm.dev/[username]/[repository-name]](https://git.vdm.dev). JCB Super Powers are managed using a combination of layers, events, tasks, methods, switches, and algorithms, which work together to provide powerful customization and extensibility options. More details on JCB Super Powers can be found in the [Super Powers Documentation](https://git.vdm.dev/joomla/super-powers/wiki). + +In summary, JCB Super Powers offer a flexible and efficient way to manage and share functionalities between JCB instances. By utilizing a sophisticated system of layers, events, tasks, methods, switches, and algorithms, developers can seamlessly integrate JCB core powers and their custom powers. For more information on how to work with JCB Super Powers, refer to the [Super Powers User Guide](https://git.vdm.dev/joomla/super-powers/wiki). + +### What can I find here? +This repository contains an index (see below) of all the approved powers within the JCB GUI. During the compilation of a component, these powers are automatically added to the repository, ensuring a well-organized and accessible collection of functionalities. + +# Index of powers + +- **Namespace**: [VDM\Minify](#vdm-minify) + + - **class Css** | [Details](src/70530246-2903-4c02-af9a-61182e4d97b8) | [Code](src/70530246-2903-4c02-af9a-61182e4d97b8/code.php) | [Settings](src/70530246-2903-4c02-af9a-61182e4d97b8/settings.json) | [70530246-2903-4c02-af9a-61182e4d97b8](src/70530246-2903-4c02-af9a-61182e4d97b8) + - **class JavaScript** | [Details](src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e) | [Code](src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/code.php) | [Settings](src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/settings.json) | [ef6438c4-76db-4aa1-bb15-bc97583bcd2e](src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e) +- **Namespace**: [VDM\Minify\Abstraction](#vdm-minify-abstraction) + + - **abstract class Minify** | [Details](src/d2b8599a-d5ce-49b1-b992-764bb7d55137) | [Code](src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.php) | [Settings](src/d2b8599a-d5ce-49b1-b992-764bb7d55137/settings.json) | [d2b8599a-d5ce-49b1-b992-764bb7d55137](src/d2b8599a-d5ce-49b1-b992-764bb7d55137) +- **Namespace**: [VDM\Minify\Path](#vdm-minify-path) + + - **class Converter** | [Details](src/39f8957f-cc73-4da5-8b60-6cf25d4ab058) | [Code](src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.php) | [Settings](src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/settings.json) | [39f8957f-cc73-4da5-8b60-6cf25d4ab058](src/39f8957f-cc73-4da5-8b60-6cf25d4ab058) +- **Namespace**: [VDM\Minify\Path\Interfaces](#vdm-minify-path-interfaces) + + - **interface ConverterInterface** | [Details](src/cc482882-5eef-476c-b835-7b0c5a17c0db) | [Code](src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.php) | [Settings](src/cc482882-5eef-476c-b835-7b0c5a17c0db/settings.json) | [cc482882-5eef-476c-b835-7b0c5a17c0db](src/cc482882-5eef-476c-b835-7b0c5a17c0db) +- **Namespace**: [VDM\Joomla\Componentbuilder\Compiler\Utilities](#vdm-joomla-componentbuilder-compiler-utilities) + + - **abstract class Minify** | [Details](src/b591855d-d106-4fe5-90ad-8e706f6267cf) | [Code](src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.php) | [Settings](src/b591855d-d106-4fe5-90ad-8e706f6267cf/settings.json) | [b591855d-d106-4fe5-90ad-8e706f6267cf](src/b591855d-d106-4fe5-90ad-8e706f6267cf) + +# Class Diagrams + +## VDM Minify +> namespace VDM\Minify +```uml +@startuml + +namespace VDM\Minify #Azure { + + + class Css #Gold { + # $maxImportSize + + setMaxImportSize() + + setImportExtensions() + # moveImportsToTop() : string + # combineImports() : string + # importFiles() : string + + execute() : string + # move() : string + # shortenColors() : string + # shortenFontWeights() : string + # shortenZeroes() : string + # stripEmptyTags() : string + # stripComments() + # stripWhitespace() : string + # extractMath() + # extractCustomProperties() + # canImportBySize() : bool + # canImportByPath() : bool + # getPathConverter() : ConverterInterface + } + + class JavaScript #Gold { + # $keywordsReserved + # $keywordsBefore + # $keywordsAfter + # $operatorsBefore + + execute() : string + # stripComments() + # extractRegex() + # stripWhitespace() : string + # getOperatorsForRegex() : string[] + # getKeywordsForRegex() : string[] + # propertyNotation() : string + # shortenBools() : string + } +} + + +@enduml +``` + +## VDM Minify Abstraction +> namespace VDM\Minify\Abstraction +```uml +@startuml + +namespace VDM\Minify\Abstraction #DarkCyan { + + + abstract Minify #Orange { + # $data + # $patterns + + $extracted + + __construct() + + add() : static + + addFile() : static + + minify() : string + + gzip() : string + + cache() : CacheItemInterface + + {abstract} execute() : string + # load() : string + # save() + # registerPattern() + # replace() : string + # executeReplacement() : string + # extractStrings() + # restoreExtractedData() : string + # canImportFile() : bool + # openFileForWriting() : resource + # writeToFile() + # {static} str_replace_first() + } +} + + +@enduml +``` + +## VDM Minify Path +> namespace VDM\Minify\Path +```uml +@startuml + +namespace VDM\Minify\Path #DarkCyan { + + + class Converter #Gold { + # $from + # $to + + __construct() + # normalize() : string + # shared() : string + + convert() : string + # dirname() : string + } +} + + +@enduml +``` + +## VDM Minify Path Interfaces +> namespace VDM\Minify\Path\Interfaces +```uml +@startuml + +namespace VDM\Minify\Path\Interfaces #Olive { + + + interface ConverterInterface #Lavender { + + convert() : string + } +} + + +@enduml +``` + +## VDM Joomla Componentbuilder Compiler Utilities +> namespace VDM\Joomla\Componentbuilder\Compiler\Utilities +```uml +@startuml + +namespace VDM\Joomla\Componentbuilder\Compiler\Utilities #LightGreen { + + + abstract Minify #Orange { + + {static} js() : string + + {static} css() : string + } +} + + +@enduml +``` + + +--- +``` + ██╗ ██████╗ ██████╗ ███╗ ███╗██╗ █████╗ + ██║██╔═══██╗██╔═══██╗████╗ ████║██║ ██╔══██╗ + ██║██║ ██║██║ ██║██╔████╔██║██║ ███████║ +██ ██║██║ ██║██║ ██║██║╚██╔╝██║██║ ██╔══██║ +╚█████╔╝╚██████╔╝╚██████╔╝██║ ╚═╝ ██║███████╗██║ ██║ + ╚════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ + ██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗ +██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝ +██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║ +██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║ +╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║ + ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝ +██████╗ ██╗ ██╗██╗██╗ ██████╗ ███████╗██████╗ +██╔══██╗██║ ██║██║██║ ██╔══██╗██╔════╝██╔══██╗ +██████╔╝██║ ██║██║██║ ██║ ██║█████╗ ██████╔╝ +██╔══██╗██║ ██║██║██║ ██║ ██║██╔══╝ ██╔══██╗ +██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║ +╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/README.md b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/README.md new file mode 100644 index 0000000..5daef89 --- /dev/null +++ b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/README.md @@ -0,0 +1,81 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# class Converter (Details) +> namespace: **VDM\Minify\Path** +```uml +@startuml +class Converter #Gold { + # $from + # $to + + __construct(string $from, string $to, ...) + # normalize(string $path) : string + # shared(string $path1, string $path2) : string + + convert(string $path) : string + # dirname(string $path) : string +} + +note right of Converter::__construct + + + + arguments: + string $from + string $to + string $root = '' +end note + +note right of Converter::normalize + Normalize path. + + return: string +end note + +note right of Converter::shared + Figure out the shared path of 2 locations. +Example: +/home/forkcms/frontend/core/layout/images/img.gif +and +/home/forkcms/frontend/cache/minified_css +share +/home/forkcms/frontend + + return: string +end note + +note right of Converter::convert + Convert paths relative from 1 file to another. +E.g. +../images/img.gif relative to /home/forkcms/frontend/core/layout/css +should become: +../../core/layout/images/img.gif relative to +/home/forkcms/frontend/cache/minified_css + + return: string +end note + +note right of Converter::dirname + Attempt to get the directory name from a path. + + return: string +end note + +@enduml +``` + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.php b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.php new file mode 100644 index 0000000..c965c90 --- /dev/null +++ b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.php @@ -0,0 +1,207 @@ + + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify\Path; + + +use VDM\Minify\Path\Interfaces\ConverterInterface; + +class Converter implements ConverterInterface +{ + /** + * @var string + */ + protected $from; + + /** + * @var string + */ + protected $to; + + /** + * @param string $from The original base path (directory, not file!) + * @param string $to The new base path (directory, not file!) + * @param string $root Root directory (defaults to `getcwd`) + */ + public function __construct($from, $to, $root = '') + { + $shared = $this->shared($from, $to); + if ($shared === '') { + // when both paths have nothing in common, one of them is probably + // absolute while the other is relative + $root = $root ?: getcwd(); + $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root . '/' . $from); + $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root . '/' . $to); + + // or traveling the tree via `..` + // attempt to resolve path, or assume it's fine if it doesn't exist + $from = @realpath($from) ?: $from; + $to = @realpath($to) ?: $to; + } + + $from = $this->dirname($from); + $to = $this->dirname($to); + + $from = $this->normalize($from); + $to = $this->normalize($to); + + $this->from = $from; + $this->to = $to; + } + + /** + * Normalize path. + * + * @param string $path + * + * @return string + */ + protected function normalize($path) + { + // deal with different operating systems' directory structure + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); + + // remove leading current directory. + if (substr($path, 0, 2) === './') { + $path = substr($path, 2); + } + + // remove references to current directory in the path. + $path = str_replace('/./', '/', $path); + + /* + * Example: + * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif + * to + * /home/forkcms/frontend/core/layout/images/img.gif + */ + do { + $path = preg_replace('/[^\/]+(? $chunk) { + if (isset($path2[$i]) && $path1[$i] == $path2[$i]) { + $shared[] = $chunk; + } else { + break; + } + } + + return implode('/', $shared); + } + + /** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css + * should become: + * ../../core/layout/images/img.gif relative to + * /home/forkcms/frontend/cache/minified_css + * + * @param string $path The relative path that needs to be converted + * + * @return string The new relative path + */ + public function convert($path) + { + // quit early if conversion makes no sense + if ($this->from === $this->to) { + return $path; + } + + $path = $this->normalize($path); + // if we're not dealing with a relative path, just return absolute + if (strpos($path, '/') === 0) { + return $path; + } + + // normalize paths + $path = $this->normalize($this->from . '/' . $path); + + // strip shared ancestor paths + $shared = $this->shared($path, $this->to); + $path = mb_substr($path, mb_strlen($shared)); + $to = mb_substr($this->to, mb_strlen($shared)); + + // add .. for every directory that needs to be traversed to new path + $to = str_repeat('../', count(array_filter(explode('/', $to)))); + + return $to . ltrim($path, '/'); + } + + /** + * Attempt to get the directory name from a path. + * + * @param string $path + * + * @return string + */ + protected function dirname($path) + { + if (@is_file($path)) { + return dirname($path); + } + + if (@is_dir($path)) { + return rtrim($path, '/'); + } + + // no known file/dir, start making assumptions + + // ends in / = dir + if (mb_substr($path, -1) === '/') { + return rtrim($path, '/'); + } + + // has a dot in the name, likely a file + if (preg_match('/.*\..*$/', basename($path)) !== 0) { + return dirname($path); + } + + // you're on your own here! + return $path; + } +} + diff --git a/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.power b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.power new file mode 100644 index 0000000..a95c881 --- /dev/null +++ b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/code.power @@ -0,0 +1,183 @@ + /** + * @var string + */ + protected $from; + + /** + * @var string + */ + protected $to; + + /** + * @param string $from The original base path (directory, not file!) + * @param string $to The new base path (directory, not file!) + * @param string $root Root directory (defaults to `getcwd`) + */ + public function __construct($from, $to, $root = '') + { + $shared = $this->shared($from, $to); + if ($shared === '') { + // when both paths have nothing in common, one of them is probably + // absolute while the other is relative + $root = $root ?: getcwd(); + $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root . '/' . $from); + $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root . '/' . $to); + + // or traveling the tree via `..` + // attempt to resolve path, or assume it's fine if it doesn't exist + $from = @realpath($from) ?: $from; + $to = @realpath($to) ?: $to; + } + + $from = $this->dirname($from); + $to = $this->dirname($to); + + $from = $this->normalize($from); + $to = $this->normalize($to); + + $this->from = $from; + $this->to = $to; + } + + /** + * Normalize path. + * + * @param string $path + * + * @return string + */ + protected function normalize($path) + { + // deal with different operating systems' directory structure + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); + + // remove leading current directory. + if (substr($path, 0, 2) === './') { + $path = substr($path, 2); + } + + // remove references to current directory in the path. + $path = str_replace('/./', '/', $path); + + /* + * Example: + * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif + * to + * /home/forkcms/frontend/core/layout/images/img.gif + */ + do { + $path = preg_replace('/[^\/]+(? $chunk) { + if (isset($path2[$i]) && $path1[$i] == $path2[$i]) { + $shared[] = $chunk; + } else { + break; + } + } + + return implode('/', $shared); + } + + /** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css + * should become: + * ../../core/layout/images/img.gif relative to + * /home/forkcms/frontend/cache/minified_css + * + * @param string $path The relative path that needs to be converted + * + * @return string The new relative path + */ + public function convert($path) + { + // quit early if conversion makes no sense + if ($this->from === $this->to) { + return $path; + } + + $path = $this->normalize($path); + // if we're not dealing with a relative path, just return absolute + if (strpos($path, '/') === 0) { + return $path; + } + + // normalize paths + $path = $this->normalize($this->from . '/' . $path); + + // strip shared ancestor paths + $shared = $this->shared($path, $this->to); + $path = mb_substr($path, mb_strlen($shared)); + $to = mb_substr($this->to, mb_strlen($shared)); + + // add .. for every directory that needs to be traversed to new path + $to = str_repeat('../', count(array_filter(explode('/', $to)))); + + return $to . ltrim($path, '/'); + } + + /** + * Attempt to get the directory name from a path. + * + * @param string $path + * + * @return string + */ + protected function dirname($path) + { + if (@is_file($path)) { + return dirname($path); + } + + if (@is_dir($path)) { + return rtrim($path, '/'); + } + + // no known file/dir, start making assumptions + + // ends in / = dir + if (mb_substr($path, -1) === '/') { + return rtrim($path, '/'); + } + + // has a dot in the name, likely a file + if (preg_match('/.*\..*$/', basename($path)) !== 0) { + return dirname($path); + } + + // you're on your own here! + return $path; + } \ No newline at end of file diff --git a/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/settings.json b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/settings.json new file mode 100644 index 0000000..76022c3 --- /dev/null +++ b/src/39f8957f-cc73-4da5-8b60-6cf25d4ab058/settings.json @@ -0,0 +1,20 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "0", + "guid": "39f8957f-cc73-4da5-8b60-6cf25d4ab058", + "implements": [ + "cc482882-5eef-476c-b835-7b0c5a17c0db" + ], + "load_selection": null, + "name": "Converter", + "power_version": "1.1.3", + "system_name": "Path.Converter", + "type": "class", + "use_selection": null, + "namespace": "VDM\\Minify\\Path.Converter", + "description": "", + "licensing_template": "\/**\r\n * Convert paths relative from 1 file to another.\r\n *\r\n * E.g.\r\n * ..\/..\/images\/icon.jpg relative to \/css\/imports\/icons.css\r\n * becomes\r\n * ..\/images\/icon.jpg relative to \/css\/minified.css\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/path-converter\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/README.md b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/README.md new file mode 100644 index 0000000..8e5d4c1 --- /dev/null +++ b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/README.md @@ -0,0 +1,21 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +> Error adding class diagram + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.php b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.php new file mode 100644 index 0000000..11bd955 --- /dev/null +++ b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify\Exceptions; + + +use VDM\Minify\Abstraction\BasicException; + + +/** + * IO Exception Class. + * + * @author Matthias Mullie + */ +class IOException extends BasicException +{ +} + diff --git a/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.power b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.power new file mode 100644 index 0000000..69309b0 --- /dev/null +++ b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/code.power @@ -0,0 +1 @@ +###CODEPOWER### \ No newline at end of file diff --git a/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/settings.json b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/settings.json new file mode 100644 index 0000000..ecc7546 --- /dev/null +++ b/src/44b4a9cf-62e6-48af-ab30-5b3d2715728e/settings.json @@ -0,0 +1,18 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "47448ada-54df-45f3-b05d-1c66c584b1bd", + "guid": "44b4a9cf-62e6-48af-ab30-5b3d2715728e", + "implements": null, + "load_selection": null, + "name": "IOException", + "power_version": "1.0.0", + "system_name": "Minify.Exceptions.IOException", + "type": "class", + "use_selection": null, + "namespace": "VDM\\Minify\\Exceptions.IOException", + "description": "\/**\r\n * IO Exception Class.\r\n *\r\n * @author Matthias Mullie \r\n *\/", + "licensing_template": "\/**\r\n * IO Exception.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/47448ada-54df-45f3-b05d-1c66c584b1bd/README.md b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/README.md new file mode 100644 index 0000000..8e5d4c1 --- /dev/null +++ b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/README.md @@ -0,0 +1,21 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +> Error adding class diagram + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.php b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.php new file mode 100644 index 0000000..f883162 --- /dev/null +++ b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify\Abstraction; + + +/** + * Basic exception. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +abstract class BasicException extends \Exception +{ +} + diff --git a/src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.power b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.power new file mode 100644 index 0000000..69309b0 --- /dev/null +++ b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/code.power @@ -0,0 +1 @@ +###CODEPOWER### \ No newline at end of file diff --git a/src/47448ada-54df-45f3-b05d-1c66c584b1bd/settings.json b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/settings.json new file mode 100644 index 0000000..8716ed6 --- /dev/null +++ b/src/47448ada-54df-45f3-b05d-1c66c584b1bd/settings.json @@ -0,0 +1,19 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "-1", + "guid": "47448ada-54df-45f3-b05d-1c66c584b1bd", + "implements": null, + "load_selection": null, + "name": "BasicException", + "power_version": "1.0.0", + "system_name": "Minify.Abstraction.BasicException", + "type": "abstract class", + "use_selection": null, + "namespace": "VDM\\Minify\\Abstraction.BasicException", + "description": "\/**\r\n * Basic exception.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "extends_custom": "\\Exception", + "licensing_template": "\/**\r\n * Basic exception.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/70530246-2903-4c02-af9a-61182e4d97b8/README.md b/src/70530246-2903-4c02-af9a-61182e4d97b8/README.md new file mode 100644 index 0000000..5edeb42 --- /dev/null +++ b/src/70530246-2903-4c02-af9a-61182e4d97b8/README.md @@ -0,0 +1,174 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# class Css (Details) +> namespace: **VDM\Minify** +```uml +@startuml +class Css #Gold { + # $maxImportSize + + setMaxImportSize(int $size) + + setImportExtensions(array $extensions) + # moveImportsToTop(string $content) : string + # combineImports(string $source, string $content, ...) : string + # importFiles(string $source, string $content) : string + + execute(string[optional] $path = null, string[] $parents = []) : string + # move(ConverterInterface $converter, string $content) : string + # shortenColors(string $content) : string + # shortenFontWeights(string $content) : string + # shortenZeroes(string $content) : string + # stripEmptyTags(string $content) : string + # stripComments() + # stripWhitespace(string $content) : string + # extractMath() + # extractCustomProperties() + # canImportBySize(string $path) : bool + # canImportByPath(string $path) : bool + # getPathConverter(string $source, string $target) : ConverterInterface +} + +note right of Css::setMaxImportSize + Set the maximum size if files to be imported. +Files larger than this size (in kB) will not be imported into the CSS. +Importing files into the CSS as data-uri will save you some connections, +but we should only import relatively small decorative images so that our +CSS file doesn't get too bulky. + +end note + +note left of Css::setImportExtensions + Set the type of extensions to be imported into the CSS (to save network +connections). +Keys of the array should be the file extensions & respective values +should be the data type. + +end note + +note right of Css::moveImportsToTop + Move any import statements to the top. + + return: string +end note + +note left of Css::combineImports + Combine CSS from import statements. +Import statements will be loaded and their content merged into the original +file, to save HTTP requests. + + return: string + + arguments: + string $source + string $content + string[] $parents +end note + +note right of Css::importFiles + Import files into the CSS, base64-ized. +original file, to save HTTP requests. + + return: string +end note + +note left of Css::execute + Minify the data. +Perform CSS optimizations. + + return: string +end note + +note right of Css::move + Moving a css file should update all relative urls. +Relative references (e.g. ../images/image.gif) in a certain css file, +will have to be updated when a file is being saved at another location +(e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). + + return: string +end note + +note left of Css::shortenColors + Shorthand hex color codes. +#FF0000 -> #F00. + + return: string +end note + +note right of Css::shortenFontWeights + Shorten CSS font weights. + + return: string +end note + +note left of Css::shortenZeroes + Shorthand 0 values to plain 0, instead of e.g. -0em. + + return: string +end note + +note right of Css::stripEmptyTags + Strip empty tags from source code. + + return: string +end note + +note left of Css::stripComments + Strip comments from source code. + +end note + +note right of Css::stripWhitespace + Strip whitespace. + + return: string +end note + +note left of Css::extractMath + Replace all occurrences of functions that may contain math, where +whitespace around operators needs to be preserved (e.g. calc, clamp). + +end note + +note right of Css::extractCustomProperties + Replace custom properties, whose values may be used in scenarios where +we wouldn't want them to be minified (e.g. inside calc). + +end note + +note left of Css::canImportBySize + Check if file is small enough to be imported. + + return: bool +end note + +note right of Css::canImportByPath + Check if file a file can be imported, going by the path. + + return: bool +end note + +note left of Css::getPathConverter + Return a converter to update relative paths to be relative to the new +destination. + + return: ConverterInterface +end note + +@enduml +``` + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/70530246-2903-4c02-af9a-61182e4d97b8/code.php b/src/70530246-2903-4c02-af9a-61182e4d97b8/code.php new file mode 100644 index 0000000..baee099 --- /dev/null +++ b/src/70530246-2903-4c02-af9a-61182e4d97b8/code.php @@ -0,0 +1,754 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify; + + +use VDM\Minify\Exceptions\FileImportException; +use VDM\Minify\Path\Converter; +use VDM\Minify\Path\Interfaces\ConverterInterface; +use VDM\Minify\Abstraction\Minify; + + +/** + * CSS minifier. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie + * @author Tijs Verkoyen + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class Css extends Minify +{ + /** + * @var int maximum inport size in kB + */ + protected $maxImportSize = 5; + + /** + * @var string[] valid import extensions + */ + protected $importExtensions = array( + 'gif' => 'data:image/gif', + 'png' => 'data:image/png', + 'jpe' => 'data:image/jpeg', + 'jpg' => 'data:image/jpeg', + 'jpeg' => 'data:image/jpeg', + 'svg' => 'data:image/svg+xml', + 'woff' => 'data:application/x-font-woff', + 'woff2' => 'data:application/x-font-woff2', + 'avif' => 'data:image/avif', + 'apng' => 'data:image/apng', + 'webp' => 'data:image/webp', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'xbm' => 'image/x-xbitmap', + ); + + /** + * Set the maximum size if files to be imported. + * + * Files larger than this size (in kB) will not be imported into the CSS. + * Importing files into the CSS as data-uri will save you some connections, + * but we should only import relatively small decorative images so that our + * CSS file doesn't get too bulky. + * + * @param int $size Size in kB + */ + public function setMaxImportSize($size) + { + $this->maxImportSize = $size; + } + + /** + * Set the type of extensions to be imported into the CSS (to save network + * connections). + * Keys of the array should be the file extensions & respective values + * should be the data type. + * + * @param string[] $extensions Array of file extensions + */ + public function setImportExtensions(array $extensions) + { + $this->importExtensions = $extensions; + } + + /** + * Move any import statements to the top. + * + * @param string $content Nearly finished CSS content + * + * @return string + */ + protected function moveImportsToTop($content) + { + if (preg_match_all('/(;?)(@import (?url\()?(?P["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { + // remove from content + foreach ($matches[0] as $import) { + $content = str_replace($import, '', $content); + } + + // add to top + $content = implode(';', $matches[2]) . ';' . trim($content, ';'); + } + + return $content; + } + + /** + * Combine CSS from import statements. + * + * Import statements will be loaded and their content merged into the original + * file, to save HTTP requests. + * + * @param string $source The file to combine imports for + * @param string $content The CSS content to combine imports for + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string + * + * @throws FileImportException + */ + protected function combineImports($source, $content, $parents) + { + $importRegexes = array( + // @import url(xxx) + '/ + # import statement + @import + # whitespace + \s+ + # open url() + url\( + # (optional) open path enclosure + (?P["\']?) + # fetch path + (?P.+?) + # (optional) close path enclosure + (?P=quotes) + # close url() + \) + # (optional) trailing whitespace + \s* + # (optional) media statement(s) + (?P[^;]*) + # (optional) trailing whitespace + \s* + # (optional) closing semi-colon + ;? + /ix', + + // @import 'xxx' + '/ + # import statement + @import + # whitespace + \s+ + # open path enclosure + (?P["\']) + # fetch path + (?P.+?) + # close path enclosure + (?P=quotes) + # (optional) trailing whitespace + \s* + # (optional) media statement(s) + (?P[^;]*) + # (optional) trailing whitespace + \s* + # (optional) closing semi-colon + ;? + /ix', + ); + + // find all relative imports in css + $matches = []; + foreach ($importRegexes as $importRegex) { + if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = [...$matches, ...$regexMatches]; + } + } + + $search = []; + $replace = []; + + // loop the matches + foreach ($matches as $match) { + // get the path for the file that will be imported + $importPath = dirname($source) . '/' . $match['path']; + + // only replace the import with the content if we can grab the + // content of the file + if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { + continue; + } + + // check if current file was not imported previously in the same + // import chain. + if (in_array($importPath, $parents)) { + throw new FileImportException('Failed to import file "' . $importPath . '": circular reference detected.'); + } + + // grab referenced file & minify it (which may include importing + // yet other @import statements recursively) + $minifier = new self($importPath); + $minifier->setMaxImportSize($this->maxImportSize); + $minifier->setImportExtensions($this->importExtensions); + $importContent = $minifier->execute($source, $parents); + + // check if this is only valid for certain media + if (!empty($match['media'])) { + $importContent = '@media ' . $match['media'] . '{' . $importContent . '}'; + } + + // add to replacement array + $search[] = $match[0]; + $replace[] = $importContent; + } + + // replace the import statements + return str_replace($search, $replace, $content); + } + + /** + * Import files into the CSS, base64-ized. + * + * @url(image.jpg) images will be loaded and their content merged into the + * original file, to save HTTP requests. + * + * @param string $source The file to import files for + * @param string $content The CSS content to import files for + * + * @return string + */ + protected function importFiles($source, $content) + { + $regex = '/url\((["\']?)(.+?)\\1\)/i'; + if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + $search = []; + $replace = []; + + // loop the matches + foreach ($matches as $match) { + $extension = substr(strrchr($match[2], '.'), 1); + if ($extension && !array_key_exists($extension, $this->importExtensions)) { + continue; + } + + // get the path for the file that will be imported + $path = $match[2]; + $path = dirname($source) . '/' . $path; + + // only replace the import with the content if we're able to get + // the content of the file, and it's relatively small + if ($this->canImportFile($path) && $this->canImportBySize($path)) { + // grab content && base64-ize + $importContent = $this->load($path); + $importContent = base64_encode($importContent); + + // build replacement + $search[] = $match[0]; + $replace[] = 'url(' . $this->importExtensions[$extension] . ';base64,' . $importContent . ')'; + } + } + + // replace the import statements + $content = str_replace($search, $replace, $content); + } + + return $content; + } + + /** + * Minify the data. + * Perform CSS optimizations. + * + * @param string[optional] $path Path to write the data to + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string The minified data + */ + public function execute($path = null, $parents = []) + { + $content = ''; + + // loop CSS data (raw data and files) + foreach ($this->data as $source => $css) { + /* + * Let's first take out strings & comments, since we can't just + * remove whitespace anywhere. If whitespace occurs inside a string, + * we should leave it alone. E.g.: + * p { content: "a test" } + */ + $this->extractStrings(); + $this->stripComments(); + $this->extractMath(); + $this->extractCustomProperties(); + $css = $this->replace($css); + + $css = $this->stripWhitespace($css); + $css = $this->shortenColors($css); + $css = $this->shortenZeroes($css); + $css = $this->shortenFontWeights($css); + $css = $this->stripEmptyTags($css); + + // restore the string we've extracted earlier + $css = $this->restoreExtractedData($css); + + $source = is_int($source) ? '' : $source; + $parents = $source ? array_merge($parents, array($source)) : $parents; + $css = $this->combineImports($source, $css, $parents); + $css = $this->importFiles($source, $css); + + /* + * If we'll save to a new path, we'll have to fix the relative paths + * to be relative no longer to the source file, but to the new path. + * If we don't write to a file, fall back to same path so no + * conversion happens (because we still want it to go through most + * of the move code, which also addresses url() & @import syntax...) + */ + $converter = $this->getPathConverter($source, $path ?: $source); + $css = $this->move($converter, $css); + + // combine css + $content .= $css; + } + + $content = $this->moveImportsToTop($content); + + return $content; + } + + /** + * Moving a css file should update all relative urls. + * Relative references (e.g. ../images/image.gif) in a certain css file, + * will have to be updated when a file is being saved at another location + * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). + * + * @param ConverterInterface $converter Relative path converter + * @param string $content The CSS content to update relative urls for + * + * @return string + */ + protected function move(ConverterInterface $converter, $content) + { + /* + * Relative path references will usually be enclosed by url(). @import + * is an exception, where url() is not necessary around the path (but is + * allowed). + * This *could* be 1 regular expression, where both regular expressions + * in this array are on different sides of a |. But we're using named + * patterns in both regexes, the same name on both regexes. This is only + * possible with a (?J) modifier, but that only works after a fairly + * recent PCRE version. That's why I'm doing 2 separate regular + * expressions & combining the matches after executing of both. + */ + $relativeRegexes = array( + // url(xxx) + '/ + # open url() + url\( + \s* + # open path enclosure + (?P["\'])? + # fetch path + (?P.+?) + # close path enclosure + (?(quotes)(?P=quotes)) + \s* + # close url() + \) + /ix', + + // @import "xxx" + '/ + # import statement + @import + # whitespace + \s+ + # we don\'t have to check for @import url(), because the + # condition above will already catch these + # open path enclosure + (?P["\']) + # fetch path + (?P.+?) + # close path enclosure + (?P=quotes) + /ix', + ); + + // find all relative urls in css + $matches = []; + foreach ($relativeRegexes as $relativeRegex) { + if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = [...$matches, ...$regexMatches]; + } + } + + $search = []; + $replace = []; + + // loop all urls + foreach ($matches as $match) { + // determine if it's a url() or an @import match + $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); + + $url = $match['path']; + if ($this->canImportByPath($url)) { + // attempting to interpret GET-params makes no sense, so let's discard them for awhile + $params = strrchr($url, '?'); + $url = $params ? substr($url, 0, -strlen($params)) : $url; + + // fix relative url + $url = $converter->convert($url); + + // now that the path has been converted, re-apply GET-params + $url .= $params; + } + + /* + * Urls with control characters above 0x7e should be quoted. + * According to Mozilla's parser, whitespace is only allowed at the + * end of unquoted urls. + * Urls with `)` (as could happen with data: uris) should also be + * quoted to avoid being confused for the url() closing parentheses. + * And urls with a # have also been reported to cause issues. + * Urls with quotes inside should also remain escaped. + * + * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation + * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 + * @see https://github.com/matthiasmullie/minify/issues/193 + */ + $url = trim($url); + if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { + $url = $match['quotes'] . $url . $match['quotes']; + } + + // build replacement + $search[] = $match[0]; + if ($type === 'url') { + $replace[] = 'url(' . $url . ')'; + } elseif ($type === 'import') { + $replace[] = '@import "' . $url . '"'; + } + } + + // replace urls + return str_replace($search, $replace, $content); + } + + /** + * Shorthand hex color codes. + * #FF0000 -> #F00. + * + * @param string $content The CSS content to shorten the hex color codes for + * + * @return string + */ + protected function shortenColors($content) + { + $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); + + // remove alpha channel if it's pointless... + $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content); + $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content); + + $colors = array( + // we can shorten some even more by replacing them with their color name + '#F0FFFF' => 'azure', + '#F5F5DC' => 'beige', + '#A52A2A' => 'brown', + '#FF7F50' => 'coral', + '#FFD700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4B0082' => 'indigo', + '#FFFFF0' => 'ivory', + '#F0E68C' => 'khaki', + '#FAF0E6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#808000' => 'olive', + '#CD853F' => 'peru', + '#FFC0CB' => 'pink', + '#DDA0DD' => 'plum', + '#800080' => 'purple', + '#F00' => 'red', + '#FA8072' => 'salmon', + '#A0522D' => 'sienna', + '#C0C0C0' => 'silver', + '#FFFAFA' => 'snow', + '#D2B48C' => 'tan', + '#FF6347' => 'tomato', + '#EE82EE' => 'violet', + '#F5DEB3' => 'wheat', + // or the other way around + 'WHITE' => '#fff', + 'BLACK' => '#000', + ); + + return preg_replace_callback( + '/(?<=[: ])(' . implode('|', array_keys($colors)) . ')(?=[; }])/i', + fn($match) => $colors[strtoupper((string) $match[0])], + $content + ); + } + + /** + * Shorten CSS font weights. + * + * @param string $content The CSS content to shorten the font weights for + * + * @return string + */ + protected function shortenFontWeights($content) + { + $weights = array( + 'normal' => 400, + 'bold' => 700, + ); + + $callback = fn($match) => $match[1] . $weights[$match[2]]; + + return preg_replace_callback('/(font-weight\s*:\s*)(' . implode('|', array_keys($weights)) . ')(?=[;}])/', $callback, $content); + } + + /** + * Shorthand 0 values to plain 0, instead of e.g. -0em. + * + * @param string $content The CSS content to shorten the zero values for + * + * @return string + */ + protected function shortenZeroes($content) + { + // we don't want to strip units in `calc()` expressions: + // `5px - 0px` is valid, but `5px - 0` is not + // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but + // `10 * 0` is invalid + // we've extracted calcs earlier, so we don't need to worry about this + + // reusable bits of code throughout these regexes: + // before & after are used to make sure we don't match lose unintended + // 0-like values (e.g. in #000, or in http://url/1.0) + // units can be stripped from 0 values, or used to recognize non 0 + // values (where wa may be able to strip a .0 suffix) + $before = '(?<=[:(, ])'; + $after = '(?=[ ,);}])'; + $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; + + // strip units after zeroes (0px -> 0) + // NOTE: it should be safe to remove all units for a 0 value, but in + // practice, Webkit (especially Safari) seems to stumble over at least + // 0%, potentially other units as well. Only stripping 'px' for now. + // @see https://github.com/matthiasmullie/minify/issues/60 + $content = preg_replace('/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content); + + // strip 0-digits (.0 -> 0) + $content = preg_replace('/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content); + // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px + $content = preg_replace('/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content); + // strip trailing 0: 50.00 -> 50, 50.00px -> 50px + $content = preg_replace('/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content); + // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 + $content = preg_replace('/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content); + + // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) + $content = preg_replace('/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content); + + // IE doesn't seem to understand a unitless flex-basis value (correct - + // it goes against the spec), so let's add it in again (make it `%`, + // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) + // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex + $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content); + $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content); + + return $content; + } + + /** + * Strip empty tags from source code. + * + * @param string $content + * + * @return string + */ + protected function stripEmptyTags($content) + { + $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); + $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '/*' . $count . '*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); + + $this->registerPattern('/\/\*.*?\*\//s', ''); + } + + /** + * Strip whitespace. + * + * @param string $content The CSS content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // remove leading & trailing whitespace + $content = preg_replace('/^\s*/m', '', $content); + $content = preg_replace('/\s*$/m', '', $content); + + // replace newlines with a single space + $content = preg_replace('/\s+/', ' ', $content); + + // remove whitespace around meta characters + // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex + $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); + $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); + $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); + $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); + + // whitespace around + and - can only be stripped inside some pseudo- + // classes, like `:nth-child(3+2n)` + // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or + // selectors like `div.weird- p` + $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); + $content = preg_replace('/:(' . implode('|', $pseudos) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); + + // remove semicolon/whitespace followed by closing bracket + $content = str_replace(';}', '}', $content); + + return trim($content); + } + + /** + * Replace all occurrences of functions that may contain math, where + * whitespace around operators needs to be preserved (e.g. calc, clamp). + */ + protected function extractMath() + { + $functions = array('calc', 'clamp', 'min', 'max'); + $pattern = '/\b(' . implode('|', $functions) . ')(\(.+?)(?=$|;|})/m'; + + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $pattern, &$callback) { + $function = $match[1]; + $length = strlen((string) $match[2]); + $expr = ''; + $opened = 0; + + // the regular expression for extracting math has 1 significant problem: + // it can't determine the correct closing parenthesis... + // instead, it'll match a larger portion of code to where it's certain that + // the calc() musts have ended, and we'll figure out which is the correct + // closing parenthesis here, by counting how many have opened + for ($i = 0; $i < $length; ++$i) { + $char = $match[2][$i]; + $expr .= $char; + if ($char === '(') { + ++$opened; + } elseif ($char === ')' && --$opened === 0) { + break; + } + } + + // now that we've figured out where the calc() starts and ends, extract it + $count = count($minifier->extracted); + $placeholder = 'math(' . $count . ')'; + $minifier->extracted[$placeholder] = $function . '(' . trim(substr($expr, 1, -1)) . ')'; + + // and since we've captured more code than required, we may have some leftover + // calc() in here too - go recursive on the remaining but of code to go figure + // that out and extract what is needed + $rest = $minifier->str_replace_first($function . $expr, '', $match[0]); + $rest = preg_replace_callback($pattern, $callback, $rest); + + return $placeholder . $rest; + }; + + $this->registerPattern($pattern, $callback); + } + + /** + * Replace custom properties, whose values may be used in scenarios where + * we wouldn't want them to be minified (e.g. inside calc). + */ + protected function extractCustomProperties() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $this->registerPattern( + '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', + function ($match) use ($minifier) { + $placeholder = '--custom-' . count($minifier->extracted) . ':0'; + $minifier->extracted[$placeholder] = $match[1] . ':' . trim((string) $match[2]); + + return $placeholder; + } + ); + } + + /** + * Check if file is small enough to be imported. + * + * @param string $path The path to the file + * + * @return bool + */ + protected function canImportBySize($path) + { + return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; + } + + /** + * Check if file a file can be imported, going by the path. + * + * @param string $path + * + * @return bool + */ + protected function canImportByPath($path) + { + return preg_match('/^(data:|https?:|\\/)/', $path) === 0; + } + + /** + * Return a converter to update relative paths to be relative to the new + * destination. + * + * @param string $source + * @param string $target + * + * @return ConverterInterface + */ + protected function getPathConverter($source, $target) + { + return new Converter($source, $target); + } +} + diff --git a/src/70530246-2903-4c02-af9a-61182e4d97b8/code.power b/src/70530246-2903-4c02-af9a-61182e4d97b8/code.power new file mode 100644 index 0000000..d8643bc --- /dev/null +++ b/src/70530246-2903-4c02-af9a-61182e4d97b8/code.power @@ -0,0 +1,721 @@ + /** + * @var int maximum inport size in kB + */ + protected $maxImportSize = 5; + + /** + * @var string[] valid import extensions + */ + protected $importExtensions = array( + 'gif' => 'data:image/gif', + 'png' => 'data:image/png', + 'jpe' => 'data:image/jpeg', + 'jpg' => 'data:image/jpeg', + 'jpeg' => 'data:image/jpeg', + 'svg' => 'data:image/svg+xml', + 'woff' => 'data:application/x-font-woff', + 'woff2' => 'data:application/x-font-woff2', + 'avif' => 'data:image/avif', + 'apng' => 'data:image/apng', + 'webp' => 'data:image/webp', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'xbm' => 'image/x-xbitmap', + ); + + /** + * Set the maximum size if files to be imported. + * + * Files larger than this size (in kB) will not be imported into the CSS. + * Importing files into the CSS as data-uri will save you some connections, + * but we should only import relatively small decorative images so that our + * CSS file doesn't get too bulky. + * + * @param int $size Size in kB + */ + public function setMaxImportSize($size) + { + $this->maxImportSize = $size; + } + + /** + * Set the type of extensions to be imported into the CSS (to save network + * connections). + * Keys of the array should be the file extensions & respective values + * should be the data type. + * + * @param string[] $extensions Array of file extensions + */ + public function setImportExtensions(array $extensions) + { + $this->importExtensions = $extensions; + } + + /** + * Move any import statements to the top. + * + * @param string $content Nearly finished CSS content + * + * @return string + */ + protected function moveImportsToTop($content) + { + if (preg_match_all('/(;?)(@import (?url\()?(?P["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { + // remove from content + foreach ($matches[0] as $import) { + $content = str_replace($import, '', $content); + } + + // add to top + $content = implode(';', $matches[2]) . ';' . trim($content, ';'); + } + + return $content; + } + + /** + * Combine CSS from import statements. + * + * Import statements will be loaded and their content merged into the original + * file, to save HTTP requests. + * + * @param string $source The file to combine imports for + * @param string $content The CSS content to combine imports for + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string + * + * @throws FileImportException + */ + protected function combineImports($source, $content, $parents) + { + $importRegexes = array( + // @import url(xxx) + '/ + # import statement + @import + # whitespace + \s+ + # open url() + url\( + # (optional) open path enclosure + (?P["\']?) + # fetch path + (?P.+?) + # (optional) close path enclosure + (?P=quotes) + # close url() + \) + # (optional) trailing whitespace + \s* + # (optional) media statement(s) + (?P[^;]*) + # (optional) trailing whitespace + \s* + # (optional) closing semi-colon + ;? + /ix', + + // @import 'xxx' + '/ + # import statement + @import + # whitespace + \s+ + # open path enclosure + (?P["\']) + # fetch path + (?P.+?) + # close path enclosure + (?P=quotes) + # (optional) trailing whitespace + \s* + # (optional) media statement(s) + (?P[^;]*) + # (optional) trailing whitespace + \s* + # (optional) closing semi-colon + ;? + /ix', + ); + + // find all relative imports in css + $matches = []; + foreach ($importRegexes as $importRegex) { + if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = [...$matches, ...$regexMatches]; + } + } + + $search = []; + $replace = []; + + // loop the matches + foreach ($matches as $match) { + // get the path for the file that will be imported + $importPath = dirname($source) . '/' . $match['path']; + + // only replace the import with the content if we can grab the + // content of the file + if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { + continue; + } + + // check if current file was not imported previously in the same + // import chain. + if (in_array($importPath, $parents)) { + throw new FileImportException('Failed to import file "' . $importPath . '": circular reference detected.'); + } + + // grab referenced file & minify it (which may include importing + // yet other @import statements recursively) + $minifier = new self($importPath); + $minifier->setMaxImportSize($this->maxImportSize); + $minifier->setImportExtensions($this->importExtensions); + $importContent = $minifier->execute($source, $parents); + + // check if this is only valid for certain media + if (!empty($match['media'])) { + $importContent = '@media ' . $match['media'] . '{' . $importContent . '}'; + } + + // add to replacement array + $search[] = $match[0]; + $replace[] = $importContent; + } + + // replace the import statements + return str_replace($search, $replace, $content); + } + + /** + * Import files into the CSS, base64-ized. + * + * @url(image.jpg) images will be loaded and their content merged into the + * original file, to save HTTP requests. + * + * @param string $source The file to import files for + * @param string $content The CSS content to import files for + * + * @return string + */ + protected function importFiles($source, $content) + { + $regex = '/url\((["\']?)(.+?)\\1\)/i'; + if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + $search = []; + $replace = []; + + // loop the matches + foreach ($matches as $match) { + $extension = substr(strrchr($match[2], '.'), 1); + if ($extension && !array_key_exists($extension, $this->importExtensions)) { + continue; + } + + // get the path for the file that will be imported + $path = $match[2]; + $path = dirname($source) . '/' . $path; + + // only replace the import with the content if we're able to get + // the content of the file, and it's relatively small + if ($this->canImportFile($path) && $this->canImportBySize($path)) { + // grab content && base64-ize + $importContent = $this->load($path); + $importContent = base64_encode($importContent); + + // build replacement + $search[] = $match[0]; + $replace[] = 'url(' . $this->importExtensions[$extension] . ';base64,' . $importContent . ')'; + } + } + + // replace the import statements + $content = str_replace($search, $replace, $content); + } + + return $content; + } + + /** + * Minify the data. + * Perform CSS optimizations. + * + * @param string[optional] $path Path to write the data to + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string The minified data + */ + public function execute($path = null, $parents = []) + { + $content = ''; + + // loop CSS data (raw data and files) + foreach ($this->data as $source => $css) { + /* + * Let's first take out strings & comments, since we can't just + * remove whitespace anywhere. If whitespace occurs inside a string, + * we should leave it alone. E.g.: + * p { content: "a test" } + */ + $this->extractStrings(); + $this->stripComments(); + $this->extractMath(); + $this->extractCustomProperties(); + $css = $this->replace($css); + + $css = $this->stripWhitespace($css); + $css = $this->shortenColors($css); + $css = $this->shortenZeroes($css); + $css = $this->shortenFontWeights($css); + $css = $this->stripEmptyTags($css); + + // restore the string we've extracted earlier + $css = $this->restoreExtractedData($css); + + $source = is_int($source) ? '' : $source; + $parents = $source ? array_merge($parents, array($source)) : $parents; + $css = $this->combineImports($source, $css, $parents); + $css = $this->importFiles($source, $css); + + /* + * If we'll save to a new path, we'll have to fix the relative paths + * to be relative no longer to the source file, but to the new path. + * If we don't write to a file, fall back to same path so no + * conversion happens (because we still want it to go through most + * of the move code, which also addresses url() & @import syntax...) + */ + $converter = $this->getPathConverter($source, $path ?: $source); + $css = $this->move($converter, $css); + + // combine css + $content .= $css; + } + + $content = $this->moveImportsToTop($content); + + return $content; + } + + /** + * Moving a css file should update all relative urls. + * Relative references (e.g. ../images/image.gif) in a certain css file, + * will have to be updated when a file is being saved at another location + * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). + * + * @param ConverterInterface $converter Relative path converter + * @param string $content The CSS content to update relative urls for + * + * @return string + */ + protected function move(ConverterInterface $converter, $content) + { + /* + * Relative path references will usually be enclosed by url(). @import + * is an exception, where url() is not necessary around the path (but is + * allowed). + * This *could* be 1 regular expression, where both regular expressions + * in this array are on different sides of a |. But we're using named + * patterns in both regexes, the same name on both regexes. This is only + * possible with a (?J) modifier, but that only works after a fairly + * recent PCRE version. That's why I'm doing 2 separate regular + * expressions & combining the matches after executing of both. + */ + $relativeRegexes = array( + // url(xxx) + '/ + # open url() + url\( + \s* + # open path enclosure + (?P["\'])? + # fetch path + (?P.+?) + # close path enclosure + (?(quotes)(?P=quotes)) + \s* + # close url() + \) + /ix', + + // @import "xxx" + '/ + # import statement + @import + # whitespace + \s+ + # we don\'t have to check for @import url(), because the + # condition above will already catch these + # open path enclosure + (?P["\']) + # fetch path + (?P.+?) + # close path enclosure + (?P=quotes) + /ix', + ); + + // find all relative urls in css + $matches = []; + foreach ($relativeRegexes as $relativeRegex) { + if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = [...$matches, ...$regexMatches]; + } + } + + $search = []; + $replace = []; + + // loop all urls + foreach ($matches as $match) { + // determine if it's a url() or an @import match + $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); + + $url = $match['path']; + if ($this->canImportByPath($url)) { + // attempting to interpret GET-params makes no sense, so let's discard them for awhile + $params = strrchr($url, '?'); + $url = $params ? substr($url, 0, -strlen($params)) : $url; + + // fix relative url + $url = $converter->convert($url); + + // now that the path has been converted, re-apply GET-params + $url .= $params; + } + + /* + * Urls with control characters above 0x7e should be quoted. + * According to Mozilla's parser, whitespace is only allowed at the + * end of unquoted urls. + * Urls with `)` (as could happen with data: uris) should also be + * quoted to avoid being confused for the url() closing parentheses. + * And urls with a # have also been reported to cause issues. + * Urls with quotes inside should also remain escaped. + * + * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation + * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 + * @see https://github.com/matthiasmullie/minify/issues/193 + */ + $url = trim($url); + if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { + $url = $match['quotes'] . $url . $match['quotes']; + } + + // build replacement + $search[] = $match[0]; + if ($type === 'url') { + $replace[] = 'url(' . $url . ')'; + } elseif ($type === 'import') { + $replace[] = '@import "' . $url . '"'; + } + } + + // replace urls + return str_replace($search, $replace, $content); + } + + /** + * Shorthand hex color codes. + * #FF0000 -> #F00. + * + * @param string $content The CSS content to shorten the hex color codes for + * + * @return string + */ + protected function shortenColors($content) + { + $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); + + // remove alpha channel if it's pointless... + $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content); + $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content); + + $colors = array( + // we can shorten some even more by replacing them with their color name + '#F0FFFF' => 'azure', + '#F5F5DC' => 'beige', + '#A52A2A' => 'brown', + '#FF7F50' => 'coral', + '#FFD700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4B0082' => 'indigo', + '#FFFFF0' => 'ivory', + '#F0E68C' => 'khaki', + '#FAF0E6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#808000' => 'olive', + '#CD853F' => 'peru', + '#FFC0CB' => 'pink', + '#DDA0DD' => 'plum', + '#800080' => 'purple', + '#F00' => 'red', + '#FA8072' => 'salmon', + '#A0522D' => 'sienna', + '#C0C0C0' => 'silver', + '#FFFAFA' => 'snow', + '#D2B48C' => 'tan', + '#FF6347' => 'tomato', + '#EE82EE' => 'violet', + '#F5DEB3' => 'wheat', + // or the other way around + 'WHITE' => '#fff', + 'BLACK' => '#000', + ); + + return preg_replace_callback( + '/(?<=[: ])(' . implode('|', array_keys($colors)) . ')(?=[; }])/i', + fn($match) => $colors[strtoupper((string) $match[0])], + $content + ); + } + + /** + * Shorten CSS font weights. + * + * @param string $content The CSS content to shorten the font weights for + * + * @return string + */ + protected function shortenFontWeights($content) + { + $weights = array( + 'normal' => 400, + 'bold' => 700, + ); + + $callback = fn($match) => $match[1] . $weights[$match[2]]; + + return preg_replace_callback('/(font-weight\s*:\s*)(' . implode('|', array_keys($weights)) . ')(?=[;}])/', $callback, $content); + } + + /** + * Shorthand 0 values to plain 0, instead of e.g. -0em. + * + * @param string $content The CSS content to shorten the zero values for + * + * @return string + */ + protected function shortenZeroes($content) + { + // we don't want to strip units in `calc()` expressions: + // `5px - 0px` is valid, but `5px - 0` is not + // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but + // `10 * 0` is invalid + // we've extracted calcs earlier, so we don't need to worry about this + + // reusable bits of code throughout these regexes: + // before & after are used to make sure we don't match lose unintended + // 0-like values (e.g. in #000, or in http://url/1.0) + // units can be stripped from 0 values, or used to recognize non 0 + // values (where wa may be able to strip a .0 suffix) + $before = '(?<=[:(, ])'; + $after = '(?=[ ,);}])'; + $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; + + // strip units after zeroes (0px -> 0) + // NOTE: it should be safe to remove all units for a 0 value, but in + // practice, Webkit (especially Safari) seems to stumble over at least + // 0%, potentially other units as well. Only stripping 'px' for now. + // @see https://github.com/matthiasmullie/minify/issues/60 + $content = preg_replace('/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content); + + // strip 0-digits (.0 -> 0) + $content = preg_replace('/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content); + // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px + $content = preg_replace('/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content); + // strip trailing 0: 50.00 -> 50, 50.00px -> 50px + $content = preg_replace('/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content); + // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 + $content = preg_replace('/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content); + + // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) + $content = preg_replace('/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content); + + // IE doesn't seem to understand a unitless flex-basis value (correct - + // it goes against the spec), so let's add it in again (make it `%`, + // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) + // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex + $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content); + $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content); + + return $content; + } + + /** + * Strip empty tags from source code. + * + * @param string $content + * + * @return string + */ + protected function stripEmptyTags($content) + { + $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); + $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '/*' . $count . '*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); + + $this->registerPattern('/\/\*.*?\*\//s', ''); + } + + /** + * Strip whitespace. + * + * @param string $content The CSS content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // remove leading & trailing whitespace + $content = preg_replace('/^\s*/m', '', $content); + $content = preg_replace('/\s*$/m', '', $content); + + // replace newlines with a single space + $content = preg_replace('/\s+/', ' ', $content); + + // remove whitespace around meta characters + // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex + $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); + $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); + $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); + $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); + + // whitespace around + and - can only be stripped inside some pseudo- + // classes, like `:nth-child(3+2n)` + // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or + // selectors like `div.weird- p` + $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); + $content = preg_replace('/:(' . implode('|', $pseudos) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); + + // remove semicolon/whitespace followed by closing bracket + $content = str_replace(';}', '}', $content); + + return trim($content); + } + + /** + * Replace all occurrences of functions that may contain math, where + * whitespace around operators needs to be preserved (e.g. calc, clamp). + */ + protected function extractMath() + { + $functions = array('calc', 'clamp', 'min', 'max'); + $pattern = '/\b(' . implode('|', $functions) . ')(\(.+?)(?=$|;|})/m'; + + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $pattern, &$callback) { + $function = $match[1]; + $length = strlen((string) $match[2]); + $expr = ''; + $opened = 0; + + // the regular expression for extracting math has 1 significant problem: + // it can't determine the correct closing parenthesis... + // instead, it'll match a larger portion of code to where it's certain that + // the calc() musts have ended, and we'll figure out which is the correct + // closing parenthesis here, by counting how many have opened + for ($i = 0; $i < $length; ++$i) { + $char = $match[2][$i]; + $expr .= $char; + if ($char === '(') { + ++$opened; + } elseif ($char === ')' && --$opened === 0) { + break; + } + } + + // now that we've figured out where the calc() starts and ends, extract it + $count = count($minifier->extracted); + $placeholder = 'math(' . $count . ')'; + $minifier->extracted[$placeholder] = $function . '(' . trim(substr($expr, 1, -1)) . ')'; + + // and since we've captured more code than required, we may have some leftover + // calc() in here too - go recursive on the remaining but of code to go figure + // that out and extract what is needed + $rest = $minifier->str_replace_first($function . $expr, '', $match[0]); + $rest = preg_replace_callback($pattern, $callback, $rest); + + return $placeholder . $rest; + }; + + $this->registerPattern($pattern, $callback); + } + + /** + * Replace custom properties, whose values may be used in scenarios where + * we wouldn't want them to be minified (e.g. inside calc). + */ + protected function extractCustomProperties() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $this->registerPattern( + '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', + function ($match) use ($minifier) { + $placeholder = '--custom-' . count($minifier->extracted) . ':0'; + $minifier->extracted[$placeholder] = $match[1] . ':' . trim((string) $match[2]); + + return $placeholder; + } + ); + } + + /** + * Check if file is small enough to be imported. + * + * @param string $path The path to the file + * + * @return bool + */ + protected function canImportBySize($path) + { + return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; + } + + /** + * Check if file a file can be imported, going by the path. + * + * @param string $path + * + * @return bool + */ + protected function canImportByPath($path) + { + return preg_match('/^(data:|https?:|\\/)/', $path) === 0; + } + + /** + * Return a converter to update relative paths to be relative to the new + * destination. + * + * @param string $source + * @param string $target + * + * @return ConverterInterface + */ + protected function getPathConverter($source, $target) + { + return new Converter($source, $target); + } \ No newline at end of file diff --git a/src/70530246-2903-4c02-af9a-61182e4d97b8/settings.json b/src/70530246-2903-4c02-af9a-61182e4d97b8/settings.json new file mode 100644 index 0000000..eaf6c05 --- /dev/null +++ b/src/70530246-2903-4c02-af9a-61182e4d97b8/settings.json @@ -0,0 +1,31 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "d2b8599a-d5ce-49b1-b992-764bb7d55137", + "guid": "70530246-2903-4c02-af9a-61182e4d97b8", + "implements": null, + "load_selection": null, + "name": "Css", + "power_version": "1.3.70", + "system_name": "Minify.Css", + "type": "class", + "use_selection": { + "use_selection0": { + "use": "b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b", + "as": "default" + }, + "use_selection1": { + "use": "39f8957f-cc73-4da5-8b60-6cf25d4ab058", + "as": "default" + }, + "use_selection2": { + "use": "cc482882-5eef-476c-b835-7b0c5a17c0db", + "as": "default" + } + }, + "namespace": "VDM\\Minify\\Css", + "description": "\/**\r\n * CSS minifier.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @author Tijs Verkoyen \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "licensing_template": "\/**\r\n * CSS Minifier.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/README.md b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/README.md new file mode 100644 index 0000000..8e5d4c1 --- /dev/null +++ b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/README.md @@ -0,0 +1,21 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +> Error adding class diagram + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.php b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.php new file mode 100644 index 0000000..73be9bb --- /dev/null +++ b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify\Exceptions; + + +use VDM\Minify\Abstraction\BasicException; + + +/** + * File Import Exception Class. + * + * @author Matthias Mullie + */ +class FileImportException extends BasicException +{ +} + diff --git a/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.power b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.power new file mode 100644 index 0000000..69309b0 --- /dev/null +++ b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/code.power @@ -0,0 +1 @@ +###CODEPOWER### \ No newline at end of file diff --git a/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/settings.json b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/settings.json new file mode 100644 index 0000000..33ecbda --- /dev/null +++ b/src/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b/settings.json @@ -0,0 +1,18 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "47448ada-54df-45f3-b05d-1c66c584b1bd", + "guid": "b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b", + "implements": null, + "load_selection": null, + "name": "FileImportException", + "power_version": "1.0.0", + "system_name": "Minify.Exceptions.FileImportException", + "type": "class", + "use_selection": null, + "namespace": "VDM\\Minify\\Exceptions.FileImportException", + "description": "\/**\r\n * File Import Exception Class.\r\n *\r\n * @author Matthias Mullie \r\n *\/", + "licensing_template": "\/**\r\n * File Import Exception.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/b591855d-d106-4fe5-90ad-8e706f6267cf/README.md b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/README.md new file mode 100644 index 0000000..d343a1d --- /dev/null +++ b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/README.md @@ -0,0 +1,45 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# abstract class Minify (Details) +> namespace: **VDM\Joomla\Componentbuilder\Compiler\Utilities** +```uml +@startuml +abstract Minify #Orange { + + {static} js(string $data) : string + + {static} css(string $data) : string +} + +note right of Minify::js + Minify JavaScript + + since: 3.2.0 + return: string +end note + +note right of Minify::css + Minify Css + + since: 3.2.0 + return: string +end note + +@enduml +``` + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.php b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.php new file mode 100644 index 0000000..b6b4daa --- /dev/null +++ b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.php @@ -0,0 +1,91 @@ + + * @git Joomla Component Builder + * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace VDM\Joomla\Componentbuilder\Compiler\Utilities; + + +use VDM\Minify\Css; +use VDM\Minify\JavaScript; + + +/** + * Compiler Minifier + * + * @since 3.2.0 + */ +abstract class Minify +{ + /** + * Minify JavaScript Class + * + * @var JavaScript + * @since 3.2.0 + */ + public static JavaScript $js; + + /** + * Minify Css Class + * + * @var Css + * @since 3.2.0 + */ + public static Css $css; + + /** + * Minify JavaScript + * + * @param string $data + * + * @return string + * @since 3.2.0 + */ + public static function js(string $data): string + { + // check if instance already set + if (empty(self::$js)) + { + // set instanceof on JavaScript + self::$js = new JavaScript; + } + + // add the data + self::$js->add($data); + + // return minified + return self::$js->minify(); + } + + /** + * Minify Css + * + * @param string $data + * + * @return string + * @since 3.2.0 + */ + public static function css(string $data): string + { + // check if instance already set + if (empty(self::$css)) + { + // set instanceof on Css + self::$css = new Css; + } + + // add the data + self::$css->add($data); + + // return minified + return self::$css->minify(); + } + +} + diff --git a/src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.power b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.power new file mode 100644 index 0000000..6521ad3 --- /dev/null +++ b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/code.power @@ -0,0 +1,63 @@ + /** + * Minify JavaScript Class + * + * @var JavaScript + * @since 3.2.0 + */ + public static JavaScript $js; + + /** + * Minify Css Class + * + * @var Css + * @since 3.2.0 + */ + public static Css $css; + + /** + * Minify JavaScript + * + * @param string $data + * + * @return string + * @since 3.2.0 + */ + public static function js(string $data): string + { + // check if instance already set + if (empty(self::$js)) + { + // set instanceof on JavaScript + self::$js = new JavaScript; + } + + // add the data + self::$js->add($data); + + // return minified + return self::$js->minify(); + } + + /** + * Minify Css + * + * @param string $data + * + * @return string + * @since 3.2.0 + */ + public static function css(string $data): string + { + // check if instance already set + if (empty(self::$css)) + { + // set instanceof on Css + self::$css = new Css; + } + + // add the data + self::$css->add($data); + + // return minified + return self::$css->minify(); + } diff --git a/src/b591855d-d106-4fe5-90ad-8e706f6267cf/settings.json b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/settings.json new file mode 100644 index 0000000..5917b4d --- /dev/null +++ b/src/b591855d-d106-4fe5-90ad-8e706f6267cf/settings.json @@ -0,0 +1,27 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "0", + "guid": "b591855d-d106-4fe5-90ad-8e706f6267cf", + "implements": null, + "load_selection": null, + "name": "Minify", + "power_version": "1.0.0", + "system_name": "JCB.Compiler.Utilities.Minify", + "type": "abstract class", + "use_selection": { + "use_selection0": { + "use": "70530246-2903-4c02-af9a-61182e4d97b8", + "as": "default" + }, + "use_selection1": { + "use": "ef6438c4-76db-4aa1-bb15-bc97583bcd2e", + "as": "default" + } + }, + "namespace": "VDM\\Joomla\\Componentbuilder.Compiler.Utilities.Minify", + "description": "Compiler Minifier\r\n\r\n@since 3.2.0", + "licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 4th September, 2022\r\n * @author Llewellyn van der Merwe \r\n * @git Joomla Component Builder \r\n * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.\r\n * @license GNU General Public License version 2 or later; see LICENSE.txt\r\n *\/\r\n", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/cc482882-5eef-476c-b835-7b0c5a17c0db/README.md b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/README.md new file mode 100644 index 0000000..8101ab7 --- /dev/null +++ b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/README.md @@ -0,0 +1,36 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# interface ConverterInterface (Details) +> namespace: **VDM\Minify\Path\Interfaces** +```uml +@startuml +interface ConverterInterface #Lavender { + + convert(string $path) : string +} + +note right of ConverterInterface::convert + Convert file paths. + + return: string +end note + +@enduml +``` + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.php b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.php new file mode 100644 index 0000000..e64774d --- /dev/null +++ b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify\Path\Interfaces; + +interface ConverterInterface +{ + /** + * Convert file paths. + * + * @param string $path The path to be converted + * + * @return string The new path + */ + public function convert($path); +} + diff --git a/src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.power b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.power new file mode 100644 index 0000000..45f3490 --- /dev/null +++ b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/code.power @@ -0,0 +1,8 @@ + /** + * Convert file paths. + * + * @param string $path The path to be converted + * + * @return string The new path + */ + public function convert($path); \ No newline at end of file diff --git a/src/cc482882-5eef-476c-b835-7b0c5a17c0db/settings.json b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/settings.json new file mode 100644 index 0000000..5346e85 --- /dev/null +++ b/src/cc482882-5eef-476c-b835-7b0c5a17c0db/settings.json @@ -0,0 +1,18 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "0", + "guid": "cc482882-5eef-476c-b835-7b0c5a17c0db", + "implements": null, + "load_selection": null, + "name": "ConverterInterface", + "power_version": "1.1.3", + "system_name": "Path.Interfaces.ConverterInterface", + "type": "interface", + "use_selection": null, + "namespace": "VDM\\Minify\\Path.Interfaces.ConverterInterface", + "description": "", + "licensing_template": "\/**\r\n * Convert file paths.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/path-converter\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/README.md b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/README.md new file mode 100644 index 0000000..5adb74e --- /dev/null +++ b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/README.md @@ -0,0 +1,177 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# abstract class Minify (Details) +> namespace: **VDM\Minify\Abstraction** +```uml +@startuml +abstract Minify #Orange { + # $data + # $patterns + + $extracted + + __construct($data = null) + + add(string|string[] $data = null) : static + + addFile(string|string[] $data = null) : static + + minify(string[optional] $path = null) : string + + gzip(string[optional] $path = null, int[optional] $level = 9) : string + + cache(CacheItemInterface $item) : CacheItemInterface + + {abstract} execute(string[optional] $path = null) : string + # load(string $data) : string + # save(string $content, string $path) + # registerPattern(string $pattern, string|callable $replacement = '') + # replace(string $content) : string + # executeReplacement(string|callable $replacement, array $match) : string + # extractStrings(string[optional] $chars = '\'"', string[optional] $placeholderPrefix = '') + # restoreExtractedData(string $content) : string + # canImportFile(string $path) : bool + # openFileForWriting(string $path) : resource + # writeToFile(resource $handler, string $content, ...) + # {static} str_replace_first($search, $replace, ...) +} + +note right of Minify::__construct + Init the minify class - optionally, code may be passed along already. + +end note + +note left of Minify::add + Add a file or straight-up code to be minified. + + return: static +end note + +note right of Minify::addFile + Add a file to be minified. + + return: static +end note + +note left of Minify::minify + Minify the data & (optionally) saves it to a file. + + return: string +end note + +note right of Minify::gzip + Minify & gzip the data & (optionally) saves it to a file. + + return: string +end note + +note left of Minify::cache + Minify the data & write it to a CacheItemInterface object. + + return: CacheItemInterface +end note + +note right of Minify::execute + Minify the data. + + return: string +end note + +note left of Minify::load + Load data. + + return: string +end note + +note right of Minify::save + Save to file. + +end note + +note left of Minify::registerPattern + Register a pattern to execute against the source content. +If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work. +If you need that functionality, use a callback instead. + +end note + +note right of Minify::replace + We can't "just" run some regular expressions against JavaScript: it's a +complex language. E.g. having an occurrence of // xyz would be a comment, +unless it's used within a string. Of you could have something that looks +like a 'string', but inside a comment. +The only way to accurately replace these pieces is to traverse the JS one +character at a time and try to find whatever starts first. + + return: string +end note + +note left of Minify::executeReplacement + If $replacement is a callback, execute it, passing in the match data. +If it's a string, just pass it through. + + return: string +end note + +note right of Minify::extractStrings + Strings are a pattern we need to match, in order to ignore potential +code-like content inside them, but we just want all of the string +content to remain untouched. +This method will replace all string content with simple STRING# +placeholder text, so we've rid all strings from characters that may be +misinterpreted. Original string content will be saved in $this->extracted +and after doing all other minifying, we can restore the original content +via restoreStrings(). + +end note + +note left of Minify::restoreExtractedData + This method will restore all extracted data (strings, regexes) that were +replaced with placeholder text in extract*(). The original content was +saved in $this->extracted. + + return: string +end note + +note right of Minify::canImportFile + Check if the path is a regular file and can be read. + + return: bool +end note + +note left of Minify::openFileForWriting + Attempts to open file specified by $path for writing. + + return: resource +end note + +note right of Minify::writeToFile + Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. + + + arguments: + resource $handler + string $content + string $path = '' +end note + +note left of Minify::str_replace_first + + arguments: + $search + $replace + $subject +end note + +@enduml +``` + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.php b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.php new file mode 100644 index 0000000..23a9a45 --- /dev/null +++ b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.php @@ -0,0 +1,513 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ + +namespace VDM\Minify\Abstraction; + + +use VDM\Minify\Exceptions\IOException; +use VDM\Psr\Cache\CacheItemInterface; + + +/** + * Abstract minifier class. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +abstract class Minify +{ + /** + * The data to be minified. + * + * @var string[] + */ + protected $data = []; + + /** + * Array of patterns to match. + * + * @var string[] + */ + protected $patterns = []; + + /** + * This array will hold content of strings and regular expressions that have + * been extracted from the JS source code, so we can reliably match "code", + * without having to worry about potential "code-like" characters inside. + * + * @internal + * + * @var string[] + */ + public $extracted = []; + + /** + * Init the minify class - optionally, code may be passed along already. + */ + public function __construct(/* $data = null, ... */) + { + // it's possible to add the source through the constructor as well ;) + if (func_num_args()) { + call_user_func_array(array($this, 'add'), func_get_args()); + } + } + + /** + * Add a file or straight-up code to be minified. + * + * @param string|string[] $data + * + * @return static + */ + public function add($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $data) { + if (is_array($data)) { + call_user_func_array(array($this, 'add'), $data); + continue; + } + + // redefine var + $data = (string) $data; + + // load data + $value = $this->load($data); + $key = ($data != $value) ? $data : count($this->data); + + // replace CR linefeeds etc. + // @see https://github.com/matthiasmullie/minify/pull/139 + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // store data + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Add a file to be minified. + * + * @param string|string[] $data + * + * @return static + * + * @throws IOException + */ + public function addFile($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $path) { + if (is_array($path)) { + call_user_func_array(array($this, 'addFile'), $path); + continue; + } + + // redefine var + $path = (string) $path; + + // check if we can read the file + if (!$this->canImportFile($path)) { + throw new IOException('The file "' . $path . '" could not be opened for reading. Check if PHP has enough permissions.'); + } + + $this->add($path); + } + + return $this; + } + + /** + * Minify the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function minify($path = null) + { + $content = $this->execute($path); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify & gzip the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * @param int[optional] $level Compression level, from 0 to 9 + * + * @return string The minified & gzipped data + */ + public function gzip($path = null, $level = 9) + { + $content = $this->execute($path); + $content = gzencode($content, $level, FORCE_GZIP); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify the data & write it to a CacheItemInterface object. + * + * @param CacheItemInterface $item Cache item to write the data to + * + * @return CacheItemInterface Cache item with the minifier data + */ + public function cache(CacheItemInterface $item) + { + $content = $this->execute(); + $item->set($content); + + return $item; + } + + /** + * Minify the data. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + abstract public function execute($path = null); + + /** + * Load data. + * + * @param string $data Either a path to a file or the content itself + * + * @return string + */ + protected function load($data) + { + // check if the data is a file + if ($this->canImportFile($data)) { + $data = file_get_contents($data); + + // strip BOM, if any + if (substr($data, 0, 3) == "\xef\xbb\xbf") { + $data = substr($data, 3); + } + } + + return $data; + } + + /** + * Save to file. + * + * @param string $content The minified data + * @param string $path The path to save the minified data to + * + * @throws IOException + */ + protected function save($content, $path) + { + $handler = $this->openFileForWriting($path); + + $this->writeToFile($handler, $content); + + @fclose($handler); + } + + /** + * Register a pattern to execute against the source content. + * + * If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work. + * If you need that functionality, use a callback instead. + * + * @param string $pattern PCRE pattern + * @param string|callable $replacement Replacement value for matched pattern + */ + protected function registerPattern($pattern, $replacement = '') + { + // study the pattern, we'll execute it more than once + $pattern .= 'S'; + + $this->patterns[] = array($pattern, $replacement); + } + + /** + * We can't "just" run some regular expressions against JavaScript: it's a + * complex language. E.g. having an occurrence of // xyz would be a comment, + * unless it's used within a string. Of you could have something that looks + * like a 'string', but inside a comment. + * The only way to accurately replace these pieces is to traverse the JS one + * character at a time and try to find whatever starts first. + * + * @param string $content The content to replace patterns in + * + * @return string The (manipulated) content + */ + protected function replace($content) + { + $contentLength = strlen($content); + $output = ''; + $processedOffset = 0; + $positions = array_fill(0, count($this->patterns), -1); + $matches = []; + + while ($processedOffset < $contentLength) { + // find first match for all patterns + foreach ($this->patterns as $i => $pattern) { + list($pattern, $replacement) = $pattern; + + // we can safely ignore patterns for positions we've unset earlier, + // because we know these won't show up anymore + if (array_key_exists($i, $positions) == false) { + continue; + } + + // no need to re-run matches that are still in the part of the + // content that hasn't been processed + if ($positions[$i] >= $processedOffset) { + continue; + } + + $match = null; + if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE, $processedOffset)) { + $matches[$i] = $match; + + // we'll store the match position as well; that way, we + // don't have to redo all preg_matches after changing only + // the first (we'll still know where those others are) + $positions[$i] = $match[0][1]; + } else { + // if the pattern couldn't be matched, there's no point in + // executing it again in later runs on this same content; + // ignore this one until we reach end of content + unset($matches[$i], $positions[$i]); + } + } + + // no more matches to find: everything's been processed, break out + if (!$matches) { + // output the remaining content + $output .= substr($content, $processedOffset); + break; + } + + // see which of the patterns actually found the first thing (we'll + // only want to execute that one, since we're unsure if what the + // other found was not inside what the first found) + $matchOffset = min($positions); + $firstPattern = array_search($matchOffset, $positions); + $match = $matches[$firstPattern]; + + // execute the pattern that matches earliest in the content string + list(, $replacement) = $this->patterns[$firstPattern]; + + // add the part of the input between $processedOffset and the first match; + // that content wasn't matched by anything + $output .= substr($content, $processedOffset, $matchOffset - $processedOffset); + // add the replacement for the match + $output .= $this->executeReplacement($replacement, $match); + // advance $processedOffset past the match + $processedOffset = $matchOffset + strlen($match[0][0]); + } + + return $output; + } + + /** + * If $replacement is a callback, execute it, passing in the match data. + * If it's a string, just pass it through. + * + * @param string|callable $replacement Replacement value + * @param array $match Match data, in PREG_OFFSET_CAPTURE form + * + * @return string + */ + protected function executeReplacement($replacement, $match) + { + if (!is_callable($replacement)) { + return $replacement; + } + // convert $match from the PREG_OFFSET_CAPTURE form to the form the callback expects + foreach ($match as &$matchItem) { + $matchItem = $matchItem[0]; + } + + return $replacement($match); + } + + /** + * Strings are a pattern we need to match, in order to ignore potential + * code-like content inside them, but we just want all of the string + * content to remain untouched. + * + * This method will replace all string content with simple STRING# + * placeholder text, so we've rid all strings from characters that may be + * misinterpreted. Original string content will be saved in $this->extracted + * and after doing all other minifying, we can restore the original content + * via restoreStrings(). + * + * @param string[optional] $chars + * @param string[optional] $placeholderPrefix + */ + protected function extractStrings($chars = '\'"', $placeholderPrefix = '') + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $placeholderPrefix) { + // check the second index here, because the first always contains a quote + if ($match[2] === '') { + /* + * Empty strings need no placeholder; they can't be confused for + * anything else anyway. + * But we still needed to match them, for the extraction routine + * to skip over this particular string. + */ + return $match[0]; + } + + $count = count($minifier->extracted); + $placeholder = $match[1] . $placeholderPrefix . $count . $match[1]; + $minifier->extracted[$placeholder] = $match[1] . $match[2] . $match[1]; + + return $placeholder; + }; + + /* + * The \\ messiness explained: + * * Don't count ' or " as end-of-string if it's escaped (has backslash + * in front of it) + * * Unless... that backslash itself is escaped (another leading slash), + * in which case it's no longer escaping the ' or " + * * So there can be either no backslash, or an even number + * * multiply all of that times 4, to account for the escaping that has + * to be done to pass the backslash into the PHP string without it being + * considered as escape-char (times 2) and to get it in the regex, + * escaped (times 2) + */ + $this->registerPattern('/([' . $chars . '])(.*?(?extracted. + * + * @param string $content + * + * @return string + */ + protected function restoreExtractedData($content) + { + if (!$this->extracted) { + // nothing was extracted, nothing to restore + return $content; + } + + $content = strtr($content, $this->extracted); + + $this->extracted = []; + + return $content; + } + + /** + * Check if the path is a regular file and can be read. + * + * @param string $path + * + * @return bool + */ + protected function canImportFile($path) + { + $parsed = parse_url($path); + if ( + // file is elsewhere + isset($parsed['host']) || + // file responds to queries (may change, or need to bypass cache) + isset($parsed['query']) + ) { + return false; + } + + return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path); + } + + /** + * Attempts to open file specified by $path for writing. + * + * @param string $path The path to the file + * + * @return resource Specifier for the target file + * + * @throws IOException + */ + protected function openFileForWriting($path) + { + if ($path === '' || ($handler = @fopen($path, 'w')) === false) { + throw new IOException('The file "' . $path . '" could not be opened for writing. Check if PHP has enough permissions.'); + } + + return $handler; + } + + /** + * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. + * + * @param resource $handler The resource to write to + * @param string $content The content to write + * @param string $path The path to the file (for exception printing only) + * + * @throws IOException + */ + protected function writeToFile($handler, $content, $path = '') + { + if ( + !is_resource($handler) || + ($result = @fwrite($handler, $content)) === false || + ($result < strlen($content)) + ) { + throw new IOException('The file "' . $path . '" could not be written to. Check your disk space and file permissions.'); + } + } + + protected static function str_replace_first($search, $replace, $subject) + { + $pos = strpos((string) $subject, (string) $search); + if ($pos !== false) { + return substr_replace((string) $subject, (string) $replace, $pos, strlen((string) $search)); + } + + return $subject; + } +} + diff --git a/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.power b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.power new file mode 100644 index 0000000..94d8eda --- /dev/null +++ b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/code.power @@ -0,0 +1,482 @@ + /** + * The data to be minified. + * + * @var string[] + */ + protected $data = []; + + /** + * Array of patterns to match. + * + * @var string[] + */ + protected $patterns = []; + + /** + * This array will hold content of strings and regular expressions that have + * been extracted from the JS source code, so we can reliably match "code", + * without having to worry about potential "code-like" characters inside. + * + * @internal + * + * @var string[] + */ + public $extracted = []; + + /** + * Init the minify class - optionally, code may be passed along already. + */ + public function __construct(/* $data = null, ... */) + { + // it's possible to add the source through the constructor as well ;) + if (func_num_args()) { + call_user_func_array(array($this, 'add'), func_get_args()); + } + } + + /** + * Add a file or straight-up code to be minified. + * + * @param string|string[] $data + * + * @return static + */ + public function add($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $data) { + if (is_array($data)) { + call_user_func_array(array($this, 'add'), $data); + continue; + } + + // redefine var + $data = (string) $data; + + // load data + $value = $this->load($data); + $key = ($data != $value) ? $data : count($this->data); + + // replace CR linefeeds etc. + // @see https://github.com/matthiasmullie/minify/pull/139 + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // store data + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Add a file to be minified. + * + * @param string|string[] $data + * + * @return static + * + * @throws IOException + */ + public function addFile($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $path) { + if (is_array($path)) { + call_user_func_array(array($this, 'addFile'), $path); + continue; + } + + // redefine var + $path = (string) $path; + + // check if we can read the file + if (!$this->canImportFile($path)) { + throw new IOException('The file "' . $path . '" could not be opened for reading. Check if PHP has enough permissions.'); + } + + $this->add($path); + } + + return $this; + } + + /** + * Minify the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function minify($path = null) + { + $content = $this->execute($path); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify & gzip the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * @param int[optional] $level Compression level, from 0 to 9 + * + * @return string The minified & gzipped data + */ + public function gzip($path = null, $level = 9) + { + $content = $this->execute($path); + $content = gzencode($content, $level, FORCE_GZIP); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify the data & write it to a CacheItemInterface object. + * + * @param CacheItemInterface $item Cache item to write the data to + * + * @return CacheItemInterface Cache item with the minifier data + */ + public function cache(CacheItemInterface $item) + { + $content = $this->execute(); + $item->set($content); + + return $item; + } + + /** + * Minify the data. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + abstract public function execute($path = null); + + /** + * Load data. + * + * @param string $data Either a path to a file or the content itself + * + * @return string + */ + protected function load($data) + { + // check if the data is a file + if ($this->canImportFile($data)) { + $data = file_get_contents($data); + + // strip BOM, if any + if (substr($data, 0, 3) == "\xef\xbb\xbf") { + $data = substr($data, 3); + } + } + + return $data; + } + + /** + * Save to file. + * + * @param string $content The minified data + * @param string $path The path to save the minified data to + * + * @throws IOException + */ + protected function save($content, $path) + { + $handler = $this->openFileForWriting($path); + + $this->writeToFile($handler, $content); + + @fclose($handler); + } + + /** + * Register a pattern to execute against the source content. + * + * If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work. + * If you need that functionality, use a callback instead. + * + * @param string $pattern PCRE pattern + * @param string|callable $replacement Replacement value for matched pattern + */ + protected function registerPattern($pattern, $replacement = '') + { + // study the pattern, we'll execute it more than once + $pattern .= 'S'; + + $this->patterns[] = array($pattern, $replacement); + } + + /** + * We can't "just" run some regular expressions against JavaScript: it's a + * complex language. E.g. having an occurrence of // xyz would be a comment, + * unless it's used within a string. Of you could have something that looks + * like a 'string', but inside a comment. + * The only way to accurately replace these pieces is to traverse the JS one + * character at a time and try to find whatever starts first. + * + * @param string $content The content to replace patterns in + * + * @return string The (manipulated) content + */ + protected function replace($content) + { + $contentLength = strlen($content); + $output = ''; + $processedOffset = 0; + $positions = array_fill(0, count($this->patterns), -1); + $matches = []; + + while ($processedOffset < $contentLength) { + // find first match for all patterns + foreach ($this->patterns as $i => $pattern) { + list($pattern, $replacement) = $pattern; + + // we can safely ignore patterns for positions we've unset earlier, + // because we know these won't show up anymore + if (array_key_exists($i, $positions) == false) { + continue; + } + + // no need to re-run matches that are still in the part of the + // content that hasn't been processed + if ($positions[$i] >= $processedOffset) { + continue; + } + + $match = null; + if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE, $processedOffset)) { + $matches[$i] = $match; + + // we'll store the match position as well; that way, we + // don't have to redo all preg_matches after changing only + // the first (we'll still know where those others are) + $positions[$i] = $match[0][1]; + } else { + // if the pattern couldn't be matched, there's no point in + // executing it again in later runs on this same content; + // ignore this one until we reach end of content + unset($matches[$i], $positions[$i]); + } + } + + // no more matches to find: everything's been processed, break out + if (!$matches) { + // output the remaining content + $output .= substr($content, $processedOffset); + break; + } + + // see which of the patterns actually found the first thing (we'll + // only want to execute that one, since we're unsure if what the + // other found was not inside what the first found) + $matchOffset = min($positions); + $firstPattern = array_search($matchOffset, $positions); + $match = $matches[$firstPattern]; + + // execute the pattern that matches earliest in the content string + list(, $replacement) = $this->patterns[$firstPattern]; + + // add the part of the input between $processedOffset and the first match; + // that content wasn't matched by anything + $output .= substr($content, $processedOffset, $matchOffset - $processedOffset); + // add the replacement for the match + $output .= $this->executeReplacement($replacement, $match); + // advance $processedOffset past the match + $processedOffset = $matchOffset + strlen($match[0][0]); + } + + return $output; + } + + /** + * If $replacement is a callback, execute it, passing in the match data. + * If it's a string, just pass it through. + * + * @param string|callable $replacement Replacement value + * @param array $match Match data, in PREG_OFFSET_CAPTURE form + * + * @return string + */ + protected function executeReplacement($replacement, $match) + { + if (!is_callable($replacement)) { + return $replacement; + } + // convert $match from the PREG_OFFSET_CAPTURE form to the form the callback expects + foreach ($match as &$matchItem) { + $matchItem = $matchItem[0]; + } + + return $replacement($match); + } + + /** + * Strings are a pattern we need to match, in order to ignore potential + * code-like content inside them, but we just want all of the string + * content to remain untouched. + * + * This method will replace all string content with simple STRING# + * placeholder text, so we've rid all strings from characters that may be + * misinterpreted. Original string content will be saved in $this->extracted + * and after doing all other minifying, we can restore the original content + * via restoreStrings(). + * + * @param string[optional] $chars + * @param string[optional] $placeholderPrefix + */ + protected function extractStrings($chars = '\'"', $placeholderPrefix = '') + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $placeholderPrefix) { + // check the second index here, because the first always contains a quote + if ($match[2] === '') { + /* + * Empty strings need no placeholder; they can't be confused for + * anything else anyway. + * But we still needed to match them, for the extraction routine + * to skip over this particular string. + */ + return $match[0]; + } + + $count = count($minifier->extracted); + $placeholder = $match[1] . $placeholderPrefix . $count . $match[1]; + $minifier->extracted[$placeholder] = $match[1] . $match[2] . $match[1]; + + return $placeholder; + }; + + /* + * The \\ messiness explained: + * * Don't count ' or " as end-of-string if it's escaped (has backslash + * in front of it) + * * Unless... that backslash itself is escaped (another leading slash), + * in which case it's no longer escaping the ' or " + * * So there can be either no backslash, or an even number + * * multiply all of that times 4, to account for the escaping that has + * to be done to pass the backslash into the PHP string without it being + * considered as escape-char (times 2) and to get it in the regex, + * escaped (times 2) + */ + $this->registerPattern('/([' . $chars . '])(.*?(?extracted. + * + * @param string $content + * + * @return string + */ + protected function restoreExtractedData($content) + { + if (!$this->extracted) { + // nothing was extracted, nothing to restore + return $content; + } + + $content = strtr($content, $this->extracted); + + $this->extracted = []; + + return $content; + } + + /** + * Check if the path is a regular file and can be read. + * + * @param string $path + * + * @return bool + */ + protected function canImportFile($path) + { + $parsed = parse_url($path); + if ( + // file is elsewhere + isset($parsed['host']) || + // file responds to queries (may change, or need to bypass cache) + isset($parsed['query']) + ) { + return false; + } + + return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path); + } + + /** + * Attempts to open file specified by $path for writing. + * + * @param string $path The path to the file + * + * @return resource Specifier for the target file + * + * @throws IOException + */ + protected function openFileForWriting($path) + { + if ($path === '' || ($handler = @fopen($path, 'w')) === false) { + throw new IOException('The file "' . $path . '" could not be opened for writing. Check if PHP has enough permissions.'); + } + + return $handler; + } + + /** + * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. + * + * @param resource $handler The resource to write to + * @param string $content The content to write + * @param string $path The path to the file (for exception printing only) + * + * @throws IOException + */ + protected function writeToFile($handler, $content, $path = '') + { + if ( + !is_resource($handler) || + ($result = @fwrite($handler, $content)) === false || + ($result < strlen($content)) + ) { + throw new IOException('The file "' . $path . '" could not be written to. Check your disk space and file permissions.'); + } + } + + protected static function str_replace_first($search, $replace, $subject) + { + $pos = strpos((string) $subject, (string) $search); + if ($pos !== false) { + return substr_replace((string) $subject, (string) $replace, $pos, strlen((string) $search)); + } + + return $subject; + } \ No newline at end of file diff --git a/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/settings.json b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/settings.json new file mode 100644 index 0000000..840dc66 --- /dev/null +++ b/src/d2b8599a-d5ce-49b1-b992-764bb7d55137/settings.json @@ -0,0 +1,27 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "0", + "guid": "d2b8599a-d5ce-49b1-b992-764bb7d55137", + "implements": null, + "load_selection": null, + "name": "Minify", + "power_version": "1.3.70", + "system_name": "Minify.Abstraction.Minify", + "type": "abstract class", + "use_selection": { + "use_selection0": { + "use": "44b4a9cf-62e6-48af-ab30-5b3d2715728e", + "as": "default" + }, + "use_selection1": { + "use": "171fcb39-ae27-4c2b-b091-2f70b824288b", + "as": "default" + } + }, + "namespace": "VDM\\Minify\\Abstraction.Minify", + "description": "\/**\r\n * Abstract minifier class.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "licensing_template": "\/**\r\n * Abstract minifier class.\r\n *\r\n * Please report bugs on https:\/\/github.com\/matthiasmullie\/minify\/issues\r\n *\r\n * @author Matthias Mullie \r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/\r\n", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/README.md b/src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/README.md new file mode 100644 index 0000000..7712e74 --- /dev/null +++ b/src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/README.md @@ -0,0 +1,108 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# class JavaScript (Details) +> namespace: **VDM\Minify** +```uml +@startuml +class JavaScript #Gold { + # $keywordsReserved + # $keywordsBefore + # $keywordsAfter + # $operatorsBefore + + execute(string[optional] $path = null) : string + # stripComments() + # extractRegex() + # stripWhitespace(string $content) : string + # getOperatorsForRegex(array $operators, string $delimiter = '/') : string[] + # getKeywordsForRegex(array $keywords, string $delimiter = '/') : string[] + # propertyNotation(string $content) : string + # shortenBools(string $content) : string +} + +note right of JavaScript::execute + Minify the data. +Perform JS optimizations. + + return: string +end note + +note left of JavaScript::stripComments + Strip comments from source code. + +end note + +note right of JavaScript::extractRegex + JS can have /-delimited regular expressions, like: /ab+c/.match(string). +The content inside the regex can contain characters that may be confused +for JS code: e.g. it could contain whitespace it needs to match & we +don't want to strip whitespace in there. +The regex can be pretty simple: we don't have to care about comments, +(which also use slashes) because stripComments() will have stripped those +already. +This method will replace all string content with simple REGEX# +placeholder text, so we've rid all regular expressions from characters +that may be misinterpreted. Original regex content will be saved in +$this->extracted and after doing all other minifying, we can restore the +original content via restoreRegex() + +end note + +note left of JavaScript::stripWhitespace + Strip whitespace. +We won't strip *all* whitespace, but as much as possible. The thing that +we'll preserve are newlines we're unsure about. +JavaScript doesn't require statements to be terminated with a semicolon. +It will automatically fix missing semicolons with ASI (automatic semi- +colon insertion) at the end of line causing errors (without semicolon.) +Because it's sometimes hard to tell if a newline is part of a statement +that should be terminated or not, we'll just leave some of them alone. + + return: string +end note + +note right of JavaScript::getOperatorsForRegex + We'll strip whitespace around certain operators with regular expressions. +This will prepare the given array by escaping all characters. + + return: string[] +end note + +note left of JavaScript::getKeywordsForRegex + We'll strip whitespace around certain keywords with regular expressions. +This will prepare the given array by escaping all characters. + + return: string[] +end note + +note right of JavaScript::propertyNotation + Replaces all occurrences of array['key'] by array.key. + + return: string +end note + +note left of JavaScript::shortenBools + Replaces true & false by !0 and !1. + + return: string +end note + +@enduml +``` + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/code.php b/src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/code.php new file mode 100644 index 0000000..fd226d3 --- /dev/null +++ b/src/ef6438c4-76db-4aa1-bb15-bc97583bcd2e/code.php @@ -0,0 +1,861 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace VDM\Minify; + + +use VDM\Minify\Abstraction\Minify; + + +/** + * JavaScript Minifier Class. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie + * @author Tijs Verkoyen + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class JavaScript extends Minify +{ + /** + * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. + * + * Note that regular expressions using that bit must have the PCRE_UTF8 + * pattern modifier (/u) set. + * + * @internal + * + * @var string + */ + const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; + + /** + * Full list of JavaScript reserved words. + * Will be loaded from /data/js/keywords_reserved.txt. + * + * @see https://mathiasbynens.be/notes/reserved-keywords + * + * @var string[] + */ + protected $keywordsReserved = [ + 'do', + 'if', + 'in', + 'for', + 'let', + 'new', + 'try', + 'var', + 'case', + 'else', + 'enum', + 'eval', + 'null', + 'this', + 'true', + 'void', + 'with', + 'break', + 'catch', + 'class', + 'const', + 'false', + 'super', + 'throw', + 'while', + 'yield', + 'delete', + 'export', + 'import', + 'public', + 'return', + 'static', + 'switch', + 'typeof', + 'default', + 'extends', + 'finally', + 'package', + 'private', + 'continue', + 'debugger', + 'function', + 'arguments', + 'interface', + 'protected', + 'implements', + 'instanceof', + 'abstract', + 'boolean', + 'byte', + 'char', + 'double', + 'final', + 'float', + 'goto', + 'int', + 'long', + 'native', + 'short', + 'synchronized', + 'throws', + 'transient', + 'volatile' + ]; + + /** + * List of JavaScript reserved words that accept a + * after them. Some end of lines are not the end of a statement, like with + * these keywords. + * + * E.g.: we shouldn't insert a ; after this else + * else + * console.log('this is quite fine') + * + * Will be loaded from /data/js/keywords_before.txt + * + * @var string[] + */ + protected $keywordsBefore = [ + 'do', + 'in', + 'let', + 'new', + 'var', + 'case', + 'else', + 'enum', + 'void', + 'with', + 'class', + 'const', + 'yield', + 'delete', + 'export', + 'import', + 'public', + 'static', + 'typeof', + 'extends', + 'package', + 'private', + 'function', + 'protected', + 'implements', + 'instanceof' + ]; + + /** + * List of JavaScript reserved words that accept a + * before them. Some end of lines are not the end of a statement, like when + * continued by one of these keywords on the newline. + * + * E.g.: we shouldn't insert a ; before this instanceof + * variable + * instanceof String + * + * Will be loaded from /data/js/keywords_after.txt + * + * @var string[] + */ + protected $keywordsAfter = [ + 'in', + 'public', + 'extends', + 'private', + 'protected', + 'implements', + 'instanceof' + ]; + + /** + * List of all JavaScript operators. + * + * Will be loaded from /data/js/operators.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operators = [ + '+', + '-', + '*', + '/', + '%', + '=', + '+=', + '-=', + '*=', + '/=', + '%=', + '<<=', + '>>=', + '>>>=', + '&=', + '^=', + '|=', + '&', + '|', + '^', + '~', + '<<', + '>>', + '>>>', + '==', + '===', + '!=', + '!==', + '>', + '<', + '>=', + '<=', + '&&', + '||', + '!', + '.', + '[', + ']', + '?', + ':', + ',', + ';', + '(', + ')', + '{', + '}' + ]; + + /** + * List of JavaScript operators that accept a after + * them. Some end of lines are not the end of a statement, like with these + * operators. + * + * Note: Most operators are fine, we've only removed ++ and --. + * ++ & -- have to be joined with the value they're in-/decrementing. + * + * Will be loaded from /data/js/operators_before.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsBefore = [ + '+', + '-', + '*', + '/', + '%', + '=', + '+=', + '-=', + '*=', + '/=', + '%=', + '<<=', + '>>=', + '>>>=', + '&=', + '^=', + '|=', + '&', + '|', + '^', + '~', + '<<', + '>>', + '>>>', + '==', + '===', + '!=', + '!==', + '>', + '<', + '>=', + '<=', + '&&', + '||', + '!', + '.', + '[', + '?', + ':', + ',', + ';', + '(', + '{', + ]; + + /** + * List of JavaScript operators that accept a before + * them. Some end of lines are not the end of a statement, like when + * continued by one of these operators on the newline. + * + * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. + * There can't be a newline separating ! or ~ and whatever it is negating. + * ++ & -- have to be joined with the value they're in-/decrementing. + * ) & ] are "special" in that they have lots or usecases. () for example + * is used for function calls, for grouping, in if () and for (), ... + * + * Will be loaded from /data/js/operators_after.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsAfter = [ + '+', + '-', + '*', + '/', + '%', + '=', + '+=', + '-=', + '*=', + '/=', + '%=', + '<<=', + '>>=', + '>>>=', + '&=', + '^=', + '|=', + '&', + '|', + '^', + '<<', + '>>', + '>>>', + '==', + '===', + '!=', + '!==', + '>', + '<', + '>=', + '<=', + '&&', + '||', + '.', + '[', + ']', + '?', + ':', + ',', + ';', + '(', + ')', + '}', + ]; + + /** + * Minify the data. + * Perform JS optimizations. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function execute($path = null) + { + $content = ''; + + /* + * Let's first take out strings, comments and regular expressions. + * All of these can contain JS code-like characters, and we should make + * sure any further magic ignores anything inside of these. + * + * Consider this example, where we should not strip any whitespace: + * var str = "a test"; + * + * Comments will be removed altogether, strings and regular expressions + * will be replaced by placeholder text, which we'll restore later. + */ + $this->extractStrings('\'"`'); + $this->stripComments(); + $this->extractRegex(); + + // loop files + foreach ($this->data as $source => $js) { + // take out strings, comments & regex (for which we've registered + // the regexes just a few lines earlier) + $js = $this->replace($js); + + $js = $this->propertyNotation($js); + $js = $this->shortenBools($js); + $js = $this->stripWhitespace($js); + + // combine js: separating the scripts by a ; + $content .= $js . ';'; + } + + // clean up leftover `;`s from the combination of multiple scripts + $content = ltrim($content, ';'); + $content = (string) substr($content, 0, -1); + + /* + * Earlier, we extracted strings & regular expressions and replaced them + * with placeholder text. This will restore them. + */ + $content = $this->restoreExtractedData($content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + if ( + substr((string) $match[2], 0, 1) === '!' || + strpos((string) $match[2], '@license') !== false || + strpos((string) $match[2], '@preserve') !== false + ) { + // preserve multi-line comments that start with /*! + // or contain @license or @preserve annotations + $count = count($minifier->extracted); + $placeholder = '/*' . $count . '*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $match[1] . $placeholder . $match[3]; + } + + return $match[1] . $match[3]; + }; + + // multi-line comments + $this->registerPattern('/(\n?)\/\*(.*?)\*\/(\n?)/s', $callback); + + // single-line comments + $this->registerPattern('/\/\/.*$/m', ''); + } + + /** + * JS can have /-delimited regular expressions, like: /ab+c/.match(string). + * + * The content inside the regex can contain characters that may be confused + * for JS code: e.g. it could contain whitespace it needs to match & we + * don't want to strip whitespace in there. + * + * The regex can be pretty simple: we don't have to care about comments, + * (which also use slashes) because stripComments() will have stripped those + * already. + * + * This method will replace all string content with simple REGEX# + * placeholder text, so we've rid all regular expressions from characters + * that may be misinterpreted. Original regex content will be saved in + * $this->extracted and after doing all other minifying, we can restore the + * original content via restoreRegex() + */ + protected function extractRegex() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '"' . $count . '"'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + + // match all chars except `/` and `\` + // `\` is allowed though, along with whatever char follows (which is the + // one being escaped) + // this should allow all chars, except for an unescaped `/` (= the one + // closing the regex) + // then also ignore bare `/` inside `[]`, where they don't need to be + // escaped: anything inside `[]` can be ignored safely + $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; + + // a regular expression can only be followed by a few operators or some + // of the RegExp methods (a `\` followed by a variable or value is + // likely part of a division, not a regex) + $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); + $before = '(^|[=:,;\+\-\*\?\/\}\(\{\[&\|!]|' . implode('|', $keywords) . ')\s*'; + $propertiesAndMethods = array( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 + 'constructor', + 'flags', + 'global', + 'ignoreCase', + 'multiline', + 'source', + 'sticky', + 'unicode', + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 + 'compile(', + 'exec(', + 'test(', + 'toSource(', + 'toString(', + ); + $delimiters = array_fill(0, count($propertiesAndMethods), '/'); + $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); + $after = '(?=\s*([\.,;:\)\}&\|+]|\/\/|$|\.(' . implode('|', $propertiesAndMethods) . ')))'; + $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); + + // regular expressions following a `)` are rather annoying to detect... + // quite often, `/` after `)` is a division operator & if it happens to + // be followed by another one (or a comment), it is likely to be + // confused for a regular expression + // however, it's perfectly possible for a regex to follow a `)`: after + // a single-line `if()`, `while()`, ... statement, for example + // since, when they occur like that, they're always the start of a + // statement, there's only a limited amount of ways they can be useful: + // by calling the regex methods directly + // if a regex following `)` is not followed by `.`, + // it's quite likely not a regex + $before = '\)\s*'; + $after = '(?=\s*\.(' . implode('|', $propertiesAndMethods) . '))'; + $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); + + // 1 more edge case: a regex can be followed by a lot more operators or + // keywords if there's a newline (ASI) in between, where the operator + // actually starts a new statement + // (https://github.com/matthiasmullie/minify/issues/56) + $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); + $after = '(?=\s*\n\s*(' . implode('|', $operators) . '))'; + $this->registerPattern('/' . $pattern . $after . '/', $callback); + } + + /** + * Strip whitespace. + * + * We won't strip *all* whitespace, but as much as possible. The thing that + * we'll preserve are newlines we're unsure about. + * JavaScript doesn't require statements to be terminated with a semicolon. + * It will automatically fix missing semicolons with ASI (automatic semi- + * colon insertion) at the end of line causing errors (without semicolon.) + * + * Because it's sometimes hard to tell if a newline is part of a statement + * that should be terminated or not, we'll just leave some of them alone. + * + * @param string $content The content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // uniform line endings, make them all line feed + $content = str_replace(array("\r\n", "\r"), "\n", $content); + + // collapse all non-line feed whitespace into a single space + $content = preg_replace('/[^\S\n]+/', ' ', $content); + + // strip leading & trailing whitespace + $content = str_replace(array(" \n", "\n "), "\n", $content); + + // collapse consecutive line feeds into just 1 + $content = preg_replace('/\n+/', "\n", $content); + + $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); + $operators = $this->getOperatorsForRegex($this->operators, '/'); + $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); + $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); + + // strip whitespace that ends in (or next line begin with) an operator + // that allows statements to be broken up over multiple lines + unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); + $content = preg_replace( + array( + '/(' . implode('|', $operatorsBefore) . ')\s+/', + '/\s+(' . implode('|', $operatorsAfter) . ')/', + ), + '\\1', + $content + ); + + // make sure + and - can't be mistaken for, or joined into ++ and -- + $content = preg_replace( + array( + '/(?%&|', $delimiter); + $operators['='] = '(? '\b' . $value . '\b'); + + $keywords = array_combine($keywords, $escaped); + + return $keywords; + } + + /** + * Replaces all occurrences of array['key'] by array.key. + * + * @param string $content + * + * @return string + */ + protected function propertyNotation($content) + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $keywords = $this->keywordsReserved; + $callback = function ($match) use ($minifier, $keywords) { + $property = trim($minifier->extracted[$match[1]], '\'"'); + + /* + * Check if the property is a reserved keyword. In this context (as + * property of an object literal/array) it shouldn't matter, but IE8 + * freaks out with "Expected identifier". + */ + if (in_array($property, $keywords)) { + return $match[0]; + } + + /* + * See if the property is in a variable-like format (e.g. + * array['key-here'] can't be replaced by array.key-here since '-' + * is not a valid character there. + */ + if (!preg_match('/^' . $minifier::REGEX_VARIABLE . '$/u', $property)) { + return $match[0]; + } + + return '.' . $property; + }; + + /* + * Figure out if previous character is a variable name (of the array + * we want to use property notation on) - this is to make sure + * standalone ['value'] arrays aren't confused for keys-of-an-array. + * We can (and only have to) check the last character, because PHP's + * regex implementation doesn't allow unfixed-length look-behind + * assertions. + */ + preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); + $previousChar = $previousChar[1]; + + /* + * Make sure word preceding the ['value'] is not a keyword, e.g. + * return['x']. Because -again- PHP's regex implementation doesn't allow + * unfixed-length look-behind assertions, I'm just going to do a lot of + * separate look-behind assertions, one for each keyword. + */ + $keywords = $this->getKeywordsForRegex($keywords); + $keywords = '(? + * after them. Some end of lines are not the end of a statement, like with + * these keywords. + * + * E.g.: we shouldn't insert a ; after this else + * else + * console.log('this is quite fine') + * + * Will be loaded from /data/js/keywords_before.txt + * + * @var string[] + */ + protected $keywordsBefore = [ + 'do', + 'in', + 'let', + 'new', + 'var', + 'case', + 'else', + 'enum', + 'void', + 'with', + 'class', + 'const', + 'yield', + 'delete', + 'export', + 'import', + 'public', + 'static', + 'typeof', + 'extends', + 'package', + 'private', + 'function', + 'protected', + 'implements', + 'instanceof' + ]; + + /** + * List of JavaScript reserved words that accept a + * before them. Some end of lines are not the end of a statement, like when + * continued by one of these keywords on the newline. + * + * E.g.: we shouldn't insert a ; before this instanceof + * variable + * instanceof String + * + * Will be loaded from /data/js/keywords_after.txt + * + * @var string[] + */ + protected $keywordsAfter = [ + 'in', + 'public', + 'extends', + 'private', + 'protected', + 'implements', + 'instanceof' + ]; + + /** + * List of all JavaScript operators. + * + * Will be loaded from /data/js/operators.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operators = [ + '+', + '-', + '*', + '/', + '%', + '=', + '+=', + '-=', + '*=', + '/=', + '%=', + '<<=', + '>>=', + '>>>=', + '&=', + '^=', + '|=', + '&', + '|', + '^', + '~', + '<<', + '>>', + '>>>', + '==', + '===', + '!=', + '!==', + '>', + '<', + '>=', + '<=', + '&&', + '||', + '!', + '.', + '[', + ']', + '?', + ':', + ',', + ';', + '(', + ')', + '{', + '}' + ]; + + /** + * List of JavaScript operators that accept a after + * them. Some end of lines are not the end of a statement, like with these + * operators. + * + * Note: Most operators are fine, we've only removed ++ and --. + * ++ & -- have to be joined with the value they're in-/decrementing. + * + * Will be loaded from /data/js/operators_before.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsBefore = [ + '+', + '-', + '*', + '/', + '%', + '=', + '+=', + '-=', + '*=', + '/=', + '%=', + '<<=', + '>>=', + '>>>=', + '&=', + '^=', + '|=', + '&', + '|', + '^', + '~', + '<<', + '>>', + '>>>', + '==', + '===', + '!=', + '!==', + '>', + '<', + '>=', + '<=', + '&&', + '||', + '!', + '.', + '[', + '?', + ':', + ',', + ';', + '(', + '{', + ]; + + /** + * List of JavaScript operators that accept a before + * them. Some end of lines are not the end of a statement, like when + * continued by one of these operators on the newline. + * + * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. + * There can't be a newline separating ! or ~ and whatever it is negating. + * ++ & -- have to be joined with the value they're in-/decrementing. + * ) & ] are "special" in that they have lots or usecases. () for example + * is used for function calls, for grouping, in if () and for (), ... + * + * Will be loaded from /data/js/operators_after.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsAfter = [ + '+', + '-', + '*', + '/', + '%', + '=', + '+=', + '-=', + '*=', + '/=', + '%=', + '<<=', + '>>=', + '>>>=', + '&=', + '^=', + '|=', + '&', + '|', + '^', + '<<', + '>>', + '>>>', + '==', + '===', + '!=', + '!==', + '>', + '<', + '>=', + '<=', + '&&', + '||', + '.', + '[', + ']', + '?', + ':', + ',', + ';', + '(', + ')', + '}', + ]; + + /** + * Minify the data. + * Perform JS optimizations. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function execute($path = null) + { + $content = ''; + + /* + * Let's first take out strings, comments and regular expressions. + * All of these can contain JS code-like characters, and we should make + * sure any further magic ignores anything inside of these. + * + * Consider this example, where we should not strip any whitespace: + * var str = "a test"; + * + * Comments will be removed altogether, strings and regular expressions + * will be replaced by placeholder text, which we'll restore later. + */ + $this->extractStrings('\'"`'); + $this->stripComments(); + $this->extractRegex(); + + // loop files + foreach ($this->data as $source => $js) { + // take out strings, comments & regex (for which we've registered + // the regexes just a few lines earlier) + $js = $this->replace($js); + + $js = $this->propertyNotation($js); + $js = $this->shortenBools($js); + $js = $this->stripWhitespace($js); + + // combine js: separating the scripts by a ; + $content .= $js . ';'; + } + + // clean up leftover `;`s from the combination of multiple scripts + $content = ltrim($content, ';'); + $content = (string) substr($content, 0, -1); + + /* + * Earlier, we extracted strings & regular expressions and replaced them + * with placeholder text. This will restore them. + */ + $content = $this->restoreExtractedData($content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + if ( + substr((string) $match[2], 0, 1) === '!' || + strpos((string) $match[2], '@license') !== false || + strpos((string) $match[2], '@preserve') !== false + ) { + // preserve multi-line comments that start with /*! + // or contain @license or @preserve annotations + $count = count($minifier->extracted); + $placeholder = '/*' . $count . '*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $match[1] . $placeholder . $match[3]; + } + + return $match[1] . $match[3]; + }; + + // multi-line comments + $this->registerPattern('/(\n?)\/\*(.*?)\*\/(\n?)/s', $callback); + + // single-line comments + $this->registerPattern('/\/\/.*$/m', ''); + } + + /** + * JS can have /-delimited regular expressions, like: /ab+c/.match(string). + * + * The content inside the regex can contain characters that may be confused + * for JS code: e.g. it could contain whitespace it needs to match & we + * don't want to strip whitespace in there. + * + * The regex can be pretty simple: we don't have to care about comments, + * (which also use slashes) because stripComments() will have stripped those + * already. + * + * This method will replace all string content with simple REGEX# + * placeholder text, so we've rid all regular expressions from characters + * that may be misinterpreted. Original regex content will be saved in + * $this->extracted and after doing all other minifying, we can restore the + * original content via restoreRegex() + */ + protected function extractRegex() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '"' . $count . '"'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + + // match all chars except `/` and `\` + // `\` is allowed though, along with whatever char follows (which is the + // one being escaped) + // this should allow all chars, except for an unescaped `/` (= the one + // closing the regex) + // then also ignore bare `/` inside `[]`, where they don't need to be + // escaped: anything inside `[]` can be ignored safely + $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; + + // a regular expression can only be followed by a few operators or some + // of the RegExp methods (a `\` followed by a variable or value is + // likely part of a division, not a regex) + $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); + $before = '(^|[=:,;\+\-\*\?\/\}\(\{\[&\|!]|' . implode('|', $keywords) . ')\s*'; + $propertiesAndMethods = array( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 + 'constructor', + 'flags', + 'global', + 'ignoreCase', + 'multiline', + 'source', + 'sticky', + 'unicode', + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 + 'compile(', + 'exec(', + 'test(', + 'toSource(', + 'toString(', + ); + $delimiters = array_fill(0, count($propertiesAndMethods), '/'); + $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); + $after = '(?=\s*([\.,;:\)\}&\|+]|\/\/|$|\.(' . implode('|', $propertiesAndMethods) . ')))'; + $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); + + // regular expressions following a `)` are rather annoying to detect... + // quite often, `/` after `)` is a division operator & if it happens to + // be followed by another one (or a comment), it is likely to be + // confused for a regular expression + // however, it's perfectly possible for a regex to follow a `)`: after + // a single-line `if()`, `while()`, ... statement, for example + // since, when they occur like that, they're always the start of a + // statement, there's only a limited amount of ways they can be useful: + // by calling the regex methods directly + // if a regex following `)` is not followed by `.`, + // it's quite likely not a regex + $before = '\)\s*'; + $after = '(?=\s*\.(' . implode('|', $propertiesAndMethods) . '))'; + $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); + + // 1 more edge case: a regex can be followed by a lot more operators or + // keywords if there's a newline (ASI) in between, where the operator + // actually starts a new statement + // (https://github.com/matthiasmullie/minify/issues/56) + $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); + $after = '(?=\s*\n\s*(' . implode('|', $operators) . '))'; + $this->registerPattern('/' . $pattern . $after . '/', $callback); + } + + /** + * Strip whitespace. + * + * We won't strip *all* whitespace, but as much as possible. The thing that + * we'll preserve are newlines we're unsure about. + * JavaScript doesn't require statements to be terminated with a semicolon. + * It will automatically fix missing semicolons with ASI (automatic semi- + * colon insertion) at the end of line causing errors (without semicolon.) + * + * Because it's sometimes hard to tell if a newline is part of a statement + * that should be terminated or not, we'll just leave some of them alone. + * + * @param string $content The content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // uniform line endings, make them all line feed + $content = str_replace(array("\r\n", "\r"), "\n", $content); + + // collapse all non-line feed whitespace into a single space + $content = preg_replace('/[^\S\n]+/', ' ', $content); + + // strip leading & trailing whitespace + $content = str_replace(array(" \n", "\n "), "\n", $content); + + // collapse consecutive line feeds into just 1 + $content = preg_replace('/\n+/', "\n", $content); + + $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); + $operators = $this->getOperatorsForRegex($this->operators, '/'); + $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); + $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); + + // strip whitespace that ends in (or next line begin with) an operator + // that allows statements to be broken up over multiple lines + unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); + $content = preg_replace( + array( + '/(' . implode('|', $operatorsBefore) . ')\s+/', + '/\s+(' . implode('|', $operatorsAfter) . ')/', + ), + '\\1', + $content + ); + + // make sure + and - can't be mistaken for, or joined into ++ and -- + $content = preg_replace( + array( + '/(?%&|', $delimiter); + $operators['='] = '(? '\b' . $value . '\b'); + + $keywords = array_combine($keywords, $escaped); + + return $keywords; + } + + /** + * Replaces all occurrences of array['key'] by array.key. + * + * @param string $content + * + * @return string + */ + protected function propertyNotation($content) + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $keywords = $this->keywordsReserved; + $callback = function ($match) use ($minifier, $keywords) { + $property = trim($minifier->extracted[$match[1]], '\'"'); + + /* + * Check if the property is a reserved keyword. In this context (as + * property of an object literal/array) it shouldn't matter, but IE8 + * freaks out with "Expected identifier". + */ + if (in_array($property, $keywords)) { + return $match[0]; + } + + /* + * See if the property is in a variable-like format (e.g. + * array['key-here'] can't be replaced by array.key-here since '-' + * is not a valid character there. + */ + if (!preg_match('/^' . $minifier::REGEX_VARIABLE . '$/u', $property)) { + return $match[0]; + } + + return '.' . $property; + }; + + /* + * Figure out if previous character is a variable name (of the array + * we want to use property notation on) - this is to make sure + * standalone ['value'] arrays aren't confused for keys-of-an-array. + * We can (and only have to) check the last character, because PHP's + * regex implementation doesn't allow unfixed-length look-behind + * assertions. + */ + preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); + $previousChar = $previousChar[1]; + + /* + * Make sure word preceding the ['value'] is not a keyword, e.g. + * return['x']. Because -again- PHP's regex implementation doesn't allow + * unfixed-length look-behind assertions, I'm just going to do a lot of + * separate look-behind assertions, one for each keyword. + */ + $keywords = $this->getKeywordsForRegex($keywords); + $keywords = '(?\r\n * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved\r\n * @license MIT License\r\n *\/", + "head": "", + "composer": "" +} \ No newline at end of file diff --git a/super-powers.json b/super-powers.json new file mode 100644 index 0000000..b6d23cb --- /dev/null +++ b/super-powers.json @@ -0,0 +1,92 @@ +{ + "47448ada-54df-45f3-b05d-1c66c584b1bd": { + "name": "BasicException", + "type": "abstract class", + "namespace": "VDM\\Minify\\Abstraction", + "code": "src\/47448ada-54df-45f3-b05d-1c66c584b1bd\/code.php", + "power": "src\/47448ada-54df-45f3-b05d-1c66c584b1bd\/code.power", + "settings": "src\/47448ada-54df-45f3-b05d-1c66c584b1bd\/settings.json", + "path": "src\/47448ada-54df-45f3-b05d-1c66c584b1bd", + "guid": "47448ada-54df-45f3-b05d-1c66c584b1bd" + }, + "44b4a9cf-62e6-48af-ab30-5b3d2715728e": { + "name": "IOException", + "type": "class", + "namespace": "VDM\\Minify\\Exceptions", + "code": "src\/44b4a9cf-62e6-48af-ab30-5b3d2715728e\/code.php", + "power": "src\/44b4a9cf-62e6-48af-ab30-5b3d2715728e\/code.power", + "settings": "src\/44b4a9cf-62e6-48af-ab30-5b3d2715728e\/settings.json", + "path": "src\/44b4a9cf-62e6-48af-ab30-5b3d2715728e", + "guid": "44b4a9cf-62e6-48af-ab30-5b3d2715728e" + }, + "d2b8599a-d5ce-49b1-b992-764bb7d55137": { + "name": "Minify", + "type": "abstract class", + "namespace": "VDM\\Minify\\Abstraction", + "code": "src\/d2b8599a-d5ce-49b1-b992-764bb7d55137\/code.php", + "power": "src\/d2b8599a-d5ce-49b1-b992-764bb7d55137\/code.power", + "settings": "src\/d2b8599a-d5ce-49b1-b992-764bb7d55137\/settings.json", + "path": "src\/d2b8599a-d5ce-49b1-b992-764bb7d55137", + "guid": "d2b8599a-d5ce-49b1-b992-764bb7d55137" + }, + "b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b": { + "name": "FileImportException", + "type": "class", + "namespace": "VDM\\Minify\\Exceptions", + "code": "src\/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b\/code.php", + "power": "src\/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b\/code.power", + "settings": "src\/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b\/settings.json", + "path": "src\/b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b", + "guid": "b34dbbeb-89bd-4f01-8bbd-f7ba0fb6280b" + }, + "cc482882-5eef-476c-b835-7b0c5a17c0db": { + "name": "ConverterInterface", + "type": "interface", + "namespace": "VDM\\Minify\\Path\\Interfaces", + "code": "src\/cc482882-5eef-476c-b835-7b0c5a17c0db\/code.php", + "power": "src\/cc482882-5eef-476c-b835-7b0c5a17c0db\/code.power", + "settings": "src\/cc482882-5eef-476c-b835-7b0c5a17c0db\/settings.json", + "path": "src\/cc482882-5eef-476c-b835-7b0c5a17c0db", + "guid": "cc482882-5eef-476c-b835-7b0c5a17c0db" + }, + "39f8957f-cc73-4da5-8b60-6cf25d4ab058": { + "name": "Converter", + "type": "class", + "namespace": "VDM\\Minify\\Path", + "code": "src\/39f8957f-cc73-4da5-8b60-6cf25d4ab058\/code.php", + "power": "src\/39f8957f-cc73-4da5-8b60-6cf25d4ab058\/code.power", + "settings": "src\/39f8957f-cc73-4da5-8b60-6cf25d4ab058\/settings.json", + "path": "src\/39f8957f-cc73-4da5-8b60-6cf25d4ab058", + "guid": "39f8957f-cc73-4da5-8b60-6cf25d4ab058" + }, + "70530246-2903-4c02-af9a-61182e4d97b8": { + "name": "Css", + "type": "class", + "namespace": "VDM\\Minify", + "code": "src\/70530246-2903-4c02-af9a-61182e4d97b8\/code.php", + "power": "src\/70530246-2903-4c02-af9a-61182e4d97b8\/code.power", + "settings": "src\/70530246-2903-4c02-af9a-61182e4d97b8\/settings.json", + "path": "src\/70530246-2903-4c02-af9a-61182e4d97b8", + "guid": "70530246-2903-4c02-af9a-61182e4d97b8" + }, + "ef6438c4-76db-4aa1-bb15-bc97583bcd2e": { + "name": "JavaScript", + "type": "class", + "namespace": "VDM\\Minify", + "code": "src\/ef6438c4-76db-4aa1-bb15-bc97583bcd2e\/code.php", + "power": "src\/ef6438c4-76db-4aa1-bb15-bc97583bcd2e\/code.power", + "settings": "src\/ef6438c4-76db-4aa1-bb15-bc97583bcd2e\/settings.json", + "path": "src\/ef6438c4-76db-4aa1-bb15-bc97583bcd2e", + "guid": "ef6438c4-76db-4aa1-bb15-bc97583bcd2e" + }, + "b591855d-d106-4fe5-90ad-8e706f6267cf": { + "name": "Minify", + "type": "abstract class", + "namespace": "VDM\\Joomla\\Componentbuilder\\Compiler\\Utilities", + "code": "src\/b591855d-d106-4fe5-90ad-8e706f6267cf\/code.php", + "power": "src\/b591855d-d106-4fe5-90ad-8e706f6267cf\/code.power", + "settings": "src\/b591855d-d106-4fe5-90ad-8e706f6267cf\/settings.json", + "path": "src\/b591855d-d106-4fe5-90ad-8e706f6267cf", + "guid": "b591855d-d106-4fe5-90ad-8e706f6267cf" + } +} \ No newline at end of file