chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests) - Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR imports keep post/product types instead of collapsing to page - Docs: trade-pro theme port spec (R3) and implementation plan Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""Conservative WordPress WXR adapter helpers.
|
||||
|
||||
Never execute PHP, Elementor widgets, shortcodes or serialized metadata.
|
||||
Only known text/image widgets are converted to sanitized HTML with warnings
|
||||
for unknown proprietary elements; original metadata is always retained.
|
||||
"""
|
||||
import json, re
|
||||
from html import escape
|
||||
from .core import sanitize
|
||||
|
||||
WIDGETS={'heading','text-editor','image','button','icon-box','image-box','divider','spacer','video','html'}
|
||||
|
||||
def elementor_to_html(raw):
|
||||
if not isinstance(raw,str) or not raw.strip():return {'html':'','warnings':[],'widgets':0}
|
||||
try:data=json.loads(raw)
|
||||
except (ValueError,TypeError):return {'html':'','warnings':['Elementor JSON 解析失败:保留原始字段,需人工检查'],'widgets':0}
|
||||
if not isinstance(data,list):return {'html':'','warnings':['Elementor 数据根节点不是数组,需人工检查'],'widgets':0}
|
||||
parts=[];warnings=[];count=[0]
|
||||
def walk(elements,depth=0):
|
||||
if depth>12: warnings.append('Elementor 嵌套超过安全限制');return
|
||||
if not isinstance(elements,list):return
|
||||
for item in elements[:300]:
|
||||
if not isinstance(item,dict):continue
|
||||
typ=item.get('widgetType') or ''
|
||||
settings=item.get('settings') or {}
|
||||
if not isinstance(settings,dict):settings={}
|
||||
if typ:
|
||||
count[0]+=1
|
||||
if typ not in WIDGETS:
|
||||
if len(warnings)<60:warnings.append('未转换的 Elementor 组件:'+str(typ)[:65])
|
||||
elif typ=='heading':
|
||||
size=settings.get('header_size','h2');size=size if size in ('h1','h2','h3','h4','h5','h6') else 'h2'
|
||||
parts.append(f'<{size}>{escape(str(settings.get("title") or ""))}</{size}>')
|
||||
elif typ=='text-editor':
|
||||
parts.append(sanitize(str(settings.get('editor') or '')))
|
||||
elif typ in ('image','image-box'):
|
||||
image=settings.get('image') or {}
|
||||
url=image.get('url','') if isinstance(image,dict) else ''
|
||||
if isinstance(url,str) and url.startswith(('https://','http://','/')):
|
||||
parts.append(f'<p><img src="{escape(url,quote=True)}" alt="{escape(str(settings.get("title") or ""),quote=True)}"></p>')
|
||||
if typ=='image-box' and settings.get('title_text'):
|
||||
parts.append('<h3>'+escape(str(settings['title_text']))+'</h3>')
|
||||
if typ=='image-box' and settings.get('description_text'):
|
||||
parts.append('<p>'+escape(str(settings['description_text']))+'</p>')
|
||||
elif typ=='button':
|
||||
href=settings.get('link') or {}
|
||||
dest=href.get('url','') if isinstance(href,dict) else ''
|
||||
if isinstance(dest,str) and dest.startswith(('https://','http://','/')):
|
||||
parts.append(f'<a href="{escape(dest,quote=True)}">{escape(str(settings.get("text") or "更多信息"))}</a>')
|
||||
elif typ=='html':
|
||||
parts.append(sanitize(str(settings.get('html') or '')))
|
||||
elif typ in ('video','divider','spacer','icon-box'):
|
||||
if len(warnings)<60:warnings.append(f'{typ} 布局/嵌入需人工复核')
|
||||
walk(item.get('elements') or [],depth+1)
|
||||
walk(data)
|
||||
return {'html':'\n'.join(parts),'warnings':warnings,'widgets':count[0]}
|
||||
|
||||
def wp_taxonomy(item):
|
||||
data={}
|
||||
for node in item:
|
||||
if node.tag.rsplit('}',1)[-1]!='category':continue
|
||||
domain=node.get('domain','')
|
||||
name=(node.text or '').strip()
|
||||
if not name:continue
|
||||
if domain in ('category','product_cat','vehicle_cat'):data['category']=name
|
||||
elif domain in ('product_brand','brand','pa_brand'):data['brand']=name
|
||||
elif domain=='product_type':data['product_type']=name
|
||||
elif domain.startswith('pa_'):data[domain[3:]]=name
|
||||
return data
|
||||
|
||||
def wp_media_map(root):
|
||||
"""WXR attachment ID -> original URL, useful for featured and gallery images."""
|
||||
media={}
|
||||
for item in root.findall('./channel/item'):
|
||||
typ=post_id=url=''
|
||||
for node in item:
|
||||
local=node.tag.rsplit('}',1)[-1]
|
||||
if local=='post_type':typ=node.text or ''
|
||||
elif local=='post_id':post_id=node.text or ''
|
||||
elif local=='attachment_url':url=node.text or ''
|
||||
if typ=='attachment' and post_id and url and url.startswith(('http://','https://')):
|
||||
media[post_id]=url
|
||||
return media
|
||||
Reference in New Issue
Block a user