Gregarius编辑feed时中文乱码的解决

Gregarius编辑feeds的时候,中文总是乱码(版本r1787),其实解决方法很简单的:

找到admin/channels.php这个文件,找到第900行,修改为:

//$title = htmlentities($title,ENT_QUOTES);
$title = htmlspecialchars($title, ENT_QUOTES);

这个是feed标题乱码的修正,再向下翻到Description部分,大概在988行左右,修改为:

// Description
//$descr = trim(htmlentities(strip_tags($descr), ENT_QUOTES));
$descr = trim(htmlspecialchars(strip_tags($descr), ENT_QUOTES));

这样就ok了。

原因很简单,htmlentities这个函数在转义到html标记的时候,会吧中文字符中一些字节也进行了转义,形成了乱码,而htmlspecialchars这个函数只转义特定的5个字符,不会存在这种情况。

hack之后,单引号和双引号都能输入了,但是反斜杠\仍然是不能输入的,应该是在输入处理的时候过滤掉了,或者进行了其他的转换,不过一般也用不到,就不改了。西文环境用户的处理习惯就是和我们不大一样啊。

参考

adodb5连接sybase的一个错误

这个错误比较蹊跷,所以拿出来说一说。环境:php 5.2.3, adodb 5.04, ubuntu 7.10 Gutsy.

前两天为了使用adodb和sqlite3,就把adodb升级成了只支持php5的adodb5(5.0.4),过两天发现另外一个使用sybase的程序不正常了,运行时不工作直接退出,没有任何错误信息,通过添加die('hi');的方式定位到错误发生在$db = &ADONewConnection('sybase');这一行。

adodb对sybase的支持一直不强,不过还没有到不工作的地步,但这个错误没有任何提示,实在是不好找,无奈之下用ZendStudio来跟踪一下,弄了一个最简单的小程序,当然,它是不工作没错误直接结束:

#! /usr/bin/php
<?php
require_once('adodb/adodb.inc.php');
$db = &ADONewConnection('sybase');
print_r($db);
$db->Connect('server3', 'sa', '', 'dbname');
$rs = $db->Execute('select 1');
print_r($rs);
?>

ZendStudio下Debug就报错了:

Compile Error: /home/fwolf/dev/include/adodb5/drivers/adodb-sybase.inc.php line 271 - Cannot make static method ADOConnection::UnixDate() non static in class ADODB_sybase

拿着错误信息上网一查,原来是在adodb-sybase.inc.php文件中,180行开始的地方定义了两个函数UnixDate()和UnixTimeStamp()(提示错误在271行,271行是类ADODB_sybase的定义结束位置,所以这个错误是在代码编译是产生的,而不是运行时),而这两个函数在adodb.inc.php中是作为static函数定义的(2481、2505行),php5不允许覆盖static函数(这个在oop中好像是叫重载,太长时间没摸书本,记不清了),所以产生编译错误,程序中止。在adodb.inc.php的4089行,包含adodb-sybase.inc.php的时候又加上了@符号:

@include_once($file);

所以错误信息被屏蔽,不显示了。ZendStudio由于Debug的原因,可能所有的错误都捕捉到了,忽略@。去掉@之后,直接运行就也可以报错了。

问题弄清楚了,如何改呢,我想大概有两种方式:

  1. 修改adodb.inc.php,不将UnixDate()函数定义为static。
  2. 修改adodb-sybase.inc.php,取消两个函数的重复定义。

相比之下,觉得第一种方法更好一些,第二种方法可能会引发其它的错误。所以改下来一共是去掉四个static:

Line 2481: ADOConnection::UnixDate()
Line 2505: ADOConnection::UnixTimeStamp()
Line 3204: ADORecordSet::UnixDate()
Line 3215: ADORecordSet::UnixTimeStamp()

简单测试了一下,基本工作正常。

参考

PS: ZendStudio使用单独的一套php.ini,放在/path/to/ZendStudio-5.5.0/bin/php5目录下,我总是忘记,每次include path不对劲的时候都要找半天。

[PHP]在bash中输出彩色文本

先看图吧:

PHP在bash中输出彩色文本

乱不乱?这是我自己用的一个web游戏机器人程序的输出,游戏自然一切都是数字说了算,可每次看这一大堆数字实在是让我头疼,从中找出有用的数据太难了,对齐、缩进都办法也不怎么见效,突然我想到,如果用escape color输出彩色的文本,应该看起来会舒服很多吧?

说到escape color,用过bash的人应该都见过,只是不知道它叫这么名字而已。ls中彩色的文件、目录名就是用escape color来实现的。早些年用得比较多的telnet形式的bbs,它的颜色也是escape color,最明显的特征就是bbs文章被转贴到别处的时候,经常会多出一些ESC什么的字符出来,这就是escape color的控制字符。

想完整的了解escape color,资料很多,wikipedia – ANSI escape code就有比较完整的解释,中文的资料也不少,比如啦啦~在bash下输出彩色的文本,甚至还有人做出了颜色表:The 256 color mode of xterm

不用觉得眼花缭乱,简单说来,escape code就是在终端(terminal)中输出的控制字符,不光包括文字颜色,还可以控制很多其它内容,当然,颜色是比较常用的,所以习惯称之为escape color。我在这里也只讲颜色,其它控制字符的使用方式大同小异,但需要主意不同的终端类型可能会略有区别。

escape color的格式为:

<ESC>[{attr};{fg};{bg}m

其中<ESC>[是起始字符,在bash中一般是16进制的0x1b,也就是八进制的033,十进制的27,另外一种表现方式就是^[,熟悉吧,在bash中按一下Ctrl+v再按ESC它就出来了。后面分别是attr模式、fg前景色、bg背景色,中间用逗号;间隔,最后用字母m结尾。

attr的取值有:

0   Reset All Attributes (return to normal mode) #重置所有设置,回归自然
1   Bright (Usually turns on BOLD) # 高亮/加粗
2   Dim
3   Underline
5   Blink
7   Reverse
8   Hidden

fg的取值有:

30  Black
31  Red
32  Green
33  Yellow
34  Blue
35  Magenta
36  Cyan
37  White

bg的取值有:

40  Black
41  Red
42  Green
43  Yellow
44  Blue
45  Magenta
46  Cyan
47  White

可以看到,attr/fg/bg的用值范围不重叠,所以空缺哪个无所谓。

在用php输出的时候,只需要在正常字符串内容中,加入适当的escape color代码就可以了,其实其它应用程序也是类似操作的,并不需要额外的设置,比如:

$ php -r 'echo "\x1b[5;31;44m第一种色彩\x1b[3;34;41m第二种色彩\x1b[0m 回归自然\n";'

看到彩色了吧?秀一下我那个机器人美化之后的界面:

PHP在bash中输出彩色文本

顺便说一句,把字符串储存到文件中的时候,escape color也可以一起存上的,浏览时,cat和tail默认就支持颜色显示,less则需要带上-R参数才行。

PDO和sqlite的一点体会

用php写了一个小工具,顺便体验了下PDO和sqlite,一个是php5自带的数据库层,一个是简易的文件型数据库,没什么章法,简单记录在这里。

  • 配合pdo使用,只用安装php5-sqlite即可,php5-sqlite3这个extension可能是单独的sqlite支持,就是类似mysql,有专门的sqlite_connect函数。
  • 系统中也可以安装单独的sqlite3(不带3的是sqlite2),采用类似mysql的shell方式管理库文件。
  • 通过PDO好像无法查询数据库的结构等信息,只能操作DML。
  • 不知道pdo使用sybase(dblib)的时候是否能在sql中使用limit,以及使用了是否有效,要到hardy 8.04 php5-sybase才支持pdo,到时候再试验。
  • Bug: rowCount()无法工作于pdo_sqlite数据库,其他一些函数也有小问题,官方文档的user comment中有说到。
  • PDO的exec方法可以一次执行多条sql,但不返回结果,而query则只能执行一条,返回结果信息。
  • 使用PDO的prepare和PDOStatement的execute不仅有助于加速同一sql不同参数的调用速度,还有助于防止sql注入攻击。
  • prepare的:name不能是表名,大概只能是where中的变值,prepare中的字符型:name不需要带上引号,这一点很方便。

总体感觉,PDO还没有AdoDb方便,大概是用熟了的感觉,但功能上的确要少一些。sqlite数据库表现不错,但由于数据类型、结构、功能相对简单,还是主要用在小型、简单一点的应用里更合适。

Update @ 2008-03-23

来一个使用PDO调sqlite库的例子:

try {
    $db = new PDO("sqlite:/path/to/db/file", null, null, array(PDO::ATTR_PERSISTENT => true));
} catch (PDOException $ex) {
    die("[Error] " . $ex->getMessage() . "\n");
}

// Useless but carefully is better, set encoding.
// Sqlite always use utf-8 internaly, UTF-8 is also it's default value.
$db->query('PRAGMA encoding = "UTF-8"');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Begin query
$stmt = $db->query("PRAGMA table_info({$tbl})");
// PHP bug: rowCount() not work with pdo_sqlite
if (0 == count($stmt->fetchAll())) {
    // Table not exists or have no columns
    ......

另外在使用ADODB的时候,它默认好像并不直接支持sqlite3,而是sqlite2(ADODB版本为V5.04 13 Feb 2008 (c) 2000-2008 John Lim (jlim#natsoft.com)),所以只能通过pdo来实现(即ADODB中使用的数据库类型为pdo),可pdo在这个版本的ADODB中支持也不是很全面,drivers目录下甚至没有adodb-pdo_sqlite.inc.php文件(网上搜了一下,这个文件似乎在某些地方有),所以,只能这样用了:

//ADODB设定
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

try
{
    $conn = &ADONewConnection('pdo');
    @$conn->Connect('sqlite://path/to/db/file');
}
catch (Exception $e)
{
    adodb_backtrace($e->getTrace());
    exit();
}

// Check create table, adodb&sqlite3 not work very well, MetaTables() un-usable
//$rs = $conn->MetaTables();
$rs = $conn->Execute("SELECT name FROM sqlite_master WHERE type='table' AND name='my_table_name'");
if (1 > $rs->RecordCount())
{
    // Table does not exists
    ......

Connect前面的@如果去掉,会有Notice错误出现:

Notice: Undefined property:  ADODB_pdo_base::$_genIDSQL in /home/fwolf/dev/php_includes/adodb5/drivers/adodb-pdo.inc.php on line 106

Notice: Undefined property:  ADODB_pdo_base::$_dropSeqSQL in /home/fwolf/dev/php_includes/adodb5/drivers/adodb-pdo.inc.php on line 108

所以,只能说是ADODB对sqlite3的兼容性还不是很好。

mod_fcgid出现500错误的解决

MT上的php5是用mod_fcgid方式运行的,但运行一段时间之后,有时候会莫名其妙的出现500错误,也看不出是哪个php程序错误,不过html文件倒能正常解析,查看用户的$HOME/statistics/logs/error_log文件,会发现大量这样的内容:

[Fri Aug 10 01:00:43 2007] [error] [client 202.108.23.76] Premature end of scrip
t headers: index.php
[Fri Aug 10 01:01:20 2007] [error] [client 60.191.82.228] Premature end of scrip
t headers: index.php

而在系统的log文件/var/log/apache/error_log中,是这样的错误:

[Fri Aug 10 01:03:05 2007] [notice] mod_fcgid: process /var/www/vhosts/fwolf.com
/httpdocs/blog/index.php(21666) exit(communication error), terminated by calling
 exit(), return code: 120

G到一些资料,第一类错误是脚本执行被中断,没有返回完整的http header;第二类错误是cgi程序的执行被强行中止。根本原因是fastcgi会自动kill掉一些发呆的、长时间没有响应的进程(要不它比较快,比较省内存啊),但是fastcgi的默认idle时间设置过于苛刻(官方文档中每个选项都有默认的时间值),所以有些处理比较慢、需要调用外部资源的程序就有可能被误杀,所以先略微调整一下,继续观察:

<IfModule mod_fcgid.c>
    IdleTimeout 600
    ProcessLifeTime 3600
    MaxProcessCount 8
    DefaultMinClassProcessCount 3
    DefaultMaxClassProcessCount 3
    IPCConnectTimeout 30
    IPCCommTimeout 600
    #MaxRequestsPerProcess 500
</IfModule>
  • IdleTimeout 发呆时限
  • ProcessLifeTime 一个进程的最长生命周期,过期之后无条件kill
  • MaxProcessCount 最大进程个数
  • DefaultMinClassProcessCount 每个程序启动的最小进程个数
  • DefaultMaxClassProcessCount 每个程序启动的最大进程个数
  • IPCConnectTimeout 程序响应超时时间
  • IPCCommTimeout 与程序通讯的最长时间,上面的错误有可能就是这个值设置过小造成的
  • MaxRequestsPerProcess 每个进程最多完成处理个数,达成后自杀,因为PHP最多只处理500次请求。不过这个是mod_fcgid 1.11版本添加的,我们主机上暂时不支持。

上述选项的确切作用我也不是十分清楚,先用着这个设置,再根据情况调整。

参考:

DOMDocument->loadHTML()处理中文的一点问题

DOM是php比较新的xml和html处理类,可以像javascript那样方便的操作DOM树,网上更多的是介绍它处理XML的情况,今天我来介绍一个用它处理html时的中文问题,php版本为5.1.6,所有php代码均为utf8编码。

我要处理的html是使用curl从网页上读取过来的,一个是百度的首页,gb2312字符集,一个是有道的首页,utf8字符集,两者的html头部分分别如下:

<html><head><title>百度一下,你就知道   </title><meta http-equiv=Content-Type content="text/html;charset=gb2312">

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/pack23501M/index.css" type="text/css"/>
<script type="text/javascript" src="/pack23501M/all.js"></script>
<title>有道</title>

可以看出百度的代码非常不规范,而有道就好多了,这虽然是题外话,其实还是有些关系的,后面会提到。

以上两段html代码,用同样的方式处理结果却不同,比如下面简单的处理(输出网页的title):

$dom = new DOMDocument();
@$dom->loadHTML($html);
echo $dom->getElementsByTagName('title')->item(0)->nodeValue;
...
$html = $dom->saveHTML();

有道的输出结果是正常的,百度却是乱码:

ç™¾åº¦ä¸€ä¸‹ï¼Œä½ å°±çŸ¥é“

由于php文档的loadHTML上说了,DOM内部处理全部都是utf8的,所以除了传入内容要utf8化之外,传入的内容中最好还有声明字符集的html代码:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

注意,这就是DOM处理html和xml最大的不同了,xml一般要求在第一行就显示的声明字符集,而html则灵活得多,可声明可不声明。不过不管输出的内容是正常还是乱码,dom内的nodeValue和最终的输出结果都是一致的,说明dom工作正常,问题就在输入数据上。

于是,针对百度的gb2312网页内容,增加了两项处理,第一项是使用mb_convert_encoding把网页内容由gb2312编码转换为utf8编码,第二项是把html中的:

<meta http-equiv=Content-Type content="text/html;charset=gb2312">

替换成了utf8的:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

这样按说应该是可以了,但百度的处理结果仍然是乱码,百思而不得其解,偶然当中发现如果$html的值是这样的话输出是正常的:

$html = mb_convert_encoding('<title>测试test</title>', 'gb2312', 'utf-8');
$html = '<meta http-equiv="Content-Type" content="text/html;charset=gb2312">' . $this->html;

这说明,DOM正确识别了html代码中的Content-Type描述,即使html是gb2312编码的,DOM也能夠自动转换为正确的代码。

现在的情况是这样的:

  • DOM工作正常
  • html已经转换为utf8编码
  • Content-Type描述也已经调整

怎么还是会出问题呢?先看看下面的Content-Type描述代码:

$meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';

看清楚喽,如果用$meta直接替换百度html代码中的那句meta,不会生效,仍然乱码;可如果把$meta添加到整个html代码前面,也就是<html>前面,输出就正常了,神奇吧。

于是我就推测,之前百度代码处理乱码的原因,可能是在它的html代码中,meta前面有个含有中文的<title>,DOM在解析到<title>的时候,遇到了非ascii字符,而这时没有解析到<meta>,DOM不知道整个html代码是什么字符集,也就无法正确判断<title>的编码,于是糊里糊涂的进行了错误的字符集转换。

为了证实我的猜测,试着这样处理一下:只修改<meta>,把定义位置放在<title>前面,把缺少的引号加上,但是字符集声明仍然为gb2312,html代码也不进行iconv转换,就像下面这样(注意为gb2312编码):

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=gb2312">
<title>百度一下,你就知道   </title>

执行,输出正常,而且是正常的gb2312编码,没有乱码。所以我的猜测是正确的,关于Content-Type的meta声明一定要放在<title>前面才行。另外上例中如果把nodeValue输出,是utf8编码的,也就是DOM的内部使用编码,说明DOM输入和输出的时候都会进行字符集转换(根据html代码中的字符集声明)。

最后,总结一下,curl读过来的网页数据,全部iconv为utf8编码,然后把声明Content-Type的<meta>替换到紧跟在<head>的位置上,再用DOM处理就不会出现乱码了。