wpmind/modules/geo/includes/HtmlToMarkdown.php
LinuxJoy 02cb582d6c chore: 补全 strict_types 声明 + CHANGELOG 2.5.0-3.2.1
- 29 个 PHP 文件添加 declare(strict_types=1)
- CHANGELOG 补全 2.5.0、3.0.0、3.1.0、3.2.0、3.2.1 版本记录

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-06 13:09:28 +08:00

276 lines
6.2 KiB
PHP

<?php
/**
* HTML to Markdown Converter
*
* Converts HTML content to Markdown format.
*
* @package WPMind\Modules\Geo
* @since 3.0.0
*/
declare(strict_types=1);
namespace WPMind\Modules\Geo;
/**
* Class HtmlToMarkdown
*
* Converts HTML to Markdown using regex-based approach.
* For complex HTML, consider using a library like league/html-to-markdown.
*/
class HtmlToMarkdown {
/**
* Convert HTML to Markdown.
*
* @param string $html The HTML content to convert.
* @return string Markdown content.
*/
public function convert( string $html ): string {
// Process blocks first.
$html = $this->process_blocks( $html );
// Convert headings.
$html = $this->convert_headings( $html );
// Convert links.
$html = $this->convert_links( $html );
// Convert images.
$html = $this->convert_images( $html );
// Convert lists.
$html = $this->convert_lists( $html );
// Convert code blocks.
$html = $this->convert_code( $html );
// Convert blockquotes.
$html = $this->convert_blockquotes( $html );
// Convert emphasis.
$html = $this->convert_emphasis( $html );
// Convert paragraphs.
$html = $this->convert_paragraphs( $html );
// Clean up.
$html = $this->cleanup( $html );
return $html;
}
/**
* Process WordPress blocks (Gutenberg).
*
* @param string $html The HTML content.
* @return string Processed content.
*/
private function process_blocks( string $html ): string {
// Remove block comments.
$html = preg_replace( '/<!-- \/?wp:[^>]+ -->/', '', $html );
// Handle figure/figcaption.
$html = preg_replace(
'/<figure[^>]*>(.*?)<figcaption[^>]*>(.*?)<\/figcaption><\/figure>/is',
"$1\n*$2*\n",
$html
);
$html = preg_replace( '/<\/?figure[^>]*>/', '', $html );
return $html;
}
/**
* Convert headings.
*
* @param string $html The HTML content.
* @return string Content with Markdown headings.
*/
private function convert_headings( string $html ): string {
for ( $i = 6; $i >= 1; $i-- ) {
$prefix = str_repeat( '#', $i );
$html = preg_replace(
"/<h{$i}[^>]*>(.*?)<\/h{$i}>/is",
"\n\n{$prefix} $1\n\n",
$html
);
}
return $html;
}
/**
* Convert links.
*
* @param string $html The HTML content.
* @return string Content with Markdown links.
*/
private function convert_links( string $html ): string {
return preg_replace(
'/<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is',
'[$2]($1)',
$html
);
}
/**
* Convert images.
*
* @param string $html The HTML content.
* @return string Content with Markdown images.
*/
private function convert_images( string $html ): string {
// With alt text.
$html = preg_replace(
'/<img[^>]+src=["\']([^"\']+)["\'][^>]+alt=["\']([^"\']*)["\'][^>]*\/?>/is',
'![$2]($1)',
$html
);
// Without alt text.
$html = preg_replace(
'/<img[^>]+src=["\']([^"\']+)["\'][^>]*\/?>/is',
'![]($1)',
$html
);
return $html;
}
/**
* Convert lists.
*
* @param string $html The HTML content.
* @return string Content with Markdown lists.
*/
private function convert_lists( string $html ): string {
// Unordered lists.
$html = preg_replace( '/<ul[^>]*>/i', "\n", $html );
$html = preg_replace( '/<\/ul>/i', "\n", $html );
// Ordered lists.
$html = preg_replace( '/<ol[^>]*>/i', "\n", $html );
$html = preg_replace( '/<\/ol>/i', "\n", $html );
// List items - unordered.
$html = preg_replace( '/<li[^>]*>(.*?)<\/li>/is', "- $1\n", $html );
return $html;
}
/**
* Convert code blocks.
*
* @param string $html The HTML content.
* @return string Content with Markdown code.
*/
private function convert_code( string $html ): string {
// Code blocks (pre > code).
$html = preg_replace_callback(
'/<pre[^>]*><code[^>]*(?:class=["\'][^"\']*language-([^"\'\s]+)[^"\']*["\'])?[^>]*>(.*?)<\/code><\/pre>/is',
function ( $matches ) {
$lang = $matches[1] ?? '';
$code = html_entity_decode( $matches[2], ENT_QUOTES, 'UTF-8' );
return "\n\n```{$lang}\n{$code}\n```\n\n";
},
$html
);
// Standalone pre.
$html = preg_replace_callback(
'/<pre[^>]*>(.*?)<\/pre>/is',
function ( $matches ) {
$code = html_entity_decode( $matches[1], ENT_QUOTES, 'UTF-8' );
return "\n\n```\n{$code}\n```\n\n";
},
$html
);
// Inline code.
$html = preg_replace(
'/<code[^>]*>(.*?)<\/code>/is',
'`$1`',
$html
);
return $html;
}
/**
* Convert blockquotes.
*
* @param string $html The HTML content.
* @return string Content with Markdown blockquotes.
*/
private function convert_blockquotes( string $html ): string {
return preg_replace_callback(
'/<blockquote[^>]*>(.*?)<\/blockquote>/is',
function ( $matches ) {
$content = trim( strip_tags( $matches[1] ) );
$lines = explode( "\n", $content );
$quoted = array_map(
function ( $line ) {
return '> ' . trim( $line );
},
$lines
);
return "\n\n" . implode( "\n", $quoted ) . "\n\n";
},
$html
);
}
/**
* Convert emphasis (bold, italic).
*
* @param string $html The HTML content.
* @return string Content with Markdown emphasis.
*/
private function convert_emphasis( string $html ): string {
// Bold.
$html = preg_replace( '/<(strong|b)[^>]*>(.*?)<\/\1>/is', '**$2**', $html );
// Italic.
$html = preg_replace( '/<(em|i)[^>]*>(.*?)<\/\1>/is', '*$2*', $html );
// Strikethrough.
$html = preg_replace( '/<(del|s|strike)[^>]*>(.*?)<\/\1>/is', '~~$2~~', $html );
return $html;
}
/**
* Convert paragraphs.
*
* @param string $html The HTML content.
* @return string Content with proper paragraph spacing.
*/
private function convert_paragraphs( string $html ): string {
$html = preg_replace( '/<p[^>]*>(.*?)<\/p>/is', "\n\n$1\n\n", $html );
$html = preg_replace( '/<br\s*\/?>/i', "\n", $html );
return $html;
}
/**
* Clean up the converted content.
*
* @param string $html The content to clean.
* @return string Cleaned content.
*/
private function cleanup( string $html ): string {
// Remove remaining HTML tags.
$html = strip_tags( $html );
// Decode HTML entities.
$html = html_entity_decode( $html, ENT_QUOTES, 'UTF-8' );
// Normalize line breaks.
$html = preg_replace( '/\n{3,}/', "\n\n", $html );
// Trim whitespace.
$html = trim( $html );
return $html;
}
}