Passed
Push — master ( 17602b...453007 )
by Chris
04:37
created

Parse   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 65
lcom 1
cbo 12
dl 0
loc 241
rs 3.2
c 0
b 0
f 0

How to fix   Complexity   

Complex Class

Complex classes like Parse often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Parse, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
This project is Licenced under The MIT License (MIT)
4
5
Copyright (c) 2014 Christopher Seufert
6
7
Permission is hereby granted, free of charge, to any person obtaining a copy
8
of this software and associated documentation files (the "Software"), to deal
9
in the Software without restriction, including without limitation the rights
10
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
copies of the Software, and to permit persons to whom the Software is
12
furnished to do so, subject to the following conditions:
13
14
The above copyright notice and this permission notice shall be included in
15
all copies or substantial portions of the Software.
16
17
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
THE SOFTWARE.
24
25
 */
26
namespace Seufert\Hamle;
27
use Seufert\Hamle\Exception\ParseError;
28
use Seufert\Hamle\Parse\Filter as ParseFilter;
29
use Seufert\Hamle\Text;
30
31
/**
32
 * HAML Enhanced - Parser, parses hamle files,
33
 * executes it and leaves a .php file to cache it
34
 *
35
 * @author Chris Seufert <[email protected]>
36
 * @package hamle
37
 */
38
class Parse {
39
  /**
40
   * @param array $indents Array of indent levels
41
   */
42
  protected $indents;
43
  /**
44
   * @var Tag[] Array of Root Document Tags
45
   */
46
  public $root;
47
  /**
48
   * @var array $lines Each Line read in from template
49
   */
50
  protected $lines;
51
  /**
52
   * Regex for parsing each HAMLE line
53
   */
54
55
  const REGEX_PARSE_LINE = <<<'ENDREGEX'
56
/^(\s*)(?:(?:([a-zA-Z0-9-]*)((?:[\.#!][\w\-\_]+)*)(\[(?:(?:\{\$[^\}]+\})?[^\\\]{]*?(?:\\.)*?(?:{[^\$])*?)+\])?)|([_\/]{1,2})|([\|:\$]\w+)|({?\$[^}]+}?)|)(?: (.*))?$/
57
ENDREGEX;
58
59
  /**
60
   * @var int Current Line Number
61
   */
62
  protected $lineNo;
63
  /**
64
   * @var int Total Lines in File
65
   */
66
  protected $lineCount;
67
68
  function __construct() {
69
    $this->init();
70
  }
71
72
  /**
73
   * Clear Lines, and Line Number, so if output is
74
   * called, no output will be produced
75
   */
76
  protected function init() {
77
    $this->lines = array();
78
    $this->lineNo = 0;
79
    $this->lineCount = 0;
80
    $this->root = array();
81
  }
82
83
  protected function loadLines($s) {
84
    $this->lines = explode("\n", str_replace("\r", "", $s));
85
    $this->lineCount = count($this->lines);
86
    $this->lineNo = 0;
87
  }
88
89
  function parseFilter(ParseFilter $filter) {
90
    foreach ($this->root as $k => $tag) {
91
      $this->root[$k] = $filter->filterTag($tag);
92
    }
93
  }
94
95
  function parseSnip($s) {
96
    //save root tags
97
    /** @var Tag[] $roots */
98
    $roots = $this->root;
99
    $this->root = array();
100
    $this->loadLines($s);
101
    $this->procLines();
102
    $this->root = array_merge($roots, $this->root);
103
  }
104
105
  function applySnip() {
106
    /** @var Tag\Snippet[] $fwdSnip */
107
    $fwdSnip = array();
108
    /** @var Tag\Snippet[] $revSnip */
109
    $revSnip = array();
110
    /** @var Tag[] $roots */
111
    $roots = array();
112
    foreach ($this->root as $snip)
113
      if ($snip instanceOf Tag\Snippet) {
114
        if ($snip->getType() == "append") {
115
          array_unshift($revSnip, $snip);
116
        } else {
117
          $fwdSnip[] = $snip;
118
        }
119
      } else {
120
        $roots[] = $snip;
121
      }
122
    foreach ($fwdSnip as $snip)
123
      foreach ($roots as $root)
124
        $snip->apply($root);
125
    foreach ($revSnip as $snip)
126
      foreach ($roots as $root)
127
        $snip->apply($root);
128
    $this->root = $roots;
129
  }
130
131
  /**
132
   * Parse HAMLE template, from a string
133
   * @param string $s String to parse
134
   * @return string Parsed HAMLE as HTML
135
   */
136
  function str($s) {
137
    $this->init();
138
    $this->loadLines($s);
139
    $this->procLines();
140
  }
141
142
  function procLines() {
143
    /* @var $heir Tag[] Tag Heirachy Array */
144
    $heir = array();
145
    while ($this->lineNo < $this->lineCount) {
146
      $line = $this->lines[$this->lineNo];
147
      if (trim($line)) if (preg_match(self::REGEX_PARSE_LINE, $line, $m)) {
148
        if (FALSE !== strpos($m[1], "\t"))
149
          throw new ParseError("Tabs are not supported in templates at this time");
150
        $indent = strlen($m[1]);
151
        $tag = isset($m[2]) ? $tag = $m[2] : "";
152
        $classid = isset($m[3]) ? $m[3] : "";
153
        $params = str_replace(array('\[', '\]', '\\&'), array('[', ']', '%26'), isset($m[4]) ? $m[4] : "");
154
        $textcode = isset($m[5]) ? $m[5] : "";
155
        $text = isset($m[8]) ? $m[8] : "";
156
        $code = isset($m[6]) ? $m[6] : "";
157
        $i = self::indentLevel($indent);
158
        unset($m[0]);
159
        switch (strlen($code) ? $code[0] : ($textcode ? $textcode : "")) {
160
          case "|": //Control Tag
161
            if ($code == "|snippet")
162
              $hTag = new Tag\Snippet($text);
163
            elseif ($code == "|form")
164
              $hTag = new Tag\Form($text);
165
            elseif ($code == "|formhint")
166
              $hTag = new Tag\FormHint($text);
167
            elseif ($code == "|else") {
168
              $hTag = new Tag\Control(substr($code, 1), $heir[$i - 1]);
169
              $hTag->setVar($text);
170
            } else {
171
              $hTag = new Tag\Control(substr($code, 1));
172
              $hTag->setVar($text);
173
            }
174
            break;
175
          case ":": //Filter Tag
176
            $hTag = new Tag\Filter(substr($code, 1));
177
            $hTag->addContent($text, Text::TOKEN_CODE);
178
            foreach ($this->consumeBlock($indent) as $l)
179
              $hTag->addContent($l, Text::TOKEN_CODE);
180
            break;
181
          case "_": //String Tag
182
          case "__": //Unescape String Tag
183
            $hTag = new Tag\Text($textcode);
184
            $hTag->addContent($text);
185
            break;
186
          case "/": // HTML Comment
187
          case "//": // Non Printed Comment
188
            $hTag = new Tag\Comment($textcode);
189
            $hTag->addContent($text);
190
            foreach ($this->consumeBlock($indent) as $l)
191
              $hTag->addContent($l, Text::TOKEN_CODE);
192
            break;
193
          default:
194
            $attr = array();
195
            if(isset($params[0]) && $params[0] == "[") {
196
              $param = substr($params, 1, strlen($params) - 2);
197
              $param = str_replace('+','%2B', $param);
198
              $param = str_replace('\\&','%26', $param);
199
//              parse_str($param, $attr);
200
              $attr = $this->parseQueryString($param);
201
            }
202
            $class = array(); $id = ""; $ref = "";
203
            preg_match_all('/[#\.!][a-zA-Z0-9\-\_]+/m', $classid, $cid);
204
            if (isset($cid[0])) foreach ($cid[0] as $s) {
205
              if ($s[0] == "#") $id = substr($s, 1);
206
              if ($s[0] == ".") $class[] = substr($s, 1);
207
              if ($s[0] == "!") $ref = substr($s, 1);
208
            }
209
            if($ref)
210
              $hTag = new Tag\DynHtml($tag, $class, $attr, $id, $ref);
211
            else
212
              $hTag = new Tag\Html($tag, $class, $attr, $id);
213
            $hTag->addContent($text);
214
            break;
215
        }
216
        $heir[$i] = $hTag;
217
        if ($i > 0)
218
          $heir[$i - 1]->addChild($hTag);
219
        else
220
          $this->root[] = $hTag;
221
      } else
222
        throw new ParseError("Unable to parse line {$this->lineNo}\n\"$line\"/" . preg_last_error());
223
      $this->lineNo++;
224
    }
225
  }
226
227
  function parseQueryString($qs) {
228
    $out = [];
229
    foreach(explode('&',$qs) as $s) {
230
      $kv = explode('=',$s,2);
231
      $out[urldecode($kv[0])] = isset($kv[1])?urldecode($kv[1]):null;
232
    }
233
    return $out;
234
  }
235
236
  function output() {
237
    $out = "<?php\nuse Seufert\\Hamle;\n?>";
238
    foreach ($this->root as $tag)
239
      $out .= $tag->render();
240
    return $out;
241
242
  }
243
244
  function consumeBlock($indent) {
245
    $out = array();
246
    $m = array();
247
    while ($this->lineNo + 1 < $this->lineCount &&
248
        (!trim($this->lines[$this->lineNo + 1]) ||
249
            preg_match('/^(\s){' . $indent . '}((\s)+[^\s].*)$/',
250
                $this->lines[$this->lineNo + 1], $m))) {
251
      if (trim($this->lines[$this->lineNo + 1]))
252
        $out[] = $m[2];
253
      $this->lineNo++;
254
    }
255
    return $out;
256
  }
257
258
  function indentLevel($indent) {
259
    if (!isset($this->indents)) $this->indents = array();
260
    if (!count($this->indents)) {
261
      $this->indents = array(0 => $indent);
262
      // Key = indent level, Value = Depth in spaces
263
      return 0;
264
    }
265
    foreach ($this->indents as $k => $v) {
266
      if ($v == $indent) {
267
        $this->indents = array_slice($this->indents, 0, $k + 1);
268
        return $k;
269
      }
270
    }
271
    $this->indents[] = $indent;
272
    return max(array_keys($this->indents));
273
  }
274
275
  function getLineNo() {
276
    return $this->lineNo;
277
  }
278
}
279