` tags in a full export (for passing into `prepare()` on a body fragment). */ public static function get_embedded_style_css( string $html ): string { return self::extract_styles_from_html_string( $html ); } /** * Split a full Google Docs HTML export at the front-matter separator (paragraph with ====== / ---, or hr). * * When present, only `body_html` should receive heading/list formatting; header is plain text for template mapping. * * @return array{header_html: string, body_html: string}|null null if no separator node found in the DOM. */ public static function split_export_at_separator( string $html ): ?array { if ( ! class_exists( 'DOMDocument' ) ) { return null; } $html = trim( $html ); if ( '' === $html ) { return null; } $dom = self::load_html_dom( $html ); if ( ! $dom ) { return null; } $blocks = self::export_content_blocks( $dom ); if ( empty( $blocks ) ) { return null; } $header_parts = array(); $body_parts = array(); $past_sep = false; foreach ( $blocks as $block ) { if ( ! $past_sep ) { if ( self::node_is_separator( $block ) ) { $past_sep = true; continue; } $header_parts[] = $dom->saveHTML( $block ); continue; } $body_parts[] = $dom->saveHTML( $block ); } if ( ! $past_sep ) { return null; } return array( 'header_html' => implode( '', $header_parts ), 'body_html' => implode( '', $body_parts ), ); } /** * Process a full Google Docs HTML export (preferred entry point). * * @param string $html Full export HTML. */ public static function prepare_from_export( string $html ): string { $html = trim( $html ); if ( '' === $html ) { return ''; } if ( class_exists( 'DOMDocument' ) ) { $converted = self::prepare_export_with_dom( $html ); if ( '' !== $converted ) { return $converted; } } $fragment = self::extract_body_fragment_regex( $html ); if ( null !== $fragment ) { return self::prepare( $fragment, self::extract_styles_from_html_string( $html ) ); } return self::prepare( $html, self::extract_styles_from_html_string( $html ) ); } /** * Sanitize HTML body for post_content. * * @param string $html HTML fragment or full export document. * @param string $style_css Optional CSS from document
when $html is body-only. */ public static function prepare( string $html, string $style_css = '' ): string { $html = trim( $html ); if ( '' === $html ) { return ''; } if ( class_exists( 'DOMDocument' ) ) { $converted = self::prepare_with_dom( $html, $style_css ); if ( '' !== $converted ) { return $converted; } } return trim( wp_kses_post( self::normalize_google_markup( $html ) ) ); } /** * Load full export, strip front matter, convert headings. */ private static function prepare_export_with_dom( string $html ): string { $dom = self::load_html_dom( $html ); if ( ! $dom ) { return ''; } $style_map = self::parse_style_map( $dom ); $root = self::resolve_content_root( $dom ); if ( ! $root ) { return ''; } self::remove_front_matter( $root ); self::transform_content( $root, $style_map ); return self::serialize_root_children( $dom, $root ); } /** * Parse fragment or document with optional injected CSS. */ private static function prepare_with_dom( string $html, string $style_css = '' ): string { $wrapped = ! preg_match( '/<\s*(?:html|body)\b/i', $html ); if ( $wrapped ) { $html = ']*>.*?={' . $min_equals . ',}\s*(.+?)<\/p>/is'; $patterns[] = '/
]*>.*?={' . $min_equals . ',}.*?<\/p>\s*(.*)$/is'; $patterns[] = '/={' . $min_equals . ',}\s*<\/p>\s*(.*)$/is'; foreach ( Template_Separator::all_marks() as $mark ) { $quoted = preg_quote( $mark, '/' ); $patterns[] = '/
]*>.*?' . $quoted . '\s*(.+?)<\/p>/is'; $patterns[] = '/
]*>.*?' . $quoted . '.*?<\/p>\s*(.*)$/is'; $patterns[] = '/' . $quoted . '\s*<\/p>\s*(.*)$/is'; } foreach ( $patterns as $pattern ) { if ( preg_match( $pattern, $raw, $match ) && '' !== trim( $match[1] ) ) { return trim( $match[1] ); } } return null; } /** * @return string */ private static function extract_styles_from_html_string( string $raw ): string { if ( ! preg_match_all( '/