]*>|#i', $content, $tags, PREG_OFFSET_CAPTURE ) ) {
return $content;
}
$result = '';
$last = 0;
$depth = 0;
$start = null;
$found = false;
foreach ( $tags[0] as $tag ) {
$is_open = '/' !== $tag[0][1];
if ( $is_open && 0 === $depth ) {
$start = self::is_ours( $tag[0] ) && ! self::framed( $content, $tag[1] ) ? $tag[1] : null;
}
$depth += $is_open ? 1 : -1;
if ( 0 === $depth && null !== $start ) {
$end = $tag[1] + strlen( $tag[0] );
$table = substr( $content, $start, $end - $start );
$result .= substr( $content, $last, $start - $last ) . self::wrap( $table );
$last = $end;
$start = null;
$found = true;
}
$depth = max( 0, $depth );
}
if ( ! $found ) {
return $content;
}
wp_enqueue_style( 'threece-elements' );
return $result . substr( $content, $last );
}
/**
* The table tag carries our class as a whole word.
*
* @param string $tag Opening table tag.
*/
private static function is_ours( $tag ): bool {
return (bool) preg_match( '#\sclass\s*=\s*(["\'])(?:[^"\']*\s)?' . self::CLASS_NAME . '(?:\s[^"\']*)?\1#i', $tag );
}
/**
* The opening tag of the table carries one of our modifier classes.
*
* @param string $table Table HTML.
* @param string $modifier Modifier without prefix, e.g. stack.
*/
private static function has_class( $table, $modifier ): bool {
return (bool) preg_match( '#^
]*\sclass\s*=\s*(["\'])(?:[^"\']*\s)?' . self::CLASS_NAME . '--' . preg_quote( $modifier, '#' ) . '(?:\s[^"\']*)?\1#i', $table );
}
/**
* Card layout on phones: every body cell gets the text of its column header as data-label. Header from thead or
* from a first row of th cells, column spans counted. Tables with row spans get no labels, they would be wrong.
*
* @param string $table Table HTML.
*/
private static function cell_labels( $table ): string {
if ( preg_match( '#\srowspan\s*=#i', $table ) || ! class_exists( 'DOMDocument' ) ) {
return $table;
}
$doc = new DOMDocument();
$previous = libxml_use_internal_errors( true );
$loaded = $doc->loadHTML( '' . $table . '
', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
libxml_clear_errors();
libxml_use_internal_errors( $previous );
$xp = new DOMXPath( $doc );
$box = $doc->getElementsByTagName( 'div' )->item( 0 );
$tbl = $doc->getElementsByTagName( 'table' )->item( 0 );
if ( ! $loaded || ! $box || ! $tbl ) {
return $table;
}
// Rows of this table only, not of tables nested in its cells.
$rows = $xp->query( './tr | ./thead/tr | ./tbody/tr | ./tfoot/tr', $tbl );
$head = $xp->query( './thead/tr[1]', $tbl )->item( 0 );
if ( ! $head && $rows && $rows->length ) {
$first = $rows->item( 0 );
$head = $first && 0.0 === $xp->evaluate( 'count(./td)', $first ) && $xp->evaluate( 'count(./th)', $first ) > 0 ? $first : null;
}
if ( ! $head || ! $rows ) {
return $table;
}
$labels = array();
foreach ( $xp->query( './th | ./td', $head ) as $cell ) {
if ( ! $cell instanceof DOMElement ) {
continue;
}
$text = (string) $xp->evaluate( 'normalize-space(.)', $cell );
$labels = array_merge( $labels, array_fill( 0, max( 1, (int) $cell->getAttribute( 'colspan' ) ), $text ) );
}
foreach ( $rows as $tr ) {
if ( $tr === $head ) {
continue;
}
$column = 0;
foreach ( $xp->query( './th | ./td', $tr ) as $cell ) {
if ( ! $cell instanceof DOMElement ) {
continue;
}
if ( isset( $labels[ $column ] ) && '' !== $labels[ $column ] ) {
$cell->setAttribute( 'data-label', $labels[ $column ] );
}
$column += max( 1, (int) $cell->getAttribute( 'colspan' ) );
}
}
$html = '';
foreach ( $xp->query( './node()', $box ) as $node ) {
$html .= $doc->saveHTML( $node );
}
return $html;
}
/**
* The table already sits right inside our frame (content filtered twice).
*
* @param string $content Content.
* @param int $offset Position of the table tag.
*/
private static function framed( $content, $offset ): bool {
return (bool) preg_match( '#