]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - includes/StringUtils.php
MediaWiki 1.16.2
[autoinstallsdev/mediawiki.git] / includes / StringUtils.php
index 374fb002b6110013c3a71b0637d95edeb79848bf..0be88df5ae35d35648705ffd165dc88cb3d6931e 100644 (file)
@@ -4,14 +4,14 @@
  */
 class StringUtils {
        /**
-        * Perform an operation equivalent to 
+        * Perform an operation equivalent to
         *
         *     preg_replace( "!$startDelim(.*?)$endDelim!", $replace, $subject );
         *
         * except that it's worst-case O(N) instead of O(N^2)
         *
         * Compared to delimiterReplace(), this implementation is fast but memory-
-        * hungry and inflexible. The memory requirements are such that I don't 
+        * hungry and inflexible. The memory requirements are such that I don't
         * recommend using it on anything but guaranteed small chunks of text.
         */
        static function hungryDelimiterReplace( $startDelim, $endDelim, $replace, $subject ) {
@@ -27,9 +27,9 @@ class StringUtils {
                }
                return $output;
        }
-       
+
        /**
-        * Perform an operation equivalent to 
+        * Perform an operation equivalent to
         *
         *   preg_replace_callback( "!$startDelim(.*)$endDelim!s$flags", $callback, $subject )
         *
@@ -40,9 +40,9 @@ class StringUtils {
         */
        # If the start delimiter ends with an initial substring of the end delimiter,
        # e.g. in the case of C-style comments, the behaviour differs from the model
-       # regex. In this implementation, the end must share no characters with the 
-       # start, so e.g. /*/ is not considered to be both the start and end of a 
-       # comment. /*/xy/*/ is considered to be a single comment with contents /xy/. 
+       # regex. In this implementation, the end must share no characters with the
+       # start, so e.g. /*/ is not considered to be both the start and end of a
+       # comment. /*/xy/*/ is considered to be a single comment with contents /xy/.
        static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, $subject, $flags = '' ) {
                $inputPos = 0;
                $outputPos = 0;
@@ -53,13 +53,13 @@ class StringUtils {
                $strcmp = strpos( $flags, 'i' ) === false ? 'strcmp' : 'strcasecmp';
                $endLength = strlen( $endDelim );
                $m = array();
-               
-               while ( $inputPos < strlen( $subject ) && 
-                 preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) ) 
+
+               while ( $inputPos < strlen( $subject ) &&
+                 preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
                {
                        $tokenOffset = $m[0][1];
                        if ( $m[1][0] != '' ) {
-                               if ( $foundStart && 
+                               if ( $foundStart &&
                                  $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
                                {
                                        # An end match is present at the same location
@@ -77,16 +77,20 @@ class StringUtils {
                        }
 
                        if ( $tokenType == 'start' ) {
-                               $inputPos = $tokenOffset + $tokenLength;
                                # Only move the start position if we haven't already found a start
                                # This means that START START END matches outer pair
                                if ( !$foundStart ) {
                                        # Found start
+                                       $inputPos = $tokenOffset + $tokenLength;
                                        # Write out the non-matching section
                                        $output .= substr( $subject, $outputPos, $tokenOffset - $outputPos );
                                        $outputPos = $tokenOffset;
                                        $contentPos = $inputPos;
                                        $foundStart = true;
+                               } else {
+                                       # Move the input position past the *first character* of START,
+                                       # to protect against missing END when it overlaps with START
+                                       $inputPos = $tokenOffset + 1;
                                }
                        } elseif ( $tokenType == 'end' ) {
                                if ( $foundStart ) {
@@ -112,13 +116,13 @@ class StringUtils {
        }
 
        /*
-        * Perform an operation equivalent to 
+        * Perform an operation equivalent to
         *
         *   preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
         *
         * @param string $startDelim Start delimiter regular expression
         * @param string $endDelim End delimiter regular expression
-        * @param string $replace Replacement string. May contain $1, which will be 
+        * @param string $replace Replacement string. May contain $1, which will be
         *               replaced by the text between the delimiters
         * @param string $subject String to search
         * @return string The string with the matches replaced
@@ -138,10 +142,10 @@ class StringUtils {
         */
        static function explodeMarkup( $separator, $text ) {
                $placeholder = "\x00";
-               
+
                // Remove placeholder instances
                $text = str_replace( $placeholder, '', $text );
-               
+
                // Replace instances of the separator inside HTML-like tags with the placeholder
                $replacer = new DoubleReplacer( $separator, $placeholder );
                $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
@@ -151,7 +155,7 @@ class StringUtils {
                foreach( $items as $i => $str ) {
                        $items[$i] = str_replace( $placeholder, $separator, $str );
                }
-               
+
                return $items;
        }
 
@@ -167,10 +171,22 @@ class StringUtils {
                $string = str_replace( '$', '\\$', $string );
                return $string;
        }
+
+       /**
+        * Workalike for explode() with limited memory usage.
+        * Returns an Iterator
+        */
+       static function explode( $separator, $subject ) {
+               if ( substr_count( $subject, $separator ) > 1000 ) {
+                       return new ExplodeIterator( $separator, $subject );
+               } else {
+                       return new ArrayIterator( explode( $separator, $subject ) );
+               }
+       }
 }
 
 /**
- * Base class for "replacers", objects used in preg_replace_callback() and 
+ * Base class for "replacers", objects used in preg_replace_callback() and
  * StringUtils::delimiterReplaceCallback()
  */
 class Replacer {
@@ -207,7 +223,7 @@ class DoubleReplacer extends Replacer {
                $this->to = $to;
                $this->index = $index;
        }
-       
+
        function replace( $matches ) {
                return str_replace( $this->from, $this->to, $matches[$this->index] );
        }
@@ -283,6 +299,17 @@ class ReplacementArray {
                $this->fss = false;
        }
 
+       function removePair( $from ) {
+               unset($this->data[$from]);
+               $this->fss = false;
+       }
+
+       function removeArray( $data ) {
+               foreach( $data as $from => $to )
+                       $this->removePair( $from );
+               $this->fss = false;
+       }
+
        function replace( $subject ) {
                if ( function_exists( 'fss_prep_replace' ) ) {
                        wfProfileIn( __METHOD__.'-fss' );
@@ -300,4 +327,89 @@ class ReplacementArray {
        }
 }
 
+/**
+ * An iterator which works exactly like:
+ * 
+ * foreach ( explode( $delim, $s ) as $element ) {
+ *    ...
+ * }
+ *
+ * Except it doesn't use 193 byte per element
+ */
+class ExplodeIterator implements Iterator {
+       // The subject string
+       var $subject, $subjectLength;
+
+       // The delimiter
+       var $delim, $delimLength;
+
+       // The position of the start of the line
+       var $curPos;
+
+       // The position after the end of the next delimiter
+       var $endPos;
+
+       // The current token
+       var $current;
+
+       /** 
+        * Construct a DelimIterator
+        */
+       function __construct( $delim, $s ) {
+               $this->subject = $s;
+               $this->delim = $delim;
+
+               // Micro-optimisation (theoretical)
+               $this->subjectLength = strlen( $s );
+               $this->delimLength = strlen( $delim );
+
+               $this->rewind();
+       }
+
+       function rewind() {
+               $this->curPos = 0;
+               $this->endPos = strpos( $this->subject, $this->delim );
+               $this->refreshCurrent();
+       }
+
+
+       function refreshCurrent() {
+               if ( $this->curPos === false ) {
+                       $this->current = false;
+               } elseif ( $this->curPos >= $this->subjectLength ) {
+                       $this->current = '';
+               } elseif ( $this->endPos === false ) {
+                       $this->current = substr( $this->subject, $this->curPos );
+               } else {
+                       $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos );
+               }
+       }
+
+       function current() {
+               return $this->current;
+       }
+
+       function key() {
+               return $this->curPos;
+       }
+
+       function next() {
+               if ( $this->endPos === false ) {
+                       $this->curPos = false;
+               } else {
+                       $this->curPos = $this->endPos + $this->delimLength;
+                       if ( $this->curPos >= $this->subjectLength ) {
+                               $this->endPos = false;
+                       } else {
+                               $this->endPos = strpos( $this->subject, $this->delim, $this->curPos );
+                       }
+               }
+               $this->refreshCurrent();
+               return $this->current;
+       }
+
+       function valid() {
+               return $this->curPos !== false;
+       }
+}