> */ public function parse( $content ) { $content = str_replace( array( "\r\n", "\r" ), "\n", $content ); $lines = explode( "\n", $content ); $output = array(); $block = array( 'type' => 'paragraph', 'lines' => array(), ); foreach ( $lines as $line ) { if ( '' === $line ) { if ( ! empty( $block['lines'] ) ) { $output[] = $block; $block = array( 'type' => 'paragraph', 'lines' => array(), ); } $output[] = array( 'type' => 'spacer', ); continue; } $parsed_line = array( 'type' => $this->line_has_visible_lyrics( $line ) ? 'lyrics' : 'chords-only', 'segments' => $this->parse_line( $line ), ); if ( $this->line_is_block_markup( $line, $parsed_line['segments'] ) ) { if ( ! empty( $block['lines'] ) ) { $output[] = $block; $block = array( 'type' => 'paragraph', 'lines' => array(), ); } $output[] = array( 'type' => 'html-block', 'lines' => array( $parsed_line ), ); continue; } $block['lines'][] = $parsed_line; } if ( ! empty( $block['lines'] ) ) { $output[] = $block; } return $output; } /** * Parse a single line. * * @param string $line Input line. * @return array> */ private function parse_line( $line ) { $line = str_replace( array( '[[', ']]' ), array( self::ESCAPED_OPEN, self::ESCAPED_CLOSE ), $line ); $tokens = preg_split( '/(<[^>]+>)/', $line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $segments = array(); $current = null; foreach ( $tokens as $token ) { if ( $this->is_html_token( $token ) ) { $segments[] = array( 'type' => 'html', 'chord' => null, 'lyric' => $this->restore_escaped_brackets( $token ), ); continue; } $parsed = $this->parse_text_token( $token, $current, $line ); foreach ( $parsed['segments'] as $segment ) { $segments[] = $segment; } $current = $parsed['pending_chord']; } if ( null !== $current ) { $segments[] = $this->build_segment( $current, '', 'text' ); } if ( empty( $segments ) ) { $segments[] = $this->build_segment( null, $line, 'text' ); } return array_values( array_filter( $segments, static function ( $segment ) { return 'html' === $segment['type'] || '' !== $segment['lyric'] || null !== $segment['chord']; } ) ); } /** * Parse a text token, preserving pending chord context. * * @param string $token Text token. * @param string|null $pending_chord Chord awaiting attachment. * @param string $source_line Full source line for filter context. * @return array */ private function parse_text_token( $token, $pending_chord, $source_line ) { $segments = array(); $offset = 0; $length = strlen( $token ); while ( $offset < $length ) { if ( preg_match( '/\[(?[A-G](?:#|b)?(?:m|min|maj|sus|add|dim|aug|\+)?[^\]]*)\]/', $token, $matches, PREG_OFFSET_CAPTURE, $offset ) ) { $match_text = $matches[0][0]; $match_offset = $matches[0][1]; $chord_value = $matches['chord'][0]; if ( $match_offset > $offset ) { $segments[] = $this->build_segment( $pending_chord, substr( $token, $offset, $match_offset - $offset ), 'text' ); $pending_chord = null; } elseif ( null !== $pending_chord ) { $segments[] = $this->build_segment( $pending_chord, '', 'text' ); $pending_chord = null; } $pending_chord = apply_filters( 'da_plectrum_parse_chord', $chord_value, $source_line ); $offset = $match_offset + strlen( $match_text ); continue; } $segments[] = $this->build_segment( $pending_chord, substr( $token, $offset ), 'text' ); $pending_chord = null; break; } return array( 'segments' => $segments, 'pending_chord' => $pending_chord, ); } /** * Build a segment. * * @param string|null $chord Chord value. * @param string $text Segment text. * @param string $type Segment type. * @return array */ private function build_segment( $chord, $text, $type ) { return array( 'type' => $type, 'chord' => $chord, 'lyric' => $this->restore_escaped_brackets( $text ), ); } /** * Determine whether a token is HTML markup. * * @param string $token Token to inspect. * @return bool */ private function is_html_token( $token ) { return 1 === preg_match( '/^<[^>]+>$/', $token ); } /** * Restore escaped bracket placeholders. * * @param string $text Text to restore. * @return string */ private function restore_escaped_brackets( $text ) { return str_replace( array( self::ESCAPED_OPEN, self::ESCAPED_CLOSE ), array( '[', ']' ), $text ); } /** * Determine whether a line contains visible lyric text. * * @param string $line Raw line. * @return bool */ private function line_has_visible_lyrics( $line ) { $prepared = str_replace( array( '[[', ']]' ), array( '', '' ), $line ); $stripped = preg_replace( '/\[[^\]]+\]/', '', wp_strip_all_tags( $prepared ) ); return '' !== trim( (string) $stripped ); } /** * Determine whether a parsed line is standalone block markup. * * @param string $line Raw source line. * @param array> $segments Parsed line segments. * @return bool */ private function line_is_block_markup( $line, $segments ) { $trimmed = trim( $line ); if ( 1 === preg_match( '/^<(h[1-6]|p|ul|ol|li|blockquote|pre|code|div|section|article)\b[^>]*>.*<\/\1>\s*$/is', $trimmed ) ) { return true; } if ( 1 !== count( $segments ) ) { return false; } $segment = $segments[0]; return 'html' === $segment['type'] && 1 === preg_match( '/^\s*<\/?(?:h[1-6]|p|ul|ol|li|blockquote|pre|code|div|section|article)[^>]*>\s*$/i', (string) $segment['lyric'] ); } }