Passed
Branch master (dbe7ce)
by P.R.
03:59
created

Html::generateElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 4
dl 0
loc 12
ccs 7
cts 7
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Plaisio\Helper;
5
6
use SetBased\Exception\FallenException;
7
use SetBased\Helper\Cast;
8
9
/**
10
 * A utility class for generating HTML elements, tags, and attributes.
11
 */
12
final class Html
13
{
14
  //--------------------------------------------------------------------------------------------------------------------
15
  /**
16
   * The encoding of the generated HTML code.
17
   *
18
   * @var string
19
   *
20
   * @since 1.0.0
21
   * @api
22
   */
23
  public static $encoding = 'UTF-8';
24
25
  /**
26
   * Counter for generating unique element IDs.
27
   *
28
   * @var int
29
   */
30
  private static $autoId = 0;
31
32
  /**
33
   * Map from (some) unicode characters to ASCII characters.
34
   *
35
   * @var array
36
   */
37
  private static $trans = ['ß' => 'sz',
38
                           'à' => 'a',
39
                           'á' => 'a',
40
                           'â' => 'a',
41
                           'ã' => 'a',
42
                           'ä' => 'a',
43
                           'å' => 'a',
44
                           'æ' => 'ae',
45
                           'ç' => 'c',
46
                           'è' => 'e',
47
                           'é' => 'e',
48
                           'ê' => 'e',
49
                           'ë' => 'e',
50
                           'ì' => 'i',
51
                           'í' => 'i',
52
                           'î' => 'i',
53
                           'ï' => 'i',
54
                           'ð' => 'e',
55
                           'ñ' => 'n',
56
                           'ò' => 'o',
57
                           'ó' => 'o',
58
                           'ô' => 'o',
59
                           'õ' => 'o',
60
                           'ö' => 'o',
61
                           '÷' => 'x',
62
                           'ø' => 'o',
63
                           'ù' => 'u',
64
                           'ú' => 'u',
65
                           'û' => 'u',
66
                           'ü' => 'u',
67
                           'ý' => 'y',
68
                           'þ' => 'b',
69
                           'ÿ' => 'y',
70
                           'č' => 'c',
71
                           'ł' => 'l',
72
                           'š' => 's',
73
                           'ů' => 'u',
74
                           'ž' => 'z',
75
                           'а' => 'a',
76
                           'б' => 'b',
77
                           'в' => 'v',
78
                           'г' => 'g',
79
                           'д' => 'd',
80
                           'е' => 'e',
81
                           'ж' => 'zh',
82
                           'з' => 'z',
83
                           'и' => 'i',
84
                           'й' => 'i',
85
                           'к' => 'k',
86
                           'л' => 'l',
87
                           'м' => 'm',
88
                           'н' => 'n',
89
                           'о' => 'o',
90
                           'п' => 'p',
91
                           'р' => 'r',
92
                           'с' => 's',
93
                           'т' => 't',
94
                           'у' => 'u',
95
                           'ф' => 'f',
96
                           'х' => 'kh',
97
                           'ц' => 'ts',
98
                           'ч' => 'ch',
99
                           'ш' => 'sh',
100
                           'щ' => 'shch',
101
                           'ъ' => '',
102
                           'ы' => 'y',
103
                           'ь' => '',
104
                           'э' => 'e',
105
                           'ю' => 'iu',
106
                           'я' => 'ia',
107
                           'ё' => 'e'];
108
109
  //--------------------------------------------------------------------------------------------------------------------
110
  /**
111
   * Returns a string with proper conversion of special characters to HTML entities of an attribute of a HTML tag.
112
   *
113
   * Boolean attributes (e.g. checked, disabled and draggable, autocomplete also) are set when the value is none empty.
114
   *
115
   * @param string $name  The name of the attribute.
116
   * @param mixed  $value The value of the attribute.
117
   *
118
   * @return string
119
   *
120
   * @since 1.0.0
121
   * @api
122
   */
123 27
  public static function generateAttribute(string $name, $value): string
124
  {
125 27
    $html = '';
126
127
    switch ($name)
128
    {
129
      // Boolean attributes.
130 27
      case 'autofocus':
131 27
      case 'checked':
132 27
      case 'disabled':
133 27
      case 'hidden':
134 27
      case 'ismap':
135 27
      case 'multiple':
136 27
      case 'novalidate':
137 27
      case 'readonly':
138 27
      case 'required':
139 27
      case 'selected':
140 27
      case 'spellcheck':
141 3
        if (!empty($value))
142
        {
143 2
          $html = ' ';
144 2
          $html .= $name;
145 2
          $html .= '="';
146 2
          $html .= $name;
147 2
          $html .= '"';
148
        }
149 3
        break;
150
151
      // Annoying boolean attribute exceptions.
152 25
      case 'draggable':
153 25
      case 'contenteditable':
154 3
        if ($value!==null)
155
        {
156 3
          $html = ' ';
157 3
          $html .= $name;
158 3
          $html .= (!empty($value)) ? '="true"' : '="false"';
159
        }
160 3
        break;
161
162 23
      case 'autocomplete':
163 2
        if ($value!==null)
164
        {
165 2
          $html = ' ';
166 2
          $html .= $name;
167 2
          $html .= (!empty($value)) ? '="on"' : '="off"';
168
        }
169 2
        break;
170
171 21
      case 'translate':
172 3
        if ($value!==null)
173
        {
174 3
          $html = ' ';
175 3
          $html .= $name;
176 3
          $html .= (!empty($value)) ? '="yes"' : '="no"';
177
        }
178 3
        break;
179
180 19
      case 'class' and is_array($value):
181 3
        $classes = implode(' ', self::cleanClasses($value));
182 3
        if ($classes!=='')
183
        {
184 2
          $html = ' class="';
185 2
          $html .= htmlspecialchars($classes, ENT_QUOTES, self::$encoding);
186 2
          $html .= '"';
187
        }
188 3
        break;
189
190
      default:
191 16
        if ($value!==null && $value!=='')
192
        {
193 13
          $html = ' ';
194 13
          $html .= htmlspecialchars($name, ENT_QUOTES, self::$encoding);
195 13
          $html .= '="';
196 13
          $html .= self::txt2Html($value);
197 13
          $html .= '"';
198
        }
199 16
        break;
200
    }
201
202 27
    return $html;
203
  }
204
205
  //--------------------------------------------------------------------------------------------------------------------
206
  /**
207
   * Generates HTML code for an element.
208
   *
209
   * Note: tags for void elements such as '<br/>' are not supported.
210
   *
211
   * @param string                     $tagName    The name of the tag, e.g. a, form.
212
   * @param array                      $attributes The attributes of the tag. Special characters in the attributes will
213
   *                                               be replaced with HTML entities.
214
   * @param bool|int|float|string|null $innerText  The inner text of the tag.
215
   * @param bool                       $isHtml     If set the inner text is a HTML snippet, otherwise special
216
   *                                               characters in the inner text will be replaced with HTML entities.
217
   *
218
   * @return string
219
   *
220
   * @since 1.0.0
221
   * @api
222
   */
223 12
  public static function generateElement(string $tagName,
224
                                         array $attributes = [],
225
                                         $innerText = '',
226
                                         bool $isHtml = false): string
227
  {
228 12
    $html = self::generateTag($tagName, $attributes);
229 12
    $html .= ($isHtml) ? $innerText : self::txt2Html($innerText);
230 12
    $html .= '</';
231 12
    $html .= $tagName;
232 12
    $html .= '>';
233
234 12
    return $html;
235
  }
236
237
  //--------------------------------------------------------------------------------------------------------------------
238
  /**
239
   * Generates HTML code for a start tag of an element.
240
   *
241
   * @param string $tagName    The name of the tag, e.g. a, form.
242
   * @param array  $attributes The attributes of the tag. Special characters in the attributes will be replaced with
243
   *                           HTML entities.
244
   *
245
   * @return string
246
   *
247
   * @since 1.0.0
248
   * @api
249
   */
250 12
  public static function generateTag(string $tagName, array $attributes = []): string
251
  {
252 12
    $html = '<';
253 12
    $html .= $tagName;
254 12
    foreach ($attributes as $name => $value)
255
    {
256
      // Ignore attributes with leading underscore.
257 10
      if (strpos($name, '_')!==0) $html .= self::generateAttribute($name, $value);
258
    }
259 12
    $html .= '>';
260
261 12
    return $html;
262
  }
263
264
  //--------------------------------------------------------------------------------------------------------------------
265
  /**
266
   * Generates HTML code for a void element.
267
   *
268
   * Void elements are: area, base, br, col, embed, hr, img, input, keygen, link, menuitem, meta, param, source, track,
269
   * wbr. See <http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements>
270
   *
271
   * @param string $tagName    The name of the tag, e.g. img, link.
272
   * @param array  $attributes The attributes of the tag. Special characters in the attributes will be replaced with
273
   *                           HTML entities.
274
   *
275
   * @return string
276
   *
277
   * @since 1.0.0
278
   * @api
279
   */
280 2
  public static function generateVoidElement(string $tagName, array $attributes = []): string
281
  {
282 2
    $html = '<';
283 2
    $html .= $tagName;
284 2
    foreach ($attributes as $name => $value)
285
    {
286
      // Ignore attributes with leading underscore.
287 2
      if (strpos($name, '_')!==0) $html .= self::generateAttribute($name, $value);
288
    }
289 2
    $html .= '/>';
290
291 2
    return $html;
292
  }
293
294
  //--------------------------------------------------------------------------------------------------------------------
295
  /**
296
   * Returns a string that can be safely used as an ID for an element. The format of the id is 'abc_<n>' where n is
297
   * incremented with each call of this method.
298
   *
299
   * @return string
300
   *
301
   * @since 1.0.0
302
   * @api
303
   */
304 1
  public static function getAutoId(): string
305
  {
306 1
    self::$autoId++;
307
308 1
    return 'plaisio-id-'.self::$autoId;
309
  }
310
311
  //--------------------------------------------------------------------------------------------------------------------
312
  /**
313
   * Returns a string with special characters converted to HTML entities.
314
   * This method is a wrapper around [htmlspecialchars](http://php.net/manual/en/function.htmlspecialchars.php).
315
   *
316
   * @param bool|int|float|string|null $value The string with optionally special characters.
317
   *
318
   * @return string
319
   *
320
   * @since 1.0.0
321
   * @api
322
   */
323 24
  public static function txt2Html($value): string
324
  {
325
    switch (true)
326
    {
327 24
      case is_string($value):
328 17
        return htmlspecialchars($value, ENT_QUOTES, self::$encoding);
329
330 10
      case is_int($value):
331 9
      case is_float($value):
332 8
      case $value===null:
333 5
        return (string)$value;
334
335 6
      case $value===true:
336 1
        return '1';
337
338 5
      case $value===false:
339 3
        return '0';
340
341
      default:
342 2
        throw new FallenException('type', is_object($value) ? get_class($value) : gettype($value));
343
    }
344
  }
345
346
  //--------------------------------------------------------------------------------------------------------------------
347
  /**
348
   * Returns the slug of a string that can be safely used in an URL.
349
   *
350
   * @param string|null $string The string.
351
   *
352
   * @return string
353
   *
354
   * @since 1.1.0
355
   * @api
356
   */
357 1
  public static function txt2Slug(?string $string): string
358
  {
359 1
    if ($string===null) return '';
360
361 1
    return trim(preg_replace('/[^0-9a-z]+/', '-', strtr(mb_strtolower($string), self::$trans)), '-');
362
  }
363
364
  //--------------------------------------------------------------------------------------------------------------------
365
  /**
366
   * Removes empty and duplicate classes from an array with classes.
367
   *
368
   * @param array $classes The classes.
369
   *
370
   * @return array
371
   */
372 3
  private static function cleanClasses(array $classes): array
373
  {
374 3
    $ret = [];
375
376 3
    foreach ($classes as $class)
377
    {
378 2
      $tmp = Cast::toManString($class, '');
379 2
      if ($tmp!=='') $ret[] = $tmp;
380
    }
381
382 3
    return array_unique($ret);
383
  }
384
385
  //--------------------------------------------------------------------------------------------------------------------
386
}
387
388
//----------------------------------------------------------------------------------------------------------------------
389