Adding \wcf\system\Regex
authorTim Düsterhus <timwolla@arcor.de>
Sat, 17 Dec 2011 12:39:32 +0000 (13:39 +0100)
committerTim Düsterhus <timwolla@arcor.de>
Sat, 17 Dec 2011 12:47:23 +0000 (13:47 +0100)
That way we can typehint regexes and have object-oriented access on regexes.

There is no longer a need to add or escape the delimiters, this is all done by the Regex-class.
The modifiers are added via a bitmask as the second parameter:
Valid modifiers are:
Regex::CASE_INSENSITIVE (add i)
Regex::EVAL_REPLACEMENT (add e)
Regex::UNGREEDY (add U)
Regex::NO_ANALYSE (don't add s)

Usage:

```
$regex = new Regex('^http(s)?://(www\.)woltlab\.(com|de|info)/', Regex::CASE_INSENSITIVE);
var_dump($regex->match('http://www.woltlab.com/pluginstore/')); // int(1)
var_dump($regex->getMatches()); // array with all matched substrings

var_dump(Regex::compile('[a-z]')->replace('asdf345', '')); // string(3) "345"
var_dump(Regex::compile('[a-z]')->replace('asdf345', new Callback(function ($matches) {
return 'x';
})); // string(7) "xxxx345"
```

wcfsetup/install/files/lib/data/language/LanguageEditor.class.php
wcfsetup/install/files/lib/data/template/TemplateEditor.class.php
wcfsetup/install/files/lib/system/Regex.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/WCFSetup.class.php
wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php
wcfsetup/install/files/lib/system/template/TemplateEngine.class.php
wcfsetup/install/files/lib/util/DirectoryUtil.class.php

index 7b60cb67ea94d66fcc431f29a6af432cfd2949fb..75293ad9b0627e6bb296dc9a3e7e7140d8dc1c3b 100644 (file)
@@ -10,6 +10,7 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\io\File;
 use wcf\system\language\LanguageFactory;
 use wcf\system\package\PackageDependencyHandler;
+use wcf\system\Regex;
 use wcf\system\WCF;
 use wcf\util\XML;
 use wcf\util\DirectoryUtil;
@@ -290,7 +291,7 @@ class LanguageEditor extends DatabaseObjectEditor {
                if ($languageID != '.*') $languageID = intval($languageID);
                if ($packageID != '.*') $packageID = intval($packageID);
 
-               DirectoryUtil::getInstance(WCF_DIR.'language/')->removePattern('~'.$packageID.'_'.$languageID.'_'.$category.'\.php$~');
+               DirectoryUtil::getInstance(WCF_DIR.'language/')->removePattern(new Regex($packageID.'_'.$languageID.'_'.$category.'\.php$'));
        }
        
        /**
@@ -298,9 +299,9 @@ class LanguageEditor extends DatabaseObjectEditor {
         */
        public function deleteCompiledTemplates() {
                // templates
-               DirectoryUtil::getInstance(WCF_DIR.'templates/compiled/')->removePattern('~.*_'.$this->languageID.'_.*\.php$~');
+               DirectoryUtil::getInstance(WCF_DIR.'templates/compiled/')->removePattern(new Regex('.*_'.$this->languageID.'_.*\.php$'));
                // acp templates
-               DirectoryUtil::getInstance(WCF_DIR.'acp/templates/compiled/')->removePattern('~.*_'.$this->languageID.'_.*\.php$~');
+               DirectoryUtil::getInstance(WCF_DIR.'acp/templates/compiled/')->removePattern(new Regex('.*_'.$this->languageID.'_.*\.php$'));
        }
        
        /**
index 1c58518f731cd268bfb9e6d752d38355219d86d5..c9f6efd7e30c9567b478d9eeb9acc1d9a48ff2a2 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\data\template;
 use wcf\data\DatabaseObjectEditor;
 use wcf\system\io\File;
+use wcf\system\Regex;
 use wcf\system\WCF;
 use wcf\util\DirectoryUtil;
 
@@ -112,6 +113,6 @@ class TemplateEditor extends DatabaseObjectEditor {
         * Deletes the compiled files of this template.
         */
        public function deleteCompiledFiles() {
-               DirectoryUtil::getInstance(WCF_DIR . 'templates/compiled/')->removePattern('~' . intval($this->packageID) . '_.*_' . preg_quote($this->templateName, '~') . '.php~');
+               DirectoryUtil::getInstance(WCF_DIR . 'templates/compiled/')->removePattern(new Regex(intval($this->packageID) . '_.*_' . preg_quote($this->templateName) . '.php$'));
        }
 }
diff --git a/wcfsetup/install/files/lib/system/Regex.class.php b/wcfsetup/install/files/lib/system/Regex.class.php
new file mode 100644 (file)
index 0000000..5f9ade7
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+namespace wcf\system;
+use \wcf\system\exception\SystemException;
+
+/**
+ * Represents a regex.
+ * 
+ * @author     Tim Düsterhus
+ * @copyright  2011 Tim Düsterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system
+ * @category   Community Framework
+ */
+final class Regex {
+       /**
+        * The delimiter that is used internally.
+        * 
+        * @var string
+        */
+       const REGEX_DELIMITER = '/';
+       
+       /**
+        * Do not apply any modifiers.
+        * 
+        * @var integer
+        */
+       const MODIFIER_NONE = 0;
+       
+       /**
+        * Case insensitive matching.
+        * 
+        * @var integer
+        */
+       const CASE_INSENSITIVE = 1;
+       
+       /**
+        * Ungreedy matching.
+        * 
+        * @var integer
+        */
+       const UNGREEDY = 2;
+       
+       /**
+        * eval() replacement of  Regex::replace()
+        *
+        * @var integer
+        */
+       const EVAL_REPLACEMENT = 4;
+       
+       /**
+        * Do not spend extra time on analysing.
+        * 
+        * @var integer
+        */
+       const NO_ANALYSE = 8;
+       
+       /**
+        * The compiled regex (:D)
+        *
+        * @var string
+        */
+       private $regex = '';
+       
+       /**
+        * The last matches
+        *
+        * @var array
+        */
+       private $matches = array();
+       
+       /**
+        * Creates a regex.
+        *
+        * @param       string  $regex
+        * @param       integer $modifier
+        */
+       public function __construct($regex, $modifier = self::MODIFIER_NONE) {
+               // escape delimiter
+               $regex = str_replace(self::REGEX_DELIMITER, '\\'.self::REGEX_DELIMITER, $regex);
+               
+               // add delimiter
+               $this->regex = self::REGEX_DELIMITER.$regex.self::REGEX_DELIMITER;
+               
+               // add modifiers
+               if ($modifier & self::CASE_INSENSITIVE) $this->regex .= 'i';
+               if ($modifier & self::UNGREEDY) $this->regex .= 'U';
+               if ($modifier & self::EVAL_REPLACEMENT) $this->regex .= 'e';
+               if (~$modifier & self::NO_ANALYSE) $this->regex .= 's';
+       }
+       
+       /**
+        * @see Regex::__construct()
+        */
+       public static function compile($regex, $modifier = self::MODIFIER_NONE) {
+               return new self($regex, $modifier);
+       }
+       
+       /**
+        * @see Regex::match()
+        */
+       public function __invoke($string) {
+               return $this->match($string);
+       }
+       
+       /**
+        * Checks whether the regex matches the given string.
+        * 
+        * @param       string  $string         String to match.
+        * @param       boolean $all            Find all matches.
+        * @return      integer                 Return value of preg_match(_all)
+        */
+       public function match($string, $all = false) {
+               if ($all) {
+                       $result = preg_match_all($this->regex, $string, $this->matches);
+               }
+               else {
+                       $result = preg_match($this->regex, $string, $this->matches);
+               }
+               
+               if ($result === false) {
+                       throw new SystemException('Could not execute match on '.$this->regex);
+               }
+               return $result;
+       }
+       
+       /**
+        * Replaces part of the string with the regex.
+        *
+        * @param       string  $string         String to work on.
+        * @param       mixed   $replacement    Either replacement-string or instance of \wcf\system\Callback
+        * @return      string
+        */
+       public function replace($string, $replacement) {
+               if ($replacement instanceof Callback) {
+                       $result = preg_replace_callback($this->regex, $replacement, $string);
+               }
+               else {
+                       $result = preg_replace($this->regex, $replacement, $string);
+               }
+               
+               if ($result === false) {
+                       throw new SystemException('Could not execute replace on '.$this->regex);
+               }
+               return $result;
+       }
+       
+       /**
+        * Splits the string with the regex.
+        *
+        * @param       string  $string         String to split.
+        * @return      array<string>
+        */
+       public function split($string) {
+               $result = preg_split($this->regex, $string);
+               
+               if ($result === false) {
+                       throw new SystemException('Could not execute split on '.$this->regex);
+               }
+               return $result;
+       }
+       
+       /**
+        * Returns the matches of the last string.
+        *
+        * @return      array
+        */
+       public function getMatches() {
+               return $this->matches;
+       }
+       
+       /**
+        * Returns the compiled regex.
+        *
+        * @return      string
+        */
+       public function getRegex() {
+               return $this->regex;
+       }
+}
index 364664439921d8960b3174cbbbbd6a575806805a..9457492f14419bbd7f694ac697cdab5b339a5ef5 100644 (file)
@@ -13,6 +13,7 @@ use wcf\system\io\File;
 use wcf\system\io\Tar;
 use wcf\system\language\LanguageFactory;
 use wcf\system\package\PackageArchive;
+use wcf\system\Regex;
 use wcf\system\session\ACPSessionFactory;
 use wcf\system\session\SessionHandler;
 use wcf\system\setup\Installer;
@@ -1037,7 +1038,7 @@ class WCFSetup extends WCF {
                
                // delete tmp files
                $directory = TMP_DIR.'/';
-               DirectoryUtil::getInstance($directory)->removePattern('~\.tar(\.gz)?$~', true);
+               DirectoryUtil::getInstance($directory)->removePattern(new Regex('\.tar(\.gz)?$'), true);
        }
        
        /**
index 04bcc94a186e9cb2dd2f43d32ccf98c35982edd6..5ba9642a015359e5ddbf381172b51bb4a7842fa6 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\cache\source;
 use wcf\system\exception\SystemException;
 use wcf\system\io\File;
 use wcf\system\Callback;
+use wcf\system\Regex;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
 use wcf\util\DirectoryUtil;
@@ -106,7 +107,7 @@ class DiskCacheSource implements ICacheSource {
                        if (!@touch($filename, 1)) {
                                @unlink($filename);
                        }
-               }), '%^'.$directory.$filepattern.'$%i');
+               }), new Regex('^'.$directory.$filepattern.'$', Regex::CASE_INSENSITIVE));
        }
        
        /**
@@ -207,7 +208,7 @@ class DiskCacheSource implements ICacheSource {
                while ($row = $statement->fetchArray()) {
                        $packageDir = FileUtil::getRealPath(WCF_DIR.$row['packageDir']);
                        $cacheDir = $packageDir.'cache';
-                       DirectoryUtil::getInstance($cacheDir)->removePattern('~.*\.php$~');
+                       DirectoryUtil::getInstance($cacheDir)->removePattern(new Regex('.*\.php$'));
                }
        }
 }
index b9757dbc2fa8a524236a651e148502bc836d545c..441e6b201d6eff4abc0f19c8c8022b5e540426f6 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\template;
 use wcf\system\cache\CacheHandler;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\SystemException;
+use wcf\system\Regex;
 use wcf\system\SingletonFactory;
 use wcf\util\HeaderUtil;
 use wcf\util\StringUtil;
@@ -586,7 +587,7 @@ class TemplateEngine extends SingletonFactory {
                if (empty($compileDir)) $compileDir = WCF_DIR.'templates/compiled/';
 
                // delete compiled templates
-               DirectoryUtil::getInstance($compileDir)->removePattern('~.*_.*_.*\.php$~');
+               DirectoryUtil::getInstance($compileDir)->removePattern(new Regex('.*_.*_.*\.php$'));
        }
        
        /**
index 7e09a2c044233eb0002507d337de96ed57e8c5fe..8586b50c0b988c244eea3da918407a7d92e4bcb7 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\util;
 use wcf\system\Callback;
+use wcf\system\Regex;
 use wcf\system\exception\SystemException;
 
 /**
@@ -123,20 +124,20 @@ class DirectoryUtil {
        /**
         * Returns a sorted list of files.
         * 
-        * @param       integer         $order                  sort-order
-        * @param       string          $pattern                pattern to match
-        * @param       boolean         $negativeMatch          true if the pattern should be inversed
+        * @param       integer                 $order                  sort-order
+        * @param       wcf\system\Regex        $pattern                pattern to match
+        * @param       boolean                 $negativeMatch          true if the pattern should be inversed
         * @return      array<string>
         */
-       public function getFiles($order = SORT_ASC, $pattern = '', $negativeMatch = false) {
+       public function getFiles($order = SORT_ASC, Regex $pattern = null, $negativeMatch = false) {
                // scan the folder
                $this->scanFiles();
                $files = $this->files;
                
                // sort out non matching files
-               if (!empty($pattern)) {
+               if ($pattern !== null) {
                        foreach ($files as $filename => $value) {
-                               if (((bool) preg_match($pattern, $filename)) == $negativeMatch) unset($files[$filename]);
+                               if (((bool) $pattern->match($filename)) === $negativeMatch) unset($files[$filename]);
                        }
                }
                
@@ -160,19 +161,19 @@ class DirectoryUtil {
         * Returns a sorted list of files, with DirectoryIterator object as value
         * 
         * @param       integer                 $order                  sort order
-        * @param       string                  $pattern                pattern to match
+        * @param       wcf\system\Regex        $pattern                pattern to match
         * @param       boolean                 $negativeMatch          should the pattern be inversed
         * @return      array<\DirectoryIterator>
         */
-       public function getFileObjects($order = SORT_ASC, $pattern = '', $negativeMatch = false) {
+       public function getFileObjects($order = SORT_ASC, Regex $pattern = null, $negativeMatch = false) {
                // scan the folder
                $this->scanFileObjects();
                $objects = $this->fileObjects;
                
                // sort out non matching files
-               if (!empty($pattern)) {
+               if ($pattern !== null) {
                        foreach ($objects as $filename => $value) {
-                               if (((bool) preg_match($pattern, $filename)) == $negativeMatch) unset($objects[$filename]);
+                               if (((bool) $pattern->match($filename)) === $negativeMatch) unset($objects[$filename]);
                        }
                }
                
@@ -256,11 +257,13 @@ class DirectoryUtil {
         * Executes a callback on each file and returns false if callback is invalid.
         * 
         * @param       wcf\system\Callback             $callback
-        * @param       string                          $pattern        callback is only applied to files matching the given pattern
+        * @param       wcf\system\Regex                $pattern        callback is only applied to files matching the given pattern
         * @return      boolean
         */
-       public function executeCallback(Callback $callback, $pattern = '') {
-               $files = $this->getFileObjects(self::SORT_NONE, $pattern);
+       public function executeCallback(Callback $callback, Regex $pattern = null) {
+               if ($pattern !== null) $files = $this->getFileObjects(self::SORT_NONE, $pattern);
+               else $files = $this->getFileObjects(self::SORT_NONE);
+               
                foreach ($files as $filename => $obj) {
                        $callback($filename, $obj);
                }
@@ -272,7 +275,7 @@ class DirectoryUtil {
         * Recursive remove of directory.
         */
        public function removeAll() {
-               $this->removePattern('');
+               $this->removePattern(new Regex('.'));
                
                // destroy cached instance
                unset(static::$instances[$this->recursive][$this->directory]);
@@ -281,10 +284,10 @@ class DirectoryUtil {
        /**
         * Removes all files that match the given pattern.
         * 
-        * @param       string                  $pattern                pattern to match
+        * @param       wcf\system\Regex        $pattern                pattern to match
         * @param       boolean                 $negativeMatch          should the pattern be inversed
         */
-       public function removePattern($pattern, $negativeMatch = false) {
+       public function removePattern(Regex $pattern, $negativeMatch = false) {
                if (!$this->recursive) throw new SystemException('Removing of files only works in recursive mode');
                
                $files = $this->getFileObjects(self::SORT_NONE, $pattern, $negativeMatch);