不合规范的html段落php处理细则

最近业余时间在维护一个rss聚合应用,就发现很多网站feed的条目摘要存在各种问题,用strip_tags一刀切吧,对摘要的段落和样式扭曲了

例如:有一些网站的摘要是截断输出,例如指定的摘要长度截断,这样会导致摘要中出现非闭合的html标签,下面的摘要是一个例子:

$str=<<<EOF
<P>  【手机中国 导购】时间过得真快,转眼就我们就已经度过了2013年的上半年,而我们也悄无声息地老了半岁。不过随着时间的流逝,手机行业也在快速的进步着,其发展速度之快可以用日新月异来形容了。</P>
 <P align=center><IMG style="BORDER-BOTTOM: black 1px solid; BORDER-LEFT: black 1px solid; BORDER-TOP: black 1px solid; BORDER-RIGHT: black 1px solid" alt="2.2GHz骁龙800四核 上半年热门机N宗最 " align=1 src="http://imgm.cnmo.com/cnmo_product/18_500x375/698/ceFYnyzZgUijQ.jpg"><BR>2012年的旗舰机型HTC Butterfly</P>
 <P>  回首2012年,手机市场还处于一个相对比较矛盾的时期,国产手机的初露锋芒以及国际大牌的推陈出新,让消费者有些摸不清头脑。到了2013年之后,虽然这个现象还存在着,唯一不同的就是消费者已经逐渐习惯了这个现状,整个手机行业也是在不断的向前进。</P>
 <P>  毫不夸张的说,今天刚刚上市了一款各个方面都表现突出的机皇级旗舰机,也许明天就被其他品牌旗舰所取代,这是一个不争的事实。但相比来说,每个品牌每款旗舰也都有自己的特长,比如处理器主频高或是屏幕尺寸大等等。</P>
 <P align=center><IMG style="BORDER-BOTTOM: black 1px solid; BORDER-LEFT: black 1px solid; BORDER-TOP: black 1px solid; BORDER-RIGHT: black 1px solid" alt="6.44英寸屏骁龙800 上半年热门机N宗最 " src="http://img.cnmo-img.com.cn/905/904155.jpg"></P>
 <P>  俗话说风水轮流转,曾经榜上有名的强机也许今天就名落孙山,物竞天择,适者生存这句话说的不无道理。今天笔者也给大家统计了2013上半年最新智能手机N宗最,下面就让我们一起看一下吧。<STRONG>
EOF;


上面的摘要有几点不合法:
1.html标签大写
2.最后一个strong由于截断没有正常闭合,strong的父标签p丢失
3.标签的属性中出现了一些样式属性和定义,像:align,style

下面说一说它们的影响
2:如果不加处理的输出会造成页面样式混乱,像非正常闭合的strong浏览器会自动把它后面的输出算成它的子元素.
3:样式定义可能影响你的页面样式,图片溢出你的摘要容器
1:不会造成视觉上的错误,但它会影响你的html合法性

下面来说说处理方法
3:可以用正则把属性给替换掉,像

preg_replace("/<([a-z][a-z0-9]*)(?:[^>]*(\ssrc=['\"][^'\"]*['\"]))?[^>]*?(\/?)>/i",'<$1$2$3>',$str);


2:可以用DOMNode::C14N方法来规范,它可以把丢失的标签给补上,只不过<img />会变成<img></img>

等等:
1.为什么不用strip_tags来处理呢?
是可以,虽然它也可以保留指定的标签,但我会把哪些不安全的标签交给htmlentities

2.好像dom可以删除属性吧!
对,这是下面要讲的,综合处理1,2,3的代码如下

$doc = new DOMDocument();
$doc->formatOutput=false;

$doc->loadHTML(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8'));
$nodes = $doc->getElementsByTagName('*');
foreach ( $nodes as $node ) {
	$delAtts=array();
	//找到节点的所有属性
	$nodeN=$node->tagName;
	$nodeAtts=$node->attributes;
	foreach($nodeAtts as $attN=>$att){
		//是img保留src属性
		if(strtolower($attN)=='src' && strtolower($nodeN)=='img') continue;
		//不是直接删除所有属性
		array_push($delAtts,$attN);
	}
	foreach($delAtts as $A){
		$node->removeAttribute($A);
	}
}
$doc->saveHTML();
$pstr=$doc->GetElementsByTagName('body')->item(0)->C14N();
//clear empty tag
$pstr=preg_replace('/<(\w+)>(\s| )*<\/\1>/i',"",$pstr);


大体上已经OK了,$pstr的内容是body包裹的$str,最后只需要把body解决掉就可以.
最后要说的有几点:
1.一定不要在遍历属性时把它删除,例如:img有三个属性style,src,alt,它只会删除掉style,style后面的并不会删除
2.一定不要用saveHTML()的返回值作为后续处理的内容,后果是汉字变成如下的东东:

&#12288;&#12288;&#22238;&#39318;2012&#24180;&#65292;&#25163;&#26426;&#24066;&#22330;&#36824

也不要怕,只需要再调一次

mb_convert_encoding($str, 'UTF-8','HTML-ENTITIES')


就ok了,为了偷懒,所以它的返回值不要用

3.$doc->GetElementsByTagName('body')->item(0)->C14N();


也可以换成:

$doc->documentElement->C14N();


只不过返回值不光有body还有html标签,不在乎的话也可以用它,毕竟比GetElementsByTagName更省事

发表评论

电子邮件地址不会被公开。 必填项已用*标注