public $default_tags = [ /* * Title underline or horizontal break * * a title underline is after a string which is then considered as a title, eg "----------" * a horizontal break is after a blank line, see "hr" below * * the following blank line and preceding blank line are ignored * * the first two characters only are used to determine the underline type, leading spaces are ignored * the key is referred to as the $underline_type or $title_type */ '==' => '<h1 style="color: black; font-size: 1.3em; margin-bottom: 1.5em">', '--' => '<h2 style="color: black; font-size: 1.1em; margin-top: 1.5em;">', '++' => '<h3>', '**' => '<h4>',
/* * Table row separator * * eg "+-----+-----+" for any data, or "+=====+=====+" for column headers * "+=" may be used under the first row of cells for column headers, the cells will be rendered as "<th>" * the first two characters are used to determine the row type, leading spaces are ignored * * the top row separator of a table may start with "+~" * a "~" may be used in place of a "-" to indicate the horizontal alignement of text in cells of a given column, * the "~" must be after or before a "+", or somewhere in between two "+", eg "+~-------+---~----+-------~+--------+" * "|left | center | right|default |" * this applies to data cells only * a number in a cell is aligned on the right by default * * a table may not be inside a cell, doing so will merge cells together * a list may be inside a cell but will not be rendered with "<ul>" or "<ol>" tags * * the key is referred to as the $row_type */ '+-' => [ 'table' => '<table style="border-collapse: collapse; margin-bottom: .8em; margin-top: .2em">', 'tr' => '<tr>', ],
/* * Table cell separator * * eg "| 123 | 456 |", the closing "|" is mandatory, leading spaces are ignored * a cell may span several lines, eg "| this is in data |" * | in the same cell |" * a cell may contain a table which will be rendered as such * a list within a table cell will not be rendered with "<ul>" or "<ol>" tags * note the use "%s" used by the "text-align" property, see add_table_row() */ '|' => [ 'td' => '<td style="border: 1px solid gray; padding: .2em; vertical-align: top; text-align: %s">', 'th' => '<th style="border: 1px solid gray; padding: .2em; vertical-align: top">', ],
/* * Unordered and ordered lists * * lists may be embedded, the key is used to differenciate them, leading spaces are ignored * * note the use the "ul" key including for ordered lists * * a list item may be broken into several lines excluding blank lines * a list item inside a table will be processed as a string and not rendered as a html list * a table may be inside a list and will be rendered as such * * the key is referred to as the $list_type */
/** * Adds a blank line in a multiple-line list item * * @param array $lines * @param int $index */ function add_blank_line_in_list_item(array $lines, $index) { if ($this->need_blank_line($lines, $index)) { $this->html[] = $this->start_tags['br']; } }
/** * Adds a line * * @param string $string */ function add_line($line) { $this->add_string($line); $this->html[] = $this->start_tags['br']; }
/** * Adds a list item, eg "</li>abc<li> * * @param string $list_type eg "- " * @param string $list_item * @see $list_type in self::$default_tags */ function add_list_item($list_type, $list_item) { $this->html[] = $this->end_tags[$list_type]['li']; $this->html[] = $this->start_tags[$list_type]['li']; $this->add_string($list_item); }
/** * Adds a string * * @param string $string */ function add_string($string) { $this->html[] = htmlspecialchars($string); }
/** * Adds a table row, eg "<tr><td>abc</td></tr> * * @param string $row_type * @see $row_type in self::$default_tags */ function add_table_row($row_type = '+-') { $this->html[] = $this->start_tags['+-']['tr'];
if ($this->table_cell_type == 'th') { // this is the first row, sets the cells to data or headers $cell_type = $row_type == '+-' ? 'td' : 'th'; } else { // this is not the first row, cells may only have data $cell_type = 'td'; }
/** * Closes embedded lists, eg "</li></ul></li></ul>" * * @param string $list_type eg "- ", there must be an entry with the same value in $this->list_types except the last one * * @see $list_type in self::$default_tags */ function close_embedded_lists($list_type) { do { $previous_list_type = array_pop($this->list_types); $this->html[] = $this->end_tags[$previous_list_type]['li']; $this->html[] = $this->end_tags[$previous_list_type]['ul']; $last_list_type = end($this->list_types); } while ($last_list_type and $last_list_type != $list_type); }
/** * Closes the current list or embeded lists, eg "</li></ul></li></ul>" * * @see $list_type in self::$default_tags */ function close_list() { while ($list_type = array_pop($this->list_types)) { $this->html[] = $this->end_tags[$list_type]['li']; $this->html[] = $this->end_tags[$list_type]['ul']; } }
/** * Closes a table, eg "</table> */ function close_table() { if ($this->table_cells) { // the last table row separator has been omitted, adds the row $this->add_table_row(); }
/** * Returns the ordered list item including its start number and type * * The ol tag type is limited to "1", "A", "a". * The ol tag start number is limited to 26 for the letter type. * * @param array $lines * @param int $index * @return array|null [$list_type, $ol_start, $ol_type, $list_item] * eg ["#1", "1", "1", "abc"] or ["#A", "1", "A", "abc"] * @see $list_type in self::$default_tags */ function get_ordered_list_item(array $lines, $index) { if ($this->table_cell_type or $this->get_underline($lines, $index + 1)) { // this is a list inside a table (not processed) or actually a title as it is underlined return null; }
/** * Returns the table row type * * @param array $lines * @param int $index * @return string * @see $row_type in self::$default_tags */ function get_table_row_type(array $lines, $index) { $row_type = preg_match('/^(\+[=~-])/', $lines[$index], $match) ? $match[1] : null;
return $row_type; }
/** * Returns the title type * * @param array $lines * @param int $index * @return string * @see $underline_type in self::$default_tags */ function get_title_type(array $lines, $index) { $title_type = $this->get_underline($lines, $index + 1);
return $title_type; }
/** * Returns the underline or title type * * @param array $lines * @param int $index * @return string|null the underline key, eg "==" * @see $underline_type in self::$default_tags */ function get_underline($lines, $index) { if (! isset($lines[$index])){ return null;
/** * Return the unordered list item including its type
* @param string $line * @return array|null [$list_type, $list_item], eg ["- ", "abc"] * @see $list_type in self::$default_tags */ function get_unordered_list_item($line) { if ($this->table_cell_type or ! preg_match('~^([=*+-] ) *(.*)$~', $line, $match)) { // this is a list inside a table (not processed) or not a list item return null; }
list(, $list_type, $list_item) = $match;
return [$list_type, $list_item]; }
/** * Checks wether the following line is a title skipping blank lines * * @param array $lines * @param int $index * @return boolean */ function is_title_next_line(array $lines, $index) { // skips blank lines do { $index++; } while (isset($lines[$index]) and $lines[$index] == '');
/** * Checks wether a blank line is needed after a line * * @param array $lines * @param int $index */ function need_blank_line(array $lines, $index) { $index++;
$no_need_blank_line = (! isset($lines[$index]) or $this->get_ordered_list_item($lines, $index) or $this->get_unordered_list_item($lines[$index]) or $this->get_table_row_type($lines, $index) or $this->get_table_cells($lines, $index));
/** * Processes a blank line, eg "br />" * * @param array $lines * @param string $index */ function process_blank_line(array $lines, $index = null) { if ($this->table_cell_type) { // this is the end of a table, closes the table $this->close_table(); $this->ignore_blank_line = true; }
if ($this->list_types) { // this is the end of a list or embedded lists, closes the list(s) $this->close_list(); $this->ignore_blank_line = true; }
if (! $index) { // this is (passed) the end of the text return; }
if ($this->ignore_blank_line or $this->get_underline($lines, $index + 1) or $this->is_title_next_line($lines, $index)) { // the previous line is an underline table or list, // or the following line is a horizontal line or a title, ignores the blank line
} else if (isset($lines[$index])) { // this is a line break, adds the line break $this->html[] = $this->start_tags['br']; }
$this->ignore_blank_line = false; }
/** * Processes a line of text * * @param array $lines * @param int $index */ function process_line(array $lines, $index) { $line = $lines[$index];
if (! $line) { $this->process_blank_line($lines, $index);
} else if ($title_type = $this->get_title_type($lines, $index)) { $this->process_title($line, $title_type);
} else if ($this->get_underline($lines, $index)) { $this->process_underline($line);
} else if ($list_item = $this->get_unordered_list_item($line)) { $this->process_unordered_list_item($list_item); $this->add_blank_line_in_list_item($lines, $index);
/** * Processes a table row * * @param string $row_type * @param string $row_separator * @see $row_type in self::$default_tags */ function process_table_row($row_type, $row_separator = null) { if (! $this->table_cell_type) { // this is a new table, opens the table $this->open_table(); $this->get_table_cells_alignment($row_separator);
} else if ($this->table_cells) { // this an existing table, adds the row $this->add_table_row($row_type); }
$this->ignore_blank_line = false; }
/** * Converts the text into html * * @param string $text * @return string */ function process_text($text) { $lines = $this->explode_text($text);
foreach ($lines as $index => $line) { $this->process_line($lines, $index); }
/** * Processes an underline, eg "----------" * * @param string $underline */ function process_underline($underline) { if (! $this->ignore_blank_line) { // this is an horizontal line, adds the horizontal line $this->html[] = $this->start_tags['hr']; }
if (! $current_list_type) { // this is a new list, opens the list $this->open_list($list_type, $list_item, $list_start_tag);
} else if ($list_type == $current_list_type) { // this is an item in the same list, adds the item $this->add_list_item($list_type, $list_item);
} else if (! in_array($list_type, $this->list_types)) { // this is an embedded list, opens the list $this->open_list($list_type, $list_item, $list_start_tag);
} else { // this is an item from a parent list, closes the embedded list(s), adds the item $this->close_embedded_lists($list_type); $this->add_list_item($list_type, $list_item); }
$this->ignore_blank_line = false; }
/** * Sets a end tag * * @param mixed $tag, eg "<h1>" or ["li" => "<li>", "ul" => "<ul>"] * @throws Exception * @return mixed eg "</h1>" or ["li" => "</li>", "ul" => "</ul>"] */ function set_end_tag($tag) { if (is_array($tag)) { $end_tag = array_map([$this, 'set_end_tag'], $tag);