@@ -11,6 +11,7 @@ final class Processor implements ProcessorInterface
1111 private $ parser ;
1212 private $ defaultHandler ;
1313 private $ recursionDepth = null ;
14+ private $ maxIterations = 1 ;
1415
1516 public function __construct (ExtractorInterface $ extractor , ParserInterface $ parser )
1617 {
@@ -74,17 +75,42 @@ public function setDefaultHandler(callable $handler)
7475 $ this ->defaultHandler = $ handler ;
7576 }
7677
78+ /**
79+ * Entry point for shortcode processing. Implements iterative algorithm for
80+ * both limited and unlimited number of iterations.
81+ *
82+ * @param string $text Text to process
83+ *
84+ * @return string
85+ */
86+ public function process ($ text )
87+ {
88+ $ iterations = $ this ->maxIterations === null ? 1 : $ this ->maxIterations ;
89+ while ($ iterations --)
90+ {
91+ $ newText = $ this ->processIteration ($ text , 0 );
92+ if ($ newText === $ text )
93+ {
94+ break ;
95+ }
96+ $ text = $ newText ;
97+ $ iterations += $ this ->maxIterations === null ? 1 : 0 ;
98+ }
99+
100+ return $ text ;
101+ }
102+
77103 /**
78104 * Expects matches sorted by position returned from Extractor. Matches are
79105 * processed from the last to the first to avoid replace position errors.
80106 * Edge cases are described in README.
81107 *
82- * @param string $text
83- * @param int $level
108+ * @param string $text Current text state
109+ * @param int $level Current recursion depth level
84110 *
85111 * @return string
86112 */
87- public function process ($ text , $ level = 0 )
113+ private function processIteration ($ text , $ level )
88114 {
89115 if (null !== $ this ->recursionDepth && $ level > $ this ->recursionDepth )
90116 {
@@ -93,12 +119,11 @@ public function process($text, $level = 0)
93119
94120 /** @var $matches Match[] */
95121 $ matches = array_reverse ($ this ->extractor ->extract ($ text ));
96-
97122 foreach ($ matches as $ match )
98123 {
99124 $ shortcode = $ this ->parser ->parse ($ match ->getString ());
100125 $ content = $ shortcode ->hasContent ()
101- ? $ this ->process ($ shortcode ->getContent (), $ level + 1 )
126+ ? $ this ->processIteration ($ shortcode ->getContent (), $ level + 1 )
102127 : $ shortcode ->getContent ();
103128 $ shortcode = new Shortcode ($ shortcode ->getName (), $ shortcode ->getParameters (), $ content );
104129 $ handler = $ this ->getHandler ($ shortcode ->getName ());
@@ -133,6 +158,27 @@ public function setRecursionDepth($depth)
133158 return $ this ;
134159 }
135160
161+ /**
162+ * Maximum number of iterations, null means infinite, any integer greater
163+ * than or equal to zero sets value.
164+ *
165+ * @param int|null $iterations
166+ *
167+ * @return self
168+ */
169+ public function setMaxIterations ($ iterations )
170+ {
171+ if (null !== $ iterations && !(is_int ($ iterations ) && $ iterations >= 0 ))
172+ {
173+ $ msg = 'Maximum number of iterations must be null (infinite) or integer >= 0! ' ;
174+ throw new \InvalidArgumentException ($ msg );
175+ }
176+
177+ $ this ->maxIterations = $ iterations ;
178+
179+ return $ this ;
180+ }
181+
136182 /**
137183 * @deprecated Use self::setRecursionDepth() instead
138184 *
0 commit comments