內容管理系統中常需支援類似 WordPress Shortcode 的標籤語法:[gallery ids="1,2,3"]、[contact-form id="5"]。核心需求:正則匹配標籤 → 提取屬性 → 呼叫對應處理函式 → 替換回內容。
<?php
function my_wp_plugin_tag_action($content, $tag, $function, $args = false) {
// 1. 正則匹配所有目標標籤
// $tag 範例:'/\[gallery\s+([^\]]*)\]/' 捕獲屬性部分
preg_match_all($tag, $content, $matches);
if (count($matches) > 0) {
// 2. 去重 (避免同一標籤重複處理)
$matches = array_unique($matches[0]); // 取完整匹配 (index 0)
$tag_results = array();
$found_tags = array();
// 3. 逐個處理
foreach ($matches as $idx => $full_tag) {
// 解析屬性字串為陣列 (簡易版,實際建議用 shortcode_parse_atts)
$attrs = shortcode_parse_atts($full_tag); // 需自行實作或引入 WP 核心函式
// 呼叫處理函式
$result = call_user_func($function, $attrs, $full_tag);
$tag_results[] = $result;
$found_tags[] = $full_tag;
}
// 4. 批次替換
$content = str_replace($found_tags, $tag_results, $content);
}
return $content;
}
// 屬性解析輔助函式 (簡化版)
function shortcode_parse_atts($tag) {
$pattern = '/(\w+)\s*=\s*"([^"]*)"/';
preg_match_all($pattern, $tag, $m);
return array_combine($m[1], $m[2]);
}
// 1. 定義處理函式
function handle_gallery($attrs, $full_tag) {
$ids = explode(',', $attrs['ids'] ?? '');
$html = '<div class="gallery">';
foreach ($ids as $id) {
$html .= '<img src="/wp-content/uploads/' . trim($id) . '.jpg" />';
}
$html .= '</div>';
return $html;
}
function handle_contact_form($attrs, $full_tag) {
return '<form action="/contact" method="post">...</form>';
}
// 2. 內容處理流程
$content = '
歡迎來到相簿:
[gallery ids="101,102,103"]
聯繫我們:
[contact-form id="5"]
';
// 3. 依序處理各標籤類型
$content = my_wp_plugin_tag_action($content, '/\[gallery\s+([^\]]*)\]/', 'handle_gallery');
$content = my_wp_plugin_tag_action($content, '/\[contact-form\s+([^\]]*)\]/', 'handle_contact_form');
echo $content;
class ShortcodeProcessor {
private $tags = array();
public function register($tagName, $callback) {
// 自動建立正則:[tagName attrs...]
$this->tags[$tagName] = '/\[' . $tagName . '\s+([^\]]*)\]/';
$this->callbacks[$tagName] = $callback;
}
public function process($content) {
foreach ($this->tags as $name => $regex) {
$content = $this->replaceTags($content, $regex, $this->callbacks[$name]);
}
return $content;
}
private function replaceTags($content, $regex, $callback) {
preg_match_all($regex, $content, $matches);
if (empty($matches[0])) return $content;
$replacements = array();
foreach ($matches[0] as $fullTag) {
$attrs = $this->parseAtts($fullTag);
$replacements[$fullTag] = call_user_func($callback, $attrs, $fullTag);
}
return strtr($content, $replacements); // strtr 比 str_replace 更安全
}
private function parseAtts($tag) {
// 完整支援:單引號、雙引號、無引號、布林屬性
$pattern = '/(\w+)\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|(\S+))/';
preg_match_all($pattern, $tag, $m, PREG_SET_ORDER);
$attrs = array();
foreach ($m as $match) {
$attrs[$match[1]] = $match[2] ?? $match[3] ?? $match[4] ?? true;
}
return $attrs;
}
}
// 使用
$processor = new ShortcodeProcessor();
$processor->register('gallery', 'handle_gallery');
$processor->register('contact-form', 'handle_contact_form');
$processor->register('video', function($attrs) {
return '<video src="' . ($attrs['src'] ?? '') . '" controls></video>';
});
$output = $processor->process($content);
| 決策點 | 選擇 | 理由 |
|---|---|---|
| 正則邊界 | \[tag\s+([^\]]*)\] |
不支援巢狀標籤,簡化解析邏輯 |
| 屬性解析 | 支援雙引號/單引號/無引號 | 相容多種寫作習慣 |
| 替換策略 | 收集後批次 strtr |
避免替換結果被二次匹配、保持順序 |
| 錯誤處理 | 回調拋出異常時保留原標籤 | 不讓單一標籤錯誤破壞整頁內容 |
static $compiled = [])此模式廣泛應用於 CMS、部落格系統、郵件模板引擎,掌握其核心機制可靈活擴展各類動態內容標籤。