AlkantarClanX12
Current Path : /home/thanudqk/siamfreetour.com/wp-content/plugins/tablepress/libraries/ |
Current File : /home/thanudqk/siamfreetour.com/wp-content/plugins/tablepress/libraries/html-parser.class.php |
<?php /** * HTML Parsing class for TablePress, used for import of HTML files. * * @package TablePress * @subpackage Import * @author Tobias Bäthge * @since 2.0.0 */ // Prohibit direct script loading. defined( 'ABSPATH' ) || die( 'No direct script access allowed!' ); /** * HTML Parsing class * * @package TablePress * @subpackage Import * @author Tobias Bäthge * @since 2.0.0 */ abstract class HTML_Parser { /** * Parses HTML string into a two-dimensional array, maybe with options. * * @since 2.0.0 * * @param string $html Data to be parsed. * @return array<string, mixed>|WP_Error Array with table data and options (current table head and foot row) on success, WP_Error on error. */ public static function parse( string $html ) /* : array|WP_Error */ { if ( false === stripos( $html, '<table' ) || false === stripos( $html, '</table>' ) ) { return new WP_Error( 'table_import_html_no_table_found' ); } // Prepend XML declaration, for better encoding support. $full_html = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . $html; if ( function_exists( 'libxml_disable_entity_loader' ) ) { /* * Don't expand external entities, see https://websec.io/2012/08/27/Preventing-XXE-in-PHP.html. * Silence warnings as the function is deprecated in PHP 8, but can be necessary with LIBXML_NOENT being defined, see https://core.trac.wordpress.org/changeset/50714. */ @libxml_disable_entity_loader( true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,Generic.PHP.DeprecatedFunctions.Deprecated } // No warnings/errors raised, but stored internally. libxml_use_internal_errors( true ); $dom = new DOMDocument( '1.0', 'UTF-8' ); // No strict checking for invalid HTML. $dom->strictErrorChecking = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $result = $dom->loadHTML( $full_html ); if ( ! $result ) { return new WP_Error( 'table_import_html_dom_load_html_failed' ); } $dom_tables = $dom->getElementsByTagName( 'table' ); if ( 0 === count( $dom_tables ) ) { return new WP_Error( 'table_import_html_dom_get_tables' ); } libxml_clear_errors(); // Clear errors so that we only catch those inside the table in the next line. $table = simplexml_import_dom( $dom_tables->item( 0 ) ); // @phpstan-ignore argument.type if ( is_null( $table ) ) { return new WP_Error( 'table_import_html_simplexml_import_dom_failed' ); } $errors = libxml_get_errors(); libxml_clear_errors(); if ( ! empty( $errors ) ) { $output = '<strong>' . __( 'The imported file contains errors:', 'tablepress' ) . '</strong><br><br>'; foreach ( $errors as $error ) { switch ( $error->level ) { case LIBXML_ERR_WARNING: $output .= "Warning {$error->code}: {$error->message} in line {$error->line}, column {$error->column}<br>"; break; case LIBXML_ERR_ERROR: $output .= "Error {$error->code}: {$error->message} in line {$error->line}, column {$error->column}<br>"; break; case LIBXML_ERR_FATAL: $output .= "Fatal Error {$error->code}: {$error->message} in line {$error->line}, column {$error->column}<br>"; break; } } wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } $html_table = array( 'data' => array(), 'options' => array(), ); if ( isset( $table->thead ) ) { $head_rows = self::_import_html_rows( $table->thead[0]->tr ); // @phpstan-ignore property.nonObject $html_table['data'] = array_merge( $html_table['data'], $head_rows ); $html_table['options']['table_head'] = count( $head_rows ); } if ( isset( $table->tbody ) ) { $html_table['data'] = array_merge( $html_table['data'], self::_import_html_rows( $table->tbody[0]->tr ) ); // @phpstan-ignore property.nonObject } if ( isset( $table->tr ) ) { $html_table['data'] = array_merge( $html_table['data'], self::_import_html_rows( $table->tr ) ); } if ( isset( $table->tfoot ) ) { $foot_rows = self::_import_html_rows( $table->tfoot[0]->tr ); // @phpstan-ignore property.nonObject $html_table['data'] = array_merge( $html_table['data'], $foot_rows ); $html_table['options']['table_foot'] = count( $foot_rows ); } return $html_table; } /** * Converts table HTML rows to an array. * * @since 2.0.0 * * @param SimpleXMLElement $element XMLElement. * @return array<int, array<int, string>> SimpleXMLElement exported to an array. */ protected static function _import_html_rows( SimpleXMLElement $element ): array { $rows = array(); // Container for the table data. $rowspans = array(); // Container for information about rowspans in rows that follow the currently processed row. $row_idx = 0; foreach ( $element as $row ) { // If all cells in a row should be merged with the cells in the row above, add the trigger word to each of them (should be very rare). while ( isset( $rowspans[ $row_idx ] ) && count( $rowspans[ $row_idx ] ) === count( $rows[ $row_idx - 1 ] ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $rows[] = $rowspans[ $row_idx ]; ++$row_idx; } $new_row = array(); $column_idx = 0; foreach ( $row as $cell ) { // If a cell in a row should be merged with the cell above it, add the trigger word to it. while ( isset( $rowspans[ $row_idx ][ $column_idx ] ) ) { $new_row[] = $rowspans[ $row_idx ][ $column_idx ]; ++$column_idx; } $cell_xml = $cell->asXML(); // Get content between <td>...</td>, or <th>...</th>, possibly with HTML. if ( false !== $cell_xml && 1 === preg_match( '#<t[d|h].*?>(.*)</t[d|h]>#is', $cell_xml, $matches ) ) { /* * Decode HTML entities again, as there might be some left especially in attributes of HTML tags in the cells, * see https://www.php.net/manual/en/simplexmlelement.asxml.php#107137. */ $new_row[] = html_entity_decode( $matches[1], ENT_NOQUOTES, 'UTF-8' ); // Search for colspan and rowspan attributes in the cell's HTML tag. $colspan = 1; $rowspan = 1; if ( 1 === preg_match( '#<t[d|h].*colspan=["\']?(\d+)["\']?.*?>#is', $cell_xml, $matches ) ) { $colspan = (int) $matches[1]; } if ( 1 === preg_match( '#<t[d|h].*rowspan=["\']?(\d+)["\']?.*?>#is', $cell_xml, $matches ) ) { $rowspan = (int) $matches[1]; } // Add cells with the colspan trigger word, if merged cells across columns were found. for ( $i = 1; $i < $colspan; $i++ ) { $new_row[] = '#colspan#'; } // If merged cells across rows were found, add trigger words to a temporary variable. for ( $i = 1; $i < $rowspan; $i++ ) { if ( ! isset( $rowspans[ $row_idx + $i ] ) ) { $rowspans[ $row_idx + $i ] = array(); } $rowspans[ $row_idx + $i ][ $column_idx ] = '#rowspan#'; for ( $j = 1; $j < $colspan; $j++ ) { $rowspans[ $row_idx + $i ][ $column_idx + $j ] = '#span#'; } } } else { // Add an empty cell if no content could be extracted from the cell's HTML tag. $new_row[] = ''; } ++$column_idx; } // After the last cell in a row: If a cell in a row should be merged with the cell above it, add the trigger word to it. while ( isset( $rowspans[ $row_idx ][ $column_idx ] ) ) { $new_row[] = $rowspans[ $row_idx ][ $column_idx ]; ++$column_idx; } $rows[] = $new_row; ++$row_idx; } // After the last data row: If all cells in a row should be merged with the cells in the row above, add the trigger word to each of them (should be very rare). while ( isset( $rowspans[ $row_idx ] ) && count( $rowspans[ $row_idx ] ) === count( $rows[ $row_idx - 1 ] ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found $rows[] = $rowspans[ $row_idx ]; ++$row_idx; } return $rows; } } // class HTML_Parser